From bbfee1c854ae4e28fc61cb9cb1c14e30da053202 Mon Sep 17 00:00:00 2001 From: Patrick Franz Date: Sun, 1 Feb 2026 20:39:17 +0100 Subject: [PATCH] Import qt6-tools_6.10.2.orig.tar.xz [dgit import orig qt6-tools_6.10.2.orig.tar.xz] --- .cmake.conf | 7 + .gitmodules | 3 + .gitreview | 4 + .tag | 1 + CMakeLists.txt | 30 + LICENSES/Apache-2.0.txt | 61 + LICENSES/BSD-3-Clause.txt | 9 + LICENSES/BSL-1.0.txt | 9 + LICENSES/CC0-1.0.txt | 121 + LICENSES/GFDL-1.3-no-invariants-only.txt | 451 + LICENSES/GPL-2.0-only.txt | 339 + LICENSES/GPL-3.0-only.txt | 674 + LICENSES/LGPL-3.0-only.txt | 165 + LICENSES/LLVM-exception.txt | 14 + LICENSES/LicenseRef-Qt-Commercial.txt | 8 + LICENSES/Qt-GPL-exception-1.0.txt | 22 + REUSE.toml | 164 + cmake/FindWrapLibClang.cmake | 182 + coin/axivion/ci_config_linux.json | 25 + coin/axivion/start_analysis.sh | 41 + coin/module_config.yaml | 23 + configure.cmake | 157 + dependencies.yaml | 10 + dist/REUSE.toml | 8 + dist/changes-5.0.1 | 71 + dist/changes-5.0.2 | 25 + dist/changes-5.1.0 | 48 + dist/changes-5.1.1 | 25 + dist/changes-5.10.0 | 27 + dist/changes-5.10.1 | 35 + dist/changes-5.11.0 | 65 + dist/changes-5.11.1 | 24 + dist/changes-5.11.2 | 24 + dist/changes-5.11.3 | 32 + dist/changes-5.12.0 | 60 + dist/changes-5.12.1 | 59 + dist/changes-5.12.10 | 26 + dist/changes-5.12.2 | 24 + dist/changes-5.12.3 | 24 + dist/changes-5.12.4 | 33 + dist/changes-5.12.5 | 37 + dist/changes-5.13.0 | 37 + dist/changes-5.13.1 | 29 + dist/changes-5.13.2 | 27 + dist/changes-5.14.0 | 63 + dist/changes-5.14.1 | 26 + dist/changes-5.14.2 | 24 + dist/changes-5.15.0 | 58 + dist/changes-5.15.1 | 47 + dist/changes-5.15.2 | 72 + dist/changes-5.2.0 | 59 + dist/changes-5.2.1 | 42 + dist/changes-5.6.3 | 27 + dist/changes-5.7.0 | 32 + dist/changes-5.8.0 | 41 + dist/changes-5.9.0 | 34 + dist/changes-5.9.1 | 63 + dist/changes-5.9.2 | 28 + dist/changes-5.9.3 | 24 + dist/changes-5.9.4 | 28 + dist/changes-5.9.5 | 34 + dist/changes-5.9.6 | 24 + dist/changes-6.0.0 | 16 + examples/CMakeLists.txt | 26 + examples/assistant/CMakeLists.txt | 2 + examples/assistant/assistant.pro | 5 + .../doc/images/remotecontrol-example.png | Bin 0 -> 71508 bytes .../doc/images/simpletextviewer-example.png | Bin 0 -> 85194 bytes .../simpletextviewer-findfiledialog.png | Bin 0 -> 17875 bytes .../images/simpletextviewer-mainwindow.png | Bin 0 -> 24247 bytes examples/assistant/doc/src/remotecontrol.qdoc | 14 + .../assistant/doc/src/simpletextviewer.qdoc | 428 + .../assistant/remotecontrol/CMakeLists.txt | 50 + examples/assistant/remotecontrol/enter.png | Bin 0 -> 315 bytes examples/assistant/remotecontrol/main.cpp | 14 + .../assistant/remotecontrol/remotecontrol.cpp | 113 + .../assistant/remotecontrol/remotecontrol.h | 36 + .../assistant/remotecontrol/remotecontrol.pro | 12 + .../assistant/remotecontrol/remotecontrol.qrc | 5 + .../assistant/remotecontrol/remotecontrol.ui | 228 + .../assistant/simpletextviewer/CMakeLists.txt | 44 + .../assistant/simpletextviewer/assistant.cpp | 109 + .../assistant/simpletextviewer/assistant.h | 28 + .../simpletextviewer/documentation/about.txt | 9 + .../documentation/browse.html | 34 + .../documentation/filedialog.html | 48 + .../documentation/findfile.html | 32 + .../documentation/images/browse.png | Bin 0 -> 38895 bytes .../documentation/images/fadedfilemenu.png | Bin 0 -> 11616 bytes .../documentation/images/filedialog.png | Bin 0 -> 17875 bytes .../documentation/images/handbook.png | Bin 0 -> 1060 bytes .../documentation/images/icon.png | Bin 0 -> 5513 bytes .../documentation/images/mainwindow.png | Bin 0 -> 24819 bytes .../documentation/images/open.png | Bin 0 -> 18042 bytes .../documentation/images/wildcard.png | Bin 0 -> 17976 bytes .../simpletextviewer/documentation/index.html | 41 + .../simpletextviewer/documentation/intro.html | 28 + .../documentation/openfile.html | 36 + .../documentation/simpletextviewer.qch | Bin 0 -> 196608 bytes .../documentation/simpletextviewer.qhc | Bin 0 -> 16384 bytes .../documentation/simpletextviewer.qhcp | 30 + .../documentation/simpletextviewer.qhp | 49 + .../documentation/wildcardmatching.html | 57 + .../simpletextviewer/findfiledialog.cpp | 176 + .../simpletextviewer/findfiledialog.h | 60 + examples/assistant/simpletextviewer/main.cpp | 14 + .../assistant/simpletextviewer/mainwindow.cpp | 117 + .../assistant/simpletextviewer/mainwindow.h | 51 + .../simpletextviewer/simpletextviewer.pro | 19 + .../assistant/simpletextviewer/textedit.cpp | 38 + .../assistant/simpletextviewer/textedit.h | 26 + examples/designer/CMakeLists.txt | 18 + examples/designer/README | 10 + .../designer/calculatorbuilder/CMakeLists.txt | 49 + .../calculatorbuilder/calculatorbuilder.pro | 8 + .../calculatorbuilder/calculatorbuilder.qrc | 5 + .../calculatorbuilder/calculatorform.ui | 303 + examples/designer/calculatorbuilder/main.cpp | 63 + .../designer/calculatorform/CMakeLists.txt | 42 + .../calculatorform/calculatorform.cpp | 22 + .../designer/calculatorform/calculatorform.h | 27 + .../calculatorform/calculatorform.pro | 11 + .../designer/calculatorform/calculatorform.ui | 284 + examples/designer/calculatorform/main.cpp | 15 + .../designer/calculatorform_mi/CMakeLists.txt | 38 + .../calculatorform_mi/calculatorform.cpp | 28 + .../calculatorform_mi/calculatorform.h | 25 + .../calculatorform_mi/calculatorform.ui | 303 + .../calculatorform_mi/calculatorform_mi.pro | 11 + examples/designer/calculatorform_mi/main.cpp | 15 + .../containerextension/CMakeLists.txt | 55 + .../containerextension/containerextension.pro | 29 + .../containerextension/multipagewidget.cpp | 102 + .../containerextension/multipagewidget.h | 51 + .../multipagewidgetcontainerextension.cpp | 74 + .../multipagewidgetcontainerextension.h | 39 + .../multipagewidgetextensionfactory.cpp | 25 + .../multipagewidgetextensionfactory.h | 26 + .../multipagewidgetplugin.cpp | 154 + .../multipagewidgetplugin.h | 46 + .../customwidgetplugin/CMakeLists.txt | 53 + .../customwidgetplugin/analogclock.cpp | 92 + .../designer/customwidgetplugin/analogclock.h | 25 + .../customwidgetplugin/customwidgetplugin.cpp | 121 + .../customwidgetplugin/customwidgetplugin.h | 35 + .../customwidgetplugin/customwidgetplugin.pro | 23 + examples/designer/designer.pro | 14 + .../doc/images/calculatorbuilder-example.webp | Bin 0 -> 7108 bytes .../doc/images/calculatorform-example.webp | Bin 0 -> 6926 bytes .../images/containerextension-example.webp | Bin 0 -> 22794 bytes .../images/customwidgetplugin-example.webp | Bin 0 -> 26828 bytes .../doc/images/taskmenuextension-dialog.webp | Bin 0 -> 20384 bytes .../doc/images/taskmenuextension-example.webp | Bin 0 -> 20164 bytes .../doc/images/taskmenuextension-menu.webp | Bin 0 -> 18886 bytes .../doc_src_examples_containerextension.pro | 7 + .../doc_src_examples_customwidgetplugin.pro | 7 + .../doc_src_examples_taskmenuextension.pro | 7 + .../designer/doc/src/calculatorbuilder.qdoc | 87 + examples/designer/doc/src/calculatorform.qdoc | 82 + .../designer/doc/src/calculatorform_mi.qdoc | 74 + .../designer/doc/src/containerextension.qdoc | 476 + .../designer/doc/src/customwidgetplugin.qdoc | 207 + .../designer/doc/src/taskmenuextension.qdoc | 418 + .../designer/taskmenuextension/CMakeLists.txt | 55 + .../taskmenuextension/taskmenuextension.pro | 27 + .../designer/taskmenuextension/tictactoe.cpp | 142 + .../designer/taskmenuextension/tictactoe.h | 47 + .../taskmenuextension/tictactoedialog.cpp | 61 + .../taskmenuextension/tictactoedialog.h | 35 + .../taskmenuextension/tictactoeplugin.cpp | 103 + .../taskmenuextension/tictactoeplugin.h | 43 + .../taskmenuextension/tictactoetaskmenu.cpp | 63 + .../taskmenuextension/tictactoetaskmenu.h | 50 + examples/examples.pro | 4 + examples/help/CMakeLists.txt | 1 + examples/help/README | 7 + .../help/contextsensitivehelp/CMakeLists.txt | 45 + .../contextsensitivehelp.pro | 19 + .../contextsensitivehelp/docs/amount.html | 11 + .../contextsensitivehelp/docs/filter.html | 12 + .../contextsensitivehelp/docs/plants.html | 44 + .../help/contextsensitivehelp/docs/rain.html | 11 + .../contextsensitivehelp/docs/source.html | 33 + .../docs/temperature.html | 13 + .../help/contextsensitivehelp/docs/time.html | 11 + .../docs/wateringmachine.qch | Bin 0 -> 61440 bytes .../docs/wateringmachine.qhc | Bin 0 -> 94208 bytes .../docs/wateringmachine.qhcp | 14 + .../docs/wateringmachine.qhp | 25 + .../help/contextsensitivehelp/helpbrowser.cpp | 64 + .../help/contextsensitivehelp/helpbrowser.h | 27 + examples/help/contextsensitivehelp/main.cpp | 13 + .../wateringconfigdialog.cpp | 31 + .../wateringconfigdialog.h | 24 + .../wateringconfigdialog.ui | 446 + .../images/context-sensitive-help-example.png | Bin 0 -> 41582 bytes .../help/doc/src/contextsensitivehelp.qdoc | 14 + examples/help/help.pro | 4 + examples/linguist/CMakeLists.txt | 12 + examples/linguist/README | 6 + examples/linguist/arrowpad/CMakeLists.txt | 47 + examples/linguist/arrowpad/arrowpad.cpp | 27 + examples/linguist/arrowpad/arrowpad.h | 31 + examples/linguist/arrowpad/arrowpad.pro | 17 + examples/linguist/arrowpad/arrowpad_en.ts | 10 + examples/linguist/arrowpad/arrowpad_fr.ts | 3 + examples/linguist/arrowpad/arrowpad_nl.ts | 3 + examples/linguist/arrowpad/main.cpp | 28 + examples/linguist/arrowpad/mainwindow.cpp | 24 + examples/linguist/arrowpad/mainwindow.h | 31 + .../doc/images/linguist-arrowpad_en.png | Bin 0 -> 1429 bytes .../doc/images/linguist-arrowpad_fr.png | Bin 0 -> 1671 bytes .../doc/images/linguist-arrowpad_nl.png | Bin 0 -> 1706 bytes .../doc/images/linguist-hellotr_en.png | Bin 0 -> 3367 bytes .../doc/images/linguist-hellotr_la.png | Bin 0 -> 753 bytes .../linguist/doc/images/linguist-i18n.png | Bin 0 -> 22531 bytes ...uist-localizedclock-idbased-dialog_de.webp | Bin 0 -> 4058 bytes ...uist-localizedclock-idbased-dialog_en.webp | Bin 0 -> 3626 bytes .../linguist-localizedclock-idbased_de.webp | Bin 0 -> 6644 bytes .../linguist-localizedclock-idbased_en.webp | Bin 0 -> 7444 bytes .../images/linguist-localizedclock_de_DE.webp | Bin 0 -> 7040 bytes .../images/linguist-localizedclock_en_GB.webp | Bin 0 -> 6452 bytes .../images/linguist-localizedclock_en_US.webp | Bin 0 -> 6874 bytes ...linguist-localizedclock_switchlang_de.webp | Bin 0 -> 6224 bytes ...linguist-localizedclock_switchlang_en.webp | Bin 0 -> 6208 bytes .../doc/images/linguist-trollprint_10_en.png | Bin 0 -> 1951 bytes .../images/linguist-trollprint_10_pt_bad.png | Bin 0 -> 2073 bytes .../images/linguist-trollprint_10_pt_good.png | Bin 0 -> 2120 bytes .../doc/images/linguist-trollprint_11_en.png | Bin 0 -> 2019 bytes .../doc/images/linguist-trollprint_11_pt.png | Bin 0 -> 2152 bytes .../snippets/doc_src_examples_arrowpad.cpp | 6 + .../snippets/doc_src_examples_arrowpad.qdoc | 25 + .../snippets/doc_src_examples_hellotr.qdoc | 38 + .../snippets/doc_src_examples_trollprint.cpp | 40 + examples/linguist/doc/src/arrowpad.qdoc | 220 + examples/linguist/doc/src/hellotr.qdoc | 171 + examples/linguist/doc/src/i18n.qdoc | 20 + .../doc/src/localizedclock-idbased.qdoc | 301 + .../doc/src/localizedclock-switchlang.qdoc | 128 + examples/linguist/doc/src/localizedclock.qdoc | 214 + examples/linguist/doc/src/trollprint.qdoc | 229 + examples/linguist/hellotr/CMakeLists.txt | 45 + examples/linguist/hellotr/hellotr.pro | 12 + examples/linguist/hellotr/hellotr_en.ts | 10 + examples/linguist/hellotr/hellotr_la.ts | 3 + examples/linguist/hellotr/main.cpp | 33 + examples/linguist/i18n/CMakeLists.txt | 46 + examples/linguist/i18n/i18n.pro | 32 + examples/linguist/i18n/languagechooser.cpp | 141 + examples/linguist/i18n/languagechooser.h | 50 + examples/linguist/i18n/main.cpp | 14 + examples/linguist/i18n/mainwindow.cpp | 62 + examples/linguist/i18n/mainwindow.h | 39 + .../linguist/i18n/translations/i18n_ar.ts | 59 + .../linguist/i18n/translations/i18n_cs.ts | 59 + .../linguist/i18n/translations/i18n_de.ts | 59 + .../linguist/i18n/translations/i18n_el.ts | 59 + .../linguist/i18n/translations/i18n_en.ts | 59 + .../linguist/i18n/translations/i18n_eo.ts | 59 + .../linguist/i18n/translations/i18n_fr.ts | 59 + .../linguist/i18n/translations/i18n_it.ts | 59 + .../linguist/i18n/translations/i18n_ja.ts | 59 + .../linguist/i18n/translations/i18n_ko.ts | 59 + .../linguist/i18n/translations/i18n_nb.ts | 59 + .../linguist/i18n/translations/i18n_ru.ts | 59 + .../linguist/i18n/translations/i18n_sv.ts | 59 + .../linguist/i18n/translations/i18n_zh.ts | 59 + examples/linguist/linguist.pro | 5 + .../localizedclock-idbased/CMakeLists.txt | 61 + .../linguist/localizedclock-idbased/Main.qml | 77 + .../localizedclock-idbased/dialog.cpp | 17 + .../linguist/localizedclock-idbased/dialog.h | 26 + .../linguist/localizedclock-idbased/dialog.ui | 100 + .../localizedclock-idbased/i18n/clock_ar.ts | 43 + .../localizedclock-idbased/i18n/clock_de.ts | 39 + .../localizedclock-idbased/i18n/clock_en.ts | 40 + .../localizedclock-idbased/i18n/clock_es.ts | 39 + .../localizedclock-idbased/i18n/clock_fr.ts | 39 + .../localizedclock-idbased/i18n/clock_it.ts | 39 + .../localizedclock-idbased/i18n/clock_ja.ts | 38 + .../localizedclock-idbased/i18n/clock_ko.ts | 38 + .../localizedclock-idbased/i18n/clock_pt.ts | 39 + .../localizedclock-idbased/i18n/clock_zh.ts | 38 + .../linguist/localizedclock-idbased/main.cpp | 60 + .../timezonemanager.cpp | 45 + .../localizedclock-idbased/timezonemanager.h | 38 + .../CMakeLists.txt | 59 + .../localizedclock-switchlocale/Main.qml | 125 + .../localizedclock-switchlocale/globe.png | Bin 0 -> 985 bytes .../i18n/clock_ar.ts | 25 + .../i18n/clock_de.ts | 21 + .../i18n/clock_en.ts | 16 + .../i18n/clock_es.ts | 21 + .../i18n/clock_fr.ts | 21 + .../i18n/clock_it.ts | 21 + .../i18n/clock_ja.ts | 20 + .../i18n/clock_ko.ts | 20 + .../i18n/clock_pt.ts | 21 + .../i18n/clock_zh.ts | 20 + .../localizedclock-switchlocale/main.cpp | 16 + .../translatormanager.cpp | 28 + .../translatormanager.h | 25 + .../linguist/localizedclock/CMakeLists.txt | 54 + examples/linguist/localizedclock/Main.qml | 63 + .../linguist/localizedclock/i18n/clock_ar.ts | 30 + .../linguist/localizedclock/i18n/clock_de.ts | 26 + .../linguist/localizedclock/i18n/clock_en.ts | 16 + .../linguist/localizedclock/i18n/clock_es.ts | 26 + .../linguist/localizedclock/i18n/clock_fr.ts | 26 + .../linguist/localizedclock/i18n/clock_it.ts | 26 + .../linguist/localizedclock/i18n/clock_ja.ts | 25 + .../linguist/localizedclock/i18n/clock_ko.ts | 25 + .../linguist/localizedclock/i18n/clock_pt.ts | 26 + .../linguist/localizedclock/i18n/clock_zh.ts | 25 + examples/linguist/localizedclock/main.cpp | 62 + examples/linguist/trollprint/CMakeLists.txt | 44 + examples/linguist/trollprint/main.cpp | 25 + examples/linguist/trollprint/mainwindow.cpp | 59 + examples/linguist/trollprint/mainwindow.h | 37 + examples/linguist/trollprint/printpanel.cpp | 48 + examples/linguist/trollprint/printpanel.h | 32 + examples/linguist/trollprint/trollprint.pro | 13 + examples/linguist/trollprint/trollprint_en.ts | 10 + examples/linguist/trollprint/trollprint_pt.ts | 67 + examples/uitools/CMakeLists.txt | 1 + .../textfinder-example-userinterface.webp | Bin 0 -> 13754 bytes examples/uitools/doc/src/textfinder.qdoc | 155 + examples/uitools/textfinder/CMakeLists.txt | 56 + examples/uitools/textfinder/forms/input.txt | 3 + .../uitools/textfinder/forms/textfinder.ui | 99 + examples/uitools/textfinder/main.cpp | 18 + examples/uitools/textfinder/textfinder.cpp | 119 + examples/uitools/textfinder/textfinder.h | 33 + examples/uitools/textfinder/textfinder.pro | 10 + examples/uitools/textfinder/textfinder.qrc | 6 + examples/uitools/uitools.pro | 2 + licenseRule.json | 249 + qt_cmdline.cmake | 0 src/CMakeLists.txt | 58 + src/assistant/CMakeLists.txt | 61 + src/assistant/assistant/CMakeLists.txt | 204 + src/assistant/assistant/Info_mac.plist | 20 + src/assistant/assistant/aboutdialog.cpp | 149 + src/assistant/assistant/aboutdialog.h | 57 + src/assistant/assistant/assistant.desktop | 9 + src/assistant/assistant/assistant.icns | Bin 0 -> 118925 bytes src/assistant/assistant/assistant.ico | Bin 0 -> 125945 bytes .../assistant/assistant.metainfo.xml | 40 + src/assistant/assistant/bookmarkdialog.cpp | 199 + src/assistant/assistant/bookmarkdialog.h | 51 + src/assistant/assistant/bookmarkdialog.ui | 156 + .../assistant/bookmarkfiltermodel.cpp | 278 + src/assistant/assistant/bookmarkfiltermodel.h | 80 + src/assistant/assistant/bookmarkitem.cpp | 148 + src/assistant/assistant/bookmarkitem.h | 53 + src/assistant/assistant/bookmarkmanager.cpp | 530 + src/assistant/assistant/bookmarkmanager.h | 128 + .../assistant/bookmarkmanagerwidget.cpp | 291 + .../assistant/bookmarkmanagerwidget.h | 65 + .../assistant/bookmarkmanagerwidget.ui | 137 + src/assistant/assistant/bookmarkmodel.cpp | 425 + src/assistant/assistant/bookmarkmodel.h | 78 + src/assistant/assistant/bookmarkwidget.ui | 85 + src/assistant/assistant/centralwidget.cpp | 612 + src/assistant/assistant/centralwidget.h | 146 + src/assistant/assistant/cmdlineparser.cpp | 334 + src/assistant/assistant/cmdlineparser.h | 78 + src/assistant/assistant/contentwindow.cpp | 168 + src/assistant/assistant/contentwindow.h | 46 + .../doc/images/assistant-assistant.png | Bin 0 -> 47313 bytes .../doc/images/assistant-bookmarks.png | Bin 0 -> 6083 bytes .../doc/images/assistant-dockwidgets.png | Bin 0 -> 42222 bytes .../doc/images/assistant-examples.png | Bin 0 -> 85194 bytes .../assistant/doc/images/assistant-index.png | Bin 0 -> 4038 bytes .../assistant-preferences-documentation.png | Bin 0 -> 9533 bytes .../images/assistant-preferences-filters.png | Bin 0 -> 10729 bytes .../images/assistant-preferences-fonts.png | Bin 0 -> 5394 bytes .../images/assistant-preferences-options.png | Bin 0 -> 5730 bytes .../assistant/doc/images/assistant-search.png | Bin 0 -> 11710 bytes src/assistant/assistant/doc/internal/README | 9 + .../assistant/doc/internal/assistant.qdocconf | 22 + .../assistant/doc/internal/internal.pro | 2 + .../assistant/doc/qtassistant.qdocconf | 43 + .../snippets/doc_src_assistant-manual.qdoc | 111 + .../assistant/doc/src/assistant-example.qdoc | 27 + .../assistant/doc/src/assistant-manual.qdoc | 418 + .../doc/src/assistant-quick-guide.qdoc | 289 + src/assistant/assistant/findwidget.cpp | 194 + src/assistant/assistant/findwidget.h | 62 + src/assistant/assistant/globalactions.cpp | 227 + src/assistant/assistant/globalactions.h | 77 + .../assistant/helpbrowsersupport.cpp | 207 + src/assistant/assistant/helpbrowsersupport.h | 44 + src/assistant/assistant/helpdocsettings.cpp | 192 + src/assistant/assistant/helpdocsettings.h | 46 + .../assistant/helpdocsettingswidget.cpp | 162 + .../assistant/helpdocsettingswidget.h | 37 + .../assistant/helpdocsettingswidget.ui | 76 + src/assistant/assistant/helpenginewrapper.cpp | 791 + src/assistant/assistant/helpenginewrapper.h | 179 + src/assistant/assistant/helpviewer.cpp | 477 + src/assistant/assistant/helpviewer.h | 93 + src/assistant/assistant/helpviewerimpl.cpp | 68 + src/assistant/assistant/helpviewerimpl.h | 111 + src/assistant/assistant/helpviewerimpl_p.h | 98 + .../assistant/helpviewerimpl_qtb.cpp | 373 + .../assistant/helpviewerimpl_qwv.cpp | 370 + .../assistant/images/assistant-128.png | Bin 0 -> 4118 bytes src/assistant/assistant/images/assistant.png | Bin 0 -> 586 bytes src/assistant/assistant/images/bookmark.png | Bin 0 -> 1266 bytes .../assistant/images/closebutton.png | Bin 0 -> 288 bytes .../assistant/images/darkclosebutton.png | Bin 0 -> 170 bytes src/assistant/assistant/images/mac/book.png | Bin 0 -> 1477 bytes .../assistant/images/mac/closetab.png | Bin 0 -> 516 bytes .../assistant/images/mac/editcopy.png | Bin 0 -> 1468 bytes src/assistant/assistant/images/mac/find.png | Bin 0 -> 1836 bytes src/assistant/assistant/images/mac/home.png | Bin 0 -> 1807 bytes src/assistant/assistant/images/mac/next.png | Bin 0 -> 1310 bytes .../assistant/images/mac/previous.png | Bin 0 -> 1080 bytes src/assistant/assistant/images/mac/print.png | Bin 0 -> 2087 bytes .../assistant/images/mac/resetzoom.png | Bin 0 -> 1567 bytes .../assistant/images/mac/synctoc.png | Bin 0 -> 1838 bytes src/assistant/assistant/images/mac/zoomin.png | Bin 0 -> 1696 bytes .../assistant/images/mac/zoomout.png | Bin 0 -> 1662 bytes src/assistant/assistant/images/win/book.png | Bin 0 -> 1109 bytes .../assistant/images/win/closetab.png | Bin 0 -> 375 bytes .../assistant/images/win/editcopy.png | Bin 0 -> 1325 bytes src/assistant/assistant/images/win/find.png | Bin 0 -> 1944 bytes src/assistant/assistant/images/win/home.png | Bin 0 -> 1414 bytes src/assistant/assistant/images/win/next.png | Bin 0 -> 1038 bytes .../assistant/images/win/previous.png | Bin 0 -> 898 bytes src/assistant/assistant/images/win/print.png | Bin 0 -> 1456 bytes .../assistant/images/win/resetzoom.png | Bin 0 -> 1134 bytes .../assistant/images/win/synctoc.png | Bin 0 -> 1235 bytes src/assistant/assistant/images/win/zoomin.png | Bin 0 -> 1208 bytes .../assistant/images/win/zoomout.png | Bin 0 -> 1226 bytes src/assistant/assistant/images/wrap.png | Bin 0 -> 500 bytes src/assistant/assistant/indexwindow.cpp | 200 + src/assistant/assistant/indexwindow.h | 53 + src/assistant/assistant/main.cpp | 382 + src/assistant/assistant/mainwindow.cpp | 1154 + src/assistant/assistant/mainwindow.h | 131 + src/assistant/assistant/openpagesmanager.cpp | 354 + src/assistant/assistant/openpagesmanager.h | 89 + src/assistant/assistant/openpagesmodel.cpp | 84 + src/assistant/assistant/openpagesmodel.h | 42 + src/assistant/assistant/openpagesswitcher.cpp | 156 + src/assistant/assistant/openpagesswitcher.h | 47 + src/assistant/assistant/openpageswidget.cpp | 198 + src/assistant/assistant/openpageswidget.h | 54 + src/assistant/assistant/preferencesdialog.cpp | 252 + src/assistant/assistant/preferencesdialog.h | 64 + src/assistant/assistant/preferencesdialog.ui | 314 + src/assistant/assistant/qtdocinstaller.cpp | 91 + src/assistant/assistant/qtdocinstaller.h | 46 + src/assistant/assistant/remotecontrol.cpp | 282 + src/assistant/assistant/remotecontrol.h | 58 + src/assistant/assistant/searchwidget.cpp | 209 + src/assistant/assistant/searchwidget.h | 52 + src/assistant/assistant/stdinlistener.cpp | 50 + src/assistant/assistant/stdinlistener.h | 31 + src/assistant/assistant/stdinlistener_win.cpp | 48 + src/assistant/assistant/stdinlistener_win.h | 30 + src/assistant/assistant/topicchooser.cpp | 117 + src/assistant/assistant/topicchooser.h | 44 + src/assistant/assistant/topicchooser.ui | 111 + src/assistant/assistant/tracer.h | 37 + src/assistant/assistant/xbelsupport.cpp | 189 + src/assistant/assistant/xbelsupport.h | 49 + src/assistant/help/CMakeLists.txt | 83 + src/assistant/help/doc/qthelp.qdocconf | 50 + .../help/doc/snippets/doc_src_qthelp.cpp | 22 + .../help/doc/snippets/doc_src_qthelp.qdoc | 138 + .../help/doc/src/qthelp-examples.qdoc | 23 + src/assistant/help/doc/src/qthelp-index.qdoc | 44 + src/assistant/help/doc/src/qthelp-module.qdoc | 23 + src/assistant/help/doc/src/qthelp-toc.qdoc | 18 + src/assistant/help/doc/src/qthelp.qdoc | 284 + src/assistant/help/images/1leftarrow.png | Bin 0 -> 669 bytes src/assistant/help/images/1rightarrow.png | Bin 0 -> 706 bytes src/assistant/help/images/3leftarrow.png | Bin 0 -> 832 bytes src/assistant/help/images/3rightarrow.png | Bin 0 -> 820 bytes src/assistant/help/images/mac/minus.png | Bin 0 -> 488 bytes src/assistant/help/images/mac/plus.png | Bin 0 -> 810 bytes src/assistant/help/images/win/minus.png | Bin 0 -> 429 bytes src/assistant/help/images/win/plus.png | Bin 0 -> 709 bytes src/assistant/help/qcompressedhelpinfo.cpp | 147 + src/assistant/help/qcompressedhelpinfo.h | 43 + src/assistant/help/qfilternamedialog.cpp | 33 + src/assistant/help/qfilternamedialog.ui | 55 + src/assistant/help/qfilternamedialog_p.h | 43 + src/assistant/help/qhelp_global.cpp | 49 + src/assistant/help/qhelp_global.h | 32 + src/assistant/help/qhelpcollectionhandler.cpp | 2377 ++ src/assistant/help/qhelpcollectionhandler_p.h | 201 + src/assistant/help/qhelpcontentitem.cpp | 102 + src/assistant/help/qhelpcontentitem.h | 38 + src/assistant/help/qhelpcontentwidget.cpp | 311 + src/assistant/help/qhelpcontentwidget.h | 75 + src/assistant/help/qhelpdbreader.cpp | 503 + src/assistant/help/qhelpdbreader_p.h | 100 + src/assistant/help/qhelpengine.cpp | 157 + src/assistant/help/qhelpengine.h | 42 + src/assistant/help/qhelpenginecore.cpp | 942 + src/assistant/help/qhelpenginecore.h | 126 + src/assistant/help/qhelpfilterdata.cpp | 119 + src/assistant/help/qhelpfilterdata.h | 42 + src/assistant/help/qhelpfilterengine.cpp | 295 + src/assistant/help/qhelpfilterengine.h | 62 + .../help/qhelpfiltersettingswidget.cpp | 447 + .../help/qhelpfiltersettingswidget.h | 40 + .../help/qhelpfiltersettingswidget.ui | 83 + src/assistant/help/qhelpindexwidget.cpp | 317 + src/assistant/help/qhelpindexwidget.h | 77 + src/assistant/help/qhelplink.cpp | 24 + src/assistant/help/qhelplink.h | 21 + src/assistant/help/qhelpsearchengine.cpp | 338 + src/assistant/help/qhelpsearchengine.h | 92 + src/assistant/help/qhelpsearchenginecore.cpp | 264 + src/assistant/help/qhelpsearchenginecore.h | 50 + src/assistant/help/qhelpsearchindexreader.cpp | 292 + src/assistant/help/qhelpsearchindexreader_p.h | 62 + src/assistant/help/qhelpsearchindexwriter.cpp | 520 + src/assistant/help/qhelpsearchindexwriter_p.h | 58 + src/assistant/help/qhelpsearchquerywidget.cpp | 324 + src/assistant/help/qhelpsearchquerywidget.h | 54 + src/assistant/help/qhelpsearchresult.cpp | 90 + src/assistant/help/qhelpsearchresult.h | 37 + .../help/qhelpsearchresultwidget.cpp | 268 + src/assistant/help/qhelpsearchresultwidget.h | 39 + src/assistant/help/qoptionswidget.cpp | 186 + src/assistant/help/qoptionswidget_p.h | 61 + src/assistant/plugins/CMakeLists.txt | 6 + src/assistant/plugins/help/CMakeLists.txt | 14 + .../plugins/help/qhelpengineplugin.cpp | 95 + .../plugins/help/qhelpengineplugin.h | 65 + src/assistant/qhelpgenerator/CMakeLists.txt | 64 + .../qhelpgenerator/collectionconfigreader.cpp | 222 + .../qhelpgenerator/collectionconfigreader.h | 81 + .../qhelpgenerator/helpgenerator.cpp | 851 + src/assistant/qhelpgenerator/helpgenerator.h | 35 + src/assistant/qhelpgenerator/main.cpp | 386 + .../qhelpgenerator/qhelpdatainterface.cpp | 232 + .../qhelpgenerator/qhelpdatainterface_p.h | 98 + .../qhelpgenerator/qhelpprojectdata.cpp | 432 + .../qhelpgenerator/qhelpprojectdata_p.h | 46 + src/assistant/qlitehtml/.clang-format | 117 + src/assistant/qlitehtml/.gitmodules | 3 + src/assistant/qlitehtml/.tag | 1 + src/assistant/qlitehtml/CMakeLists.txt | 38 + .../qlitehtml/LICENSES/Apache-2.0.txt | 73 + .../qlitehtml/LICENSES/BSD-3-Clause.txt | 9 + .../qlitehtml/LICENSES/GPL-3.0-only.txt | 674 + .../LICENSES/LicenseRef-Qt-Commercial.txt | 8 + src/assistant/qlitehtml/LICENSES/MIT.txt | 9 + .../LICENSES/Qt-GPL-exception-1.0.txt | 22 + src/assistant/qlitehtml/README.md | 14 + .../qlitehtml/src/3rdparty/GUMBO-AUTHORS.txt | 2 + .../src/3rdparty/litehtml/.github/FUNDING.yml | 13 + .../litehtml/.github/workflows/cmake.yml | 38 + .../src/3rdparty/litehtml/.gitignore | 186 + .../src/3rdparty/litehtml/CMakeLists.txt | 243 + .../qlitehtml/src/3rdparty/litehtml/LICENSE | 24 + .../qlitehtml/src/3rdparty/litehtml/README.md | 38 + .../litehtml/cmake/litehtmlConfig.cmake | 3 + .../containers/cairo/cairo_container.cpp | 988 + .../containers/cairo/cairo_container.h | 103 + .../litehtml/containers/cairo/cairo_font.cpp | 371 + .../litehtml/containers/cairo/cairo_font.h | 117 + .../containers/gdiplus/gdiplus_container.cpp | 238 + .../containers/gdiplus/gdiplus_container.h | 23 + .../containers/haiku/container_haiku.cpp | 568 + .../containers/haiku/container_haiku.h | 87 + .../containers/linux/container_linux.cpp | 908 + .../containers/linux/container_linux.h | 104 + .../litehtml/containers/test/Bitmap.cpp | 129 + .../litehtml/containers/test/Bitmap.h | 36 + .../litehtml/containers/test/Font.cpp | 126 + .../3rdparty/litehtml/containers/test/Font.h | 19 + .../litehtml/containers/test/fonts/OFL.txt | 94 + .../litehtml/containers/test/fonts/readme.txt | 12 + .../test/fonts/terminus-ascii-bold-12px.yaff | 1463 + .../test/fonts/terminus-ascii-bold-14px.yaff | 1656 ++ .../test/fonts/terminus-ascii-bold-16px.yaff | 1848 ++ .../test/fonts/terminus-ascii-bold-18px.yaff | 2040 ++ .../test/fonts/terminus-ascii-bold-20px.yaff | 2232 ++ .../test/fonts/terminus-ascii-bold-22px.yaff | 2424 ++ .../test/fonts/terminus-ascii-bold-24px.yaff | 2616 ++ .../test/fonts/terminus-ascii-bold-28px.yaff | 3000 ++ .../test/fonts/terminus-ascii-bold-32px.yaff | 3383 +++ .../litehtml/containers/test/lodepng.cpp | 6661 +++++ .../litehtml/containers/test/lodepng.h | 2085 ++ .../containers/test/test_container.cpp | 94 + .../litehtml/containers/test/test_container.h | 42 + .../containers/win32/win32_container.cpp | 426 + .../containers/win32/win32_container.h | 72 + .../src/3rdparty/litehtml/include/litehtml.h | 11 + .../litehtml/include/litehtml/background.h | 69 + .../litehtml/include/litehtml/borders.h | 345 + .../litehtml/include/litehtml/codepoint.h | 51 + .../litehtml/include/litehtml/css_length.h | 119 + .../litehtml/include/litehtml/css_margins.h | 44 + .../litehtml/include/litehtml/css_offsets.h | 44 + .../litehtml/include/litehtml/css_position.h | 28 + .../include/litehtml/css_properties.h | 673 + .../litehtml/include/litehtml/css_selector.h | 300 + .../litehtml/include/litehtml/document.h | 134 + .../include/litehtml/document_container.h | 71 + .../litehtml/include/litehtml/el_anchor.h | 18 + .../litehtml/include/litehtml/el_base.h | 17 + .../include/litehtml/el_before_after.h | 39 + .../litehtml/include/litehtml/el_body.h | 17 + .../litehtml/include/litehtml/el_break.h | 17 + .../litehtml/include/litehtml/el_cdata.h | 19 + .../litehtml/include/litehtml/el_comment.h | 25 + .../litehtml/include/litehtml/el_div.h | 17 + .../litehtml/include/litehtml/el_font.h | 17 + .../litehtml/include/litehtml/el_image.h | 29 + .../litehtml/include/litehtml/el_link.h | 18 + .../litehtml/include/litehtml/el_para.h | 18 + .../litehtml/include/litehtml/el_script.h | 21 + .../litehtml/include/litehtml/el_space.h | 21 + .../litehtml/include/litehtml/el_style.h | 21 + .../litehtml/include/litehtml/el_table.h | 25 + .../litehtml/include/litehtml/el_td.h | 17 + .../litehtml/include/litehtml/el_text.h | 31 + .../litehtml/include/litehtml/el_title.h | 18 + .../litehtml/include/litehtml/el_tr.h | 17 + .../litehtml/include/litehtml/element.h | 234 + .../litehtml/include/litehtml/flex_item.h | 137 + .../litehtml/include/litehtml/flex_line.h | 56 + .../include/litehtml/formatting_context.h | 54 + .../3rdparty/litehtml/include/litehtml/html.h | 88 + .../litehtml/include/litehtml/html_tag.h | 139 + .../litehtml/include/litehtml/iterators.h | 84 + .../litehtml/include/litehtml/line_box.h | 170 + .../litehtml/include/litehtml/master_css.h | 376 + .../litehtml/include/litehtml/media_query.h | 77 + .../litehtml/include/litehtml/num_cvt.h | 19 + .../litehtml/include/litehtml/os_types.h | 30 + .../litehtml/include/litehtml/render_block.h | 40 + .../include/litehtml/render_block_context.h | 31 + .../litehtml/include/litehtml/render_flex.h | 33 + .../litehtml/include/litehtml/render_image.h | 25 + .../litehtml/include/litehtml/render_inline.h | 38 + .../include/litehtml/render_inline_context.h | 56 + .../litehtml/include/litehtml/render_item.h | 387 + .../litehtml/include/litehtml/render_table.h | 56 + .../litehtml/include/litehtml/string_id.h | 306 + .../litehtml/include/litehtml/style.h | 213 + .../litehtml/include/litehtml/stylesheet.h | 47 + .../litehtml/include/litehtml/table.h | 248 + .../litehtml/include/litehtml/tstring_view.h | 136 + .../litehtml/include/litehtml/types.h | 1000 + .../3rdparty/litehtml/include/litehtml/url.h | 139 + .../litehtml/include/litehtml/url_path.h | 51 + .../litehtml/include/litehtml/utf8_strings.h | 52 + .../litehtml/include/litehtml/web_color.h | 53 + .../src/3rdparty/litehtml/litehtml.vcxproj | 299 + .../litehtml/litehtml.vcxproj.filters | 410 + .../src/3rdparty/litehtml/src/codepoint.cpp | 82 + .../src/3rdparty/litehtml/src/css_borders.cpp | 7 + .../src/3rdparty/litehtml/src/css_length.cpp | 77 + .../3rdparty/litehtml/src/css_properties.cpp | 465 + .../3rdparty/litehtml/src/css_selector.cpp | 321 + .../src/3rdparty/litehtml/src/document.cpp | 1066 + .../litehtml/src/document_container.cpp | 44 + .../src/3rdparty/litehtml/src/el_anchor.cpp | 26 + .../src/3rdparty/litehtml/src/el_base.cpp | 13 + .../3rdparty/litehtml/src/el_before_after.cpp | 208 + .../src/3rdparty/litehtml/src/el_body.cpp | 12 + .../src/3rdparty/litehtml/src/el_break.cpp | 13 + .../src/3rdparty/litehtml/src/el_cdata.cpp | 20 + .../src/3rdparty/litehtml/src/el_comment.cpp | 25 + .../src/3rdparty/litehtml/src/el_div.cpp | 18 + .../src/3rdparty/litehtml/src/el_font.cpp | 57 + .../src/3rdparty/litehtml/src/el_image.cpp | 120 + .../src/3rdparty/litehtml/src/el_link.cpp | 39 + .../src/3rdparty/litehtml/src/el_para.cpp | 18 + .../src/3rdparty/litehtml/src/el_script.cpp | 30 + .../src/3rdparty/litehtml/src/el_space.cpp | 44 + .../src/3rdparty/litehtml/src/el_style.cpp | 36 + .../src/3rdparty/litehtml/src/el_table.cpp | 55 + .../src/3rdparty/litehtml/src/el_td.cpp | 43 + .../src/3rdparty/litehtml/src/el_text.cpp | 140 + .../src/3rdparty/litehtml/src/el_title.cpp | 16 + .../src/3rdparty/litehtml/src/el_tr.cpp | 28 + .../src/3rdparty/litehtml/src/element.cpp | 478 + .../src/3rdparty/litehtml/src/flex_item.cpp | 488 + .../src/3rdparty/litehtml/src/flex_line.cpp | 454 + .../litehtml/src/formatting_context.cpp | 441 + .../litehtml/src/gumbo/CMakeLists.txt | 75 + .../src/3rdparty/litehtml/src/gumbo/LICENSE | 202 + .../3rdparty/litehtml/src/gumbo/attribute.c | 44 + .../3rdparty/litehtml/src/gumbo/char_ref.c | 23069 ++++++++++++++++ .../3rdparty/litehtml/src/gumbo/char_ref.rl | 2554 ++ .../src/3rdparty/litehtml/src/gumbo/error.c | 279 + .../litehtml/src/gumbo/include/gumbo.h | 675 + .../src/gumbo/include/gumbo/attribute.h | 37 + .../src/gumbo/include/gumbo/char_ref.h | 60 + .../litehtml/src/gumbo/include/gumbo/error.h | 227 + .../src/gumbo/include/gumbo/insertion_mode.h | 57 + .../litehtml/src/gumbo/include/gumbo/parser.h | 57 + .../src/gumbo/include/gumbo/string_buffer.h | 84 + .../src/gumbo/include/gumbo/string_piece.h | 38 + .../src/gumbo/include/gumbo/tag_enum.h | 153 + .../src/gumbo/include/gumbo/tag_gperf.h | 105 + .../src/gumbo/include/gumbo/tag_sizes.h | 4 + .../src/gumbo/include/gumbo/tag_strings.h | 153 + .../src/gumbo/include/gumbo/token_type.h | 41 + .../src/gumbo/include/gumbo/tokenizer.h | 123 + .../gumbo/include/gumbo/tokenizer_states.h | 103 + .../litehtml/src/gumbo/include/gumbo/utf8.h | 132 + .../litehtml/src/gumbo/include/gumbo/util.h | 62 + .../litehtml/src/gumbo/include/gumbo/vector.h | 67 + .../src/3rdparty/litehtml/src/gumbo/parser.c | 4188 +++ .../litehtml/src/gumbo/string_buffer.c | 110 + .../litehtml/src/gumbo/string_piece.c | 48 + .../src/3rdparty/litehtml/src/gumbo/tag.c | 95 + .../src/3rdparty/litehtml/src/gumbo/tag.in | 150 + .../3rdparty/litehtml/src/gumbo/tokenizer.c | 2897 ++ .../src/3rdparty/litehtml/src/gumbo/utf8.c | 270 + .../src/3rdparty/litehtml/src/gumbo/util.c | 58 + .../src/3rdparty/litehtml/src/gumbo/vector.c | 123 + .../src/gumbo/visualc/include/strings.h | 4 + .../src/3rdparty/litehtml/src/html.cpp | 290 + .../src/3rdparty/litehtml/src/html_tag.cpp | 1708 ++ .../src/3rdparty/litehtml/src/iterators.cpp | 101 + .../src/3rdparty/litehtml/src/line_box.cpp | 717 + .../src/3rdparty/litehtml/src/media_query.cpp | 430 + .../src/3rdparty/litehtml/src/num_cvt.cpp | 108 + .../3rdparty/litehtml/src/render_block.cpp | 349 + .../litehtml/src/render_block_context.cpp | 154 + .../src/3rdparty/litehtml/src/render_flex.cpp | 455 + .../3rdparty/litehtml/src/render_image.cpp | 148 + .../litehtml/src/render_inline_context.cpp | 411 + .../src/3rdparty/litehtml/src/render_item.cpp | 1100 + .../3rdparty/litehtml/src/render_table.cpp | 496 + .../src/3rdparty/litehtml/src/string_id.cpp | 54 + .../src/3rdparty/litehtml/src/strtod.cpp | 275 + .../src/3rdparty/litehtml/src/style.cpp | 1220 + .../src/3rdparty/litehtml/src/stylesheet.cpp | 225 + .../src/3rdparty/litehtml/src/table.cpp | 609 + .../3rdparty/litehtml/src/tstring_view.cpp | 46 + .../src/3rdparty/litehtml/src/url.cpp | 163 + .../src/3rdparty/litehtml/src/url_path.cpp | 86 + .../3rdparty/litehtml/src/utf8_strings.cpp | 99 + .../src/3rdparty/litehtml/src/web_color.cpp | 274 + .../3rdparty/litehtml/test/codepoint_test.cpp | 148 + .../src/3rdparty/litehtml/test/cssTest.cpp | 164 + .../src/3rdparty/litehtml/test/dirent.h | 1235 + .../3rdparty/litehtml/test/mediaQueryTest.cpp | 242 + .../litehtml/test/render/-table-caption.htm | 36 + .../3rdparty/litehtml/test/render/acid1.htm | 177 + .../litehtml/test/render/acid1.htm.png | Bin 0 -> 3553 bytes .../3rdparty/litehtml/test/render/counter.htm | 79 + .../litehtml/test/render/counter.htm.png | Bin 0 -> 3300 bytes .../test/render/css-1-line-height.htm | 19 + .../test/render/css-1-line-height.htm.png | Bin 0 -> 321 bytes .../test/render/css-2-invalid-color.htm | 11 + .../test/render/css-2-invalid-color.htm.png | Bin 0 -> 151 bytes .../--flex-minimum-height-flex-items-003.htm | 54 + .../flex/--flexbox-abspos-child-001a.htm | 57 + .../flex/--flexbox-abspos-child-001b.htm | 58 + .../flex/--flexbox-abspos-child-002.htm | 65 + .../--flexbox-baseline-single-item-001a.htm | 55 + .../--flexbox-baseline-single-item-001b.htm | 56 + .../--flexbox-flex-basis-content-002a.htm | 87 + .../--flexbox-flex-basis-content-002b.htm | 87 + .../--flexbox-flex-basis-content-003a.htm | 124 + .../--flexbox-flex-basis-content-003b.htm | 125 + .../--flexbox-flex-basis-content-004a.htm | 130 + .../flex/--flexbox-gap-position-absolute.htm | 30 + ...flexbox-items-as-stacking-contexts-001.htm | 116 + .../render/flex/--flexbox-mbp-horiz-004.htm | 72 + .../flex/--flexbox-min-height-auto-001.htm | 105 + .../flex/--flexbox-min-height-auto-003.htm | 60 + .../flex/--flexbox-min-height-auto-004.htm | 66 + .../flex/--flexbox-min-width-auto-001.htm | 103 + .../flex/--flexbox-min-width-auto-003.htm | 58 + .../flex/--flexbox-min-width-auto-004.htm | 64 + .../flex/--flexbox-paint-ordering-001.htm | 92 + .../flex/--flexbox-paint-ordering-002.htm | 164 + .../flex/--flexbox-single-line-clamp-3.htm | 42 + .../flex/--flexbox-sizing-horiz-001.htm | 86 + .../flex/--flexbox-sizing-horiz-002.htm | 63 + .../render/flex/--flexbox-sizing-vert-001.htm | 100 + .../render/flex/--flexbox-sizing-vert-002.htm | 64 + .../--flexbox-with-pseudo-elements-003.htm | 69 + .../test/render/flex/--flexbox_fbfc.htm | 35 + .../test/render/flex/--flexbox_fbfc2.htm | 33 + .../--flexbox_flex-formatting-interop.htm | 41 + .../--flexbox_flex-natural-mixed-basis.htm | 39 + ...exbox_flex-natural-variable-zero-basis.htm | 39 + .../--flexbox_flex-none-wrappable-content.htm | 26 + .../render/flex/--flexbox_inline-abspos.htm | 20 + .../render/flex/--flexbox_inline-float.htm | 20 + .../test/render/flex/--flexbox_inline.htm | 20 + .../--flexbox_order-abspos-space-around.htm | 39 + .../flex/--flexbox_stf-table-singleline-2.htm | 41 + .../flex/--flexbox_table-fixed-layout.htm | 58 + .../render/flex/--flexbox_width-overflow.htm | 30 + .../render/flex/--multiline-shrink-to-fit.htm | 77 + .../render/flex/--percentage-heights-008.htm | 39 + .../render/flex/--percentage-heights-010.htm | 18 + .../test/render/flex/-align-content-001.htm | 42 + .../test/render/flex/-align-content-002.htm | 41 + .../test/render/flex/-align-content-003.htm | 41 + .../test/render/flex/-align-content-004.htm | 43 + .../test/render/flex/-align-content-005.htm | 41 + .../test/render/flex/-align-items-001.htm | 39 + .../test/render/flex/-align-items-002.htm | 40 + .../test/render/flex/-align-items-003.htm | 40 + .../test/render/flex/-align-items-004.htm | 53 + .../flex/-contain-layout-baseline-002.htm | 39 + .../-contain-layout-suppress-baseline-002.htm | 75 + .../flex/-content-height-with-scrollbars.htm | 47 + .../render/flex/-cross-axis-scrollbar.htm | 147 + .../render/flex/-css-box-justify-content.htm | 34 + .../-css-flexbox-row-reverse-wrap-reverse.htm | 47 + .../flex/-css-flexbox-row-reverse-wrap.htm | 47 + .../render/flex/-css-flexbox-row-reverse.htm | 56 + .../flex/-css-flexbox-row-wrap-reverse.htm | 46 + .../render/flex/-css-flexbox-row-wrap.htm | 47 + .../test/render/flex/-css-flexbox-row.htm | 56 + .../test/render/flex/-css-flexbox-test1.htm | 54 + .../render/flex/-direction-upright-002.htm | 142 + .../litehtml/test/render/flex/-flex-002.htm | 53 + .../litehtml/test/render/flex/-flex-003.htm | 54 + .../litehtml/test/render/flex/-flex-004.htm | 54 + .../render/flex/-flex-aspect-ratio-019.htm | 18 + .../render/flex/-flex-aspect-ratio-020.htm | 18 + .../render/flex/-flex-aspect-ratio-021.htm | 18 + .../render/flex/-flex-aspect-ratio-022.htm | 18 + .../flex/-flex-direction-row-002-visual.htm | 45 + .../flex/-flex-direction-row-vertical.htm | 51 + .../test/render/flex/-flex-inline.htm | 33 + .../-flex-minimum-height-flex-items-022.htm | 15 + .../-flex-minimum-width-flex-items-001.htm | 44 + .../-flex-minimum-width-flex-items-003.htm | 45 + .../-flex-minimum-width-flex-items-009.htm | 32 + .../flex/-flexbox-abspos-child-001b.htm | 54 + .../render/flex/-flexbox-abspos-child-002.htm | 61 + ...-flexbox-align-self-baseline-horiz-006.htm | 56 + ...-flexbox-align-self-baseline-horiz-008.htm | 57 + .../-flexbox-align-self-horiz-001-table.htm | 101 + .../flex/-flexbox-align-self-horiz-002.htm | 100 + .../flex/-flexbox-align-self-vert-002.htm | 96 + .../flex/-flexbox-align-self-vert-rtl-001.htm | 97 + .../flex/-flexbox-align-self-vert-rtl-002.htm | 82 + .../flex/-flexbox-align-self-vert-rtl-003.htm | 72 + .../flex/-flexbox-align-self-vert-rtl-004.htm | 90 + .../flex/-flexbox-align-self-vert-rtl-005.htm | 94 + .../flex/-flexbox-anonymous-items-001.htm | 25 + .../flex/-flexbox-basic-block-horiz-001v.htm | 75 + .../flex/-flexbox-basic-block-vert-001v.htm | 77 + .../-flexbox-collapsed-item-baseline-001.htm | 57 + .../-flexbox-collapsed-item-horiz-001.htm | 101 + .../-flexbox-collapsed-item-horiz-002.htm | 114 + .../-flexbox-collapsed-item-horiz-003.htm | 59 + .../flex/-flexbox-flex-basis-content-001a.htm | 86 + .../flex/-flexbox-flex-basis-content-001b.htm | 86 + .../flex/-flexbox-flex-basis-content-004b.htm | 131 + .../flex/-flexbox-flex-wrap-default.htm | 39 + .../flex/-flexbox-flex-wrap-vert-001.htm | 98 + .../flex/-flexbox-flex-wrap-vert-002.htm | 60 + .../flex/-flexbox-flex-wrap-wrap-reverse.htm | 60 + .../-flexbox-justify-content-wmvert-001.htm | 144 + .../-flexbox-mbp-horiz-001-rtl-reverse.htm | 75 + .../flex/-flexbox-mbp-horiz-001-rtl.htm | 73 + .../render/flex/-flexbox-mbp-horiz-002v.htm | 87 + .../render/flex/-flexbox-mbp-horiz-003v.htm | 81 + .../flex/-flexbox-min-width-auto-002a.htm | 66 + .../flex/-flexbox-min-width-auto-002b.htm | 66 + .../flex/-flexbox-min-width-auto-002c.htm | 68 + .../flex/-flexbox-min-width-auto-005.htm | 38 + .../flex/-flexbox-min-width-auto-006.htm | 45 + .../flex/-flexbox-overflow-horiz-001.htm.htm | 54 + .../-flexbox-whitespace-handling-001a.htm | 54 + .../-flexbox-whitespace-handling-001b.htm | 42 + .../render/flex/-flexbox-writing-mode-001.htm | 81 + .../render/flex/-flexbox-writing-mode-002.htm | 81 + .../render/flex/-flexbox-writing-mode-003.htm | 81 + .../render/flex/-flexbox-writing-mode-004.htm | 81 + .../render/flex/-flexbox-writing-mode-005.htm | 81 + .../render/flex/-flexbox-writing-mode-006.htm | 81 + .../render/flex/-flexbox-writing-mode-007.htm | 78 + .../render/flex/-flexbox-writing-mode-008.htm | 78 + .../render/flex/-flexbox-writing-mode-009.htm | 78 + .../render/flex/-flexbox-writing-mode-010.htm | 87 + .../render/flex/-flexbox-writing-mode-011.htm | 88 + .../render/flex/-flexbox-writing-mode-012.htm | 89 + .../render/flex/-flexbox-writing-mode-013.htm | 88 + .../render/flex/-flexbox-writing-mode-014.htm | 87 + .../render/flex/-flexbox-writing-mode-015.htm | 88 + .../render/flex/-flexbox-writing-mode-016.htm | 147 + ...xbox_align-items-stretch-writing-modes.htm | 62 + .../test/render/flex/-flexbox_block.htm | 19 + .../flex/-flexbox_columns-flexitems-2.htm | 32 + .../flex/-flexbox_columns-flexitems.htm | 30 + .../-flexbox_rowspan-overflow-automatic.htm | 69 + .../render/flex/-flexbox_rowspan-overflow.htm | 68 + .../test/render/flex/-flexbox_rowspan.htm | 68 + .../render/flex/-flexbox_rtl-direction.htm | 38 + .../render/flex/-flexbox_rtl-flow-reverse.htm | 38 + .../test/render/flex/-flexbox_rtl-flow.htm | 38 + .../test/render/flex/-flexbox_rtl-order.htm | 57 + .../flex/-flexbox_stf-table-singleline.htm | 38 + ...l_lays_out_contents_from_top_to_bottom.htm | 64 + .../test/render/flex/-justify-content-001.htm | 41 + .../test/render/flex/-justify-content-002.htm | 38 + .../test/render/flex/-justify-content-003.htm | 37 + .../test/render/flex/-justify-content-004.htm | 41 + .../test/render/flex/-justify-content-005.htm | 41 + .../flex/-negative-flex-margins-crash.htm | 26 + .../-nested-orthogonal-flexbox-relayout.htm | 34 + .../flex/-percentage-size-subitems-001.htm | 98 + .../flex/-table-as-item-fixed-min-width-3.htm | 25 + .../flex/-table-as-item-flex-cross-size.htm | 17 + .../-table-as-item-inflexible-in-column-1.htm | 19 + .../-table-as-item-inflexible-in-column-2.htm | 22 + .../-table-as-item-inflexible-in-row-1.htm | 19 + .../-table-as-item-inflexible-in-row-2.htm | 22 + .../flex/-table-as-item-narrow-content-2.htm | 19 + .../-table-as-item-percent-width-cell-001.htm | 41 + .../flex/-table-as-item-specified-height.htm | 20 + .../flex/-table-as-item-specified-width.htm | 21 + .../-table-as-item-stretch-cross-size-2.htm | 18 + .../-table-as-item-stretch-cross-size-5.htm | 43 + .../-table-as-item-stretch-cross-size.htm | 17 + .../render/flex/abspos-autopos-htb-ltr.htm | 39 + .../flex/abspos-autopos-htb-ltr.htm.png | Bin 0 -> 583 bytes .../render/flex/abspos-autopos-htb-rtl.htm | 39 + .../flex/abspos-autopos-htb-rtl.htm.png | Bin 0 -> 583 bytes .../render/flex/abspos-autopos-vlr-ltr.htm | 39 + .../flex/abspos-autopos-vlr-ltr.htm.png | Bin 0 -> 583 bytes .../render/flex/abspos-autopos-vlr-rtl.htm | 39 + .../flex/abspos-autopos-vlr-rtl.htm.png | Bin 0 -> 583 bytes .../render/flex/abspos-autopos-vrl-ltr.htm | 39 + .../flex/abspos-autopos-vrl-ltr.htm.png | Bin 0 -> 583 bytes .../render/flex/abspos-autopos-vrl-rtl.htm | 39 + .../flex/abspos-autopos-vrl-rtl.htm.png | Bin 0 -> 583 bytes .../test/render/flex/align-baseline.htm | 30 + .../test/render/flex/align-baseline.htm.png | Bin 0 -> 675 bytes .../test/render/flex/align-content-006.htm | 40 + .../render/flex/align-content-006.htm.png | Bin 0 -> 516 bytes .../test/render/flex/align-content_center.htm | 31 + .../render/flex/align-content_center.htm.png | Bin 0 -> 1828 bytes .../render/flex/align-content_flex-end.htm | 31 + .../flex/align-content_flex-end.htm.png | Bin 0 -> 1810 bytes .../render/flex/align-content_flex-start.htm | 32 + .../flex/align-content_flex-start.htm.png | Bin 0 -> 1828 bytes .../flex/align-content_space-around.htm | 31 + .../flex/align-content_space-around.htm.png | Bin 0 -> 2800 bytes .../flex/align-content_space-between.htm | 31 + .../flex/align-content_space-between.htm.png | Bin 0 -> 2561 bytes .../render/flex/align-content_stretch.htm | 31 + .../render/flex/align-content_stretch.htm.png | Bin 0 -> 2278 bytes .../test/render/flex/align-items-004.htm.png | Bin 0 -> 516 bytes .../test/render/flex/align-items-005.htm | 39 + .../test/render/flex/align-items-005.htm.png | Bin 0 -> 516 bytes .../test/render/flex/align-items-006.htm | 42 + .../test/render/flex/align-items-006.htm.png | Bin 0 -> 516 bytes .../test/render/flex/align-self-001.htm | 45 + .../test/render/flex/align-self-001.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-002.htm | 45 + .../test/render/flex/align-self-002.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-003.htm | 52 + .../test/render/flex/align-self-003.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-004.htm | 39 + .../test/render/flex/align-self-004.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-005.htm | 46 + .../test/render/flex/align-self-005.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-006.htm | 52 + .../test/render/flex/align-self-006.htm.png | Bin 0 -> 1026 bytes .../test/render/flex/align-self-007.htm | 47 + .../test/render/flex/align-self-007.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-008.htm | 46 + .../test/render/flex/align-self-008.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-009.htm | 53 + .../test/render/flex/align-self-009.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-010.htm | 53 + .../test/render/flex/align-self-010.htm.png | Bin 0 -> 1026 bytes .../test/render/flex/align-self-011.htm | 39 + .../test/render/flex/align-self-011.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-012.htm | 40 + .../test/render/flex/align-self-012.htm.png | Bin 0 -> 577 bytes .../test/render/flex/align-self-013.htm | 46 + .../test/render/flex/align-self-013.htm.png | Bin 0 -> 577 bytes .../test/render/flex/anonymous-block.htm | 17 + .../test/render/flex/anonymous-block.htm.png | Bin 0 -> 564 bytes ...-height-column-with-border-and-padding.htm | 17 + ...ght-column-with-border-and-padding.htm.png | Bin 0 -> 828 bytes .../render/flex/auto-height-with-flex.htm | 14 + .../render/flex/auto-height-with-flex.htm.png | Bin 0 -> 345 bytes .../flex/contain-layout-baseline-002.htm.png | Bin 0 -> 577 bytes .../render/flex/css-box-justify-content.htm | 38 + .../flex/css-box-justify-content.htm.png | Bin 0 -> 1212 bytes .../test/render/flex/display-flex-001.htm | 36 + .../test/render/flex/display-flex-001.htm.png | Bin 0 -> 516 bytes .../litehtml/test/render/flex/flex-001.htm | 40 + .../test/render/flex/flex-001.htm.png | Bin 0 -> 992 bytes .../litehtml/test/render/flex/flex-002.htm | 57 + .../test/render/flex/flex-002.htm.png | Bin 0 -> 992 bytes .../litehtml/test/render/flex/flex-003.htm | 58 + .../test/render/flex/flex-003.htm.png | Bin 0 -> 992 bytes .../litehtml/test/render/flex/flex-004.htm | 58 + .../test/render/flex/flex-004.htm.png | Bin 0 -> 992 bytes .../render/flex/flex-align-content-center.htm | 45 + .../flex/flex-align-content-center.htm.png | Bin 0 -> 771 bytes .../render/flex/flex-align-content-end.htm | 45 + .../flex/flex-align-content-end.htm.png | Bin 0 -> 1050 bytes .../flex/flex-align-content-space-around.htm | 45 + .../flex-align-content-space-around.htm.png | Bin 0 -> 1050 bytes .../flex/flex-align-content-space-between.htm | 45 + .../flex-align-content-space-between.htm.png | Bin 0 -> 1046 bytes .../render/flex/flex-align-content-start.htm | 45 + .../flex/flex-align-content-start.htm.png | Bin 0 -> 1057 bytes .../litehtml/test/render/flex/flex-base.htm | 35 + .../test/render/flex/flex-base.htm.png | Bin 0 -> 586 bytes .../test/render/flex/flex-basis-001.htm | 39 + .../test/render/flex/flex-basis-001.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-002.htm | 47 + .../test/render/flex/flex-basis-002.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-003.htm | 47 + .../test/render/flex/flex-basis-003.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-004.htm | 49 + .../test/render/flex/flex-basis-004.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-005.htm | 34 + .../test/render/flex/flex-basis-005.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-006.htm | 34 + .../test/render/flex/flex-basis-006.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-007.htm | 40 + .../test/render/flex/flex-basis-007.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-008.htm | 39 + .../test/render/flex/flex-basis-008.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-010.htm | 36 + .../test/render/flex/flex-basis-010.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-basis-011.htm | 30 + .../test/render/flex/flex-basis-011.htm.png | Bin 0 -> 789 bytes .../test/render/flex/flex-box-wrap.htm | 51 + .../test/render/flex/flex-box-wrap.htm.png | Bin 0 -> 886 bytes .../render/flex/flex-container-margin.htm | 34 + .../render/flex/flex-container-margin.htm.png | Bin 0 -> 223 bytes .../flex/flex-direction-column-001-visual.htm | 29 + .../flex-direction-column-001-visual.htm.png | Bin 0 -> 1810 bytes ...ex-direction-column-reverse-001-visual.htm | 29 + ...irection-column-reverse-001-visual.htm.png | Bin 0 -> 2024 bytes ...ex-direction-column-reverse-002-visual.htm | 43 + ...irection-column-reverse-002-visual.htm.png | Bin 0 -> 406 bytes .../flex/flex-direction-column-reverse.htm | 42 + .../flex-direction-column-reverse.htm.png | Bin 0 -> 1041 bytes .../render/flex/flex-direction-column.htm | 42 + .../render/flex/flex-direction-column.htm.png | Bin 0 -> 980 bytes .../flex/flex-direction-row-001-visual.htm | 29 + .../flex-direction-row-001-visual.htm.png | Bin 0 -> 1733 bytes .../flex-direction-row-reverse-001-visual.htm | 29 + ...x-direction-row-reverse-001-visual.htm.png | Bin 0 -> 1948 bytes .../flex-direction-row-reverse-002-visual.htm | 42 + ...x-direction-row-reverse-002-visual.htm.png | Bin 0 -> 374 bytes .../flex/flex-direction-row-reverse.htm | 40 + .../flex/flex-direction-row-reverse.htm.png | Bin 0 -> 929 bytes .../test/render/flex/flex-direction.htm | 58 + .../test/render/flex/flex-direction.htm.png | Bin 0 -> 2837 bytes .../render/flex/flex-flexitem-childmargin.htm | 52 + .../flex/flex-flexitem-childmargin.htm.png | Bin 0 -> 851 bytes .../flex-flexitem-percentage-prescation.htm | 37 + ...lex-flexitem-percentage-prescation.htm.png | Bin 0 -> 171 bytes .../test/render/flex/flex-flow-001.htm | 39 + .../test/render/flex/flex-flow-001.htm.png | Bin 0 -> 1069 bytes .../test/render/flex/flex-flow-002.htm | 40 + .../test/render/flex/flex-flow-002.htm.png | Bin 0 -> 1024 bytes .../test/render/flex/flex-flow-003.htm | 41 + .../test/render/flex/flex-flow-003.htm.png | Bin 0 -> 1024 bytes .../test/render/flex/flex-flow-004.htm | 40 + .../test/render/flex/flex-flow-004.htm.png | Bin 0 -> 1069 bytes .../test/render/flex/flex-flow-005.htm | 41 + .../test/render/flex/flex-flow-005.htm.png | Bin 0 -> 1024 bytes .../test/render/flex/flex-flow-006.htm | 41 + .../test/render/flex/flex-flow-006.htm.png | Bin 0 -> 1024 bytes .../test/render/flex/flex-flow-007.htm | 39 + .../test/render/flex/flex-flow-007.htm.png | Bin 0 -> 922 bytes .../test/render/flex/flex-flow-008.htm | 41 + .../test/render/flex/flex-flow-008.htm.png | Bin 0 -> 1024 bytes .../test/render/flex/flex-flow-009.htm | 41 + .../test/render/flex/flex-flow-009.htm.png | Bin 0 -> 1024 bytes .../test/render/flex/flex-flow-010.htm | 39 + .../test/render/flex/flex-flow-010.htm.png | Bin 0 -> 922 bytes .../test/render/flex/flex-flow-011.htm | 41 + .../test/render/flex/flex-flow-011.htm.png | Bin 0 -> 1024 bytes .../test/render/flex/flex-flow-012.htm | 41 + .../test/render/flex/flex-flow-012.htm.png | Bin 0 -> 1024 bytes .../test/render/flex/flex-grow-001.htm | 46 + .../test/render/flex/flex-grow-001.htm.png | Bin 0 -> 169 bytes .../test/render/flex/flex-grow-002.htm | 50 + .../test/render/flex/flex-grow-002.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-grow-003.htm | 49 + .../test/render/flex/flex-grow-003.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-grow-004.htm | 49 + .../test/render/flex/flex-grow-004.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-grow-005.htm | 43 + .../test/render/flex/flex-grow-005.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-grow-006.htm | 42 + .../test/render/flex/flex-grow-006.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-grow-007.htm | 49 + .../test/render/flex/flex-grow-007.htm.png | Bin 0 -> 577 bytes .../render/flex/flex-margin-no-collapse.htm | 58 + .../flex/flex-margin-no-collapse.htm.png | Bin 0 -> 754 bytes .../flex-minimum-height-flex-items-001.htm | 52 + ...flex-minimum-height-flex-items-001.htm.png | Bin 0 -> 577 bytes .../flex-minimum-height-flex-items-002.htm | 50 + ...flex-minimum-height-flex-items-002.htm.png | Bin 0 -> 610 bytes .../flex-minimum-height-flex-items-011.htm | 54 + ...flex-minimum-height-flex-items-011.htm.png | Bin 0 -> 577 bytes .../flex-minimum-width-flex-items-002.htm | 48 + .../flex-minimum-width-flex-items-002.htm.png | Bin 0 -> 577 bytes .../litehtml/test/render/flex/flex-order.htm | 40 + .../test/render/flex/flex-order.htm.png | Bin 0 -> 197 bytes .../test/render/flex/flex-shrink-001.htm | 47 + .../test/render/flex/flex-shrink-001.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-shrink-002.htm | 47 + .../test/render/flex/flex-shrink-002.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-shrink-003.htm | 44 + .../test/render/flex/flex-shrink-003.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-shrink-004.htm | 48 + .../test/render/flex/flex-shrink-004.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-shrink-005.htm | 47 + .../test/render/flex/flex-shrink-005.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-shrink-006.htm | 51 + .../test/render/flex/flex-shrink-006.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-shrink-007.htm | 44 + .../test/render/flex/flex-shrink-007.htm.png | Bin 0 -> 577 bytes .../test/render/flex/flex-shrink-008.htm | 49 + .../test/render/flex/flex-shrink-008.htm.png | Bin 0 -> 577 bytes .../flex/flexbox-abspos-child-001a.htm.png | Bin 0 -> 111 bytes .../flex/flexbox-abspos-child-001b.htm.png | Bin 0 -> 111 bytes .../flexbox-align-items-center-nested-001.htm | 52 + ...xbox-align-items-center-nested-001.htm.png | Bin 0 -> 121 bytes ...flexbox-align-self-baseline-horiz-001a.htm | 72 + ...box-align-self-baseline-horiz-001a.htm.png | Bin 0 -> 2293 bytes ...flexbox-align-self-baseline-horiz-001b.htm | 75 + ...box-align-self-baseline-horiz-001b.htm.png | Bin 0 -> 2293 bytes .../flexbox-align-self-baseline-horiz-002.htm | 92 + ...xbox-align-self-baseline-horiz-002.htm.png | Bin 0 -> 401 bytes .../flexbox-align-self-baseline-horiz-003.htm | 94 + ...xbox-align-self-baseline-horiz-003.htm.png | Bin 0 -> 398 bytes .../flexbox-align-self-baseline-horiz-004.htm | 58 + ...xbox-align-self-baseline-horiz-004.htm.png | Bin 0 -> 335 bytes .../flexbox-align-self-baseline-horiz-005.htm | 58 + ...xbox-align-self-baseline-horiz-005.htm.png | Bin 0 -> 336 bytes .../flexbox-align-self-baseline-horiz-007.htm | 46 + ...xbox-align-self-baseline-horiz-007.htm.png | Bin 0 -> 913 bytes .../flexbox-align-self-horiz-001-block.htm | 99 + ...flexbox-align-self-horiz-001-block.htm.png | Bin 0 -> 1438 bytes .../flex/flexbox-align-self-horiz-003.htm | 99 + .../flex/flexbox-align-self-horiz-003.htm.png | Bin 0 -> 1182 bytes .../flex/flexbox-align-self-horiz-004.htm | 86 + .../flex/flexbox-align-self-horiz-004.htm.png | Bin 0 -> 1113 bytes .../flex/flexbox-align-self-horiz-005.htm | 106 + .../flex/flexbox-align-self-horiz-005.htm.png | Bin 0 -> 2876 bytes .../flexbox-align-self-stretch-vert-001.htm | 52 + ...lexbox-align-self-stretch-vert-001.htm.png | Bin 0 -> 144 bytes .../flexbox-align-self-stretch-vert-002.htm | 38 + ...lexbox-align-self-stretch-vert-002.htm.png | Bin 0 -> 146 bytes .../flex/flexbox-align-self-vert-001.htm | 94 + .../flex/flexbox-align-self-vert-001.htm.png | Bin 0 -> 1145 bytes .../flex/flexbox-align-self-vert-003.htm | 70 + .../flex/flexbox-align-self-vert-003.htm.png | Bin 0 -> 616 bytes .../flex/flexbox-align-self-vert-004.htm | 81 + .../flex/flexbox-align-self-vert-004.htm.png | Bin 0 -> 805 bytes .../flex/flexbox-anonymous-items-001.htm | 29 + .../flex/flexbox-anonymous-items-001.htm.png | Bin 0 -> 145 bytes ...baseline-align-self-baseline-horiz-001.htm | 64 + ...line-align-self-baseline-horiz-001.htm.png | Bin 0 -> 281 bytes ...-baseline-align-self-baseline-vert-001.htm | 68 + ...eline-align-self-baseline-vert-001.htm.png | Bin 0 -> 305 bytes .../flex/flexbox-baseline-empty-001a.htm | 49 + .../flex/flexbox-baseline-empty-001a.htm.png | Bin 0 -> 180 bytes .../flex/flexbox-baseline-empty-001b.htm | 50 + .../flex/flexbox-baseline-empty-001b.htm.png | Bin 0 -> 180 bytes ...flexbox-baseline-multi-item-horiz-001a.htm | 47 + ...box-baseline-multi-item-horiz-001a.htm.png | Bin 0 -> 192 bytes ...flexbox-baseline-multi-item-horiz-001b.htm | 49 + ...box-baseline-multi-item-horiz-001b.htm.png | Bin 0 -> 192 bytes .../flexbox-baseline-multi-item-vert-001a.htm | 57 + ...xbox-baseline-multi-item-vert-001a.htm.png | Bin 0 -> 187 bytes .../flexbox-baseline-multi-item-vert-001b.htm | 59 + ...xbox-baseline-multi-item-vert-001b.htm.png | Bin 0 -> 187 bytes .../flexbox-baseline-multi-line-horiz-001.htm | 76 + ...xbox-baseline-multi-line-horiz-001.htm.png | Bin 0 -> 242 bytes .../flexbox-baseline-multi-line-horiz-002.htm | 76 + ...xbox-baseline-multi-line-horiz-002.htm.png | Bin 0 -> 256 bytes .../flexbox-baseline-multi-line-horiz-003.htm | 76 + ...xbox-baseline-multi-line-horiz-003.htm.png | Bin 0 -> 319 bytes .../flexbox-baseline-multi-line-vert-001.htm | 77 + ...exbox-baseline-multi-line-vert-001.htm.png | Bin 0 -> 251 bytes .../flexbox-baseline-multi-line-vert-002.htm | 78 + ...exbox-baseline-multi-line-vert-002.htm.png | Bin 0 -> 255 bytes .../flex/flexbox-basic-block-horiz-001.htm | 67 + .../flexbox-basic-block-horiz-001.htm.png | Bin 0 -> 470 bytes .../flex/flexbox-basic-block-vert-001.htm | 68 + .../flex/flexbox-basic-block-vert-001.htm.png | Bin 0 -> 328 bytes .../flex/flexbox-break-request-horiz-001a.htm | 111 + .../flexbox-break-request-horiz-001a.htm.png | Bin 0 -> 337 bytes .../flex/flexbox-break-request-horiz-001b.htm | 111 + .../flexbox-break-request-horiz-001b.htm.png | Bin 0 -> 337 bytes .../flex/flexbox-break-request-horiz-002a.htm | 110 + .../flexbox-break-request-horiz-002a.htm.png | Bin 0 -> 329 bytes .../flex/flexbox-break-request-horiz-002b.htm | 110 + .../flexbox-break-request-horiz-002b.htm.png | Bin 0 -> 329 bytes .../flex/flexbox-break-request-vert-001a.htm | 112 + .../flexbox-break-request-vert-001a.htm.png | Bin 0 -> 508 bytes .../flex/flexbox-break-request-vert-001b.htm | 112 + .../flexbox-break-request-vert-001b.htm.png | Bin 0 -> 508 bytes .../flex/flexbox-break-request-vert-002a.htm | 111 + .../flexbox-break-request-vert-002a.htm.png | Bin 0 -> 479 bytes .../flex/flexbox-break-request-vert-002b.htm | 111 + .../flexbox-break-request-vert-002b.htm.png | Bin 0 -> 479 bytes ...exbox-flex-basis-content-nocanvas-001a.htm | 83 + ...x-flex-basis-content-nocanvas-001a.htm.png | Bin 0 -> 530 bytes ...exbox-flex-basis-content-nocanvas-001b.htm | 86 + ...x-flex-basis-content-nocanvas-001b.htm.png | Bin 0 -> 537 bytes .../flexbox-flex-direction-column-reverse.htm | 61 + ...xbox-flex-direction-column-reverse.htm.png | Bin 0 -> 1202 bytes .../flex/flexbox-flex-direction-column.htm | 60 + .../flexbox-flex-direction-column.htm.png | Bin 0 -> 1202 bytes .../flex/flexbox-flex-direction-default.htm | 59 + .../flexbox-flex-direction-default.htm.png | Bin 0 -> 1202 bytes .../flexbox-flex-direction-row-reverse.htm | 61 + ...flexbox-flex-direction-row-reverse.htm.png | Bin 0 -> 1202 bytes .../flex/flexbox-flex-direction-row.htm | 60 + .../flex/flexbox-flex-direction-row.htm.png | Bin 0 -> 1202 bytes .../render/flex/flexbox-flex-flow-001.htm | 129 + .../render/flex/flexbox-flex-flow-001.htm.png | Bin 0 -> 2059 bytes .../render/flex/flexbox-flex-flow-002.htm | 129 + .../render/flex/flexbox-flex-flow-002.htm.png | Bin 0 -> 1987 bytes .../render/flex/flexbox-flex-wrap-default.htm | 43 + .../flex/flexbox-flex-wrap-default.htm.png | Bin 0 -> 578 bytes .../render/flex/flexbox-flex-wrap-flexing.htm | 40 + .../flex/flexbox-flex-wrap-flexing.htm.png | Bin 0 -> 568 bytes .../flex/flexbox-flex-wrap-horiz-001.htm | 100 + .../flex/flexbox-flex-wrap-horiz-001.htm.png | Bin 0 -> 335 bytes .../flex/flexbox-flex-wrap-horiz-002.htm | 64 + .../flex/flexbox-flex-wrap-horiz-002.htm.png | Bin 0 -> 211 bytes .../render/flex/flexbox-flex-wrap-nowrap.htm | 44 + .../flex/flexbox-flex-wrap-nowrap.htm.png | Bin 0 -> 578 bytes .../flex/flexbox-flex-wrap-vert-001.htm | 102 + .../flex/flexbox-flex-wrap-vert-001.htm.png | Bin 0 -> 316 bytes .../flex/flexbox-flex-wrap-vert-002.htm | 64 + .../flex/flexbox-flex-wrap-vert-002.htm.png | Bin 0 -> 207 bytes .../render/flex/flexbox-flex-wrap-wrap.htm | 60 + .../flex/flexbox-flex-wrap-wrap.htm.png | Bin 0 -> 1202 bytes ...flexbox-items-as-stacking-contexts-002.htm | 73 + ...box-items-as-stacking-contexts-002.htm.png | Bin 0 -> 724 bytes ...flexbox-items-as-stacking-contexts-003.htm | 73 + ...box-items-as-stacking-contexts-003.htm.png | Bin 0 -> 168 bytes .../flexbox-justify-content-horiz-001a.htm | 141 + ...flexbox-justify-content-horiz-001a.htm.png | Bin 0 -> 324 bytes .../flexbox-justify-content-horiz-001b.htm | 147 + ...flexbox-justify-content-horiz-001b.htm.png | Bin 0 -> 324 bytes .../flexbox-justify-content-horiz-002.htm | 154 + .../flexbox-justify-content-horiz-002.htm.png | Bin 0 -> 1268 bytes .../flexbox-justify-content-horiz-003.htm | 149 + .../flexbox-justify-content-horiz-003.htm.png | Bin 0 -> 429 bytes .../flexbox-justify-content-horiz-004.htm | 160 + .../flexbox-justify-content-horiz-004.htm.png | Bin 0 -> 914 bytes .../flexbox-justify-content-horiz-005.htm | 140 + .../flexbox-justify-content-horiz-005.htm.png | Bin 0 -> 866 bytes .../flexbox-justify-content-horiz-006.htm | 142 + .../flexbox-justify-content-horiz-006.htm.png | Bin 0 -> 401 bytes .../flexbox-justify-content-vert-001a.htm | 143 + .../flexbox-justify-content-vert-001a.htm.png | Bin 0 -> 450 bytes .../flexbox-justify-content-vert-001b.htm | 144 + .../flexbox-justify-content-vert-001b.htm.png | Bin 0 -> 450 bytes .../flex/flexbox-justify-content-vert-002.htm | 156 + .../flexbox-justify-content-vert-002.htm.png | Bin 0 -> 901 bytes .../flex/flexbox-justify-content-vert-003.htm | 152 + .../flexbox-justify-content-vert-003.htm.png | Bin 0 -> 312 bytes .../flex/flexbox-justify-content-vert-004.htm | 163 + .../flexbox-justify-content-vert-004.htm.png | Bin 0 -> 982 bytes .../flex/flexbox-justify-content-vert-005.htm | 146 + .../flexbox-justify-content-vert-005.htm.png | Bin 0 -> 696 bytes .../flex/flexbox-justify-content-vert-006.htm | 143 + .../flexbox-justify-content-vert-006.htm.png | Bin 0 -> 464 bytes .../flex/flexbox-margin-auto-horiz-001.htm | 85 + .../flexbox-margin-auto-horiz-001.htm.png | Bin 0 -> 345 bytes .../flex/flexbox-margin-auto-horiz-002.htm | 70 + .../flexbox-margin-auto-horiz-002.htm.png | Bin 0 -> 293 bytes .../flex/flexbox-mbp-horiz-001-reverse.htm | 73 + .../flexbox-mbp-horiz-001-reverse.htm.png | Bin 0 -> 442 bytes .../render/flex/flexbox-mbp-horiz-001.htm | 71 + .../render/flex/flexbox-mbp-horiz-001.htm.png | Bin 0 -> 439 bytes .../render/flex/flexbox-mbp-horiz-002a.htm | 75 + .../flex/flexbox-mbp-horiz-002a.htm.png | Bin 0 -> 456 bytes .../render/flex/flexbox-mbp-horiz-002b.htm | 82 + .../flex/flexbox-mbp-horiz-002b.htm.png | Bin 0 -> 456 bytes .../flex/flexbox-mbp-horiz-003-reverse.htm | 77 + .../flexbox-mbp-horiz-003-reverse.htm.png | Bin 0 -> 372 bytes .../render/flex/flexbox-mbp-horiz-003.htm | 76 + .../render/flex/flexbox-mbp-horiz-003.htm.png | Bin 0 -> 368 bytes .../render/flex/flexbox-order-from-lowest.htm | 39 + .../flex/flexbox-order-from-lowest.htm.png | Bin 0 -> 496 bytes .../flex/flexbox-order-only-flexitems.htm | 40 + .../flex/flexbox-order-only-flexitems.htm.png | Bin 0 -> 492 bytes .../flex/flexbox-overflow-horiz-001.htm | 58 + .../flex/flexbox-overflow-horiz-001.htm.png | Bin 0 -> 242 bytes .../flex/flexbox-overflow-horiz-002.htm | 54 + .../flex/flexbox-overflow-horiz-002.htm.png | Bin 0 -> 206 bytes .../flex/flexbox-overflow-horiz-003.htm | 51 + .../flex/flexbox-overflow-horiz-003.htm.png | Bin 0 -> 184 bytes .../flex/flexbox-overflow-horiz-004.htm | 51 + .../flex/flexbox-overflow-horiz-004.htm.png | Bin 0 -> 180 bytes .../flex/flexbox-overflow-horiz-005.htm | 53 + .../flex/flexbox-overflow-horiz-005.htm.png | Bin 0 -> 161 bytes .../render/flex/flexbox-overflow-vert-001.htm | 58 + .../flex/flexbox-overflow-vert-001.htm.png | Bin 0 -> 205 bytes .../render/flex/flexbox-overflow-vert-002.htm | 54 + .../flex/flexbox-overflow-vert-002.htm.png | Bin 0 -> 189 bytes .../render/flex/flexbox-overflow-vert-003.htm | 51 + .../flex/flexbox-overflow-vert-003.htm.png | Bin 0 -> 173 bytes .../render/flex/flexbox-overflow-vert-004.htm | 51 + .../flex/flexbox-overflow-vert-004.htm.png | Bin 0 -> 162 bytes .../render/flex/flexbox-overflow-vert-005.htm | 53 + .../flex/flexbox-overflow-vert-005.htm.png | Bin 0 -> 160 bytes .../flex/flexbox-paint-ordering-003.htm | 55 + .../flex/flexbox-paint-ordering-003.htm.png | Bin 0 -> 141 bytes .../render/flex/flexbox-root-node-001a.htm | 26 + .../flex/flexbox-root-node-001a.htm.png | Bin 0 -> 146 bytes .../render/flex/flexbox-root-node-001b.htm | 24 + .../flex/flexbox-root-node-001b.htm.png | Bin 0 -> 146 bytes .../flex/flexbox-single-line-clamp-1.htm | 36 + .../flex/flexbox-single-line-clamp-1.htm.png | Bin 0 -> 404 bytes .../flex/flexbox-single-line-clamp-2.htm | 43 + .../flex/flexbox-single-line-clamp-2.htm.png | Bin 0 -> 415 bytes .../render/flex/flexbox-table-fixup-001.htm | 64 + .../flex/flexbox-table-fixup-001.htm.png | Bin 0 -> 297 bytes .../flex/flexbox-whitespace-handling-002.htm | 55 + .../flexbox-whitespace-handling-002.htm.png | Bin 0 -> 902 bytes .../flex/flexbox-with-pseudo-elements-001.htm | 58 + .../flexbox-with-pseudo-elements-001.htm.png | Bin 0 -> 524 bytes .../flex/flexbox-with-pseudo-elements-002.htm | 81 + .../flexbox-with-pseudo-elements-002.htm.png | Bin 0 -> 685 bytes .../render/flex/flexbox_absolute-atomic.htm | 33 + .../flex/flexbox_absolute-atomic.htm.png | Bin 0 -> 257 bytes .../flex/flexbox_align-content-center.htm | 41 + .../flex/flexbox_align-content-center.htm.png | Bin 0 -> 353 bytes .../flex/flexbox_align-content-flexend.htm | 41 + .../flexbox_align-content-flexend.htm.png | Bin 0 -> 352 bytes .../flex/flexbox_align-content-flexstart.htm | 41 + .../flexbox_align-content-flexstart.htm.png | Bin 0 -> 352 bytes .../flexbox_align-content-spacearound.htm | 41 + .../flexbox_align-content-spacearound.htm.png | Bin 0 -> 354 bytes .../flexbox_align-content-spacebetween.htm | 41 + ...flexbox_align-content-spacebetween.htm.png | Bin 0 -> 354 bytes .../flex/flexbox_align-content-stretch-2.htm | 40 + .../flexbox_align-content-stretch-2.htm.png | Bin 0 -> 349 bytes .../flex/flexbox_align-content-stretch.htm | 41 + .../flexbox_align-content-stretch.htm.png | Bin 0 -> 354 bytes .../flex/flexbox_align-items-baseline.htm | 47 + .../flex/flexbox_align-items-baseline.htm.png | Bin 0 -> 397 bytes .../flex/flexbox_align-items-center-2.htm | 46 + .../flex/flexbox_align-items-center-2.htm.png | Bin 0 -> 393 bytes .../flex/flexbox_align-items-center.htm | 40 + .../flex/flexbox_align-items-center.htm.png | Bin 0 -> 396 bytes .../flex/flexbox_align-items-flexend-2.htm | 45 + .../flexbox_align-items-flexend-2.htm.png | Bin 0 -> 375 bytes .../flex/flexbox_align-items-flexend.htm | 39 + .../flex/flexbox_align-items-flexend.htm.png | Bin 0 -> 396 bytes .../flex/flexbox_align-items-flexstart-2.htm | 45 + .../flexbox_align-items-flexstart-2.htm.png | Bin 0 -> 397 bytes .../flex/flexbox_align-items-flexstart.htm | 39 + .../flexbox_align-items-flexstart.htm.png | Bin 0 -> 398 bytes .../flex/flexbox_align-items-stretch-2.htm | 36 + .../flexbox_align-items-stretch-2.htm.png | Bin 0 -> 149 bytes .../flex/flexbox_align-items-stretch.htm | 44 + .../flex/flexbox_align-items-stretch.htm.png | Bin 0 -> 382 bytes .../render/flex/flexbox_align-self-auto.htm | 42 + .../flex/flexbox_align-self-auto.htm.png | Bin 0 -> 396 bytes .../flex/flexbox_align-self-baseline.htm | 43 + .../flex/flexbox_align-self-baseline.htm.png | Bin 0 -> 392 bytes .../render/flex/flexbox_align-self-center.htm | 43 + .../flex/flexbox_align-self-center.htm.png | Bin 0 -> 388 bytes .../flex/flexbox_align-self-flexend.htm | 42 + .../flex/flexbox_align-self-flexend.htm.png | Bin 0 -> 392 bytes .../flex/flexbox_align-self-flexstart.htm | 42 + .../flex/flexbox_align-self-flexstart.htm.png | Bin 0 -> 390 bytes .../flex/flexbox_align-self-stretch.htm | 44 + .../flex/flexbox_align-self-stretch.htm.png | Bin 0 -> 387 bytes .../test/render/flex/flexbox_box-clear.htm | 34 + .../render/flex/flexbox_box-clear.htm.png | Bin 0 -> 896 bytes .../test/render/flex/flexbox_columns.htm | 30 + .../test/render/flex/flexbox_columns.htm.png | Bin 0 -> 103 bytes .../flex/flexbox_direction-column-reverse.htm | 36 + .../flexbox_direction-column-reverse.htm.png | Bin 0 -> 588 bytes .../render/flex/flexbox_direction-column.htm | 33 + .../flex/flexbox_direction-column.htm.png | Bin 0 -> 562 bytes .../flex/flexbox_direction-row-reverse.htm | 36 + .../flexbox_direction-row-reverse.htm.png | Bin 0 -> 210 bytes .../test/render/flex/flexbox_display.htm | 29 + .../test/render/flex/flexbox_display.htm.png | Bin 0 -> 559 bytes .../test/render/flex/flexbox_first-line.htm | 40 + .../render/flex/flexbox_first-line.htm.png | Bin 0 -> 978 bytes .../flex/flexbox_flex-0-0-0-unitless.htm | 40 + .../flex/flexbox_flex-0-0-0-unitless.htm.png | Bin 0 -> 621 bytes .../test/render/flex/flexbox_flex-0-0-0.htm | 40 + .../render/flex/flexbox_flex-0-0-0.htm.png | Bin 0 -> 621 bytes .../flexbox_flex-0-0-1-unitless-basis.htm | 40 + .../flexbox_flex-0-0-1-unitless-basis.htm.png | Bin 0 -> 603 bytes .../render/flex/flexbox_flex-0-0-N-shrink.htm | 40 + .../flex/flexbox_flex-0-0-N-shrink.htm.png | Bin 0 -> 401 bytes .../flexbox_flex-0-0-N-unitless-basis.htm | 40 + .../flexbox_flex-0-0-N-unitless-basis.htm.png | Bin 0 -> 603 bytes .../test/render/flex/flexbox_flex-0-0-N.htm | 40 + .../render/flex/flexbox_flex-0-0-N.htm.png | Bin 0 -> 605 bytes .../flex/flexbox_flex-0-0-Npercent-shrink.htm | 40 + .../flexbox_flex-0-0-Npercent-shrink.htm.png | Bin 0 -> 441 bytes .../render/flex/flexbox_flex-0-0-Npercent.htm | 40 + .../flex/flexbox_flex-0-0-Npercent.htm.png | Bin 0 -> 558 bytes .../flex/flexbox_flex-0-0-auto-shrink.htm | 40 + .../flex/flexbox_flex-0-0-auto-shrink.htm.png | Bin 0 -> 424 bytes .../render/flex/flexbox_flex-0-0-auto.htm | 40 + .../render/flex/flexbox_flex-0-0-auto.htm.png | Bin 0 -> 603 bytes .../test/render/flex/flexbox_flex-0-0.htm | 40 + .../test/render/flex/flexbox_flex-0-0.htm.png | Bin 0 -> 621 bytes .../flex/flexbox_flex-0-1-0-unitless.htm | 40 + .../flex/flexbox_flex-0-1-0-unitless.htm.png | Bin 0 -> 621 bytes .../test/render/flex/flexbox_flex-0-1-0.htm | 40 + .../render/flex/flexbox_flex-0-1-0.htm.png | Bin 0 -> 621 bytes .../flexbox_flex-0-1-1-unitless-basis.htm | 40 + .../flexbox_flex-0-1-1-unitless-basis.htm.png | Bin 0 -> 603 bytes .../render/flex/flexbox_flex-0-1-N-shrink.htm | 40 + .../flex/flexbox_flex-0-1-N-shrink.htm.png | Bin 0 -> 368 bytes .../flexbox_flex-0-1-N-unitless-basis.htm | 40 + .../flexbox_flex-0-1-N-unitless-basis.htm.png | Bin 0 -> 603 bytes .../test/render/flex/flexbox_flex-0-1-N.htm | 40 + .../render/flex/flexbox_flex-0-1-N.htm.png | Bin 0 -> 605 bytes .../flex/flexbox_flex-0-1-Npercent-shrink.htm | 40 + .../flexbox_flex-0-1-Npercent-shrink.htm.png | Bin 0 -> 368 bytes .../render/flex/flexbox_flex-0-1-Npercent.htm | 40 + .../flex/flexbox_flex-0-1-Npercent.htm.png | Bin 0 -> 558 bytes .../flex/flexbox_flex-0-1-auto-shrink.htm | 40 + .../flex/flexbox_flex-0-1-auto-shrink.htm.png | Bin 0 -> 399 bytes .../render/flex/flexbox_flex-0-1-auto.htm | 40 + .../render/flex/flexbox_flex-0-1-auto.htm.png | Bin 0 -> 603 bytes .../test/render/flex/flexbox_flex-0-1.htm | 40 + .../test/render/flex/flexbox_flex-0-1.htm.png | Bin 0 -> 621 bytes .../flex/flexbox_flex-0-N-0-unitless.htm | 40 + .../flex/flexbox_flex-0-N-0-unitless.htm.png | Bin 0 -> 621 bytes .../test/render/flex/flexbox_flex-0-N-0.htm | 40 + .../render/flex/flexbox_flex-0-N-0.htm.png | Bin 0 -> 621 bytes .../render/flex/flexbox_flex-0-N-N-shrink.htm | 40 + .../flex/flexbox_flex-0-N-N-shrink.htm.png | Bin 0 -> 368 bytes .../test/render/flex/flexbox_flex-0-N-N.htm | 40 + .../render/flex/flexbox_flex-0-N-N.htm.png | Bin 0 -> 605 bytes .../flex/flexbox_flex-0-N-Npercent-shrink.htm | 40 + .../flexbox_flex-0-N-Npercent-shrink.htm.png | Bin 0 -> 368 bytes .../render/flex/flexbox_flex-0-N-Npercent.htm | 40 + .../flex/flexbox_flex-0-N-Npercent.htm.png | Bin 0 -> 558 bytes .../flex/flexbox_flex-0-N-auto-shrink.htm | 40 + .../flex/flexbox_flex-0-N-auto-shrink.htm.png | Bin 0 -> 399 bytes .../render/flex/flexbox_flex-0-N-auto.htm | 40 + .../render/flex/flexbox_flex-0-N-auto.htm.png | Bin 0 -> 603 bytes .../test/render/flex/flexbox_flex-0-N.htm | 40 + .../test/render/flex/flexbox_flex-0-N.htm.png | Bin 0 -> 621 bytes .../test/render/flex/flexbox_flex-0-auto.htm | 45 + .../render/flex/flexbox_flex-0-auto.htm.png | Bin 0 -> 472 bytes .../flex/flexbox_flex-1-0-0-unitless.htm | 40 + .../flex/flexbox_flex-1-0-0-unitless.htm.png | Bin 0 -> 556 bytes .../test/render/flex/flexbox_flex-1-0-0.htm | 40 + .../render/flex/flexbox_flex-1-0-0.htm.png | Bin 0 -> 556 bytes .../render/flex/flexbox_flex-1-0-N-shrink.htm | 40 + .../flex/flexbox_flex-1-0-N-shrink.htm.png | Bin 0 -> 401 bytes .../test/render/flex/flexbox_flex-1-0-N.htm | 40 + .../render/flex/flexbox_flex-1-0-N.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-1-0-Npercent-shrink.htm | 40 + .../flexbox_flex-1-0-Npercent-shrink.htm.png | Bin 0 -> 441 bytes .../render/flex/flexbox_flex-1-0-Npercent.htm | 40 + .../flex/flexbox_flex-1-0-Npercent.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-1-0-auto-shrink.htm | 40 + .../flex/flexbox_flex-1-0-auto-shrink.htm.png | Bin 0 -> 424 bytes .../render/flex/flexbox_flex-1-0-auto.htm | 40 + .../render/flex/flexbox_flex-1-0-auto.htm.png | Bin 0 -> 554 bytes .../test/render/flex/flexbox_flex-1-0.htm | 40 + .../test/render/flex/flexbox_flex-1-0.htm.png | Bin 0 -> 556 bytes .../flex/flexbox_flex-1-1-0-unitless.htm | 40 + .../flex/flexbox_flex-1-1-0-unitless.htm.png | Bin 0 -> 556 bytes .../test/render/flex/flexbox_flex-1-1-0.htm | 40 + .../render/flex/flexbox_flex-1-1-0.htm.png | Bin 0 -> 556 bytes .../render/flex/flexbox_flex-1-1-N-shrink.htm | 40 + .../flex/flexbox_flex-1-1-N-shrink.htm.png | Bin 0 -> 368 bytes .../test/render/flex/flexbox_flex-1-1-N.htm | 40 + .../render/flex/flexbox_flex-1-1-N.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-1-1-Npercent-shrink.htm | 40 + .../flexbox_flex-1-1-Npercent-shrink.htm.png | Bin 0 -> 368 bytes .../render/flex/flexbox_flex-1-1-Npercent.htm | 40 + .../flex/flexbox_flex-1-1-Npercent.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-1-1-auto-shrink.htm | 40 + .../flex/flexbox_flex-1-1-auto-shrink.htm.png | Bin 0 -> 399 bytes .../render/flex/flexbox_flex-1-1-auto.htm | 40 + .../render/flex/flexbox_flex-1-1-auto.htm.png | Bin 0 -> 554 bytes .../test/render/flex/flexbox_flex-1-1.htm | 40 + .../test/render/flex/flexbox_flex-1-1.htm.png | Bin 0 -> 556 bytes .../flex/flexbox_flex-1-N-0-unitless.htm | 40 + .../flex/flexbox_flex-1-N-0-unitless.htm.png | Bin 0 -> 556 bytes .../test/render/flex/flexbox_flex-1-N-0.htm | 40 + .../render/flex/flexbox_flex-1-N-0.htm.png | Bin 0 -> 556 bytes .../render/flex/flexbox_flex-1-N-N-shrink.htm | 40 + .../flex/flexbox_flex-1-N-N-shrink.htm.png | Bin 0 -> 368 bytes .../test/render/flex/flexbox_flex-1-N-N.htm | 40 + .../render/flex/flexbox_flex-1-N-N.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-1-N-Npercent-shrink.htm | 40 + .../flexbox_flex-1-N-Npercent-shrink.htm.png | Bin 0 -> 368 bytes .../render/flex/flexbox_flex-1-N-Npercent.htm | 40 + .../flex/flexbox_flex-1-N-Npercent.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-1-N-auto-shrink.htm | 40 + .../flex/flexbox_flex-1-N-auto-shrink.htm.png | Bin 0 -> 399 bytes .../render/flex/flexbox_flex-1-N-auto.htm | 40 + .../render/flex/flexbox_flex-1-N-auto.htm.png | Bin 0 -> 554 bytes .../test/render/flex/flexbox_flex-1-N.htm | 40 + .../test/render/flex/flexbox_flex-1-N.htm.png | Bin 0 -> 556 bytes .../flex/flexbox_flex-N-0-0-unitless.htm | 40 + .../flex/flexbox_flex-N-0-0-unitless.htm.png | Bin 0 -> 556 bytes .../test/render/flex/flexbox_flex-N-0-0.htm | 40 + .../render/flex/flexbox_flex-N-0-0.htm.png | Bin 0 -> 556 bytes .../render/flex/flexbox_flex-N-0-N-shrink.htm | 40 + .../flex/flexbox_flex-N-0-N-shrink.htm.png | Bin 0 -> 401 bytes .../test/render/flex/flexbox_flex-N-0-N.htm | 40 + .../render/flex/flexbox_flex-N-0-N.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-N-0-Npercent-shrink.htm | 40 + .../flexbox_flex-N-0-Npercent-shrink.htm.png | Bin 0 -> 441 bytes .../render/flex/flexbox_flex-N-0-Npercent.htm | 40 + .../flex/flexbox_flex-N-0-Npercent.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-N-0-auto-shrink.htm | 40 + .../flex/flexbox_flex-N-0-auto-shrink.htm.png | Bin 0 -> 426 bytes .../render/flex/flexbox_flex-N-0-auto.htm | 40 + .../render/flex/flexbox_flex-N-0-auto.htm.png | Bin 0 -> 554 bytes .../test/render/flex/flexbox_flex-N-0.htm | 40 + .../test/render/flex/flexbox_flex-N-0.htm.png | Bin 0 -> 556 bytes .../flex/flexbox_flex-N-1-0-unitless.htm | 40 + .../flex/flexbox_flex-N-1-0-unitless.htm.png | Bin 0 -> 556 bytes .../test/render/flex/flexbox_flex-N-1-0.htm | 40 + .../render/flex/flexbox_flex-N-1-0.htm.png | Bin 0 -> 556 bytes .../render/flex/flexbox_flex-N-1-N-shrink.htm | 40 + .../flex/flexbox_flex-N-1-N-shrink.htm.png | Bin 0 -> 368 bytes .../test/render/flex/flexbox_flex-N-1-N.htm | 40 + .../render/flex/flexbox_flex-N-1-N.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-N-1-Npercent-shrink.htm | 40 + .../flexbox_flex-N-1-Npercent-shrink.htm.png | Bin 0 -> 368 bytes .../render/flex/flexbox_flex-N-1-Npercent.htm | 40 + .../flex/flexbox_flex-N-1-Npercent.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-N-1-auto-shrink.htm | 40 + .../flex/flexbox_flex-N-1-auto-shrink.htm.png | Bin 0 -> 399 bytes .../render/flex/flexbox_flex-N-1-auto.htm | 40 + .../render/flex/flexbox_flex-N-1-auto.htm.png | Bin 0 -> 554 bytes .../test/render/flex/flexbox_flex-N-1.htm | 40 + .../test/render/flex/flexbox_flex-N-1.htm.png | Bin 0 -> 556 bytes .../flex/flexbox_flex-N-N-0-unitless.htm | 40 + .../flex/flexbox_flex-N-N-0-unitless.htm.png | Bin 0 -> 556 bytes .../test/render/flex/flexbox_flex-N-N-0.htm | 40 + .../render/flex/flexbox_flex-N-N-0.htm.png | Bin 0 -> 556 bytes .../render/flex/flexbox_flex-N-N-N-shrink.htm | 40 + .../flex/flexbox_flex-N-N-N-shrink.htm.png | Bin 0 -> 368 bytes .../test/render/flex/flexbox_flex-N-N-N.htm | 40 + .../render/flex/flexbox_flex-N-N-N.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-N-N-Npercent-shrink.htm | 40 + .../flexbox_flex-N-N-Npercent-shrink.htm.png | Bin 0 -> 368 bytes .../render/flex/flexbox_flex-N-N-Npercent.htm | 40 + .../flex/flexbox_flex-N-N-Npercent.htm.png | Bin 0 -> 554 bytes .../flex/flexbox_flex-N-N-auto-shrink.htm | 40 + .../flex/flexbox_flex-N-N-auto-shrink.htm.png | Bin 0 -> 399 bytes .../render/flex/flexbox_flex-N-N-auto.htm | 40 + .../render/flex/flexbox_flex-N-N-auto.htm.png | Bin 0 -> 554 bytes .../test/render/flex/flexbox_flex-N-N.htm | 40 + .../test/render/flex/flexbox_flex-N-N.htm.png | Bin 0 -> 556 bytes .../test/render/flex/flexbox_flex-auto.htm | 46 + .../render/flex/flexbox_flex-auto.htm.png | Bin 0 -> 532 bytes .../render/flex/flexbox_flex-basis-shrink.htm | 40 + .../flex/flexbox_flex-basis-shrink.htm.png | Bin 0 -> 452 bytes .../test/render/flex/flexbox_flex-basis.htm | 42 + .../render/flex/flexbox_flex-basis.htm.png | Bin 0 -> 495 bytes .../render/flex/flexbox_flex-initial-2.htm | 45 + .../flex/flexbox_flex-initial-2.htm.png | Bin 0 -> 581 bytes .../test/render/flex/flexbox_flex-initial.htm | 45 + .../render/flex/flexbox_flex-initial.htm.png | Bin 0 -> 472 bytes .../flexbox_flex-natural-mixed-basis-auto.htm | 42 + ...xbox_flex-natural-mixed-basis-auto.htm.png | Bin 0 -> 396 bytes ...exbox_flex-natural-variable-auto-basis.htm | 39 + ...x_flex-natural-variable-auto-basis.htm.png | Bin 0 -> 470 bytes .../test/render/flex/flexbox_flex-natural.htm | 50 + .../render/flex/flexbox_flex-natural.htm.png | Bin 0 -> 1095 bytes .../test/render/flex/flexbox_flex-none.htm | 46 + .../render/flex/flexbox_flex-none.htm.png | Bin 0 -> 529 bytes ...exbox_flow-column-reverse-wrap-reverse.htm | 36 + ...x_flow-column-reverse-wrap-reverse.htm.png | Bin 0 -> 322 bytes .../flex/flexbox_flow-column-reverse-wrap.htm | 36 + .../flexbox_flow-column-reverse-wrap.htm.png | Bin 0 -> 324 bytes .../flex/flexbox_flow-column-wrap-reverse.htm | 35 + .../flexbox_flow-column-wrap-reverse.htm.png | Bin 0 -> 326 bytes .../render/flex/flexbox_flow-column-wrap.htm | 35 + .../flex/flexbox_flow-column-wrap.htm.png | Bin 0 -> 327 bytes .../flex/flexbox_flow-row-wrap-reverse.htm | 34 + .../flexbox_flow-row-wrap-reverse.htm.png | Bin 0 -> 315 bytes .../render/flex/flexbox_flow-row-wrap.htm | 34 + .../render/flex/flexbox_flow-row-wrap.htm.png | Bin 0 -> 315 bytes .../render/flex/flexbox_generated-flex.htm | 27 + .../flex/flexbox_generated-flex.htm.png | Bin 0 -> 241 bytes .../flex/flexbox_generated-nested-flex.htm | 27 + .../flexbox_generated-nested-flex.htm.png | Bin 0 -> 241 bytes .../test/render/flex/flexbox_generated.htm | 32 + .../render/flex/flexbox_generated.htm.png | Bin 0 -> 251 bytes .../render/flex/flexbox_item-bottom-float.htm | 34 + .../flex/flexbox_item-bottom-float.htm.png | Bin 0 -> 163 bytes .../test/render/flex/flexbox_item-clear.htm | 34 + .../render/flex/flexbox_item-clear.htm.png | Bin 0 -> 684 bytes .../test/render/flex/flexbox_item-float.htm | 32 + .../render/flex/flexbox_item-float.htm.png | Bin 0 -> 224 bytes .../render/flex/flexbox_item-top-float.htm | 33 + .../flex/flexbox_item-top-float.htm.png | Bin 0 -> 163 bytes .../flex/flexbox_item-vertical-align.htm | 38 + .../flex/flexbox_item-vertical-align.htm.png | Bin 0 -> 208 bytes .../flex/flexbox_justifycontent-center.htm | 40 + .../flexbox_justifycontent-center.htm.png | Bin 0 -> 423 bytes .../flex/flexbox_justifycontent-flex-end.htm | 40 + .../flexbox_justifycontent-flex-end.htm.png | Bin 0 -> 422 bytes .../flexbox_justifycontent-flex-start.htm | 40 + .../flexbox_justifycontent-flex-start.htm.png | Bin 0 -> 425 bytes ...ox_justifycontent-spacearound-negative.htm | 39 + ...ustifycontent-spacearound-negative.htm.png | Bin 0 -> 359 bytes ...lexbox_justifycontent-spacearound-only.htm | 35 + ...ox_justifycontent-spacearound-only.htm.png | Bin 0 -> 230 bytes .../flexbox_justifycontent-spacearound.htm | 40 + ...flexbox_justifycontent-spacearound.htm.png | Bin 0 -> 425 bytes ...x_justifycontent-spacebetween-negative.htm | 39 + ...stifycontent-spacebetween-negative.htm.png | Bin 0 -> 361 bytes ...exbox_justifycontent-spacebetween-only.htm | 35 + ...x_justifycontent-spacebetween-only.htm.png | Bin 0 -> 233 bytes .../flexbox_justifycontent-spacebetween.htm | 40 + ...lexbox_justifycontent-spacebetween.htm.png | Bin 0 -> 427 bytes .../flex/flexbox_margin-auto-overflow.htm | 35 + .../flex/flexbox_margin-auto-overflow.htm.png | Bin 0 -> 348 bytes .../test/render/flex/flexbox_margin-auto.htm | 33 + .../render/flex/flexbox_margin-auto.htm.png | Bin 0 -> 277 bytes .../render/flex/flexbox_margin-left-ex.htm | 33 + .../flex/flexbox_margin-left-ex.htm.png | Bin 0 -> 344 bytes .../test/render/flex/flexbox_margin.htm | 22 + .../test/render/flex/flexbox_margin.htm.png | Bin 0 -> 122 bytes .../test/render/flex/flexbox_nested-flex.htm | 28 + .../render/flex/flexbox_nested-flex.htm.png | Bin 0 -> 241 bytes .../test/render/flex/flexbox_object.htm | 26 + .../test/render/flex/flexbox_object.htm.png | Bin 0 -> 388 bytes .../test/render/flex/flexbox_order-box.htm | 43 + .../render/flex/flexbox_order-box.htm.png | Bin 0 -> 426 bytes .../flex/flexbox_order-noninteger-invalid.htm | 44 + .../flexbox_order-noninteger-invalid.htm.png | Bin 0 -> 92 bytes .../test/render/flex/flexbox_order.htm | 54 + .../test/render/flex/flexbox_order.htm.png | Bin 0 -> 436 bytes .../test/render/flex/flexbox_stf-abspos.htm | 38 + .../render/flex/flexbox_stf-abspos.htm.png | Bin 0 -> 116 bytes .../test/render/flex/flexbox_stf-fixpos.htm | 38 + .../render/flex/flexbox_stf-fixpos.htm.png | Bin 0 -> 78 bytes .../test/render/flex/flexbox_stf-float.htm | 38 + .../render/flex/flexbox_stf-float.htm.png | Bin 0 -> 116 bytes .../render/flex/flexbox_stf-inline-block.htm | 38 + .../flex/flexbox_stf-inline-block.htm.png | Bin 0 -> 116 bytes .../render/flex/flexbox_stf-table-caption.htm | 38 + .../flex/flexbox_stf-table-caption.htm.png | Bin 0 -> 81 bytes .../render/flex/flexbox_stf-table-cell.htm | 38 + .../flex/flexbox_stf-table-cell.htm.png | Bin 0 -> 116 bytes .../flex/flexbox_stf-table-row-group.htm | 38 + .../flex/flexbox_stf-table-row-group.htm.png | Bin 0 -> 116 bytes .../render/flex/flexbox_stf-table-row.htm | 38 + .../render/flex/flexbox_stf-table-row.htm.png | Bin 0 -> 116 bytes .../test/render/flex/flexbox_stf-table.htm | 38 + .../render/flex/flexbox_stf-table.htm.png | Bin 0 -> 116 bytes ...xbox_visibility-collapse-line-wrapping.htm | 40 + ..._visibility-collapse-line-wrapping.htm.png | Bin 0 -> 395 bytes .../flex/flexbox_visibility-collapse.htm | 34 + .../flex/flexbox_visibility-collapse.htm.png | Bin 0 -> 318 bytes .../flex/flexbox_width-overflow.htm.png | Bin 0 -> 80 bytes .../test/render/flex/flexbox_wrap-long.htm | 37 + .../render/flex/flexbox_wrap-long.htm.png | Bin 0 -> 340 bytes .../test/render/flex/flexbox_wrap-reverse.htm | 34 + .../render/flex/flexbox_wrap-reverse.htm.png | Bin 0 -> 315 bytes .../test/render/flex/flexbox_wrap.htm | 34 + .../test/render/flex/flexbox_wrap.htm.png | Bin 0 -> 315 bytes .../test/render/flex/flexible-box-float.htm | 46 + .../flex/flexible-box-float.htm.chrome.png | Bin 0 -> 14012 bytes .../render/flex/flexible-box-float.htm.png | Bin 0 -> 815 bytes .../test/render/flex/flexible-order.htm | 70 + .../test/render/flex/flexible-order.htm.png | Bin 0 -> 184 bytes ...able-with-infinite-max-intrinsic-width.htm | 21 + ...-with-infinite-max-intrinsic-width.htm.png | Bin 0 -> 575 bytes .../render/flex/justify-content_center.htm | 45 + .../flex/justify-content_center.htm.png | Bin 0 -> 2927 bytes .../render/flex/justify-content_flex-end.htm | 44 + .../flex/justify-content_flex-end.htm.png | Bin 0 -> 2104 bytes .../flex/justify-content_flex-start.htm | 44 + .../flex/justify-content_flex-start.htm.png | Bin 0 -> 2087 bytes .../flex/justify-content_space-around.htm | 44 + .../flex/justify-content_space-around.htm.png | Bin 0 -> 3057 bytes .../justify-content_space-between-001.htm | 44 + .../justify-content_space-between-001.htm.png | Bin 0 -> 2933 bytes .../layout-algorithm_algo-cross-line-001.htm | 36 + ...yout-algorithm_algo-cross-line-001.htm.png | Bin 0 -> 783 bytes .../layout-algorithm_algo-cross-line-002.htm | 37 + ...yout-algorithm_algo-cross-line-002.htm.png | Bin 0 -> 783 bytes ...multi-line-wrap-reverse-column-reverse.htm | 74 + ...i-line-wrap-reverse-column-reverse.htm.png | Bin 0 -> 1622 bytes .../multi-line-wrap-reverse-row-reverse.htm | 68 + ...ulti-line-wrap-reverse-row-reverse.htm.png | Bin 0 -> 362 bytes .../multi-line-wrap-with-column-reverse.htm | 67 + ...ulti-line-wrap-with-column-reverse.htm.png | Bin 0 -> 636 bytes .../flex/multi-line-wrap-with-row-reverse.htm | 64 + .../multi-line-wrap-with-row-reverse.htm.png | Bin 0 -> 384 bytes .../flex/multiline-reverse-wrap-baseline.htm | 61 + .../multiline-reverse-wrap-baseline.htm.png | Bin 0 -> 1312 bytes .../flex/negative-flex-margins-crash.htm.png | Bin 0 -> 180 bytes .../test/render/flex/negative-margins-001.htm | 51 + .../render/flex/negative-margins-001.htm.png | Bin 0 -> 587 bytes .../litehtml/test/render/flex/order-001.htm | 42 + .../test/render/flex/order-001.htm.png | Bin 0 -> 992 bytes .../render/flex/order-with-column-reverse.htm | 44 + .../flex/order-with-column-reverse.htm.png | Bin 0 -> 545 bytes .../render/flex/order-with-row-reverse.htm | 42 + .../flex/order-with-row-reverse.htm.png | Bin 0 -> 555 bytes .../render/flex/padding-overflow-crash.htm | 15 + .../flex/padding-overflow-crash.htm.png | Bin 0 -> 561 bytes .../render/flex/percentage-heights-004.htm | 66 + .../flex/percentage-heights-004.htm.png | Bin 0 -> 1081 bytes .../render/flex/percentage-heights-006.htm | 48 + .../flex/percentage-heights-006.htm.png | Bin 0 -> 577 bytes .../render/flex/percentage-heights-007.htm | 47 + .../flex/percentage-heights-007.htm.png | Bin 0 -> 577 bytes .../render/flex/percentage-heights-009.htm | 42 + .../flex/percentage-heights-009.htm.png | Bin 0 -> 577 bytes .../test/render/flex/space-evenly-001.htm | 40 + .../test/render/flex/space-evenly-001.htm.png | Bin 0 -> 577 bytes .../test/render/flex/support/a-green.css | 1 + .../test/render/flex/support/b-green.css | 1 + .../test/render/flex/support/c-red.css | 1 + .../test/render/flex/support/flexbox.css | 143 + .../test/render/flex/support/import-green.css | 1 + .../test/render/flex/support/import-red.css | 1 + .../test/render/flex/support/test-style.css | 18 + .../flex/table-as-item-auto-min-width.htm | 16 + .../flex/table-as-item-auto-min-width.htm.png | Bin 0 -> 577 bytes .../flex/table-as-item-fixed-min-width-2.htm | 23 + .../table-as-item-fixed-min-width-2.htm.png | Bin 0 -> 577 bytes .../flex/table-as-item-fixed-min-width.htm | 21 + .../table-as-item-fixed-min-width.htm.png | Bin 0 -> 577 bytes .../table-as-item-narrow-content-2.htm.png | Bin 0 -> 567 bytes .../flex/table-as-item-narrow-content.htm | 18 + .../flex/table-as-item-narrow-content.htm.png | Bin 0 -> 541 bytes ...ble-as-item-percent-width-cell-001.htm.png | Bin 0 -> 236 bytes .../table-as-item-stretch-cross-size-3.htm | 30 + ...table-as-item-stretch-cross-size-3.htm.png | Bin 0 -> 577 bytes .../table-as-item-stretch-cross-size-4.htm | 34 + ...table-as-item-stretch-cross-size-4.htm.png | Bin 0 -> 577 bytes ...able-with-infinite-max-intrinsic-width.htm | 19 + ...-with-infinite-max-intrinsic-width.htm.png | Bin 0 -> 584 bytes ...zero-content-size-with-scrollbar-crash.htm | 11 + ...-content-size-with-scrollbar-crash.htm.png | Bin 0 -> 89 bytes .../litehtml/test/render/render-1-inline.htm | 1 + .../test/render/render-1-inline.htm.png | Bin 0 -> 696 bytes .../litehtml/test/render/table-1-width.htm | 6 + .../test/render/table-1-width.htm.png | Bin 0 -> 192 bytes .../litehtml/test/render/table-2-width.htm | 9 + .../test/render/table-2-width.htm.png | Bin 0 -> 155 bytes .../litehtml/test/render/table-3-width.htm | 7 + .../test/render/table-3-width.htm.png | Bin 0 -> 147 bytes .../litehtml/test/render/table-4-td-width.htm | 10 + .../test/render/table-4-td-width.htm.png | Bin 0 -> 202 bytes .../3rdparty/litehtml/test/render/test1.htm | 54 + .../litehtml/test/render/test1.htm.png | Bin 0 -> 2880 bytes .../3rdparty/litehtml/test/render/test10.htm | 19 + .../litehtml/test/render/test10.htm.png | Bin 0 -> 235 bytes .../3rdparty/litehtml/test/render/test11.htm | 14 + .../litehtml/test/render/test11.htm.png | Bin 0 -> 241 bytes .../3rdparty/litehtml/test/render/test12.htm | 1 + .../litehtml/test/render/test12.htm.png | Bin 0 -> 1837 bytes .../3rdparty/litehtml/test/render/test13.htm | 56 + .../litehtml/test/render/test13.htm.png | Bin 0 -> 4867 bytes .../3rdparty/litehtml/test/render/test14.htm | 2 + .../litehtml/test/render/test14.htm.png | Bin 0 -> 142 bytes .../3rdparty/litehtml/test/render/test15.htm | 19 + .../litehtml/test/render/test15.htm.png | Bin 0 -> 112 bytes .../3rdparty/litehtml/test/render/test16.htm | 3 + .../litehtml/test/render/test16.htm.png | Bin 0 -> 164 bytes .../3rdparty/litehtml/test/render/test17.htm | 3 + .../litehtml/test/render/test17.htm.png | Bin 0 -> 244 bytes .../3rdparty/litehtml/test/render/test18.htm | 6 + .../litehtml/test/render/test18.htm.png | Bin 0 -> 326 bytes .../3rdparty/litehtml/test/render/test19.htm | 4 + .../litehtml/test/render/test19.htm.png | Bin 0 -> 159 bytes .../3rdparty/litehtml/test/render/test2.htm | 54 + .../litehtml/test/render/test2.htm.png | Bin 0 -> 718 bytes .../3rdparty/litehtml/test/render/test20.htm | 3 + .../litehtml/test/render/test20.htm.png | Bin 0 -> 118 bytes .../3rdparty/litehtml/test/render/test21.htm | 13 + .../litehtml/test/render/test21.htm.png | Bin 0 -> 227 bytes .../3rdparty/litehtml/test/render/test22.htm | 9 + .../litehtml/test/render/test22.htm.png | Bin 0 -> 199 bytes .../3rdparty/litehtml/test/render/test23.htm | 21 + .../litehtml/test/render/test23.htm.png | Bin 0 -> 1345 bytes .../3rdparty/litehtml/test/render/test24.htm | 4 + .../litehtml/test/render/test24.htm.png | Bin 0 -> 219 bytes .../3rdparty/litehtml/test/render/test25.htm | 15 + .../litehtml/test/render/test25.htm.png | Bin 0 -> 2703 bytes .../3rdparty/litehtml/test/render/test26.htm | 16 + .../litehtml/test/render/test26.htm.png | Bin 0 -> 1250 bytes .../3rdparty/litehtml/test/render/test27.htm | 13 + .../litehtml/test/render/test27.htm.png | Bin 0 -> 222 bytes .../3rdparty/litehtml/test/render/test28.htm | 8 + .../litehtml/test/render/test28.htm.png | Bin 0 -> 179 bytes .../3rdparty/litehtml/test/render/test29.htm | 48 + .../litehtml/test/render/test29.htm.png | Bin 0 -> 510 bytes .../3rdparty/litehtml/test/render/test3.htm | 76 + .../litehtml/test/render/test3.htm.png | Bin 0 -> 1392 bytes .../3rdparty/litehtml/test/render/test30.htm | 3 + .../litehtml/test/render/test30.htm.png | Bin 0 -> 194 bytes .../3rdparty/litehtml/test/render/test31.htm | 7 + .../litehtml/test/render/test31.htm.png | Bin 0 -> 141 bytes .../3rdparty/litehtml/test/render/test32.htm | 38 + .../litehtml/test/render/test32.htm.png | Bin 0 -> 2462 bytes .../3rdparty/litehtml/test/render/test33.htm | 5 + .../litehtml/test/render/test33.htm.png | Bin 0 -> 206 bytes .../3rdparty/litehtml/test/render/test34.htm | 14 + .../litehtml/test/render/test34.htm.png | Bin 0 -> 409 bytes .../3rdparty/litehtml/test/render/test35.htm | 28 + .../litehtml/test/render/test35.htm.png | Bin 0 -> 250 bytes .../3rdparty/litehtml/test/render/test36.htm | 68 + .../litehtml/test/render/test36.htm.png | Bin 0 -> 477 bytes .../3rdparty/litehtml/test/render/test37.htm | 46 + .../litehtml/test/render/test37.htm.png | Bin 0 -> 792 bytes .../3rdparty/litehtml/test/render/test38.htm | 33 + .../litehtml/test/render/test38.htm.png | Bin 0 -> 254 bytes .../3rdparty/litehtml/test/render/test39.htm | 42 + .../litehtml/test/render/test39.htm.png | Bin 0 -> 2850 bytes .../3rdparty/litehtml/test/render/test4.htm | 42 + .../litehtml/test/render/test4.htm.png | Bin 0 -> 1718 bytes .../3rdparty/litehtml/test/render/test5.htm | 31 + .../litehtml/test/render/test5.htm.png | Bin 0 -> 320 bytes .../3rdparty/litehtml/test/render/test6.htm | 29 + .../litehtml/test/render/test6.htm.png | Bin 0 -> 785 bytes .../3rdparty/litehtml/test/render/test7.htm | 19 + .../litehtml/test/render/test7.htm.png | Bin 0 -> 216 bytes .../3rdparty/litehtml/test/render/test8.htm | 32 + .../litehtml/test/render/test8.htm.png | Bin 0 -> 558 bytes .../3rdparty/litehtml/test/render/test9.htm | 2 + .../litehtml/test/render/test9.htm.png | Bin 0 -> 698 bytes .../test/render/text-before-after.htm | 18 + .../test/render/text-before-after.htm.png | Bin 0 -> 232 bytes .../litehtml/test/render/text-justify.htm | 9 + .../litehtml/test/render/text-justify.htm.png | Bin 0 -> 10626 bytes .../3rdparty/litehtml/test/render_test.cpp | 108 + .../litehtml/test/tstring_view_test.cpp | 77 + .../3rdparty/litehtml/test/url_path_test.cpp | 150 + .../src/3rdparty/litehtml/test/url_test.cpp | 206 + .../src/3rdparty/qt_attribution.json | 39 + src/assistant/qlitehtml/src/CMakeLists.txt | 175 + .../qlitehtml/src/container_qpainter.cpp | 1479 + .../qlitehtml/src/container_qpainter.h | 127 + .../qlitehtml/src/container_qpainter_p.h | 141 + src/assistant/qlitehtml/src/qlitehtml.pri | 164 + src/assistant/qlitehtml/src/qlitehtml.qbs | 248 + .../qlitehtml/src/qlitehtml_global.h | 14 + .../qlitehtml/src/qlitehtmlwidget.cpp | 789 + src/assistant/qlitehtml/src/qlitehtmlwidget.h | 83 + .../tests/manual/browser/CMakeLists.txt | 10 + .../qlitehtml/tests/manual/browser/main.cpp | 150 + .../shared/collectionconfiguration.cpp | 295 + .../shared/collectionconfiguration.h | 112 + src/designer/CMakeLists.txt | 7 + src/designer/data/README | 8 + src/designer/data/generate_header.xsl | 482 + src/designer/data/generate_impl.xsl | 845 + src/designer/data/generate_shared.xsl | 349 + src/designer/data/generate_ui.py | 96 + src/designer/data/ui3.xsd | 353 + src/designer/data/ui4.xsd | 557 + src/designer/src/CMakeLists.txt | 18 + src/designer/src/components/CMakeLists.txt | 4 + .../components/buddyeditor/buddyeditor.cpp | 398 + .../src/components/buddyeditor/buddyeditor.h | 54 + .../components/buddyeditor/buddyeditor.json | 1 + .../buddyeditor/buddyeditor_global.h | 19 + .../buddyeditor/buddyeditor_plugin.cpp | 94 + .../buddyeditor/buddyeditor_plugin.h | 56 + .../buddyeditor/buddyeditor_tool.cpp | 74 + .../components/buddyeditor/buddyeditor_tool.h | 51 + .../formeditor/default_actionprovider.cpp | 171 + .../formeditor/default_actionprovider.h | 93 + .../formeditor/default_container.cpp | 137 + .../components/formeditor/default_container.h | 184 + .../formeditor/default_layoutdecoration.cpp | 41 + .../formeditor/default_layoutdecoration.h | 31 + .../components/formeditor/defaultbrushes.xml | 542 + .../formeditor/deviceprofiledialog.cpp | 172 + .../formeditor/deviceprofiledialog.h | 66 + .../formeditor/deviceprofiledialog.ui | 112 + .../src/components/formeditor/dpi_chooser.cpp | 168 + .../src/components/formeditor/dpi_chooser.h | 56 + .../formeditor/embeddedoptionspage.cpp | 420 + .../formeditor/embeddedoptionspage.h | 67 + .../src/components/formeditor/formeditor.cpp | 158 + .../src/components/formeditor/formeditor.h | 33 + .../components/formeditor/formeditor_global.h | 19 + .../formeditor/formeditor_optionspage.cpp | 175 + .../formeditor/formeditor_optionspage.h | 43 + .../src/components/formeditor/formwindow.cpp | 2943 ++ .../src/components/formeditor/formwindow.h | 352 + .../formeditor/formwindow_dnditem.cpp | 81 + .../formeditor/formwindow_dnditem.h | 27 + .../formeditor/formwindow_widgetstack.cpp | 184 + .../formeditor/formwindow_widgetstack.h | 64 + .../formeditor/formwindowcursor.cpp | 171 + .../components/formeditor/formwindowcursor.h | 55 + .../formeditor/formwindowmanager.cpp | 1049 + .../components/formeditor/formwindowmanager.h | 150 + .../formeditor/formwindowsettings.cpp | 247 + .../formeditor/formwindowsettings.h | 47 + .../formeditor/formwindowsettings.ui | 386 + .../components/formeditor/images/color.png | Bin 0 -> 117 bytes .../formeditor/images/configure.png | Bin 0 -> 1016 bytes .../components/formeditor/images/downplus.png | Bin 0 -> 562 bytes .../formeditor/images/dropdownbutton.png | Bin 0 -> 527 bytes .../src/components/formeditor/images/edit.png | Bin 0 -> 929 bytes .../formeditor/images/editdelete-16.png | Bin 0 -> 553 bytes .../formeditor/images/emptyicon.png | Bin 0 -> 108 bytes .../formeditor/images/filenew-16.png | Bin 0 -> 454 bytes .../formeditor/images/fileopen-16.png | Bin 0 -> 549 bytes .../formeditor/images/leveldown.png | Bin 0 -> 557 bytes .../components/formeditor/images/levelup.png | Bin 0 -> 564 bytes .../formeditor/images/mac/adjustsize.png | Bin 0 -> 1929 bytes .../components/formeditor/images/mac/back.png | Bin 0 -> 678 bytes .../formeditor/images/mac/buddytool.png | Bin 0 -> 2046 bytes .../components/formeditor/images/mac/down.png | Bin 0 -> 594 bytes .../formeditor/images/mac/editbreaklayout.png | Bin 0 -> 2067 bytes .../formeditor/images/mac/editcopy.png | Bin 0 -> 1468 bytes .../formeditor/images/mac/editcut.png | Bin 0 -> 1512 bytes .../formeditor/images/mac/editdelete.png | Bin 0 -> 1097 bytes .../formeditor/images/mac/editform.png | Bin 0 -> 621 bytes .../formeditor/images/mac/editgrid.png | Bin 0 -> 751 bytes .../formeditor/images/mac/edithlayout.png | Bin 0 -> 1395 bytes .../images/mac/edithlayoutsplit.png | Bin 0 -> 1188 bytes .../formeditor/images/mac/editlower.png | Bin 0 -> 595 bytes .../formeditor/images/mac/editpaste.png | Bin 0 -> 1906 bytes .../formeditor/images/mac/editraise.png | Bin 0 -> 1213 bytes .../formeditor/images/mac/editvlayout.png | Bin 0 -> 586 bytes .../images/mac/editvlayoutsplit.png | Bin 0 -> 872 bytes .../formeditor/images/mac/filenew.png | Bin 0 -> 772 bytes .../formeditor/images/mac/fileopen.png | Bin 0 -> 904 bytes .../formeditor/images/mac/filesave.png | Bin 0 -> 1206 bytes .../formeditor/images/mac/forward.png | Bin 0 -> 655 bytes .../formeditor/images/mac/insertimage.png | Bin 0 -> 1280 bytes .../formeditor/images/mac/minus.png | Bin 0 -> 488 bytes .../components/formeditor/images/mac/plus.png | Bin 0 -> 810 bytes .../components/formeditor/images/mac/redo.png | Bin 0 -> 1752 bytes .../formeditor/images/mac/resetproperty.png | Bin 0 -> 169 bytes .../formeditor/images/mac/signalslottool.png | Bin 0 -> 1989 bytes .../images/mac/simplifyrichtext.png | Bin 0 -> 1917 bytes .../formeditor/images/mac/tabordertool.png | Bin 0 -> 1963 bytes .../formeditor/images/mac/textanchor.png | Bin 0 -> 2543 bytes .../formeditor/images/mac/textbold.png | Bin 0 -> 1611 bytes .../formeditor/images/mac/textcenter.png | Bin 0 -> 1404 bytes .../formeditor/images/mac/textitalic.png | Bin 0 -> 1164 bytes .../formeditor/images/mac/textjustify.png | Bin 0 -> 1257 bytes .../formeditor/images/mac/textleft.png | Bin 0 -> 1235 bytes .../formeditor/images/mac/textright.png | Bin 0 -> 1406 bytes .../formeditor/images/mac/textsubscript.png | Bin 0 -> 1054 bytes .../formeditor/images/mac/textsuperscript.png | Bin 0 -> 1109 bytes .../formeditor/images/mac/textunder.png | Bin 0 -> 1183 bytes .../components/formeditor/images/mac/undo.png | Bin 0 -> 1746 bytes .../components/formeditor/images/mac/up.png | Bin 0 -> 692 bytes .../formeditor/images/mac/widgettool.png | Bin 0 -> 1874 bytes .../components/formeditor/images/minus-16.png | Bin 0 -> 296 bytes .../formeditor/images/prefix-add.png | Bin 0 -> 411 bytes .../formeditor/images/qtlogo128x128.png | Bin 0 -> 2143 bytes .../formeditor/images/qtlogo16x16.png | Bin 0 -> 367 bytes .../formeditor/images/qtlogo24x24.png | Bin 0 -> 594 bytes .../formeditor/images/qtlogo32x32.png | Bin 0 -> 775 bytes .../formeditor/images/qtlogo64x64.png | Bin 0 -> 1219 bytes .../components/formeditor/images/reload.png | Bin 0 -> 1363 bytes .../formeditor/images/resetproperty.png | Bin 0 -> 169 bytes .../formeditor/images/righttoleft.png | Bin 0 -> 131 bytes .../src/components/formeditor/images/sort.png | Bin 0 -> 563 bytes .../components/formeditor/images/submenu.png | Bin 0 -> 179 bytes .../images/widgets/calendarwidget.png | Bin 0 -> 968 bytes .../formeditor/images/widgets/checkbox.png | Bin 0 -> 817 bytes .../formeditor/images/widgets/columnview.png | Bin 0 -> 518 bytes .../formeditor/images/widgets/combobox.png | Bin 0 -> 853 bytes .../images/widgets/commandlinkbutton.png | Bin 0 -> 1208 bytes .../formeditor/images/widgets/dateedit.png | Bin 0 -> 672 bytes .../images/widgets/datetimeedit.png | Bin 0 -> 1132 bytes .../formeditor/images/widgets/dial.png | Bin 0 -> 978 bytes .../images/widgets/dialogbuttonbox.png | Bin 0 -> 1003 bytes .../formeditor/images/widgets/dockwidget.png | Bin 0 -> 638 bytes .../images/widgets/doublespinbox.png | Bin 0 -> 749 bytes .../images/widgets/fontcombobox.png | Bin 0 -> 966 bytes .../formeditor/images/widgets/frame.png | Bin 0 -> 721 bytes .../images/widgets/graphicsview.png | Bin 0 -> 1182 bytes .../formeditor/images/widgets/groupbox.png | Bin 0 -> 439 bytes .../formeditor/images/widgets/hscrollbar.png | Bin 0 -> 408 bytes .../formeditor/images/widgets/hslider.png | Bin 0 -> 729 bytes .../formeditor/images/widgets/label.png | Bin 0 -> 953 bytes .../formeditor/images/widgets/lcdnumber.png | Bin 0 -> 555 bytes .../formeditor/images/widgets/line.png | Bin 0 -> 287 bytes .../formeditor/images/widgets/lineedit.png | Bin 0 -> 405 bytes .../formeditor/images/widgets/listbox.png | Bin 0 -> 797 bytes .../formeditor/images/widgets/listview.png | Bin 0 -> 756 bytes .../formeditor/images/widgets/mdiarea.png | Bin 0 -> 643 bytes .../images/widgets/plaintextedit.png | Bin 0 -> 807 bytes .../formeditor/images/widgets/progress.png | Bin 0 -> 559 bytes .../formeditor/images/widgets/pushbutton.png | Bin 0 -> 408 bytes .../formeditor/images/widgets/radiobutton.png | Bin 0 -> 586 bytes .../formeditor/images/widgets/scrollarea.png | Bin 0 -> 548 bytes .../formeditor/images/widgets/spacer.png | Bin 0 -> 686 bytes .../formeditor/images/widgets/spinbox.png | Bin 0 -> 680 bytes .../formeditor/images/widgets/table.png | Bin 0 -> 483 bytes .../formeditor/images/widgets/tabwidget.png | Bin 0 -> 572 bytes .../formeditor/images/widgets/textedit.png | Bin 0 -> 823 bytes .../formeditor/images/widgets/timeedit.png | Bin 0 -> 1353 bytes .../formeditor/images/widgets/toolbox.png | Bin 0 -> 783 bytes .../formeditor/images/widgets/toolbutton.png | Bin 0 -> 1167 bytes .../formeditor/images/widgets/vline.png | Bin 0 -> 314 bytes .../formeditor/images/widgets/vscrollbar.png | Bin 0 -> 415 bytes .../formeditor/images/widgets/vslider.png | Bin 0 -> 726 bytes .../formeditor/images/widgets/vspacer.png | Bin 0 -> 677 bytes .../formeditor/images/widgets/widget.png | Bin 0 -> 716 bytes .../formeditor/images/widgets/widgetstack.png | Bin 0 -> 828 bytes .../formeditor/images/win/adjustsize.png | Bin 0 -> 1262 bytes .../components/formeditor/images/win/back.png | Bin 0 -> 678 bytes .../formeditor/images/win/buddytool.png | Bin 0 -> 997 bytes .../components/formeditor/images/win/down.png | Bin 0 -> 594 bytes .../formeditor/images/win/editbreaklayout.png | Bin 0 -> 1321 bytes .../formeditor/images/win/editcopy.png | Bin 0 -> 1325 bytes .../formeditor/images/win/editcut.png | Bin 0 -> 1384 bytes .../formeditor/images/win/editdelete.png | Bin 0 -> 850 bytes .../formeditor/images/win/editform.png | Bin 0 -> 349 bytes .../formeditor/images/win/editgrid.png | Bin 0 -> 349 bytes .../formeditor/images/win/edithlayout.png | Bin 0 -> 455 bytes .../images/win/edithlayoutsplit.png | Bin 0 -> 860 bytes .../formeditor/images/win/editlower.png | Bin 0 -> 1038 bytes .../formeditor/images/win/editpaste.png | Bin 0 -> 1482 bytes .../formeditor/images/win/editraise.png | Bin 0 -> 1045 bytes .../formeditor/images/win/editvlayout.png | Bin 0 -> 340 bytes .../images/win/editvlayoutsplit.png | Bin 0 -> 740 bytes .../formeditor/images/win/filenew.png | Bin 0 -> 768 bytes .../formeditor/images/win/fileopen.png | Bin 0 -> 1662 bytes .../formeditor/images/win/filesave.png | Bin 0 -> 2699 bytes .../formeditor/images/win/forward.png | Bin 0 -> 655 bytes .../formeditor/images/win/insertimage.png | Bin 0 -> 885 bytes .../formeditor/images/win/minus.png | Bin 0 -> 429 bytes .../components/formeditor/images/win/plus.png | Bin 0 -> 709 bytes .../components/formeditor/images/win/redo.png | Bin 0 -> 1212 bytes .../formeditor/images/win/signalslottool.png | Bin 0 -> 1128 bytes .../images/win/simplifyrichtext.png | Bin 0 -> 1933 bytes .../formeditor/images/win/tabordertool.png | Bin 0 -> 1205 bytes .../formeditor/images/win/textanchor.png | Bin 0 -> 1581 bytes .../formeditor/images/win/textbold.png | Bin 0 -> 1134 bytes .../formeditor/images/win/textcenter.png | Bin 0 -> 627 bytes .../formeditor/images/win/textitalic.png | Bin 0 -> 829 bytes .../formeditor/images/win/textjustify.png | Bin 0 -> 695 bytes .../formeditor/images/win/textleft.png | Bin 0 -> 673 bytes .../formeditor/images/win/textright.png | Bin 0 -> 677 bytes .../formeditor/images/win/textsubscript.png | Bin 0 -> 897 bytes .../formeditor/images/win/textsuperscript.png | Bin 0 -> 864 bytes .../formeditor/images/win/textunder.png | Bin 0 -> 971 bytes .../components/formeditor/images/win/undo.png | Bin 0 -> 1181 bytes .../components/formeditor/images/win/up.png | Bin 0 -> 692 bytes .../formeditor/images/win/widgettool.png | Bin 0 -> 1039 bytes .../formeditor/itemview_propertysheet.cpp | 228 + .../formeditor/itemview_propertysheet.h | 52 + .../formeditor/layout_propertysheet.cpp | 509 + .../formeditor/layout_propertysheet.h | 44 + .../formeditor/line_propertysheet.cpp | 49 + .../formeditor/line_propertysheet.h | 33 + .../formeditor/previewactiongroup.cpp | 100 + .../formeditor/previewactiongroup.h | 52 + .../formeditor/qdesigner_resource.cpp | 2301 ++ .../formeditor/qdesigner_resource.h | 145 + .../qlayoutwidget_propertysheet.cpp | 46 + .../formeditor/qlayoutwidget_propertysheet.h | 34 + .../formeditor/qmainwindow_container.cpp | 181 + .../formeditor/qmainwindow_container.h | 45 + .../formeditor/qmdiarea_container.cpp | 243 + .../formeditor/qmdiarea_container.h | 80 + .../formeditor/qwizard_container.cpp | 185 + .../components/formeditor/qwizard_container.h | 86 + .../formeditor/spacer_propertysheet.cpp | 43 + .../formeditor/spacer_propertysheet.h | 34 + .../formeditor/templateoptionspage.cpp | 147 + .../formeditor/templateoptionspage.h | 72 + .../formeditor/templateoptionspage.ui | 59 + .../formeditor/tool_widgeteditor.cpp | 343 + .../components/formeditor/tool_widgeteditor.h | 70 + .../components/formeditor/widgetselection.cpp | 720 + .../components/formeditor/widgetselection.h | 107 + .../src/components/lib/CMakeLists.txt | 439 + src/designer/src/components/lib/lib_pch.h | 7 + .../components/lib/qdesigner_components.cpp | 235 + .../objectinspector/objectinspector.cpp | 824 + .../objectinspector/objectinspector.h | 57 + .../objectinspector/objectinspector_global.h | 23 + .../objectinspector/objectinspectormodel.cpp | 463 + .../objectinspector/objectinspectormodel_p.h | 131 + .../propertyeditor/brushpropertymanager.cpp | 271 + .../propertyeditor/brushpropertymanager.h | 64 + .../designerpropertymanager.cpp | 2236 ++ .../propertyeditor/designerpropertymanager.h | 281 + .../components/propertyeditor/fontmapping.xml | 35 + .../propertyeditor/fontpropertymanager.cpp | 452 + .../propertyeditor/fontpropertymanager.h | 97 + .../newdynamicpropertydialog.cpp | 162 + .../propertyeditor/newdynamicpropertydialog.h | 66 + .../newdynamicpropertydialog.ui | 109 + .../propertyeditor/paletteeditor.cpp | 775 + .../components/propertyeditor/paletteeditor.h | 187 + .../propertyeditor/paletteeditor.ui | 237 + .../propertyeditor/paletteeditorbutton.cpp | 49 + .../propertyeditor/paletteeditorbutton.h | 48 + .../propertyeditor/pixmapeditor.cpp | 420 + .../components/propertyeditor/pixmapeditor.h | 128 + .../propertyeditor/previewframe.cpp | 81 + .../components/propertyeditor/previewframe.h | 38 + .../propertyeditor/previewwidget.cpp | 36 + .../components/propertyeditor/previewwidget.h | 28 + .../propertyeditor/previewwidget.ui | 307 + .../propertyeditor/propertyeditor.cpp | 1277 + .../propertyeditor/propertyeditor.h | 169 + .../propertyeditor/propertyeditor_global.h | 23 + .../propertyeditor/qlonglongvalidator.cpp | 110 + .../propertyeditor/qlonglongvalidator.h | 72 + .../propertyeditor/resetdecorator.cpp | 212 + .../propertyeditor/resetdecorator.h | 80 + .../propertyeditor/stringlisteditor.cpp | 181 + .../propertyeditor/stringlisteditor.h | 54 + .../propertyeditor/stringlisteditor.ui | 229 + .../propertyeditor/stringlisteditorbutton.cpp | 43 + .../propertyeditor/stringlisteditorbutton.h | 43 + .../components/propertyeditor/texteditor.cpp | 193 + .../components/propertyeditor/texteditor.h | 76 + .../signalsloteditor/connectdialog.cpp | 296 + .../signalsloteditor/connectdialog.ui | 150 + .../signalsloteditor/connectdialog_p.h | 71 + .../signalsloteditor/signalslot_utils.cpp | 261 + .../signalsloteditor/signalslot_utils_p.h | 65 + .../signalsloteditor/signalsloteditor.cpp | 525 + .../signalsloteditor/signalsloteditor.h | 57 + .../signalsloteditor/signalsloteditor.json | 1 + .../signalsloteditor_global.h | 19 + .../signalsloteditor/signalsloteditor_p.h | 101 + .../signalsloteditor_plugin.cpp | 96 + .../signalsloteditor_plugin.h | 55 + .../signalsloteditor_tool.cpp | 87 + .../signalsloteditor/signalsloteditor_tool.h | 55 + .../signalsloteditorwindow.cpp | 820 + .../signalsloteditor/signalsloteditorwindow.h | 59 + .../tabordereditor/tabordereditor.cpp | 398 + .../tabordereditor/tabordereditor.h | 71 + .../tabordereditor/tabordereditor.json | 1 + .../tabordereditor/tabordereditor_global.h | 19 + .../tabordereditor/tabordereditor_plugin.cpp | 96 + .../tabordereditor/tabordereditor_plugin.h | 56 + .../tabordereditor/tabordereditor_tool.cpp | 75 + .../tabordereditor/tabordereditor_tool.h | 51 + .../components/taskmenu/button_taskmenu.cpp | 665 + .../src/components/taskmenu/button_taskmenu.h | 132 + .../components/taskmenu/combobox_taskmenu.cpp | 96 + .../components/taskmenu/combobox_taskmenu.h | 56 + .../taskmenu/containerwidget_taskmenu.cpp | 302 + .../taskmenu/containerwidget_taskmenu.h | 119 + .../components/taskmenu/groupbox_taskmenu.cpp | 70 + .../components/taskmenu/groupbox_taskmenu.h | 39 + .../components/taskmenu/inplace_editor.cpp | 101 + .../src/components/taskmenu/inplace_editor.h | 71 + .../taskmenu/inplace_widget_helper.cpp | 86 + .../taskmenu/inplace_widget_helper.h | 51 + .../components/taskmenu/itemlisteditor.cpp | 490 + .../src/components/taskmenu/itemlisteditor.h | 140 + .../src/components/taskmenu/itemlisteditor.ui | 120 + .../components/taskmenu/label_taskmenu.cpp | 82 + .../src/components/taskmenu/label_taskmenu.h | 43 + .../components/taskmenu/layouttaskmenu.cpp | 56 + .../src/components/taskmenu/layouttaskmenu.h | 55 + .../components/taskmenu/lineedit_taskmenu.cpp | 68 + .../components/taskmenu/lineedit_taskmenu.h | 36 + .../taskmenu/listwidget_taskmenu.cpp | 80 + .../components/taskmenu/listwidget_taskmenu.h | 47 + .../components/taskmenu/listwidgeteditor.cpp | 101 + .../components/taskmenu/listwidgeteditor.h | 40 + .../src/components/taskmenu/menutaskmenu.cpp | 70 + .../src/components/taskmenu/menutaskmenu.h | 68 + .../taskmenu/tablewidget_taskmenu.cpp | 78 + .../taskmenu/tablewidget_taskmenu.h | 47 + .../components/taskmenu/tablewidgeteditor.cpp | 427 + .../components/taskmenu/tablewidgeteditor.h | 93 + .../components/taskmenu/tablewidgeteditor.ui | 121 + .../taskmenu/taskmenu_component.cpp | 71 + .../components/taskmenu/taskmenu_component.h | 35 + .../src/components/taskmenu/taskmenu_global.h | 19 + .../components/taskmenu/textedit_taskmenu.cpp | 68 + .../components/taskmenu/textedit_taskmenu.h | 51 + .../components/taskmenu/toolbar_taskmenu.cpp | 73 + .../components/taskmenu/toolbar_taskmenu.h | 61 + .../taskmenu/treewidget_taskmenu.cpp | 77 + .../components/taskmenu/treewidget_taskmenu.h | 47 + .../components/taskmenu/treewidgeteditor.cpp | 618 + .../components/taskmenu/treewidgeteditor.h | 92 + .../components/taskmenu/treewidgeteditor.ui | 221 + .../src/components/widgetbox/widgetbox.cpp | 238 + .../src/components/widgetbox/widgetbox.h | 66 + .../src/components/widgetbox/widgetbox.xml | 939 + .../widgetbox/widgetbox_dnditem.cpp | 191 + .../components/widgetbox/widgetbox_dnditem.h | 29 + .../components/widgetbox/widgetbox_global.h | 19 + .../widgetbox/widgetboxcategorylistview.cpp | 471 + .../widgetbox/widgetboxcategorylistview.h | 79 + .../widgetbox/widgetboxtreewidget.cpp | 981 + .../widgetbox/widgetboxtreewidget.h | 111 + src/designer/src/designer/CMakeLists.txt | 175 + .../src/designer/Designer.entitlements | 9 + src/designer/src/designer/Info_mac.plist | 35 + src/designer/src/designer/appfontdialog.cpp | 384 + src/designer/src/designer/appfontdialog.h | 63 + src/designer/src/designer/assistantclient.cpp | 157 + src/designer/src/designer/assistantclient.h | 47 + src/designer/src/designer/designer.desktop | 10 + src/designer/src/designer/designer.icns | Bin 0 -> 117397 bytes src/designer/src/designer/designer.ico | Bin 0 -> 125480 bytes .../src/designer/designer.metainfo.xml | 43 + src/designer/src/designer/designer_enums.h | 14 + ...ressbook-tutorial-part3-labeled-layout.png | Bin 0 -> 22779 bytes .../doc/images/designer-action-editor.png | Bin 0 -> 20454 bytes .../images/designer-add-custom-toolbar.png | Bin 0 -> 940 bytes .../doc/images/designer-add-files-button.png | Bin 0 -> 1130 bytes .../designer-add-resource-entry-button.png | Bin 0 -> 899 bytes .../doc/images/designer-adding-dockwidget.png | Bin 0 -> 6324 bytes .../designer-adding-dynamic-property.png | Bin 0 -> 9511 bytes .../images/designer-adding-menu-action.png | Bin 0 -> 6414 bytes .../images/designer-adding-toolbar-action.png | Bin 0 -> 4793 bytes .../doc/images/designer-buddy-making.png | Bin 0 -> 8885 bytes .../doc/images/designer-buddy-mode.png | Bin 0 -> 8008 bytes .../doc/images/designer-buddy-tool.png | Bin 0 -> 2046 bytes .../doc/images/designer-choosing-form.png | Bin 0 -> 31407 bytes .../doc/images/designer-code-viewer.png | Bin 0 -> 95042 bytes .../doc/images/designer-connection-dialog.png | Bin 0 -> 29675 bytes .../images/designer-connection-editing.png | Bin 0 -> 9338 bytes .../doc/images/designer-connection-editor.png | Bin 0 -> 8404 bytes .../images/designer-connection-highlight.png | Bin 0 -> 5297 bytes .../doc/images/designer-connection-making.png | Bin 0 -> 6869 bytes .../doc/images/designer-connection-mode.png | Bin 0 -> 9595 bytes .../images/designer-connection-to-form.png | Bin 0 -> 4504 bytes .../doc/images/designer-connection-tool.png | Bin 0 -> 1989 bytes .../images/designer-containers-dockwidget.png | Bin 0 -> 3259 bytes .../doc/images/designer-containers-frame.png | Bin 0 -> 744 bytes .../images/designer-containers-groupbox.png | Bin 0 -> 1969 bytes .../designer-containers-stackedwidget.png | Bin 0 -> 2192 bytes .../images/designer-containers-tabwidget.png | Bin 0 -> 1681 bytes .../images/designer-containers-toolbox.png | Bin 0 -> 6062 bytes .../designer-creating-dynamic-property.png | Bin 0 -> 8528 bytes .../images/designer-creating-menu-entry1.png | Bin 0 -> 4705 bytes .../images/designer-creating-menu-entry2.png | Bin 0 -> 4749 bytes .../images/designer-creating-menu-entry3.png | Bin 0 -> 5031 bytes .../images/designer-creating-menu-entry4.png | Bin 0 -> 5760 bytes .../doc/images/designer-creating-menu.png | Bin 0 -> 2513 bytes .../doc/images/designer-creating-menu1.png | Bin 0 -> 2472 bytes .../doc/images/designer-creating-menu2.png | Bin 0 -> 2513 bytes .../doc/images/designer-creating-menu3.png | Bin 0 -> 2321 bytes .../doc/images/designer-creating-menu4.png | Bin 0 -> 2884 bytes .../doc/images/designer-creating-menubar.png | Bin 0 -> 7485 bytes .../doc/images/designer-creating-toolbar.png | Bin 0 -> 12037 bytes .../doc/images/designer-custom-widget-box.png | Bin 0 -> 9783 bytes .../doc/images/designer-customize-toolbar.png | Bin 0 -> 80321 bytes .../doc/images/designer-dialog-final.png | Bin 0 -> 7267 bytes .../doc/images/designer-dialog-initial.png | Bin 0 -> 26271 bytes .../doc/images/designer-dialog-layout.png | Bin 0 -> 18892 bytes .../doc/images/designer-dialog-preview.png | Bin 0 -> 9911 bytes .../doc/images/designer-disambiguation.png | Bin 0 -> 4767 bytes .../images/designer-dragging-onto-form.png | Bin 0 -> 6291 bytes .../doc/images/designer-edit-resource.png | Bin 0 -> 17929 bytes .../images/designer-edit-resources-button.png | Bin 0 -> 810 bytes .../doc/images/designer-editing-mode.png | Bin 0 -> 7908 bytes .../doc/images/designer-embedded-preview.png | Bin 0 -> 4904 bytes .../doc/images/designer-english-dialog.png | Bin 0 -> 18690 bytes .../designer/doc/images/designer-examples.png | Bin 0 -> 8692 bytes .../doc/images/designer-file-menu.png | Bin 0 -> 4641 bytes .../doc/images/designer-find-icon.png | Bin 0 -> 52951 bytes .../designer-form-layout-cleanlooks.png | Bin 0 -> 7626 bytes .../images/designer-form-layout-macintosh.png | Bin 0 -> 6673 bytes .../images/designer-form-layout-windowsXP.png | Bin 0 -> 7656 bytes .../doc/images/designer-form-layout.png | Bin 0 -> 7090 bytes .../images/designer-form-layoutfunction.png | Bin 0 -> 6864 bytes .../doc/images/designer-form-settings.png | Bin 0 -> 14971 bytes .../doc/images/designer-form-viewcode.png | Bin 0 -> 21529 bytes .../doc/images/designer-french-dialog.png | Bin 0 -> 20524 bytes .../doc/images/designer-getting-started.png | Bin 0 -> 8437 bytes .../doc/images/designer-layout-inserting.png | Bin 0 -> 7736 bytes .../doc/images/designer-main-window.png | Bin 0 -> 30843 bytes .../doc/images/designer-making-connection.png | Bin 0 -> 8709 bytes .../designer-manual-containerextension.png | Bin 0 -> 11868 bytes .../designer-manual-membersheetextension.png | Bin 0 -> 15662 bytes ...designer-manual-propertysheetextension.png | Bin 0 -> 23365 bytes .../designer-manual-taskmenuextension.png | Bin 0 -> 12378 bytes .../images/designer-multiple-screenshot.png | Bin 0 -> 151546 bytes .../doc/images/designer-object-inspector.png | Bin 0 -> 7918 bytes .../images/designer-palette-brush-editor.png | Bin 0 -> 31044 bytes .../doc/images/designer-palette-editor.png | Bin 0 -> 45039 bytes .../designer-palette-gradient-editor.png | Bin 0 -> 73055 bytes .../designer-palette-pattern-editor.png | Bin 0 -> 29906 bytes .../images/designer-preview-device-skin.png | Bin 0 -> 52508 bytes .../designer-preview-deviceskin-selection.png | Bin 0 -> 7110 bytes .../designer-preview-style-selection.png | Bin 0 -> 5590 bytes .../doc/images/designer-preview-style.png | Bin 0 -> 38787 bytes .../images/designer-preview-stylesheet.png | Bin 0 -> 30103 bytes .../doc/images/designer-promoting-widgets.png | Bin 0 -> 16376 bytes .../designer-property-editor-add-dynamic.png | Bin 0 -> 767 bytes .../designer-property-editor-configure.png | Bin 0 -> 941 bytes .../images/designer-property-editor-link.png | Bin 0 -> 14883 bytes ...esigner-property-editor-remove-dynamic.png | Bin 0 -> 322 bytes .../designer-property-editor-toolbar.png | Bin 0 -> 3409 bytes .../doc/images/designer-property-editor.png | Bin 0 -> 35879 bytes .../designer-reload-resources-button.png | Bin 0 -> 1107 bytes .../images/designer-remove-custom-toolbar.png | Bin 0 -> 711 bytes .../designer-remove-resource-entry-button.png | Bin 0 -> 640 bytes .../designer-removing-toolbar-action.png | Bin 0 -> 10914 bytes .../doc/images/designer-removing-toolbar.png | Bin 0 -> 8740 bytes .../doc/images/designer-resource-browser.png | Bin 0 -> 12647 bytes .../doc/images/designer-resource-selector.png | Bin 0 -> 14277 bytes .../doc/images/designer-resource-tool.png | Bin 0 -> 2152 bytes .../doc/images/designer-resources-adding.png | Bin 0 -> 11638 bytes .../doc/images/designer-resources-editing.png | Bin 0 -> 13809 bytes .../doc/images/designer-resources-empty.png | Bin 0 -> 8184 bytes .../doc/images/designer-resources-using.png | Bin 0 -> 4118 bytes .../doc/images/designer-screenshot.png | Bin 0 -> 140233 bytes .../doc/images/designer-selecting-widget.png | Bin 0 -> 7266 bytes .../doc/images/designer-selecting-widgets.png | Bin 0 -> 8080 bytes .../doc/images/designer-set-layout.png | Bin 0 -> 4018 bytes .../doc/images/designer-set-layout2.png | Bin 0 -> 7834 bytes .../doc/images/designer-splitter-layout.png | Bin 0 -> 81182 bytes .../images/designer-stylesheet-options.png | Bin 0 -> 13542 bytes .../doc/images/designer-stylesheet-usage.png | Bin 0 -> 7560 bytes .../doc/images/designer-tab-order-mode.png | Bin 0 -> 9588 bytes .../doc/images/designer-tab-order-tool.png | Bin 0 -> 1868 bytes .../doc/images/designer-widget-box.png | Bin 0 -> 12895 bytes .../doc/images/designer-widget-filter.png | Bin 0 -> 14018 bytes .../doc/images/designer-widget-final.png | Bin 0 -> 8498 bytes .../doc/images/designer-widget-initial.png | Bin 0 -> 14010 bytes .../doc/images/designer-widget-layout.png | Bin 0 -> 13862 bytes .../doc/images/designer-widget-morph.png | Bin 0 -> 19303 bytes .../doc/images/designer-widget-preview.png | Bin 0 -> 9291 bytes .../doc/images/designer-widget-tool.png | Bin 0 -> 1863 bytes .../images/directapproach-calculatorform.png | Bin 0 -> 7718 bytes .../doc/images/qtdesignerextensions.png | Bin 0 -> 51434 bytes .../doc/images/qtdesignerscreenshot.png | Bin 0 -> 144869 bytes .../doc/images/rgbController-arrangement.png | Bin 0 -> 8398 bytes .../rgbController-configure-connection1.png | Bin 0 -> 19812 bytes .../rgbController-configure-connection2.png | Bin 0 -> 24013 bytes .../doc/images/rgbController-final-layout.png | Bin 0 -> 8760 bytes .../images/rgbController-form-gridLayout.png | Bin 0 -> 8711 bytes .../rgbController-no-toplevel-layout.png | Bin 0 -> 1110 bytes .../images/rgbController-property-editing.png | Bin 0 -> 6716 bytes .../doc/images/rgbController-screenshot.png | Bin 0 -> 6726 bytes .../images/rgbController-selectForLayout.png | Bin 0 -> 15080 bytes .../images/rgbController-signalsAndSlots.png | Bin 0 -> 8308 bytes .../images/worldtimeclockplugin-example.png | Bin 0 -> 13055 bytes .../src/designer/doc/qtdesigner.qdocconf | 51 + .../src/designer/doc/snippets/CMakeLists.txt | 10 + .../snippets/autoconnection/CMakeLists.txt | 24 + .../autoconnection/autoconnection.pro | 6 + .../snippets/autoconnection/imagedialog.cpp | 33 + .../doc/snippets/autoconnection/imagedialog.h | 22 + .../snippets/autoconnection/imagedialog.ui | 389 + .../doc/snippets/autoconnection/main.cpp | 14 + .../src/designer/doc/snippets/designer.pro | 6 + .../doc/snippets/imagedialog/CMakeLists.txt | 24 + .../doc/snippets/imagedialog/imagedialog.pro | 4 + .../doc/snippets/imagedialog/imagedialog.ui | 389 + .../doc/snippets/imagedialog/main.cpp | 16 + ...lib_extension_default_extensionfactory.cpp | 37 + ...s_designer_src_lib_extension_extension.cpp | 17 + ...er_src_lib_extension_qextensionmanager.cpp | 20 + ...esigner_src_lib_sdk_abstractformwindow.cpp | 24 + ...r_src_lib_sdk_abstractformwindowcursor.cpp | 10 + ..._src_lib_sdk_abstractformwindowmanager.cpp | 11 + ...er_src_lib_sdk_abstractobjectinspector.cpp | 11 + ...ner_src_lib_sdk_abstractpropertyeditor.cpp | 25 + ...designer_src_lib_sdk_abstractwidgetbox.cpp | 32 + ...ols_designer_src_lib_uilib_formbuilder.cpp | 28 + .../manual/doc_src_designer-manual.cpp | 75 + .../manual/doc_src_designer-manual.js | 6 + .../manual/doc_src_designer-manual.pro | 22 + .../multipleinheritance/CMakeLists.txt | 24 + .../multipleinheritance/imagedialog.cpp | 20 + .../multipleinheritance/imagedialog.h | 17 + .../multipleinheritance/imagedialog.ui | 389 + .../doc/snippets/multipleinheritance/main.cpp | 14 + .../multipleinheritance.pro | 6 + .../snippets/noautoconnection/CMakeLists.txt | 24 + .../snippets/noautoconnection/imagedialog.cpp | 40 + .../snippets/noautoconnection/imagedialog.h | 22 + .../snippets/noautoconnection/imagedialog.ui | 389 + .../doc/snippets/noautoconnection/main.cpp | 14 + .../noautoconnection/noautoconnection.pro | 6 + .../snippets/plugins/doc_src_qtdesigner.cpp | 285 + .../snippets/singleinheritance/CMakeLists.txt | 24 + .../singleinheritance/imagedialog.cpp | 20 + .../snippets/singleinheritance/imagedialog.h | 20 + .../snippets/singleinheritance/imagedialog.ui | 389 + .../doc/snippets/singleinheritance/main.cpp | 14 + .../singleinheritance/singleinheritance.pro | 6 + .../uitools/calculatorform/CMakeLists.txt | 26 + .../uitools/calculatorform/calculatorform.pro | 5 + .../uitools/calculatorform/calculatorform.ui | 303 + .../snippets/uitools/calculatorform/main.cpp | 20 + .../doc/src/designer-custom-widgets.qdoc | 105 + .../designer/doc/src/designer-examples.qdoc | 30 + .../src/designer/doc/src/designer-manual.qdoc | 2977 ++ .../designer/doc/src/qtdesigner-index.qdoc | 55 + .../designer/doc/src/qtdesigner-module.qdoc | 21 + .../src/designer/doc/src/qtdesigner-toc.qdoc | 41 + src/designer/src/designer/images/designer.png | Bin 0 -> 4167 bytes src/designer/src/designer/main.cpp | 29 + src/designer/src/designer/mainwindow.cpp | 372 + src/designer/src/designer/mainwindow.h | 150 + src/designer/src/designer/newform.cpp | 187 + src/designer/src/designer/newform.h | 66 + .../src/designer/preferencesdialog.cpp | 79 + src/designer/src/designer/preferencesdialog.h | 43 + .../src/designer/preferencesdialog.ui | 91 + src/designer/src/designer/qdesigner.cpp | 330 + src/designer/src/designer/qdesigner.h | 69 + .../src/designer/qdesigner_actions.cpp | 1315 + src/designer/src/designer/qdesigner_actions.h | 191 + .../designer/qdesigner_appearanceoptions.cpp | 118 + .../designer/qdesigner_appearanceoptions.h | 93 + .../designer/qdesigner_appearanceoptions.ui | 57 + .../src/designer/qdesigner_formwindow.cpp | 255 + .../src/designer/qdesigner_formwindow.h | 59 + src/designer/src/designer/qdesigner_pch.h | 21 + .../src/designer/qdesigner_server.cpp | 114 + src/designer/src/designer/qdesigner_server.h | 51 + .../src/designer/qdesigner_settings.cpp | 210 + .../src/designer/qdesigner_settings.h | 56 + .../src/designer/qdesigner_toolwindow.cpp | 364 + .../src/designer/qdesigner_toolwindow.h | 81 + .../src/designer/qdesigner_workbench.cpp | 1108 + .../src/designer/qdesigner_workbench.h | 178 + .../src/designer/saveformastemplate.cpp | 135 + .../src/designer/saveformastemplate.h | 39 + .../src/designer/saveformastemplate.ui | 130 + src/designer/src/designer/uifile.icns | Bin 0 -> 157015 bytes src/designer/src/designer/versiondialog.cpp | 159 + src/designer/src/designer/versiondialog.h | 20 + src/designer/src/lib/CMakeLists.txt | 485 + .../src/lib/components/qdesigner_components.h | 45 + .../components/qdesigner_components_global.h | 24 + .../extension/default_extensionfactory.cpp | 137 + .../lib/extension/default_extensionfactory.h | 42 + src/designer/src/lib/extension/extension.cpp | 148 + src/designer/src/lib/extension/extension.h | 49 + .../src/lib/extension/extension_global.h | 24 + .../src/lib/extension/qextensionmanager.cpp | 135 + .../src/lib/extension/qextensionmanager.h | 36 + src/designer/src/lib/lib_pch.h | 27 + .../src/lib/sdk/abstractactioneditor.cpp | 83 + .../src/lib/sdk/abstractactioneditor.h | 34 + .../src/lib/sdk/abstractdialoggui.cpp | 119 + .../src/lib/sdk/abstractdialoggui_p.h | 66 + src/designer/src/lib/sdk/abstractdnditem.h | 35 + src/designer/src/lib/sdk/abstractdnditem.qdoc | 74 + .../src/lib/sdk/abstractformeditor.cpp | 571 + src/designer/src/lib/sdk/abstractformeditor.h | 100 + .../src/lib/sdk/abstractformeditorplugin.cpp | 48 + .../src/lib/sdk/abstractformeditorplugin.h | 34 + .../src/lib/sdk/abstractformwindow.cpp | 867 + src/designer/src/lib/sdk/abstractformwindow.h | 159 + .../src/lib/sdk/abstractformwindowcursor.cpp | 214 + .../src/lib/sdk/abstractformwindowcursor.h | 69 + .../src/lib/sdk/abstractformwindowmanager.cpp | 538 + .../src/lib/sdk/abstractformwindowmanager.h | 123 + .../src/lib/sdk/abstractformwindowtool.cpp | 66 + .../src/lib/sdk/abstractformwindowtool.h | 43 + .../src/lib/sdk/abstractintegration.cpp | 766 + .../src/lib/sdk/abstractintegration.h | 158 + .../src/lib/sdk/abstractintrospection.cpp | 496 + .../src/lib/sdk/abstractintrospection_p.h | 145 + src/designer/src/lib/sdk/abstractlanguage.h | 69 + .../src/lib/sdk/abstractmetadatabase.cpp | 130 + .../src/lib/sdk/abstractmetadatabase.h | 60 + .../src/lib/sdk/abstractnewformwidget.cpp | 77 + .../src/lib/sdk/abstractnewformwidget.h | 34 + .../src/lib/sdk/abstractobjectinspector.cpp | 70 + .../src/lib/sdk/abstractobjectinspector.h | 31 + .../src/lib/sdk/abstractoptionspage.h | 30 + .../src/lib/sdk/abstractoptionspage.qdoc | 56 + .../lib/sdk/abstractpromotioninterface.cpp | 74 + .../src/lib/sdk/abstractpromotioninterface.h | 53 + .../src/lib/sdk/abstractpropertyeditor.cpp | 153 + .../src/lib/sdk/abstractpropertyeditor.h | 42 + .../src/lib/sdk/abstractresourcebrowser.cpp | 16 + .../src/lib/sdk/abstractresourcebrowser.h | 33 + src/designer/src/lib/sdk/abstractsettings.h | 34 + .../src/lib/sdk/abstractsettings.qdoc | 23 + .../src/lib/sdk/abstractwidgetbox.cpp | 300 + src/designer/src/lib/sdk/abstractwidgetbox.h | 107 + .../src/lib/sdk/abstractwidgetdatabase.cpp | 320 + .../src/lib/sdk/abstractwidgetdatabase.h | 98 + .../src/lib/sdk/abstractwidgetfactory.cpp | 72 + .../src/lib/sdk/abstractwidgetfactory.h | 37 + src/designer/src/lib/sdk/container.h | 39 + src/designer/src/lib/sdk/container.qdoc | 175 + .../src/lib/sdk/dynamicpropertysheet.h | 42 + .../src/lib/sdk/dynamicpropertysheet.qdoc | 56 + src/designer/src/lib/sdk/extrainfo.cpp | 78 + src/designer/src/lib/sdk/extrainfo.h | 45 + src/designer/src/lib/sdk/layoutdecoration.h | 60 + .../src/lib/sdk/layoutdecoration.qdoc | 127 + src/designer/src/lib/sdk/membersheet.h | 50 + src/designer/src/lib/sdk/membersheet.qdoc | 225 + src/designer/src/lib/sdk/propertysheet.h | 52 + src/designer/src/lib/sdk/propertysheet.qdoc | 288 + src/designer/src/lib/sdk/sdk_global.h | 24 + src/designer/src/lib/sdk/taskmenu.cpp | 13 + src/designer/src/lib/sdk/taskmenu.h | 30 + src/designer/src/lib/sdk/taskmenu.qdoc | 114 + src/designer/src/lib/shared/actioneditor.cpp | 895 + src/designer/src/lib/shared/actioneditor_p.h | 145 + .../src/lib/shared/actionprovider_p.h | 69 + .../src/lib/shared/actionrepository.cpp | 655 + .../src/lib/shared/actionrepository_p.h | 233 + src/designer/src/lib/shared/addlinkdialog.ui | 112 + src/designer/src/lib/shared/codedialog.cpp | 264 + src/designer/src/lib/shared/codedialog_p.h | 70 + .../src/lib/shared/connectionedit.cpp | 1572 ++ .../src/lib/shared/connectionedit_p.h | 285 + .../src/lib/shared/csshighlighter.cpp | 153 + .../src/lib/shared/csshighlighter_p.h | 59 + .../src/lib/shared/defaultgradients.xml | 498 + src/designer/src/lib/shared/deviceprofile.cpp | 422 + src/designer/src/lib/shared/deviceprofile_p.h | 111 + src/designer/src/lib/shared/dialoggui.cpp | 221 + src/designer/src/lib/shared/dialoggui_p.h | 69 + .../src/lib/shared/extensionfactory_p.h | 82 + .../src/lib/shared/formlayoutmenu.cpp | 491 + .../src/lib/shared/formlayoutmenu_p.h | 62 + .../src/lib/shared/formlayoutrowdialog.ui | 166 + .../src/lib/shared/formwindowbase.cpp | 549 + .../src/lib/shared/formwindowbase_p.h | 165 + src/designer/src/lib/shared/grid.cpp | 154 + src/designer/src/lib/shared/grid_p.h | 85 + src/designer/src/lib/shared/gridpanel.cpp | 83 + src/designer/src/lib/shared/gridpanel.ui | 144 + src/designer/src/lib/shared/gridpanel_p.h | 63 + .../src/lib/shared/htmlhighlighter.cpp | 153 + .../src/lib/shared/htmlhighlighter_p.h | 65 + .../src/lib/shared/icon-naming-spec.txt | 309 + src/designer/src/lib/shared/iconloader.cpp | 106 + src/designer/src/lib/shared/iconloader_p.h | 41 + src/designer/src/lib/shared/iconselector.cpp | 651 + src/designer/src/lib/shared/iconselector_p.h | 146 + .../src/lib/shared/invisible_widget.cpp | 19 + .../src/lib/shared/invisible_widget_p.h | 37 + src/designer/src/lib/shared/layout.cpp | 1219 + src/designer/src/lib/shared/layout_p.h | 113 + src/designer/src/lib/shared/layoutinfo.cpp | 269 + src/designer/src/lib/shared/layoutinfo_p.h | 76 + src/designer/src/lib/shared/metadatabase.cpp | 243 + src/designer/src/lib/shared/metadatabase_p.h | 98 + src/designer/src/lib/shared/morphmenu.cpp | 587 + src/designer/src/lib/shared/morphmenu_p.h | 56 + .../src/lib/shared/newactiondialog.cpp | 216 + .../src/lib/shared/newactiondialog.ui | 306 + .../src/lib/shared/newactiondialog_p.h | 100 + src/designer/src/lib/shared/newformwidget.cpp | 560 + src/designer/src/lib/shared/newformwidget.ui | 155 + src/designer/src/lib/shared/newformwidget_p.h | 105 + src/designer/src/lib/shared/orderdialog.cpp | 156 + src/designer/src/lib/shared/orderdialog.ui | 162 + src/designer/src/lib/shared/orderdialog_p.h | 75 + .../src/lib/shared/plaintexteditor.cpp | 81 + .../src/lib/shared/plaintexteditor_p.h | 51 + src/designer/src/lib/shared/plugindialog.cpp | 208 + src/designer/src/lib/shared/plugindialog.ui | 99 + src/designer/src/lib/shared/plugindialog_p.h | 57 + src/designer/src/lib/shared/pluginmanager.cpp | 755 + src/designer/src/lib/shared/pluginmanager_p.h | 126 + .../lib/shared/previewconfigurationwidget.cpp | 323 + .../lib/shared/previewconfigurationwidget.ui | 91 + .../lib/shared/previewconfigurationwidget_p.h | 58 + .../src/lib/shared/previewmanager.cpp | 900 + .../src/lib/shared/previewmanager_p.h | 146 + .../src/lib/shared/promotionmodel.cpp | 169 + .../src/lib/shared/promotionmodel_p.h | 69 + .../src/lib/shared/promotiontaskmenu.cpp | 313 + .../src/lib/shared/promotiontaskmenu_p.h | 111 + .../src/lib/shared/propertylineedit.cpp | 58 + .../src/lib/shared/propertylineedit_p.h | 47 + .../src/lib/shared/qdesigner_command.cpp | 2872 ++ .../src/lib/shared/qdesigner_command2.cpp | 183 + .../src/lib/shared/qdesigner_command2_p.h | 85 + .../src/lib/shared/qdesigner_command_p.h | 1116 + .../src/lib/shared/qdesigner_dnditem.cpp | 265 + .../src/lib/shared/qdesigner_dnditem_p.h | 109 + .../src/lib/shared/qdesigner_dockwidget.cpp | 114 + .../src/lib/shared/qdesigner_dockwidget_p.h | 64 + .../src/lib/shared/qdesigner_formbuilder.cpp | 385 + .../src/lib/shared/qdesigner_formbuilder_p.h | 130 + .../shared/qdesigner_formeditorcommand.cpp | 26 + .../shared/qdesigner_formeditorcommand_p.h | 48 + .../shared/qdesigner_formwindowcommand.cpp | 113 + .../shared/qdesigner_formwindowcommand_p.h | 61 + .../shared/qdesigner_formwindowmanager.cpp | 51 + .../shared/qdesigner_formwindowmanager_p.h | 50 + .../lib/shared/qdesigner_introspection.cpp | 331 + .../lib/shared/qdesigner_introspection_p.h | 45 + .../src/lib/shared/qdesigner_membersheet.cpp | 212 + .../src/lib/shared/qdesigner_membersheet_p.h | 79 + .../src/lib/shared/qdesigner_menu.cpp | 1363 + .../src/lib/shared/qdesigner_menu_p.h | 170 + .../src/lib/shared/qdesigner_menubar.cpp | 932 + .../src/lib/shared/qdesigner_menubar_p.h | 140 + .../lib/shared/qdesigner_objectinspector.cpp | 42 + .../lib/shared/qdesigner_objectinspector_p.h | 65 + .../src/lib/shared/qdesigner_promotion.cpp | 359 + .../src/lib/shared/qdesigner_promotion_p.h | 60 + .../lib/shared/qdesigner_promotiondialog.cpp | 432 + .../lib/shared/qdesigner_promotiondialog_p.h | 132 + .../lib/shared/qdesigner_propertycommand.cpp | 1528 + .../lib/shared/qdesigner_propertycommand_p.h | 272 + .../lib/shared/qdesigner_propertyeditor.cpp | 155 + .../lib/shared/qdesigner_propertyeditor_p.h | 74 + .../lib/shared/qdesigner_propertysheet.cpp | 1721 ++ .../lib/shared/qdesigner_propertysheet_p.h | 245 + .../src/lib/shared/qdesigner_qsettings.cpp | 50 + .../src/lib/shared/qdesigner_qsettings_p.h | 51 + .../src/lib/shared/qdesigner_stackedbox.cpp | 366 + .../src/lib/shared/qdesigner_stackedbox_p.h | 126 + .../src/lib/shared/qdesigner_tabwidget.cpp | 531 + .../src/lib/shared/qdesigner_tabwidget_p.h | 116 + .../src/lib/shared/qdesigner_taskmenu.cpp | 726 + .../src/lib/shared/qdesigner_taskmenu_p.h | 93 + .../src/lib/shared/qdesigner_toolbar.cpp | 463 + .../src/lib/shared/qdesigner_toolbar_p.h | 98 + .../src/lib/shared/qdesigner_toolbox.cpp | 397 + .../src/lib/shared/qdesigner_toolbox_p.h | 102 + .../src/lib/shared/qdesigner_utils.cpp | 801 + .../src/lib/shared/qdesigner_utils_p.h | 553 + .../src/lib/shared/qdesigner_widget.cpp | 68 + .../src/lib/shared/qdesigner_widget_p.h | 84 + .../src/lib/shared/qdesigner_widgetbox.cpp | 233 + .../src/lib/shared/qdesigner_widgetbox_p.h | 63 + .../src/lib/shared/qdesigner_widgetitem.cpp | 308 + .../src/lib/shared/qdesigner_widgetitem_p.h | 109 + .../src/lib/shared/qlayout_widget.cpp | 2077 ++ .../src/lib/shared/qlayout_widget_p.h | 269 + .../src/lib/shared/qsimpleresource.cpp | 233 + .../src/lib/shared/qsimpleresource_p.h | 111 + .../src/lib/shared/qtresourceeditordialog.cpp | 2175 ++ .../src/lib/shared/qtresourceeditordialog.ui | 177 + .../src/lib/shared/qtresourceeditordialog_p.h | 57 + .../src/lib/shared/qtresourcemodel.cpp | 573 + .../src/lib/shared/qtresourcemodel_p.h | 105 + .../src/lib/shared/qtresourceview.cpp | 868 + .../src/lib/shared/qtresourceview_p.h | 92 + src/designer/src/lib/shared/rcc.cpp | 1549 ++ src/designer/src/lib/shared/rcc_p.h | 166 + .../src/lib/shared/richtexteditor.cpp | 883 + .../src/lib/shared/richtexteditor_p.h | 65 + .../src/lib/shared/selectsignaldialog.cpp | 220 + .../src/lib/shared/selectsignaldialog.ui | 93 + .../src/lib/shared/selectsignaldialog_p.h | 73 + src/designer/src/lib/shared/shared_enums_p.h | 62 + src/designer/src/lib/shared/shared_global_p.h | 38 + .../src/lib/shared/shared_settings.cpp | 319 + .../src/lib/shared/shared_settings_p.h | 109 + .../src/lib/shared/sheet_delegate.cpp | 102 + .../src/lib/shared/sheet_delegate_p.h | 47 + .../src/lib/shared/signalslotdialog.cpp | 506 + .../src/lib/shared/signalslotdialog.ui | 129 + .../src/lib/shared/signalslotdialog_p.h | 143 + src/designer/src/lib/shared/spacer_widget.cpp | 238 + src/designer/src/lib/shared/spacer_widget_p.h | 78 + .../src/lib/shared/stylesheeteditor.cpp | 402 + .../src/lib/shared/stylesheeteditor_p.h | 110 + .../240x320/Dialog_with_Buttons_Bottom.ui | 67 + .../240x320/Dialog_with_Buttons_Right.ui | 67 + .../320x240/Dialog_with_Buttons_Bottom.ui | 67 + .../320x240/Dialog_with_Buttons_Right.ui | 67 + .../480x640/Dialog_with_Buttons_Bottom.ui | 67 + .../480x640/Dialog_with_Buttons_Right.ui | 67 + .../640x480/Dialog_with_Buttons_Bottom.ui | 67 + .../640x480/Dialog_with_Buttons_Right.ui | 67 + .../forms/Dialog_with_Buttons_Bottom.ui | 71 + .../forms/Dialog_with_Buttons_Right.ui | 71 + .../templates/forms/Dialog_without_Buttons.ui | 18 + .../lib/shared/templates/forms/Main_Window.ui | 24 + .../src/lib/shared/templates/forms/Widget.ui | 21 + .../src/lib/shared/textpropertyeditor.cpp | 391 + .../src/lib/shared/textpropertyeditor_p.h | 116 + .../src/lib/shared/widgetdatabase.cpp | 848 + .../src/lib/shared/widgetdatabase_p.h | 175 + src/designer/src/lib/shared/widgetfactory.cpp | 886 + src/designer/src/lib/shared/widgetfactory_p.h | 117 + src/designer/src/lib/shared/zoomwidget.cpp | 512 + src/designer/src/lib/shared/zoomwidget_p.h | 194 + .../src/lib/uilib/abstractformbuilder.cpp | 2583 ++ .../src/lib/uilib/abstractformbuilder.h | 227 + src/designer/src/lib/uilib/formbuilder.cpp | 489 + src/designer/src/lib/uilib/formbuilder.h | 68 + .../src/lib/uilib/formbuilderextra.cpp | 773 + .../src/lib/uilib/formbuilderextra_p.h | 226 + src/designer/src/lib/uilib/properties.cpp | 694 + src/designer/src/lib/uilib/properties_p.h | 142 + .../src/lib/uilib/resourcebuilder.cpp | 235 + .../src/lib/uilib/resourcebuilder_p.h | 71 + src/designer/src/lib/uilib/textbuilder.cpp | 40 + src/designer/src/lib/uilib/textbuilder_p.h | 55 + src/designer/src/lib/uilib/ui4.cpp | 6218 +++++ src/designer/src/lib/uilib/ui4_p.h | 2933 ++ src/designer/src/lib/uilib/uilib_global.h | 24 + src/designer/src/lib/uilib/widgets.table | 161 + src/designer/src/plugins/CMakeLists.txt | 12 + .../src/plugins/activeqt/CMakeLists.txt | 26 + .../src/plugins/activeqt/activeqt.json | 1 + .../plugins/activeqt/qaxwidgetextrainfo.cpp | 78 + .../src/plugins/activeqt/qaxwidgetextrainfo.h | 53 + .../src/plugins/activeqt/qaxwidgetplugin.cpp | 107 + .../src/plugins/activeqt/qaxwidgetplugin.h | 40 + .../activeqt/qaxwidgetpropertysheet.cpp | 176 + .../plugins/activeqt/qaxwidgetpropertysheet.h | 62 + .../plugins/activeqt/qaxwidgettaskmenu.cpp | 148 + .../src/plugins/activeqt/qaxwidgettaskmenu.h | 38 + .../plugins/activeqt/qdesigneraxwidget.cpp | 235 + .../src/plugins/activeqt/qdesigneraxwidget.h | 100 + .../src/plugins/qquickwidget/CMakeLists.txt | 31 + .../qquickwidget/images/qquickwidget.png | Bin 0 -> 584 bytes .../qquickwidget/qquickwidget_plugin.cpp | 107 + .../qquickwidget/qquickwidget_plugin.h | 41 + .../src/plugins/qwebview/CMakeLists.txt | 31 + .../src/plugins/qwebview/images/qwebview.png | Bin 0 -> 1473 bytes .../src/plugins/qwebview/qwebview.json | 1 + .../src/plugins/qwebview/qwebview_plugin.cpp | 97 + .../src/plugins/qwebview/qwebview_plugin.h | 37 + .../src/plugins/tools/view3d/CMakeLists.txt | 22 + .../src/plugins/tools/view3d/view3d.cpp | 458 + .../src/plugins/tools/view3d/view3d.h | 39 + .../src/plugins/tools/view3d/view3d_global.h | 23 + .../plugins/tools/view3d/view3d_plugin.cpp | 80 + .../src/plugins/tools/view3d/view3d_plugin.h | 48 + .../src/plugins/tools/view3d/view3d_tool.cpp | 53 + .../src/plugins/tools/view3d/view3d_tool.h | 40 + src/distancefieldgenerator/CMakeLists.txt | 34 + .../distancefieldmodel.cpp | 177 + .../distancefieldmodel.h | 213 + .../distancefieldmodelworker.cpp | 327 + .../distancefieldmodelworker.h | 55 + .../doc/images/distancefieldgenerator.png | Bin 0 -> 68344 bytes .../doc/qtdistancefieldgenerator.qdocconf | 27 + .../src/distancefieldgenerator-manual.qdoc | 107 + src/distancefieldgenerator/main.cpp | 35 + src/distancefieldgenerator/mainwindow.cpp | 752 + src/distancefieldgenerator/mainwindow.h | 64 + src/distancefieldgenerator/mainwindow.ui | 217 + src/global/CMakeLists.txt | 17 + src/kmap2qmap/CMakeLists.txt | 16 + src/kmap2qmap/main.cpp | 967 + src/linguist/CMakeLists.txt | 29 + src/linguist/GenerateLUpdateProject.cmake | 164 + src/linguist/Qt6LinguistToolsMacros.cmake | 1021 + src/linguist/lconvert/CMakeLists.txt | 31 + src/linguist/lconvert/main.cpp | 269 + src/linguist/linguist/CMakeLists.txt | 311 + src/linguist/linguist/Info_mac.plist | 79 + src/linguist/linguist/batchtranslation.ui | 223 + .../linguist/batchtranslationdialog.cpp | 161 + .../linguist/batchtranslationdialog.h | 49 + src/linguist/linguist/doc/cmake-macros.qdoc | 862 + .../linguist/doc/cmake-properties.qdoc | 136 + .../linguist/doc/images/front-coding.png | Bin 0 -> 6045 bytes .../linguist/doc/images/front-publishing.png | Bin 0 -> 5101 bytes src/linguist/linguist/doc/images/front-ui.png | Bin 0 -> 3491 bytes .../doc/images/linguist-batchtranslation.png | Bin 0 -> 28270 bytes .../doc/images/linguist-check-empty.png | Bin 0 -> 385 bytes .../doc/images/linguist-check-obsolete.png | Bin 0 -> 490 bytes .../doc/images/linguist-check-off.png | Bin 0 -> 401 bytes .../linguist/doc/images/linguist-check-on.png | Bin 0 -> 612 bytes .../doc/images/linguist-check-warning.png | Bin 0 -> 612 bytes .../doc/images/linguist-context-view.webp | Bin 0 -> 20794 bytes .../linguist/doc/images/linguist-danger.png | Bin 0 -> 415 bytes .../doc/images/linguist-doneandnext.png | Bin 0 -> 801 bytes .../linguist/doc/images/linguist-examples.png | Bin 0 -> 8571 bytes .../doc/images/linguist-linguist_2.webp | Bin 0 -> 49842 bytes .../doc/images/linguist-phrasebookdialog.png | Bin 0 -> 38112 bytes .../doc/images/linguist-strings-view.webp | Bin 0 -> 6942 bytes .../linguist-translationfilesettings.png | Bin 0 -> 17953 bytes .../linguist/doc/images/linguist-ui.webp | Bin 0 -> 49930 bytes .../linguist/doc/images/linguist.webp | Bin 0 -> 49176 bytes src/linguist/linguist/doc/images/next.png | Bin 0 -> 750 bytes .../linguist/doc/images/nextunfinished.png | Bin 0 -> 777 bytes .../linguist/doc/images/xNIz78IPBu0.jpg | Bin 0 -> 1675 bytes .../cmake-find-package-linguisttools.qdocinc | 6 + src/linguist/linguist/doc/qtlinguist.qdocconf | 49 + .../doc/snippets/cmake-macros/examples.cmake | 154 + .../doc/snippets/doc_src_linguist-manual.cpp | 30 + .../doc/snippets/doc_src_linguist-manual.pro | 15 + .../linguist/doc/src/linguist-examples.qdoc | 23 + .../linguist/doc/src/linguist-manual.qdoc | 1612 ++ src/linguist/linguist/errorsview.cpp | 82 + src/linguist/linguist/errorsview.h | 41 + src/linguist/linguist/finddialog.cpp | 101 + src/linguist/linguist/finddialog.h | 50 + src/linguist/linguist/finddialog.ui | 261 + src/linguist/linguist/globals.cpp | 70 + src/linguist/linguist/globals.h | 21 + src/linguist/linguist/images/alert.png | Bin 0 -> 221 bytes src/linguist/linguist/images/appicon.png | Bin 0 -> 419 bytes src/linguist/linguist/images/check.png | Bin 0 -> 360 bytes .../darkicons/check-ampersands-disabled.png | Bin 0 -> 989 bytes .../images/darkicons/check-ampersands.png | Bin 0 -> 772 bytes .../check-ending-pontuation-disabled.png | Bin 0 -> 965 bytes .../darkicons/check-ending-pontuation.png | Bin 0 -> 817 bytes .../check-phrase-suggestions-disabled.png | Bin 0 -> 884 bytes .../darkicons/check-phrase-suggestions.png | Bin 0 -> 681 bytes .../check-place-markers-disabled.png | Bin 0 -> 880 bytes .../images/darkicons/check-place-markers.png | Bin 0 -> 668 bytes .../darkicons/check-white-spaces-disabled.png | Bin 0 -> 870 bytes .../images/darkicons/check-white-spaces.png | Bin 0 -> 664 bytes .../darkicons/copy-general-disabled.png | Bin 0 -> 567 bytes .../images/darkicons/copy-general.png | Bin 0 -> 407 bytes .../images/darkicons/cut-disabled.png | Bin 0 -> 916 bytes .../linguist/images/darkicons/cut.png | Bin 0 -> 573 bytes .../hit-help-chosen-option-disabled.png | Bin 0 -> 825 bytes .../darkicons/hit-help-chosen-option.png | Bin 0 -> 597 bytes .../images/darkicons/library-disabled.png | Bin 0 -> 571 bytes .../linguist/images/darkicons/library.png | Bin 0 -> 423 bytes ...mark-current-translation-done-disabled.png | Bin 0 -> 883 bytes ...translation-done-move-to-next-disabled.png | Bin 0 -> 1068 bytes ...-current-translation-done-move-to-next.png | Bin 0 -> 783 bytes .../mark-current-translation-done.png | Bin 0 -> 667 bytes .../darkicons/minus-square-fill-disabled.png | Bin 0 -> 404 bytes .../images/darkicons/minus-square-fill.png | Bin 0 -> 211 bytes .../next-translation-item-disabled.png | Bin 0 -> 982 bytes .../darkicons/next-translation-item.png | Bin 0 -> 713 bytes ...t-unfinished-translation-item-disabled.png | Bin 0 -> 1010 bytes .../next-unfinished-translation-item.png | Bin 0 -> 746 bytes .../images/darkicons/open-new-disabled.png | Bin 0 -> 664 bytes .../linguist/images/darkicons/open-new.png | Bin 0 -> 514 bytes .../darkicons/paste-general-disabled.png | Bin 0 -> 568 bytes .../images/darkicons/paste-general.png | Bin 0 -> 405 bytes .../darkicons/plus-square-fill-disabled.png | Bin 0 -> 432 bytes .../images/darkicons/plus-square-fill.png | Bin 0 -> 236 bytes .../previous-translation-item-disabled.png | Bin 0 -> 983 bytes .../darkicons/previous-translation-item.png | Bin 0 -> 709 bytes ...s-unfinished-translation-item-disabled.png | Bin 0 -> 989 bytes .../previous-unfinished-translation-item.png | Bin 0 -> 737 bytes .../images/darkicons/print-disabled.png | Bin 0 -> 621 bytes .../linguist/images/darkicons/print.png | Bin 0 -> 482 bytes .../darkicons/redo-arrow-right-disabled.png | Bin 0 -> 679 bytes .../images/darkicons/redo-arrow-right.png | Bin 0 -> 443 bytes .../darkicons/save-fl-disk-disabled.png | Bin 0 -> 713 bytes .../images/darkicons/save-fl-disk.png | Bin 0 -> 571 bytes .../darkicons/search-magnifier-disabled.png | Bin 0 -> 898 bytes .../images/darkicons/search-magnifier.png | Bin 0 -> 622 bytes .../darkicons/undo-arrow-left-disabled.png | Bin 0 -> 688 bytes .../images/darkicons/undo-arrow-left.png | Bin 0 -> 446 bytes .../linguist/images/darkmarks/danger-mark.png | Bin 0 -> 397 bytes .../linguist/images/darkmarks/empty-mark.png | Bin 0 -> 452 bytes .../images/darkmarks/obsolete-mark.png | Bin 0 -> 645 bytes .../linguist/images/darkmarks/off-mark.png | Bin 0 -> 424 bytes .../linguist/images/darkmarks/on-mark.png | Bin 0 -> 580 bytes .../images/darkmarks/warning-mark.png | Bin 0 -> 580 bytes .../linguist/images/generate-marks.sh | 29 + .../linguist/images/icons/linguist-128-32.png | Bin 0 -> 3638 bytes .../lighticons/check-ampersands-disabled.png | Bin 0 -> 989 bytes .../images/lighticons/check-ampersands.png | Bin 0 -> 714 bytes .../check-ending-pontuation-disabled.png | Bin 0 -> 965 bytes .../lighticons/check-ending-pontuation.png | Bin 0 -> 765 bytes .../check-phrase-suggestions-disabled.png | Bin 0 -> 884 bytes .../lighticons/check-phrase-suggestions.png | Bin 0 -> 642 bytes .../check-place-markers-disabled.png | Bin 0 -> 880 bytes .../images/lighticons/check-place-markers.png | Bin 0 -> 619 bytes .../check-white-spaces-disabled.png | Bin 0 -> 870 bytes .../images/lighticons/check-white-spaces.png | Bin 0 -> 618 bytes .../lighticons/copy-general-disabled.png | Bin 0 -> 567 bytes .../images/lighticons/copy-general.png | Bin 0 -> 362 bytes .../images/lighticons/cut-disabled.png | Bin 0 -> 916 bytes .../linguist/images/lighticons/cut.png | Bin 0 -> 598 bytes .../hit-help-chosen-option-disabled.png | Bin 0 -> 825 bytes .../lighticons/hit-help-chosen-option.png | Bin 0 -> 541 bytes .../images/lighticons/library-disabled.png | Bin 0 -> 571 bytes .../linguist/images/lighticons/library.png | Bin 0 -> 379 bytes ...mark-current-translation-done-disabled.png | Bin 0 -> 883 bytes ...translation-done-move-to-next-disabled.png | Bin 0 -> 1068 bytes ...-current-translation-done-move-to-next.png | Bin 0 -> 746 bytes .../mark-current-translation-done.png | Bin 0 -> 623 bytes .../lighticons/minus-square-fill-disabled.png | Bin 0 -> 404 bytes .../images/lighticons/minus-square-fill.png | Bin 0 -> 200 bytes .../next-translation-item-disabled.png | Bin 0 -> 982 bytes .../lighticons/next-translation-item.png | Bin 0 -> 670 bytes ...t-unfinished-translation-item-disabled.png | Bin 0 -> 1010 bytes .../next-unfinished-translation-item.png | Bin 0 -> 703 bytes .../images/lighticons/open-new-disabled.png | Bin 0 -> 664 bytes .../linguist/images/lighticons/open-new.png | Bin 0 -> 468 bytes .../lighticons/paste-general-disabled.png | Bin 0 -> 568 bytes .../images/lighticons/paste-general.png | Bin 0 -> 359 bytes .../lighticons/plus-square-fill-disabled.png | Bin 0 -> 432 bytes .../images/lighticons/plus-square-fill.png | Bin 0 -> 223 bytes .../previous-translation-item-disabled.png | Bin 0 -> 983 bytes .../lighticons/previous-translation-item.png | Bin 0 -> 660 bytes ...s-unfinished-translation-item-disabled.png | Bin 0 -> 989 bytes .../previous-unfinished-translation-item.png | Bin 0 -> 672 bytes .../images/lighticons/print-disabled.png | Bin 0 -> 621 bytes .../linguist/images/lighticons/print.png | Bin 0 -> 438 bytes .../lighticons/redo-arrow-right-disabled.png | Bin 0 -> 679 bytes .../images/lighticons/redo-arrow-right.png | Bin 0 -> 411 bytes .../lighticons/save-fl-disk-disabled.png | Bin 0 -> 713 bytes .../images/lighticons/save-fl-disk.png | Bin 0 -> 502 bytes .../lighticons/search-magnifier-disabled.png | Bin 0 -> 898 bytes .../images/lighticons/search-magnifier.png | Bin 0 -> 587 bytes .../lighticons/undo-arrow-left-disabled.png | Bin 0 -> 688 bytes .../images/lighticons/undo-arrow-left.png | Bin 0 -> 394 bytes .../images/lightmarks/danger-mark.png | Bin 0 -> 397 bytes .../linguist/images/lightmarks/empty-mark.png | Bin 0 -> 452 bytes .../images/lightmarks/obsolete-mark.png | Bin 0 -> 645 bytes .../linguist/images/lightmarks/off-mark.png | Bin 0 -> 424 bytes .../linguist/images/lightmarks/on-mark.png | Bin 0 -> 580 bytes .../images/lightmarks/warning-mark.png | Bin 0 -> 580 bytes src/linguist/linguist/images/menu-dots.png | Bin 0 -> 227 bytes src/linguist/linguist/linguist.desktop | 9 + src/linguist/linguist/linguist.icns | Bin 0 -> 101226 bytes src/linguist/linguist/linguist.ico | Bin 0 -> 123477 bytes src/linguist/linguist/linguist.metainfo.xml | 41 + src/linguist/linguist/main.cpp | 119 + src/linguist/linguist/mainwindow.cpp | 2848 ++ src/linguist/linguist/mainwindow.h | 246 + src/linguist/linguist/mainwindow.ui | 993 + src/linguist/linguist/messageeditor.cpp | 957 + src/linguist/linguist/messageeditor.h | 162 + .../linguist/messageeditorwidgets.cpp | 501 + src/linguist/linguist/messageeditorwidgets.h | 162 + src/linguist/linguist/messagehighlighter.cpp | 188 + src/linguist/linguist/messagehighlighter.h | 48 + src/linguist/linguist/messagemodel.cpp | 1589 ++ src/linguist/linguist/messagemodel.h | 501 + src/linguist/linguist/phrase.cpp | 309 + src/linguist/linguist/phrase.h | 104 + src/linguist/linguist/phrasebookbox.cpp | 210 + src/linguist/linguist/phrasebookbox.h | 51 + src/linguist/linguist/phrasebookbox.ui | 199 + src/linguist/linguist/phrasemodel.cpp | 163 + src/linguist/linguist/phrasemodel.h | 56 + src/linguist/linguist/phraseview.cpp | 283 + src/linguist/linguist/phraseview.h | 71 + src/linguist/linguist/printout.cpp | 174 + src/linguist/linguist/printout.h | 82 + src/linguist/linguist/qmlformpreviewview.cpp | 110 + src/linguist/linguist/qmlformpreviewview.h | 32 + src/linguist/linguist/recentfiles.cpp | 108 + src/linguist/linguist/recentfiles.h | 45 + src/linguist/linguist/sourcecodeview.cpp | 108 + src/linguist/linguist/sourcecodeview.h | 36 + src/linguist/linguist/statistics.cpp | 46 + src/linguist/linguist/statistics.h | 47 + src/linguist/linguist/statistics.ui | 344 + src/linguist/linguist/translatedialog.cpp | 57 + src/linguist/linguist/translatedialog.h | 51 + src/linguist/linguist/translatedialog.ui | 223 + src/linguist/linguist/translationsettings.ui | 137 + .../linguist/translationsettingsdialog.cpp | 148 + .../linguist/translationsettingsdialog.h | 43 + src/linguist/linguist/uiformpreviewview.cpp | 554 + src/linguist/linguist/uiformpreviewview.h | 89 + src/linguist/lprodump/CMakeLists.txt | 66 + src/linguist/lprodump/main.cpp | 438 + src/linguist/lrelease-pro/CMakeLists.txt | 22 + src/linguist/lrelease-pro/main.cpp | 119 + src/linguist/lrelease/CMakeLists.txt | 39 + src/linguist/lrelease/main.cpp | 291 + src/linguist/lupdate-pro/CMakeLists.txt | 22 + .../lupdate-pro/lupdate-pro.exe.manifest | 13 + src/linguist/lupdate-pro/lupdate-pro.rc | 4 + src/linguist/lupdate-pro/main.cpp | 131 + src/linguist/lupdate/CMakeLists.txt | 76 + src/linguist/lupdate/cpp.cpp | 2434 ++ src/linguist/lupdate/cpp.h | 149 + src/linguist/lupdate/java.cpp | 594 + src/linguist/lupdate/lupdate.exe.manifest | 13 + src/linguist/lupdate/lupdate.h | 129 + src/linguist/lupdate/lupdate.rc | 4 + src/linguist/lupdate/main.cpp | 1170 + src/linguist/lupdate/merge.cpp | 301 + src/linguist/lupdate/metastrings.cpp | 110 + src/linguist/lupdate/metastrings.h | 47 + src/linguist/lupdate/python.cpp | 767 + src/linguist/lupdate/qdeclarative.cpp | 465 + src/linguist/lupdate/synchronized.h | 81 + src/linguist/lupdate/ui.cpp | 180 + src/linguist/phrasebooks/danish.qph | 1018 + src/linguist/phrasebooks/dutch.qph | 1044 + src/linguist/phrasebooks/finnish.qph | 1033 + src/linguist/phrasebooks/french.qph | 1493 + src/linguist/phrasebooks/german.qph | 1075 + src/linguist/phrasebooks/hungarian.qph | 752 + src/linguist/phrasebooks/italian.qph | 1105 + src/linguist/phrasebooks/japanese.qph | 1034 + src/linguist/phrasebooks/norwegian.qph | 1004 + src/linguist/phrasebooks/polish.qph | 527 + src/linguist/phrasebooks/russian.qph | 1219 + src/linguist/phrasebooks/spanish.qph | 1086 + src/linguist/phrasebooks/swedish.qph | 1010 + src/linguist/shared/exclusive_builds.prf | 6 + src/linguist/shared/fmt.h | 17 + src/linguist/shared/ioutils.cpp | 313 + src/linguist/shared/ioutils.h | 55 + src/linguist/shared/numerus.cpp | 387 + src/linguist/shared/po.cpp | 877 + src/linguist/shared/profileevaluator.cpp | 213 + src/linguist/shared/profileevaluator.h | 79 + src/linguist/shared/profileutils.h | 31 + src/linguist/shared/proitems.cpp | 469 + src/linguist/shared/proitems.h | 489 + .../shared/projectdescriptionreader.cpp | 249 + .../shared/projectdescriptionreader.h | 33 + src/linguist/shared/qm.cpp | 716 + src/linguist/shared/qmake_global.h | 33 + src/linguist/shared/qmakebuiltins.cpp | 1895 ++ src/linguist/shared/qmakeevaluator.cpp | 2188 ++ src/linguist/shared/qmakeevaluator.h | 309 + src/linguist/shared/qmakeevaluator_p.h | 89 + src/linguist/shared/qmakeglobals.cpp | 374 + src/linguist/shared/qmakeglobals.h | 146 + src/linguist/shared/qmakeparser.cpp | 1550 ++ src/linguist/shared/qmakeparser.h | 214 + src/linguist/shared/qmakevfs.cpp | 257 + src/linguist/shared/qmakevfs.h | 105 + src/linguist/shared/qph.cpp | 166 + src/linguist/shared/qrcreader.cpp | 71 + src/linguist/shared/qrcreader.h | 24 + src/linguist/shared/registry.cpp | 135 + src/linguist/shared/registry_p.h | 48 + src/linguist/shared/runqttool.cpp | 112 + src/linguist/shared/runqttool.h | 19 + src/linguist/shared/simtexth.cpp | 202 + src/linguist/shared/simtexth.h | 88 + src/linguist/shared/translator.cpp | 801 + src/linguist/shared/translator.h | 225 + src/linguist/shared/translatormessage.cpp | 131 + src/linguist/shared/translatormessage.h | 147 + src/linguist/shared/ts.cpp | 682 + src/linguist/shared/ts.xsd | 189 + src/linguist/shared/xliff.cpp | 832 + src/linguist/shared/xmlparser.cpp | 84 + src/linguist/shared/xmlparser.h | 38 + src/pixeltool/CMakeLists.txt | 27 + src/pixeltool/Info_mac.plist | 20 + src/pixeltool/main.cpp | 47 + src/pixeltool/qpixeltool.cpp | 747 + src/pixeltool/qpixeltool.h | 89 + src/qdbus/CMakeLists.txt | 12 + src/qdbus/qdbus/CMakeLists.txt | 23 + src/qdbus/qdbus/qdbus.cpp | 512 + src/qdbus/qdbusviewer/CMakeLists.txt | 80 + src/qdbus/qdbusviewer/Info_mac.plist | 20 + .../qdbusviewer/images/qdbusviewer-128.png | Bin 0 -> 5128 bytes src/qdbus/qdbusviewer/images/qdbusviewer.icns | Bin 0 -> 146329 bytes src/qdbus/qdbusviewer/images/qdbusviewer.ico | Bin 0 -> 127592 bytes src/qdbus/qdbusviewer/images/qdbusviewer.png | Bin 0 -> 694 bytes src/qdbus/qdbusviewer/logviewer.cpp | 21 + src/qdbus/qdbusviewer/logviewer.h | 19 + src/qdbus/qdbusviewer/main.cpp | 35 + src/qdbus/qdbusviewer/mainwindow.cpp | 127 + src/qdbus/qdbusviewer/mainwindow.h | 34 + src/qdbus/qdbusviewer/propertydialog.cpp | 78 + src/qdbus/qdbusviewer/propertydialog.h | 32 + src/qdbus/qdbusviewer/qdbusmodel.cpp | 322 + src/qdbus/qdbusviewer/qdbusmodel.h | 59 + src/qdbus/qdbusviewer/qdbusviewer.cpp | 564 + src/qdbus/qdbusviewer/qdbusviewer.desktop | 9 + src/qdbus/qdbusviewer/qdbusviewer.h | 81 + .../qdbusviewer/qdbusviewer.metainfo.xml | 34 + src/qdbus/qdbusviewer/servicesproxymodel.cpp | 41 + src/qdbus/qdbusviewer/servicesproxymodel.h | 21 + src/qdoc/CMakeLists.txt | 10 + src/qdoc/catch/CMakeLists.txt | 9 + src/qdoc/catch/LICENSE.CATCH.txt | 23 + src/qdoc/catch/REUSE.toml | 7 + src/qdoc/catch/include/catch/catch.hpp | 17976 ++++++++++++ src/qdoc/catch/qt_attribution.json | 21 + src/qdoc/catch_conversions/CMakeLists.txt | 12 + .../qdoc_catch_conversions.h | 24 + .../catch_conversions/qt_catch_conversions.h | 19 + .../catch_conversions/std_catch_conversions.h | 16 + src/qdoc/catch_generators/CMakeLists.txt | 16 + .../generators/combinators/cycle_generator.h | 80 + .../generators/combinators/oneof_generator.h | 185 + .../generators/k_partition_of_r_generator.h | 113 + .../generators/path_generator.h | 853 + .../generators/qchar_generator.h | 110 + .../generators/qstring_generator.h | 92 + .../src/catch_generators/namespaces.h | 14 + .../utilities/semantics/copy_value.h | 26 + .../utilities/semantics/generator_handler.h | 97 + .../utilities/semantics/move_into_vector.h | 62 + .../utilities/statistics/distribution.h | 158 + .../utilities/statistics/percentages.h | 49 + .../catch_generators/tests/CMakeLists.txt | 20 + .../catch_k_partition_of_r_generator.cpp | 41 + .../tests/generators/catch_path_generator.cpp | 755 + .../generators/catch_qchar_generator.cpp | 102 + .../generators/catch_qstring_generator.cpp | 89 + .../combinators/catch_cycle_generator.cpp | 70 + .../combinators/catch_oneof_generator.cpp | 362 + src/qdoc/catch_generators/tests/main.cpp | 13 + .../semantics/catch_generator_handler.cpp | 28 + src/qdoc/cmake/QDocConfiguration.cmake | 13 + src/qdoc/cmake/QDocConfigureMessages.cmake | 57 + src/qdoc/qdoc/CMakeLists.txt | 118 + src/qdoc/qdoc/doc/config/qdoc.qdocconf | 66 + src/qdoc/qdoc/doc/examples/cpp.qdoc.sample | 102 + .../doc/examples/layoutmanagement.qdocinc | 13 + src/qdoc/qdoc/doc/examples/main.cpp | 16 + src/qdoc/qdoc/doc/examples/mainwindow.cpp | 213 + src/qdoc/qdoc/doc/examples/minimum.qdocconf | 46 + .../qdoc/doc/examples/objectmodel.qdocinc | 11 + src/qdoc/qdoc/doc/examples/qml.qdoc.sample | 90 + src/qdoc/qdoc/doc/examples/samples.qdocinc | 74 + .../qdoc/doc/examples/signalandslots.qdocinc | 9 + src/qdoc/qdoc/doc/files/basicqt.qdoc.sample | 64 + src/qdoc/qdoc/doc/files/compat.qdocconf | 10 + src/qdoc/qdoc/doc/files/qtgui.qdocconf | 45 + src/qdoc/qdoc/doc/images/happyguy.jpg | Bin 0 -> 53442 bytes .../qdoc/doc/images/link-to-qquickitem.png | Bin 0 -> 46571 bytes .../qdoc/doc/images/links-to-broken-links.png | Bin 0 -> 16569 bytes src/qdoc/qdoc/doc/images/links-to-links.png | Bin 0 -> 10042 bytes src/qdoc/qdoc/doc/images/qt-logo.png | Bin 0 -> 1008 bytes src/qdoc/qdoc/doc/images/training.jpg | Bin 0 -> 8368 bytes .../qdoc/doc/images/windows-pushbutton.png | Bin 0 -> 722 bytes .../qdoc/doc/images/windows-toolbutton.png | Bin 0 -> 771 bytes src/qdoc/qdoc/doc/qdoc-guide/qdoc-guide.qdoc | 686 + .../doc/qdoc-guide/qtwritingstyle-cpp.qdoc | 163 + .../doc/qdoc-guide/qtwritingstyle-qml.qdoc | 144 + src/qdoc/qdoc/doc/qdoc-manual-cmdindex.qdoc | 147 + .../qdoc/doc/qdoc-manual-contextcmds.qdoc | 1028 + src/qdoc/qdoc/doc/qdoc-manual-intro.qdoc | 299 + src/qdoc/qdoc/doc/qdoc-manual-macros.qdoc | 498 + src/qdoc/qdoc/doc/qdoc-manual-markupcmds.qdoc | 3456 +++ src/qdoc/qdoc/doc/qdoc-manual-qdocconf.qdoc | 2251 ++ src/qdoc/qdoc/doc/qdoc-manual-topiccmds.qdoc | 1132 + src/qdoc/qdoc/doc/qdoc-manual.qdoc | 69 + src/qdoc/qdoc/doc/qdoc-warnings.qdoc | 898 + src/qdoc/qdoc/doc/qtgui-qdocconf.qdoc | 260 + src/qdoc/qdoc/src/qdoc/access.h | 15 + src/qdoc/qdoc/src/qdoc/aggregate.cpp | 816 + src/qdoc/qdoc/src/qdoc/aggregate.h | 119 + src/qdoc/qdoc/src/qdoc/atom.cpp | 458 + src/qdoc/qdoc/src/qdoc/atom.h | 225 + .../boundaries/filesystem/directorypath.cpp | 126 + .../boundaries/filesystem/directorypath.h | 17 + .../qdoc/boundaries/filesystem/filepath.cpp | 125 + .../src/qdoc/boundaries/filesystem/filepath.h | 17 + .../boundaries/filesystem/resolvedfile.cpp | 96 + .../qdoc/boundaries/filesystem/resolvedfile.h | 20 + .../src/qdoc/boundaries/refined_typedef.h | 207 + .../refined_typedef_members.qdocinc | 162 + .../qdoc/src/qdoc/clang/AST/LICENSE.LLVM.txt | 279 + .../qdoc/src/qdoc/clang/AST/QualTypeNames.h | 515 + src/qdoc/qdoc/src/qdoc/clang/AST/REUSE.toml | 7 + .../src/qdoc/clang/AST/qt_attribution.json | 21 + src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp | 1980 ++ src/qdoc/qdoc/src/qdoc/clangcodeparser.h | 94 + src/qdoc/qdoc/src/qdoc/classnode.cpp | 413 + src/qdoc/qdoc/src/qdoc/classnode.h | 87 + src/qdoc/qdoc/src/qdoc/codechunk.cpp | 93 + src/qdoc/qdoc/src/qdoc/codechunk.h | 74 + src/qdoc/qdoc/src/qdoc/codemarker.cpp | 412 + src/qdoc/qdoc/src/qdoc/codemarker.h | 63 + src/qdoc/qdoc/src/qdoc/codeparser.cpp | 139 + src/qdoc/qdoc/src/qdoc/codeparser.h | 146 + src/qdoc/qdoc/src/qdoc/collectionnode.cpp | 104 + src/qdoc/qdoc/src/qdoc/collectionnode.h | 130 + src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp | 28 + src/qdoc/qdoc/src/qdoc/comparisoncategory.h | 54 + src/qdoc/qdoc/src/qdoc/config.cpp | 1476 + src/qdoc/qdoc/src/qdoc/config.h | 434 + src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp | 588 + src/qdoc/qdoc/src/qdoc/cppcodemarker.h | 34 + src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp | 1035 + src/qdoc/qdoc/src/qdoc/cppcodeparser.h | 113 + src/qdoc/qdoc/src/qdoc/doc.cpp | 444 + src/qdoc/qdoc/src/qdoc/doc.h | 95 + src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp | 4925 ++++ src/qdoc/qdoc/src/qdoc/docbookgenerator.h | 171 + src/qdoc/qdoc/src/qdoc/docparser.cpp | 2918 ++ src/qdoc/qdoc/src/qdoc/docparser.h | 178 + src/qdoc/qdoc/src/qdoc/docprivate.cpp | 30 + src/qdoc/qdoc/src/qdoc/docprivate.h | 76 + src/qdoc/qdoc/src/qdoc/docutilities.h | 28 + src/qdoc/qdoc/src/qdoc/editdistance.cpp | 68 + src/qdoc/qdoc/src/qdoc/editdistance.h | 17 + src/qdoc/qdoc/src/qdoc/enumitem.h | 36 + src/qdoc/qdoc/src/qdoc/enumnode.cpp | 82 + src/qdoc/qdoc/src/qdoc/enumnode.h | 54 + src/qdoc/qdoc/src/qdoc/examplenode.h | 43 + src/qdoc/qdoc/src/qdoc/externalpagenode.cpp | 28 + src/qdoc/qdoc/src/qdoc/externalpagenode.h | 26 + .../qdoc/src/qdoc/filesystem/fileresolver.cpp | 161 + .../qdoc/src/qdoc/filesystem/fileresolver.h | 24 + src/qdoc/qdoc/src/qdoc/functionnode.cpp | 513 + src/qdoc/qdoc/src/qdoc/functionnode.h | 202 + src/qdoc/qdoc/src/qdoc/generator.cpp | 2471 ++ src/qdoc/qdoc/src/qdoc/generator.h | 234 + src/qdoc/qdoc/src/qdoc/genustypes.h | 181 + src/qdoc/qdoc/src/qdoc/headernode.cpp | 43 + src/qdoc/qdoc/src/qdoc/headernode.h | 46 + src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp | 793 + src/qdoc/qdoc/src/qdoc/helpprojectwriter.h | 107 + src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp | 3706 +++ src/qdoc/qdoc/src/qdoc/htmlgenerator.h | 183 + src/qdoc/qdoc/src/qdoc/importrec.h | 35 + src/qdoc/qdoc/src/qdoc/inode.h | 32 + src/qdoc/qdoc/src/qdoc/location.cpp | 438 + src/qdoc/qdoc/src/qdoc/location.h | 93 + src/qdoc/qdoc/src/qdoc/macro.h | 27 + src/qdoc/qdoc/src/qdoc/main.cpp | 732 + src/qdoc/qdoc/src/qdoc/manifestwriter.cpp | 413 + src/qdoc/qdoc/src/qdoc/manifestwriter.h | 46 + src/qdoc/qdoc/src/qdoc/namespacenode.cpp | 190 + src/qdoc/qdoc/src/qdoc/namespacenode.h | 48 + src/qdoc/qdoc/src/qdoc/nativeenum.cpp | 53 + src/qdoc/qdoc/src/qdoc/nativeenum.h | 38 + src/qdoc/qdoc/src/qdoc/node.cpp | 1394 + src/qdoc/qdoc/src/qdoc/node.h | 319 + src/qdoc/qdoc/src/qdoc/openedlist.cpp | 172 + src/qdoc/qdoc/src/qdoc/openedlist.h | 47 + src/qdoc/qdoc/src/qdoc/pagenode.cpp | 117 + src/qdoc/qdoc/src/qdoc/pagenode.h | 83 + src/qdoc/qdoc/src/qdoc/parameters.cpp | 569 + src/qdoc/qdoc/src/qdoc/parameters.h | 114 + src/qdoc/qdoc/src/qdoc/parsererror.cpp | 90 + src/qdoc/qdoc/src/qdoc/parsererror.h | 26 + src/qdoc/qdoc/src/qdoc/propertynode.cpp | 135 + src/qdoc/qdoc/src/qdoc/propertynode.h | 93 + src/qdoc/qdoc/src/qdoc/proxynode.cpp | 54 + src/qdoc/qdoc/src/qdoc/proxynode.h | 23 + src/qdoc/qdoc/src/qdoc/puredocparser.cpp | 74 + src/qdoc/qdoc/src/qdoc/puredocparser.h | 31 + .../qdoc/src/qdoc/qdoccommandlineparser.cpp | 178 + .../qdoc/src/qdoc/qdoccommandlineparser.h | 28 + src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp | 1723 ++ src/qdoc/qdoc/src/qdoc/qdocdatabase.h | 395 + src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp | 1490 + src/qdoc/qdoc/src/qdoc/qdocindexfiles.h | 72 + src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp | 170 + src/qdoc/qdoc/src/qdoc/qmlcodemarker.h | 37 + src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp | 143 + src/qdoc/qdoc/src/qdoc/qmlcodeparser.h | 38 + src/qdoc/qdoc/src/qdoc/qmlenumnode.h | 36 + src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp | 760 + src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h | 137 + .../qdoc/src/qdoc/qmlpropertyarguments.cpp | 95 + src/qdoc/qdoc/src/qdoc/qmlpropertyarguments.h | 53 + src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp | 239 + src/qdoc/qdoc/src/qdoc/qmlpropertynode.h | 78 + src/qdoc/qdoc/src/qdoc/qmltypenode.cpp | 194 + src/qdoc/qdoc/src/qdoc/qmltypenode.h | 67 + src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp | 743 + src/qdoc/qdoc/src/qdoc/qmlvisitor.h | 76 + src/qdoc/qdoc/src/qdoc/quoter.cpp | 403 + src/qdoc/qdoc/src/qdoc/quoter.h | 56 + src/qdoc/qdoc/src/qdoc/relatedclass.cpp | 46 + src/qdoc/qdoc/src/qdoc/relatedclass.h | 34 + src/qdoc/qdoc/src/qdoc/sections.cpp | 998 + src/qdoc/qdoc/src/qdoc/sections.h | 208 + src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp | 56 + src/qdoc/qdoc/src/qdoc/sharedcommentnode.h | 52 + src/qdoc/qdoc/src/qdoc/singleton.h | 32 + src/qdoc/qdoc/src/qdoc/sourcefileparser.h | 95 + src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp | 309 + src/qdoc/qdoc/src/qdoc/tagfilewriter.h | 33 + src/qdoc/qdoc/src/qdoc/template_declaration.h | 502 + src/qdoc/qdoc/src/qdoc/text.cpp | 341 + src/qdoc/qdoc/src/qdoc/text.h | 87 + src/qdoc/qdoc/src/qdoc/tokenizer.cpp | 788 + src/qdoc/qdoc/src/qdoc/tokenizer.h | 179 + src/qdoc/qdoc/src/qdoc/topic.h | 29 + src/qdoc/qdoc/src/qdoc/tree.cpp | 1400 + src/qdoc/qdoc/src/qdoc/tree.h | 187 + src/qdoc/qdoc/src/qdoc/typedefnode.cpp | 64 + src/qdoc/qdoc/src/qdoc/typedefnode.h | 56 + src/qdoc/qdoc/src/qdoc/utilities.cpp | 334 + src/qdoc/qdoc/src/qdoc/utilities.h | 44 + src/qdoc/qdoc/src/qdoc/variablenode.cpp | 23 + src/qdoc/qdoc/src/qdoc/variablenode.h | 45 + src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp | 903 + src/qdoc/qdoc/src/qdoc/webxmlgenerator.h | 60 + src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp | 499 + src/qdoc/qdoc/src/qdoc/xmlgenerator.h | 56 + src/qdoc/qdoc/tests/CMakeLists.txt | 8 + src/qdoc/qdoc/tests/config/CMakeLists.txt | 24 + .../testdata/configs/exampletest.qdocconf | 2 + .../testdata/configs/expandvars.qdocconf | 13 + .../testdata/configs/includepaths.qdocconf | 2 + .../testdata/configs/includes/test.qdoc | 1 + .../config/testdata/configs/paths.qdocconf | 5 + .../testdata/configs/sourcelink.qdocconf | 5 + .../config/testdata/configs/vars.qdocconf | 17 + .../exampletest/examples/test/empty/test.pro | 1 + .../examples/test/example1/example1.pro | 1 + .../test/example2/example2.qmlproject | 1 + .../examples/test/example3/example3.pyproject | 1 + .../examples/test/example4/CMakeLists.txt | 1 + .../examples/test/example4/example4.pro | 1 + .../includepaths/include/framework/ignore.h | 1 + .../includepaths/include/more/ignore.h | 1 + .../testdata/includepaths/include/purpose.h | 1 + .../includepaths/include/system/ignore.h | 1 + .../includepaths/includepaths.qdocconf | 16 + .../config/testdata/paths/includes/test.qdoc | 1 + .../config/testdata/paths/paths.qdocconf | 2 + src/qdoc/qdoc/tests/config/tst_config.cpp | 195 + .../qdoc/tests/generatedoutput/CMakeLists.txt | 46 + .../expected_output/autolinking.html | 36 + .../expected_output/cpptypes.html | 25 + .../crossmodule/all-namespaces.html | 20 + .../crossmodule/testtype-members.html | 37 + .../crossmodule/crossmodule/testtype.html | 67 + .../crossmoduleref-sub-crossmodule.html | 30 + .../crossmodule/testtype-members.html | 37 + .../expected_output/crossmodule/testtype.html | 67 + .../includefromexampledirs/index.html | 27 + .../qml-qdoc-test-abstractparent-members.html | 20 + .../qml-qdoc-test-abstractparent.html | 72 + .../expected_output/index-linking.html | 37 + .../qdoc-test-qmlmodule.xml | 16 + .../test-componentset-example.xml | 37 + .../noautolist-docbook/testcpp-module.xml | 49 + .../noautolist/qdoc-test-qmlmodule.html | 19 + .../noautolist/test-componentset-example.html | 47 + .../noautolist/testcpp-module.html | 45 + .../expected_output/obsolete-classes.html | 32 + .../qml-linkmodule-grandchild-members.html | 26 + .../qml-qdoc-test-parent.xml | 45 + .../qml-qdoc-test-anotherchild-members.html | 28 + .../qml-qdoc-test-parent.html | 65 + .../expected_output/testcpp-module.html | 57 + .../expected_output/testcpp.index | 81 + .../testcpp/crossmoduleref.html | 48 + .../testcpp/testcpp-module.html | 57 + .../testcpp/testqdoc-test-members.html | 30 + .../testcpp/testqdoc-test.html | 167 + .../expected_output/testcpp/testqdoc.html | 64 + .../testqdoc-test-members.html | 30 + .../testqdoc-test-obsolete.html | 45 + .../expected_output/testqdoc-test.html | 167 + .../testqdoc-testderived-members.html | 33 + .../testqdoc-testderived-obsolete.html | 26 + .../expected_output/testqdoc-testderived.html | 81 + .../expected_output/testqdoc.html | 64 + .../testdata/configs/noautolist.qdocconf | 66 + .../testdata/configs/testcpp.qdocconf | 32 + .../configs/testcpp_singleexec.qdocconf | 33 + .../testdata/crossmodule/CrossModule | 2 + .../testdata/crossmodule/crossmodule.qdocconf | 28 + .../crossmodule_singleexec.qdocconf | 6 + .../testdata/crossmodule/namespaces.qdoc | 9 + .../testdata/crossmodule/testtype.cpp | 45 + .../testdata/crossmodule/testtype.h | 16 + .../testdata/dontdocument/TestCPP | 2 + .../dontdocument/dontdocument.qdocconf | 46 + .../testdata/examples/demos/demo/demo.cpp | 11 + .../testdata/examples/demos/demo/demo.pro | 2 + .../examples/demos/demo/doc/src/demo.qdoc | 11 + .../demos/demo/dontxclude/CMakeLists.txt | 2 + .../demos/demo/excludes/CMakeLists.txt | 2 + .../examples/demos/hidden/doc/src/hidden.qdoc | 11 + .../testdata/examples/demos/hidden/hidden.pro | 2 + .../generatedoutput/testdata/images/01.png | Bin 0 -> 1142 bytes .../testdata/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../excludes/anotherindex.qdoc | 14 + .../excludes/parentinclude.qdoc | 28 + .../includefromexampledirs.qdocconf | 66 + .../src/includefromparent.qdoc | 41 + .../includefromexampledirs/src/parent.qdocinc | 1 + .../indexlinking/indexlinking.qdocconf | 29 + .../testdata/indexlinking/linking.qdoc | 41 + .../generatedoutput/testdata/qml/DocTest.qml | 85 + .../generatedoutput/testdata/qml/Item.qdoc | 9 + .../testdata/qml/cmaketest/CMakeLists.txt | 2 + .../qml/cmaketest/doc/src/cmaketest.qdoc | 9 + .../testdata/qml/cmaketest/main.cpp | 1 + .../testdata/qml/componentset/ProgressBar.qml | 98 + .../testdata/qml/componentset/Switch.qml | 105 + .../testdata/qml/componentset/TabWidget.qml | 146 + .../qml/componentset/componentset.pro | 5 + .../qml/componentset/componentset.qml | 7 + .../testdata/qml/componentset/examples.qdoc | 82 + .../qml/componentset/uicomponents.qdoc.sample | 14 + .../testdata/qml/doctest/DocTest.qml | 11 + .../generatedoutput/testdata/qml/modules.qdoc | 19 + .../generatedoutput/testdata/qml/parent.qdoc | 87 + .../generatedoutput/testdata/qml/type.cpp | 127 + .../testdata/qmlpropertygroups/parent.qdoc | 37 + .../qmlpropertygroups.qdocconf | 64 + .../testdata/singleexec/singleexec.qdocconf | 4 + .../generatedoutput/testdata/testcpp/TestCPP | 5 + .../testdata/testcpp/classlists.qdoc | 51 + .../testdata/testcpp/properties.qdoc | 55 + .../testcpp/snippets/snippet_testcpp.cpp | 3 + .../testdata/testcpp/testcpp.cpp | 425 + .../testdata/testcpp/testcpp.h | 140 + .../generatedoutput/tst_generatedoutput.cpp | 283 + src/qdoc/qdoc/tests/inode/CMakeLists.txt | 20 + .../qdoc/tests/inode/tst_inodeinterface.cpp | 49 + src/qdoc/qdoc/tests/nativeenum/CMakeLists.txt | 20 + .../qdoc/tests/nativeenum/tst_nativeenum.cpp | 43 + src/qdoc/qdoc/tests/qdoc/CMakeLists.txt | 31 + .../filesystem/catch_directorypath.cpp | 195 + .../boundaries/filesystem/catch_filepath.cpp | 136 + .../qdoc/filesystem/catch_fileresolver.cpp | 307 + src/qdoc/qdoc/tests/qdoc/main.cpp | 12 + .../qdoc/tests/qdoc/node/catch_genustypes.cpp | 150 + .../qdoccommandlineparser/CMakeLists.txt | 41 + .../qdoccommandlineparser/tst_arguments.txt | 22 + .../tst_qdoccommandlineparser.cpp | 161 + src/qdoc/qdoc/tests/utilities/CMakeLists.txt | 24 + .../qdoc/tests/utilities/tst_utilities.cpp | 278 + .../validateqdocoutputfiles/CMakeLists.txt | 44 + .../tests/validateqdocoutputfiles/README.md | 91 + .../attributions/attributions.qdocconf | 16 + .../expected/docbook/attributions-test.xml | 11 + .../expected/docbook/some-attribution-1.xml | 12 + .../expected/docbook/some-attribution-2.xml | 12 + .../expected/html/attributions-test.html | 21 + .../expected/html/attributions.index | 11 + .../expected/html/some-attribution-1.html | 18 + .../expected/html/some-attribution-2.html | 18 + .../expected/webxml/attributions-test.webxml | 10 + .../expected/webxml/attributions.index | 11 + .../expected/webxml/some-attribution-1.webxml | 11 + .../expected/webxml/some-attribution-2.webxml | 11 + .../testdata/attributions/src/test.qdoc | 40 + .../auto_as_return_type.qdocconf | 24 + .../expected/docbook/qdoctest-module.xml | 34 + .../expected/docbook/qdoctests-numbers.xml | 71 + .../expected/docbook/qdoctests.xml | 29 + .../expected/html/auto-as-return-type.index | 31 + .../expected/html/qdoctest-module.html | 34 + .../html/qdoctests-numbers-members.html | 24 + .../expected/html/qdoctests-numbers.html | 82 + .../expected/html/qdoctests.html | 42 + .../expected/webxml/auto-as-return-type.index | 31 + .../expected/webxml/qdoctest-module.webxml | 4 + .../expected/webxml/qdoctests-numbers.webxml | 61 + .../expected/webxml/qdoctests.webxml | 66 + .../auto_as_return_type/src/numbers.cpp | 72 + .../auto_as_return_type/src/numbers.h | 27 + .../testdata/bug80259/bug80259.qdocconf | 32 + .../bug80259/expected/html/first-members.html | 17 + .../bug80259/expected/html/first-nested.html | 27 + .../bug80259/expected/html/first.html | 37 + .../bug80259/expected/html/index.html | 21 + .../bug80259/expected/html/second.html | 29 + .../bug80259/expected/html/testmodule.index | 13 + .../bug80259/expected/html/third.html | 29 + .../expected/webxml/first-nested.webxml | 10 + .../bug80259/expected/webxml/first.webxml | 15 + .../bug80259/expected/webxml/index.webxml | 10 + .../bug80259/expected/webxml/second.webxml | 10 + .../bug80259/expected/webxml/testmodule.index | 13 + .../bug80259/expected/webxml/third.webxml | 10 + .../bug80259/src/inc/testmodule/TestModule.h | 6 + .../bug80259/src/inc/testmodule/aaa.h | 7 + .../bug80259/src/inc/testmodule/bbb.h | 8 + .../bug80259/src/inc/testmodule/ccc.h | 7 + .../testdata/bug80259/src/main.cpp | 29 + .../testdata/bug80259/src/qdoc/index.qdoc | 10 + .../clashing_qmltypes.qdocconf | 7 + .../expected/html/barmod-qmlmodule.html | 18 + .../expected/html/clashingqmltypes.index | 11 + .../expected/html/foomod-qmlmodule.html | 18 + .../expected/html/qml-barmod-foo-members.html | 15 + .../expected/html/qml-barmod-foo.html | 33 + .../expected/html/qml-foomod-foo-members.html | 15 + .../expected/html/qml-foomod-foo.html | 33 + .../expected/html/qmltypes-overview.html | 24 + .../testdata/clashing_qmltypes/src/test.qdoc | 23 + .../class_relates/class_relates.qdocconf | 8 + .../expected/html/classrelates.index | 14 + .../class_relates/expected/html/foo-bar.html | 42 + .../expected/html/foo-module.html | 33 + .../class_relates/expected/html/foo.html | 53 + .../testdata/class_relates/src/bar.h | 17 + .../testdata/class_relates/src/foo.h | 14 + .../testdata/class_relates/src/foo.qdoc | 7 + .../cmakedocumentation.qdocconf | 30 + .../expected/docbook/car.xml | 42 + .../expected/docbook/engine.xml | 42 + .../expected/docbook/testcar-module.xml | 23 + .../docbook/testcarprivate-module.xml | 23 + .../cmakedocumentation/expected/html/car.html | 36 + .../expected/html/cmakedocumentation.index | 15 + .../expected/html/engine.html | 36 + .../expected/html/testcar-module.html | 29 + .../expected/html/testcarprivate-module.html | 29 + .../expected/webxml/car.webxml | 15 + .../expected/webxml/cmakedocumentation.index | 15 + .../expected/webxml/engine.webxml | 20 + .../expected/webxml/testcar-module.webxml | 4 + .../webxml/testcarprivate-module.webxml | 4 + .../testdata/cmakedocumentation/src/car.cpp | 46 + .../testdata/cmakedocumentation/src/car.h | 17 + ...mpiler_generated_member_functions.qdocconf | 24 + ...tests-compilergeneratedmemberfunctions.xml | 38 + .../expected/docbook/qdoctests-module.xml | 38 + .../expected/docbook/qdoctests.xml | 30 + .../compiler-generated-member-functions.index | 18 + ...pilergeneratedmemberfunctions-members.html | 20 + ...ests-compilergeneratedmemberfunctions.html | 59 + .../expected/html/qdoctests-module.html | 38 + .../expected/html/qdoctests.html | 43 + .../compiler-generated-member-functions.index | 18 + ...ts-compilergeneratedmemberfunctions.webxml | 28 + .../expected/webxml/qdoctests-module.webxml | 4 + .../expected/webxml/qdoctests.webxml | 34 + .../src/compilergeneratedmemberfunctions.cpp | 45 + .../src/compilergeneratedmemberfunctions.h | 20 + .../comprehensiveproject.qdocconf | 114 + .../expected/docbook/autolinking.xml | 35 + .../expected/docbook/classes.xml | 30 + .../expected/docbook/cpptypes.xml | 33 + .../expected/docbook/crossmoduleref.xml | 59 + .../docbook/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/docbook/obsolete-classes.xml | 31 + .../expected/docbook/qdoc-test-qmlmodule.xml | 60 + .../expected/docbook/qml-int.xml | 36 + .../docbook/qml-qdoc-test-abstractparent.xml | 79 + .../expected/docbook/qml-qdoc-test-child.xml | 79 + .../docbook/qml-qdoc-test-doctest.xml | 181 + .../expected/docbook/qml-qdoc-test-item.xml | 35 + .../docbook/qml-qdoc-test-oldtype.xml | 37 + .../expected/docbook/qml-qdoc-test-type.xml | 237 + .../docbook/qml-qdoc-test-yetanotherchild.xml | 44 + .../docbook/qml-test-nover-doctest.xml | 41 + .../docbook/qml-test-nover-typenoversion.xml | 35 + .../docbook/qml-themodule-thetype.xml | 43 + .../docbook/qml-uicomponents-progressbar.xml | 110 + .../docbook/qml-uicomponents-switch.xml | 53 + .../docbook/qml-uicomponents-tabwidget.xml | 83 + .../expected/docbook/qmlmodules.xml | 109 + .../expected/docbook/seenclass.xml | 42 + .../docbook/test-cmaketest-cmakelists-txt.xml | 14 + .../docbook/test-cmaketest-example.xml | 27 + .../docbook/test-cmaketest-main-cpp.xml | 13 + .../test-componentset-componentset-pro.xml | 18 + .../test-componentset-componentset-qml.xml | 20 + .../docbook/test-componentset-example.xml | 55 + .../test-componentset-progressbar-qml.xml | 111 + .../docbook/test-componentset-switch-qml.xml | 116 + .../test-componentset-tabwidget-qml.xml | 158 + .../docbook/test-demos-demo-demo-cpp.xml | 21 + .../docbook/test-demos-demo-demo-pro.xml | 14 + ...t-demos-demo-dontxclude-cmakelists-txt.xml | 14 + .../docbook/test-demos-demo-example.xml | 32 + .../docbook/test-demos-hidden-example.xml | 12 + .../expected/docbook/test-empty-qmlmodule.xml | 12 + .../expected/docbook/test-nover-qmlmodule.xml | 30 + .../expected/docbook/testcpp-module.xml | 91 + .../expected/docbook/testqdoc-test.xml | 375 + .../expected/docbook/testqdoc-testderived.xml | 394 + .../expected/docbook/testqdoc.xml | 73 + .../expected/docbook/themodule-qmlmodule.xml | 16 + .../docbook/uicomponents-qmlmodule.xml | 34 + .../expected/html/autolinking.html | 38 + .../expected/html/classes.html | 21 + .../expected/html/cpptypes.html | 25 + .../expected/html/crossmoduleref.html | 47 + .../html/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/html/obsolete-classes.html | 32 + .../expected/html/qdoc-test-qmlmodule.html | 28 + .../expected/html/qml-int.html | 48 + .../qml-qdoc-test-abstractparent-members.html | 23 + .../html/qml-qdoc-test-abstractparent.html | 102 + .../html/qml-qdoc-test-child-members.html | 23 + .../expected/html/qml-qdoc-test-child.html | 102 + .../html/qml-qdoc-test-doctest-members.html | 33 + .../expected/html/qml-qdoc-test-doctest.html | 196 + .../html/qml-qdoc-test-item-members.html | 15 + .../expected/html/qml-qdoc-test-item.html | 34 + .../html/qml-qdoc-test-oldtype-members.html | 16 + .../expected/html/qml-qdoc-test-oldtype.html | 37 + .../html/qml-qdoc-test-type-members.html | 37 + .../html/qml-qdoc-test-type-obsolete.html | 33 + .../expected/html/qml-qdoc-test-type.html | 214 + ...qml-qdoc-test-yetanotherchild-members.html | 19 + .../html/qml-qdoc-test-yetanotherchild.html | 51 + .../html/qml-test-nover-doctest-members.html | 17 + .../expected/html/qml-test-nover-doctest.html | 39 + .../qml-test-nover-typenoversion-members.html | 17 + .../html/qml-test-nover-typenoversion.html | 37 + .../html/qml-themodule-thetype-members.html | 19 + .../expected/html/qml-themodule-thetype.html | 48 + .../qml-uicomponents-progressbar-members.html | 24 + .../html/qml-uicomponents-progressbar.html | 102 + .../html/qml-uicomponents-switch-members.html | 21 + .../html/qml-uicomponents-switch.html | 72 + .../qml-uicomponents-tabwidget-members.html | 21 + .../html/qml-uicomponents-tabwidget.html | 88 + .../expected/html/qmlmodules.html | 50 + .../expected/html/seenclass.html | 37 + .../html/test-cmaketest-cmakelists-txt.html | 11 + .../expected/html/test-cmaketest-example.html | 21 + .../html/test-cmaketest-main-cpp.html | 10 + .../test-componentset-componentset-pro.html | 15 + .../test-componentset-componentset-qml.html | 17 + .../html/test-componentset-example.html | 55 + .../test-componentset-progressbar-qml.html | 108 + .../html/test-componentset-switch-qml.html | 113 + .../html/test-componentset-tabwidget-qml.html | 155 + .../html/test-demos-demo-demo-cpp.html | 18 + .../html/test-demos-demo-demo-pro.html | 11 + ...-demos-demo-dontxclude-cmakelists-txt.html | 11 + .../html/test-demos-demo-example.html | 24 + .../html/test-demos-hidden-example.html | 21 + .../expected/html/test-empty-qmlmodule.html | 17 + .../expected/html/test-nover-qmlmodule.html | 23 + .../expected/html/test.index | 259 + .../expected/html/test.qhp | 282 + .../expected/html/testcpp-module.html | 60 + .../expected/html/testqdoc-test-members.html | 30 + .../expected/html/testqdoc-test-obsolete.html | 45 + .../expected/html/testqdoc-test.html | 165 + .../html/testqdoc-testderived-members.html | 52 + .../html/testqdoc-testderived-obsolete.html | 26 + .../expected/html/testqdoc-testderived.html | 210 + .../expected/html/testqdoc.html | 65 + .../expected/html/testtagfile.tags | 546 + .../expected/html/themodule-qmlmodule.html | 18 + .../expected/html/uicomponents-qmlmodule.html | 24 + .../expected/webxml/autolinking.webxml | 41 + .../expected/webxml/classes.webxml | 10 + .../expected/webxml/cpptypes.webxml | 22 + .../expected/webxml/crossmoduleref.webxml | 15 + .../webxml/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/webxml/obsolete-classes.webxml | 18 + .../webxml/qdoc-test-qmlmodule.webxml | 4 + .../expected/webxml/qmlmodules.webxml | 21 + .../expected/webxml/seenclass.webxml | 10 + .../test-cmaketest-cmakelists-txt.webxml | 11 + .../webxml/test-cmaketest-example.webxml | 23 + .../webxml/test-cmaketest-main-cpp.webxml | 10 + .../test-componentset-componentset-pro.webxml | 14 + .../test-componentset-componentset-qml.webxml | 16 + .../webxml/test-componentset-example.webxml | 67 + .../test-componentset-progressbar-qml.webxml | 107 + .../test-componentset-switch-qml.webxml | 112 + .../test-componentset-tabwidget-qml.webxml | 154 + .../webxml/test-demos-demo-demo-cpp.webxml | 18 + .../webxml/test-demos-demo-demo-pro.webxml | 11 + ...emos-demo-dontxclude-cmakelists-txt.webxml | 11 + .../webxml/test-demos-demo-example.webxml | 30 + .../webxml/test-demos-hidden-example.webxml | 11 + .../webxml/test-empty-qmlmodule.webxml | 4 + .../webxml/test-nover-qmlmodule.webxml | 4 + .../expected/webxml/test.index | 247 + .../expected/webxml/testcpp-module.webxml | 4 + .../expected/webxml/testqdoc-test.webxml | 121 + .../webxml/testqdoc-testderived.webxml | 156 + .../expected/webxml/testqdoc.webxml | 285 + .../expected/webxml/testtagfile.tags | 546 + .../webxml/themodule-qmlmodule.webxml | 4 + .../webxml/uicomponents-qmlmodule.webxml | 4 + .../testdata/comprehensiveproject/src/TestCPP | 5 + .../comprehensiveproject/src/classlists.qdoc | 57 + .../comprehensiveproject/src/dont.cpp | 22 + .../testdata/comprehensiveproject/src/dont.h | 16 + .../src/examples/demos/demo/demo.cpp | 11 + .../src/examples/demos/demo/demo.pro | 2 + .../src/examples/demos/demo/doc/src/demo.qdoc | 11 + .../demos/demo/dontxclude/CMakeLists.txt | 2 + .../demos/demo/excludes/CMakeLists.txt | 2 + .../examples/demos/hidden/doc/src/hidden.qdoc | 11 + .../src/examples/demos/hidden/hidden.pro | 2 + .../comprehensiveproject/src/images/01.png | Bin 0 -> 1142 bytes .../src/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../comprehensiveproject/src/properties.qdoc | 60 + .../comprehensiveproject/src/qml/DocTest.qml | 110 + .../comprehensiveproject/src/qml/Item.qdoc | 9 + .../src/qml/cmaketest/CMakeLists.txt | 2 + .../src/qml/cmaketest/doc/src/cmaketest.qdoc | 9 + .../src/qml/cmaketest/main.cpp | 1 + .../src/qml/componentset/ProgressBar.qml | 98 + .../src/qml/componentset/Switch.qml | 105 + .../src/qml/componentset/TabWidget.qml | 146 + .../src/qml/componentset/componentset.pro | 5 + .../src/qml/componentset/componentset.qml | 7 + .../src/qml/componentset/examples.qdoc | 82 + .../qml/componentset/uicomponents.qdoc.sample | 14 + .../src/qml/doctest/DocTest.qml | 11 + .../comprehensiveproject/src/qml/modules.qdoc | 19 + .../comprehensiveproject/src/qml/parent.qdoc | 87 + .../comprehensiveproject/src/qml/type.cpp | 133 + .../src/snippets/snippet_testcpp.cpp | 3 + .../comprehensiveproject/src/testcpp.cpp | 428 + .../comprehensiveproject/src/testcpp.h | 145 + .../comprehensiveproject/src/unseenclass.qdoc | 11 + .../comprehensiveproject_headerdocs.qdocconf | 101 + .../expected/docbook/autolinking.xml | 35 + .../expected/docbook/classes.xml | 36 + .../expected/docbook/cpptypes.xml | 33 + .../expected/docbook/crossmoduleref.xml | 59 + .../docbook/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/docbook/obsolete-classes.xml | 31 + .../expected/docbook/qdoc-test-qmlmodule.xml | 54 + .../expected/docbook/qml-int.xml | 36 + .../docbook/qml-qdoc-test-abstractparent.xml | 79 + .../expected/docbook/qml-qdoc-test-child.xml | 79 + .../docbook/qml-qdoc-test-doctest.xml | 117 + .../docbook/qml-qdoc-test-oldtype.xml | 37 + .../expected/docbook/qml-qdoc-test-type.xml | 237 + .../docbook/qml-qdoc-test-yetanotherchild.xml | 44 + .../docbook/qml-test-nover-doctest.xml | 35 + .../docbook/qml-test-nover-typenoversion.xml | 35 + .../docbook/qml-themodule-thetype.xml | 43 + .../docbook/qml-uicomponents-progressbar.xml | 104 + .../docbook/qml-uicomponents-switch.xml | 47 + .../docbook/qml-uicomponents-tabwidget.xml | 77 + .../expected/docbook/qmlmodules.xml | 103 + .../expected/docbook/seenclass.xml | 42 + .../docbook/test-cmaketest-cmakelists-txt.xml | 14 + .../docbook/test-cmaketest-example.xml | 27 + .../docbook/test-cmaketest-main-cpp.xml | 13 + .../test-componentset-componentset-pro.xml | 18 + .../test-componentset-componentset-qml.xml | 20 + .../docbook/test-componentset-example.xml | 55 + .../test-componentset-progressbar-qml.xml | 111 + .../docbook/test-componentset-switch-qml.xml | 116 + .../test-componentset-tabwidget-qml.xml | 158 + .../docbook/test-demos-demo-demo-cpp.xml | 21 + .../docbook/test-demos-demo-demo-pro.xml | 14 + ...t-demos-demo-dontxclude-cmakelists-txt.xml | 14 + .../docbook/test-demos-demo-example.xml | 32 + .../docbook/test-demos-hidden-example.xml | 12 + .../expected/docbook/test-empty-qmlmodule.xml | 12 + .../expected/docbook/test-nover-qmlmodule.xml | 30 + .../expected/docbook/testcpp-module.xml | 101 + .../expected/docbook/testqdoc-test-struct.xml | 35 + .../expected/docbook/testqdoc-test.xml | 419 + .../expected/docbook/testqdoc-testderived.xml | 410 + .../expected/docbook/testqdoc-vec.xml | 43 + .../expected/docbook/testqdoc.xml | 113 + .../expected/docbook/themodule-qmlmodule.xml | 16 + .../docbook/uicomponents-qmlmodule.xml | 34 + .../expected/html/autolinking.html | 38 + .../expected/html/classes.html | 22 + .../expected/html/cpptypes.html | 25 + .../expected/html/crossmoduleref.html | 47 + .../html/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/html/obsolete-classes.html | 32 + .../expected/html/qdoc-test-qmlmodule.html | 27 + .../expected/html/qml-int.html | 48 + .../qml-qdoc-test-abstractparent-members.html | 23 + .../html/qml-qdoc-test-abstractparent.html | 102 + .../html/qml-qdoc-test-child-members.html | 23 + .../expected/html/qml-qdoc-test-child.html | 102 + .../html/qml-qdoc-test-doctest-members.html | 25 + .../expected/html/qml-qdoc-test-doctest.html | 142 + .../html/qml-qdoc-test-oldtype-members.html | 16 + .../expected/html/qml-qdoc-test-oldtype.html | 37 + .../html/qml-qdoc-test-type-members.html | 37 + .../html/qml-qdoc-test-type-obsolete.html | 33 + .../expected/html/qml-qdoc-test-type.html | 214 + ...qml-qdoc-test-yetanotherchild-members.html | 19 + .../html/qml-qdoc-test-yetanotherchild.html | 51 + .../html/qml-test-nover-doctest-members.html | 17 + .../expected/html/qml-test-nover-doctest.html | 37 + .../qml-test-nover-typenoversion-members.html | 17 + .../html/qml-test-nover-typenoversion.html | 37 + .../html/qml-themodule-thetype-members.html | 19 + .../expected/html/qml-themodule-thetype.html | 48 + .../qml-uicomponents-progressbar-members.html | 24 + .../html/qml-uicomponents-progressbar.html | 100 + .../html/qml-uicomponents-switch-members.html | 21 + .../html/qml-uicomponents-switch.html | 70 + .../qml-uicomponents-tabwidget-members.html | 21 + .../html/qml-uicomponents-tabwidget.html | 86 + .../expected/html/qmlmodules.html | 49 + .../expected/html/seenclass.html | 37 + .../html/test-cmaketest-cmakelists-txt.html | 11 + .../expected/html/test-cmaketest-example.html | 21 + .../html/test-cmaketest-main-cpp.html | 10 + .../test-componentset-componentset-pro.html | 15 + .../test-componentset-componentset-qml.html | 17 + .../html/test-componentset-example.html | 55 + .../test-componentset-progressbar-qml.html | 108 + .../html/test-componentset-switch-qml.html | 113 + .../html/test-componentset-tabwidget-qml.html | 155 + .../html/test-demos-demo-demo-cpp.html | 18 + .../html/test-demos-demo-demo-pro.html | 11 + ...-demos-demo-dontxclude-cmakelists-txt.html | 11 + .../html/test-demos-demo-example.html | 24 + .../html/test-demos-hidden-example.html | 21 + .../expected/html/test-empty-qmlmodule.html | 17 + .../expected/html/test-nover-qmlmodule.html | 23 + .../expected/html/test.index | 278 + .../expected/html/test.qhp | 282 + .../expected/html/testcpp-module.html | 60 + .../expected/html/testqdoc-test-members.html | 33 + .../expected/html/testqdoc-test-obsolete.html | 45 + .../expected/html/testqdoc-test-struct.html | 31 + .../expected/html/testqdoc-test.html | 186 + .../html/testqdoc-testderived-members.html | 56 + .../html/testqdoc-testderived-obsolete.html | 26 + .../expected/html/testqdoc-testderived.html | 217 + .../expected/html/testqdoc-vec.html | 38 + .../expected/html/testqdoc.html | 98 + .../expected/html/testtagfile.tags | 597 + .../expected/html/themodule-qmlmodule.html | 18 + .../expected/html/uicomponents-qmlmodule.html | 24 + .../expected/webxml/autolinking.webxml | 41 + .../expected/webxml/classes.webxml | 10 + .../expected/webxml/cpptypes.webxml | 22 + .../expected/webxml/crossmoduleref.webxml | 15 + .../webxml/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/webxml/obsolete-classes.webxml | 18 + .../webxml/qdoc-test-qmlmodule.webxml | 4 + .../expected/webxml/qmlmodules.webxml | 21 + .../expected/webxml/seenclass.webxml | 10 + .../test-cmaketest-cmakelists-txt.webxml | 11 + .../webxml/test-cmaketest-example.webxml | 23 + .../webxml/test-cmaketest-main-cpp.webxml | 10 + .../test-componentset-componentset-pro.webxml | 14 + .../test-componentset-componentset-qml.webxml | 16 + .../webxml/test-componentset-example.webxml | 67 + .../test-componentset-progressbar-qml.webxml | 107 + .../test-componentset-switch-qml.webxml | 112 + .../test-componentset-tabwidget-qml.webxml | 154 + .../webxml/test-demos-demo-demo-cpp.webxml | 18 + .../webxml/test-demos-demo-demo-pro.webxml | 11 + ...emos-demo-dontxclude-cmakelists-txt.webxml | 11 + .../webxml/test-demos-demo-example.webxml | 30 + .../webxml/test-demos-hidden-example.webxml | 11 + .../webxml/test-empty-qmlmodule.webxml | 4 + .../webxml/test-nover-qmlmodule.webxml | 4 + .../expected/webxml/test.index | 266 + .../expected/webxml/testcpp-module.webxml | 4 + .../webxml/testqdoc-test-struct.webxml | 10 + .../expected/webxml/testqdoc-test.webxml | 139 + .../webxml/testqdoc-testderived.webxml | 162 + .../expected/webxml/testqdoc-vec.webxml | 10 + .../expected/webxml/testqdoc.webxml | 332 + .../expected/webxml/testtagfile.tags | 597 + .../webxml/themodule-qmlmodule.webxml | 4 + .../webxml/uicomponents-qmlmodule.webxml | 4 + .../src/TestCPP | 5 + .../src/classlists.qdoc | 57 + .../src/dont.cpp | 13 + .../src/dont.h | 26 + .../src/examples/demos/demo/demo.cpp | 11 + .../src/examples/demos/demo/demo.pro | 2 + .../src/examples/demos/demo/doc/src/demo.qdoc | 11 + .../demos/demo/dontxclude/CMakeLists.txt | 2 + .../demos/demo/excludes/CMakeLists.txt | 2 + .../examples/demos/hidden/doc/src/hidden.qdoc | 11 + .../src/examples/demos/hidden/hidden.pro | 2 + .../src/images/01.png | Bin 0 -> 1142 bytes .../src/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../src/properties.qdoc | 23 + .../src/qml/DocTest.qml | 86 + .../src/qml/cmaketest/CMakeLists.txt | 2 + .../src/qml/cmaketest/doc/src/cmaketest.qdoc | 9 + .../src/qml/cmaketest/main.cpp | 1 + .../src/qml/componentset/ProgressBar.qml | 98 + .../src/qml/componentset/Switch.qml | 105 + .../src/qml/componentset/TabWidget.qml | 146 + .../src/qml/componentset/componentset.pro | 5 + .../src/qml/componentset/componentset.qml | 7 + .../src/qml/componentset/examples.qdoc | 80 + .../qml/componentset/uicomponents.qdoc.sample | 14 + .../src/qml/doctest/DocTest.qml | 11 + .../src/qml/modules.qdoc | 19 + .../src/qml/parent.qdoc | 87 + .../src/qml/type.qdoc | 131 + .../src/snippets/snippet_testcpp.cpp | 3 + .../src/testcpp.cpp | 101 + .../src/testcpp.h | 429 + .../src/testcpp.qdoc | 30 + .../src/unseenclass.qdoc | 11 + .../customsortedlists.qdocconf | 17 + .../customsortedlists/expected/docbook/a.xml | 11 + .../customsortedlists/expected/docbook/b.xml | 11 + .../customsortedlists/expected/docbook/c.xml | 11 + .../customsortedlists/expected/docbook/d.xml | 11 + .../expected/docbook/lists.xml | 102 + .../customsortedlists/expected/html/a.html | 17 + .../customsortedlists/expected/html/b.html | 17 + .../customsortedlists/expected/html/c.html | 17 + .../expected/html/customsortedlists.index | 17 + .../customsortedlists/expected/html/d.html | 17 + .../expected/html/lists.html | 54 + .../expected/webxml/a.webxml | 10 + .../expected/webxml/b.webxml | 10 + .../expected/webxml/c.webxml | 10 + .../expected/webxml/customsortedlists.index | 17 + .../expected/webxml/d.webxml | 10 + .../expected/webxml/lists.webxml | 111 + .../testdata/customsortedlists/src/test.qdoc | 46 + .../testdata/cxx20/cxx20.qdocconf | 33 + .../testdata/cxx20/expected/docbook/bar.xml | 21 + .../testdata/cxx20/expected/docbook/baz.xml | 21 + ...glywithoneclassandpartiallywithanother.xml | 27 + .../comparesstronglywiththreeclasses.xml | 22 + ...glywiththreeclassesacrossmultiplelines.xml | 22 + .../comparesstronglywithtwoclasses.xml | 26 + .../docbook/equalitycomparableclass.xml | 22 + .../testdata/cxx20/expected/docbook/foo.xml | 21 + .../docbook/partiallyorderedclass.xml | 22 + .../expected/docbook/stronglyorderedclass.xml | 22 + .../expected/docbook/weaklyorderedclass.xml | 22 + .../testdata/cxx20/expected/html/bar.html | 28 + .../testdata/cxx20/expected/html/baz.html | 28 + ...lywithoneclassandpartiallywithanother.html | 37 + .../comparesstronglywiththreeclasses.html | 29 + ...lywiththreeclassesacrossmultiplelines.html | 29 + .../html/comparesstronglywithtwoclasses.html | 33 + .../testdata/cxx20/expected/html/cxx20.index | 18 + .../html/equalitycomparableclass.html | 29 + .../testdata/cxx20/expected/html/foo.html | 28 + .../expected/html/partiallyorderedclass.html | 29 + .../expected/html/stronglyorderedclass.html | 29 + .../expected/html/weaklyorderedclass.html | 29 + .../testdata/cxx20/expected/webxml/bar.webxml | 8 + .../testdata/cxx20/expected/webxml/baz.webxml | 8 + ...withoneclassandpartiallywithanother.webxml | 11 + .../comparesstronglywiththreeclasses.webxml | 8 + ...withthreeclassesacrossmultiplelines.webxml | 8 + .../comparesstronglywithtwoclasses.webxml | 8 + .../cxx20/expected/webxml/cxx20.index | 18 + .../webxml/equalitycomparableclass.webxml | 8 + .../testdata/cxx20/expected/webxml/foo.webxml | 8 + .../webxml/partiallyorderedclass.webxml | 8 + .../webxml/stronglyorderedclass.webxml | 8 + .../expected/webxml/weaklyorderedclass.webxml | 8 + .../src/classes_with_various_ordering.cpp | 89 + .../cxx20/src/classes_with_various_ordering.h | 41 + ...ection_titles_have_unique_anchors.qdocconf | 26 + .../expected/docbook/page-one.xml | 27 + ...e-section-titles-have-unique-anchors.index | 12 + .../expected/html/page-one.html | 35 + ...e-section-titles-have-unique-anchors.index | 12 + .../expected/webxml/page-one.webxml | 30 + .../src/page_one.qdoc | 21 + .../expected/html/external-page-hyphens.index | 13 + .../expected/html/qdoctest-module.html | 32 + .../externalpage_hyphens.qdocconf | 23 + .../externalpage_hyphens/src/test.qdoc | 56 + .../globalfunc/expected/html/globals.html | 45 + .../expected/html/testglobals-module.html | 29 + .../expected/html/testglobals.index | 23 + .../testdata/globalfunc/globalfunc.qdocconf | 25 + .../testdata/globalfunc/src/TestGlobals | 1 + .../testdata/globalfunc/src/global.h | 8 + .../testdata/globalfunc/src/global.qdoc | 25 + .../headerfile/expected/docbook/headers.xml | 19 + .../expected/docbook/testheader.xml | 68 + .../headerfile/expected/docbook/tests.xml | 19 + .../headerfile/expected/html/headerfile.index | 23 + .../headerfile/expected/html/headers.html | 19 + .../headerfile/expected/html/testheader.html | 69 + .../headerfile/expected/html/tests.html | 19 + .../expected/webxml/headerfile.index | 23 + .../headerfile/expected/webxml/headers.webxml | 21 + .../expected/webxml/testheader.webxml | 34 + .../headerfile/expected/webxml/tests.webxml | 21 + .../testdata/headerfile/headerfile.qdocconf | 27 + .../testdata/headerfile/src/testheader.cpp | 43 + .../testdata/headerfile/src/testheader.h | 8 + ...other-page-with-comments-in-the-brief.html | 18 + .../expected/html/brief-adventures.html | 27 + .../expected/html/illformatted-examples.html | 31 + ...tteddocumentation-someexample-example.html | 16 + .../html/illformatteddocumentation.index | 22 + .../html/page-with-an-image-at-the-top.html | 19 + .../html/page-with-comment-after-brief.html | 18 + .../html/page-with-comment-in-brief.html | 18 + ...her-page-with-comments-in-the-brief.webxml | 11 + .../expected/webxml/brief-adventures.webxml | 17 + .../webxml/illformatted-examples.webxml | 31 + ...eddocumentation-someexample-example.webxml | 8 + .../webxml/illformatteddocumentation.index | 22 + .../page-with-an-image-at-the-top.webxml | 12 + .../page-with-comment-after-brief.webxml | 11 + .../webxml/page-with-comment-in-brief.webxml | 11 + .../illformatted_documentation.qdocconf | 30 + .../src/brief_adventures.qdoc | 75 + .../src/illformatted-examples.qdoc | 17 + .../src/some_example.qdoc | 8 + .../expected/html/imagealtastitle.index | 9 + .../html/images/leonardo-da-vinci-small.png | Bin 0 -> 9546 bytes .../html/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes ...h-an-image-that-has-a-title-attribute.html | 34 + .../image_alt_as_title.qdocconf | 24 + .../src/image_alt_as_title.qdoc | 55 + .../src/images/leonardo-da-vinci-small.png | Bin 0 -> 9546 bytes .../src/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes ...a-line-comment-in-the-see-also-command.xml | 20 + .../another-page-with-an-image-at-the-top.xml | 19 + .../docbook/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../docbook/line-comment-adventures.xml | 16 + ...-line-comment-in-the-see-also-command.html | 18 + ...another-page-with-an-image-at-the-top.html | 19 + .../html/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../html/line-comment-adventures.html | 27 + .../expected/html/linecomment.index | 11 + ...ine-comment-in-the-see-also-command.webxml | 14 + ...other-page-with-an-image-at-the-top.webxml | 13 + .../webxml/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../webxml/line-comment-adventures.webxml | 16 + .../expected/webxml/linecomment.index | 11 + .../line_comments/line_comments.qdocconf | 28 + .../src/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../src/line_comment_adventures.qdoc | 39 + .../expected/docbook/linkparentheses.xml | 33 + .../expected/html/linkparentheses.html | 27 + .../expected/html/linkparentheses.index | 7 + .../expected/webxml/linkparentheses.index | 7 + .../expected/webxml/linkparentheses.webxml | 38 + .../linkparentheses/html/linkparentheses.html | 27 + .../html/linkparentheses.index | 7 + .../linkparentheses/linkparentheses.qdocconf | 25 + .../testdata/linkparentheses/src/main.cpp | 25 + .../expected/another-page.html | 17 + .../expected/macro-expansion-in-links.index | 8 + .../expected/testpage.html | 17 + .../macro_expansion_in_links.qdocconf | 15 + .../src/anotherpage.qdoc | 10 + .../src/testpage.qdoc | 10 + .../a-minimal-qdoc-configuration.index | 7 + .../expected/readme.html | 17 + .../minimal_configuration.qdocconf | 13 + .../minimal_configuration/src/README.qdoc | 9 + .../docbook/cppmodule-module-suffix.xml | 26 + .../expected/docbook/group.xml | 11 + .../modifiedoutputfilenames-test-example.xml | 10 + .../expected/docbook/page.xml | 10 + .../expected/docbook/prefix-header-suffix.xml | 21 + .../docbook/prefix-namespace-class-suffix.xml | 21 + .../docbook/prefix-namespace-suffix.xml | 27 + .../docbook/qml-qmlmodule-suffix-type.xml | 21 + .../docbook/qmlmodule-qmlmodule-suffix.xml | 16 + .../html/cppmodule-module-suffix.html | 32 + .../expected/html/group.html | 15 + .../modifiedoutputfilenames-test-example.html | 15 + .../html/modifiedoutputfilenames.index | 16 + .../expected/html/page.html | 15 + .../expected/html/prefix-header-suffix.html | 24 + .../html/prefix-namespace-class-suffix.html | 29 + .../html/prefix-namespace-suffix.html | 35 + .../qml-qmlmodule-suffix-type-members.html | 14 + .../html/qml-qmlmodule-suffix-type.html | 29 + .../html/qmlmodule-qmlmodule-suffix.html | 18 + .../webxml/cppmodule-module-suffix.webxml | 4 + .../expected/webxml/group.webxml | 10 + ...odifiedoutputfilenames-test-example.webxml | 8 + .../webxml/modifiedoutputfilenames.index | 16 + .../expected/webxml/page.webxml | 8 + .../webxml/prefix-header-suffix.webxml | 8 + .../prefix-namespace-class-suffix.webxml | 8 + .../webxml/prefix-namespace-suffix.webxml | 11 + .../webxml/qmlmodule-qmlmodule-suffix.webxml | 4 + .../modifiedoutputfilenames.qdocconf | 23 + .../src/example/test/CMakeLists.txt | 1 + .../modifiedoutputfilenames/src/test.cpp | 24 + .../modifiedoutputfilenames/src/test.h | 12 + .../expected/docbook/boringclass.xml | 28 + .../expected/docbook/excitingclass.xml | 32 + .../expected/docbook/moduleinstate-module.xml | 23 + .../expected/html/boringclass.html | 31 + .../expected/html/excitingclass.html | 34 + .../expected/html/moduleinstate-module.html | 30 + .../expected/html/modulestate.index | 11 + .../expected/webxml/boringclass.webxml | 10 + .../expected/webxml/excitingclass.webxml | 15 + .../webxml/moduleinstate-module.webxml | 4 + .../expected/webxml/modulestate.index | 11 + .../testdata/modulestate/modulestate.qdocconf | 30 + .../src/classes_in_stateful_module.h | 15 + .../modulestate/src/module_in_a_state.qdoc | 26 + .../expected/docbook/testaction.xml | 157 + .../expected/docbook/testmodule-module.xml | 26 + .../expected/html/multisignaltest.index | 50 + .../expected/html/testaction-members.html | 28 + .../multisignal/expected/html/testaction.html | 139 + .../expected/html/testmodule-module.html | 33 + .../expected/webxml/multisignaltest.index | 50 + .../expected/webxml/testaction.webxml | 101 + .../expected/webxml/testmodule-module.webxml | 4 + .../testdata/multisignal/multisignal.qdocconf | 30 + .../testdata/multisignal/src/testaction.cpp | 117 + .../testdata/multisignal/src/testaction.h | 44 + .../expected/docbook/testclass.xml | 32 + .../expected/html/noexceptnotes.index | 11 + .../expected/html/testclass-members.html | 18 + .../expected/html/testclass.html | 51 + .../expected/webxml/noexceptnotes.index | 11 + .../expected/webxml/testclass.webxml | 18 + .../noexcept_notes/noexcept_notes.qdocconf | 24 + .../testdata/noexcept_notes/src/testclass.cpp | 17 + .../testdata/noexcept_notes/src/testclass.h | 16 + .../expected/8b5c72eb.html | 16 + .../adventures-with-non-ascii-characters.html | 46 + .../expected/e85685de.html | 16 + .../expected/html/8b5c72eb.webxml | 10 + ...dventures-with-non-ascii-characters.webxml | 45 + .../expected/html/e85685de.webxml | 10 + .../expected/html/mozzarella-7c883eff.webxml | 10 + .../html/nonasciicharacterinput.index | 24 + .../expected/html/santa-14209312.webxml | 10 + ...en-im-titel-berschrift-htm-bfa91582.webxml | 10 + .../expected/html/trademarks-test2.webxml | 10 + .../expected/html/trademarks.webxml | 19 + .../expected/mozzarella-7c883eff.html | 16 + .../expected/nonasciicharacterinput.index | 24 + .../expected/santa-14209312.html | 16 + ...aben-im-titel-berschrift-htm-bfa91582.html | 16 + .../expected/trademarks-test2.html | 17 + .../expected/trademarks.html | 28 + .../non_ascii_character_input.qdocconf | 31 + .../adventures_with_non_ascii_characters.qdoc | 112 + .../expected/docbook/crash.xml | 12 + .../expected/docbook/images/01.png | Bin 0 -> 1142 bytes .../docbook/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../qdoctests-qdocfileoutput-exhaustive.xml | 130 + .../qdoctests-qdocfileoutput-linking.xml | 19 + .../docbook/qdoctests-qdocfileoutput.xml | 80 + .../qdoctests-qdocmanuallikefileoutput.xml | 27 + .../expected/docbook/toc.xml | 23 + .../expected/html/crash.html | 16 + .../expected/html/images/01.png | Bin 0 -> 1142 bytes .../html/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/html/outputfromqdocfiles.index | 33 + .../qdoctests-qdocfileoutput-exhaustive.html | 79 + .../qdoctests-qdocfileoutput-linking.html | 37 + .../html/qdoctests-qdocfileoutput.html | 70 + .../qdoctests-qdocmanuallikefileoutput.html | 63 + .../expected/html/toc.html | 29 + .../expected/webxml/crash.webxml | 10 + .../expected/webxml/images/01.png | Bin 0 -> 1142 bytes .../webxml/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/webxml/outputfromqdocfiles.index | 33 + ...qdoctests-qdocfileoutput-exhaustive.webxml | 92 + .../qdoctests-qdocfileoutput-linking.webxml | 20 + .../webxml/qdoctests-qdocfileoutput.webxml | 95 + .../qdoctests-qdocmanuallikefileoutput.webxml | 57 + .../expected/webxml/toc.webxml | 24 + .../outputfromqdocfiles.qdocconf | 44 + .../outputfromqdocfiles/src/images/01.png | Bin 0 -> 1142 bytes .../src/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../src/qdoctests-outputfromqdocfiles.qdoc | 243 + ...octests-outputfromqdocmanuallikefiles.qdoc | 59 + .../outputfromqdocfiles/src/snippets/main.cpp | 10 + .../expected/overload-command-test.index | 17 + .../overload_command/expected/test.qhp | 26 + .../expected/testclass-members.html | 21 + .../overload_command/expected/testclass.html | 64 + .../overload_command.qdocconf | 25 + .../src/classwithoverloads.cpp | 44 + .../overload_command/src/classwithoverloads.h | 12 + .../docbook/overloadedemptytargets-module.xml | 26 + .../expected/docbook/testclass.xml | 96 + .../html/overloadedemptytargets-module.html | 34 + .../html/overloadedemptytargets.index | 24 + .../expected/html/testclass-members.html | 23 + .../expected/html/testclass.html | 121 + .../overloadedemptytargets-module.webxml | 4 + .../expected/overloadedemptytargets.index | 24 + .../expected/testclass.webxml | 41 + .../overloaded_empty_targets.qdocconf | 24 + .../src/testclass.cpp | 45 + .../overloaded_empty_targets/src/testclass.h | 26 + .../docbook/overloadedsignalsslots-module.xml | 26 + .../expected/docbook/testclass.xml | 96 + .../html/overloadedsignalsslots-module.html | 34 + .../html/overloadedsignalsslots.index | 24 + .../expected/html/testclass-members.html | 23 + .../expected/html/testclass.html | 121 + .../overloadedsignalsslots-module.webxml | 4 + .../webxml/overloadedsignalsslots.index | 24 + .../expected/webxml/testclass.webxml | 41 + .../overloaded_signals_slots.qdocconf | 27 + .../src/testclass.cpp | 49 + .../overloaded_signals_slots/src/testclass.h | 26 + .../docbook/stdpair-proxypage-proxy.xml | 16 + .../proxypage/expected/html/proxypage.index | 10 + .../html/stdpair-proxypage-proxy.html | 20 + .../testdata/proxypage/proxypage.qdocconf | 26 + .../testdata/proxypage/src/proxy.h | 11 + .../testdata/proxypage/src/proxy.qdoc | 9 + .../expected/html/qdoctest-qmlmodule.html | 25 + .../expected/html/qml-qdoctest-a-members.html | 15 + .../expected/html/qml-qdoctest-a.html | 35 + ...est-abstractapplicationwindow-members.html | 15 + ...ml-qdoctest-abstractapplicationwindow.html | 35 + ...ml-qdoctest-applicationwindow-members.html | 15 + .../html/qml-qdoctest-applicationwindow.html | 33 + .../expected/html/qml-qdoctest-b-members.html | 15 + .../expected/html/qml-qdoctest-b.html | 35 + .../expected/html/qml-qdoctest-c-members.html | 15 + .../expected/html/qml-qdoctest-c.html | 33 + .../qml-qdoctest-selfinheriting-members.html | 15 + .../html/qml-qdoctest-selfinheriting.html | 31 + .../expected/html/qmlmutualdefs.index | 13 + .../qml_mutual_definitions.qdocconf | 23 + .../testdata/qml_mutual_definitions/src/A.qml | 12 + .../src/AbstractApplicationWindow.qml | 10 + .../src/ApplicationWindow.qml | 12 + .../testdata/qml_mutual_definitions/src/B.qml | 12 + .../testdata/qml_mutual_definitions/src/C.qml | 12 + .../src/SelfInheriting.qml | 12 + .../testdata/qml_mutual_definitions/src/foo.h | 2 + .../qml_mutual_definitions/src/foo.qdoc | 8 + ...ick-controls-abstractapplicationwindow.xml | 21 + ...qml-qtquick-controls-applicationwindow.xml | 27 + ...-some-module-abstractapplicationwindow.xml | 34 + .../qml-some-module-applicationwindow.xml | 28 + .../docbook/qtquick-controls-qmlmodule.xml | 19 + .../docbook/some-module-qmlmodule.xml | 25 + .../expected/html/qml-namespaces.index | 12 + ...ols-abstractapplicationwindow-members.html | 14 + ...ck-controls-abstractapplicationwindow.html | 29 + ...ck-controls-applicationwindow-members.html | 14 + ...ml-qtquick-controls-applicationwindow.html | 31 + ...ule-abstractapplicationwindow-members.html | 14 + ...some-module-abstractapplicationwindow.html | 34 + ...some-module-applicationwindow-members.html | 15 + .../qml-some-module-applicationwindow.html | 35 + .../html/qtquick-controls-qmlmodule.html | 19 + .../expected/html/some-module-qmlmodule.html | 19 + .../qml_namespaces/qml_namespaces.qdocconf | 16 + .../src/AbstractApplicationWindow.qml | 15 + .../qml_namespaces/src/ApplicationWindow.qml | 15 + .../testdata/qml_namespaces/src/modules.qdoc | 14 + .../expected/docbook/cppcar.xml | 28 + .../docbook/qml-qmlnativetypesynopsis-car.xml | 66 + .../qml-qmlnativetypesynopsis-truck.xml | 66 + .../qmlnativetypesynopsis-qmlmodule.xml | 25 + .../expected/html/cppcar.html | 33 + .../html/qml-nativetype-synopsis-test.index | 20 + ...qml-qmlnativetypesynopsis-car-members.html | 15 + .../html/qml-qmlnativetypesynopsis-car.html | 50 + ...l-qmlnativetypesynopsis-truck-members.html | 15 + .../html/qml-qmlnativetypesynopsis-truck.html | 50 + .../html/qmlnativetypesynopsis-qmlmodule.html | 19 + .../expected/webxml/cppcar.webxml | 10 + .../webxml/qml-nativetype-synopsis-test.index | 20 + .../qmlnativetypesynopsis-qmlmodule.webxml | 4 + .../qml_nativetype_synopsis.qdocconf | 24 + .../qml_nativetype_synopsis/src/cppcar.cpp | 68 + .../qml_nativetype_synopsis/src/cppcar.h | 10 + .../expected/docbook/class.xml | 136 + .../expected/docbook/module-module.xml | 18 + .../expected/docbook/qml-qmlmodule-type.xml | 249 + .../expected/docbook/qmlmodule-qmlmodule.xml | 16 + .../expected/html/class-members.html | 18 + .../expected/html/class.html | 59 + .../expected/html/module-module.html | 28 + .../html/qml-qmlmodule-type-members.html | 21 + .../expected/html/qml-qmlmodule-type.html | 114 + .../expected/html/qmlenumvaluesfromcpp.index | 33 + .../expected/html/qmlenumvaluesfromcpp.qhp | 51 + .../expected/html/qmlmodule-qmlmodule.html | 18 + .../expected/html/test.tag | 96 + .../expected/webxml/class.webxml | 54 + .../expected/webxml/module-module.webxml | 4 + .../webxml/qmlenumvaluesfromcpp.index | 33 + .../webxml/qmlmodule-qmlmodule.webxml | 4 + .../expected/webxml/test.tag | 96 + .../qmlenumvaluesfromcpp.qdocconf | 35 + .../qmlenumvaluesfromcpp/src/class.cpp | 36 + .../testdata/qmlenumvaluesfromcpp/src/class.h | 10 + .../qmlenumvaluesfromcpp/src/qmltype.qdoc | 47 + .../testdata/relatesordering/a.cpp | 11 + .../testdata/relatesordering/a.h | 5 + .../testdata/relatesordering/b.cpp | 12 + .../relatesordering/expected/bar.html | 35 + .../expected/module-module.html | 22 + .../expected/relatesordering.index | 11 + .../relatesordering/relatesordering.qdocconf | 8 + .../expected/docbook/autolinking.xml | 32 + .../scopedenum/expected/docbook/cpptypes.xml | 30 + .../expected/docbook/crossmoduleref.xml | 49 + .../expected/docbook/obsolete-classes.xml | 31 + .../expected/docbook/scoped-enum-linking.xml | 12 + .../expected/docbook/testcpp-module.xml | 77 + .../expected/docbook/testqdoc-test.xml | 252 + .../expected/docbook/testqdoc-testderived.xml | 82 + .../scopedenum/expected/docbook/testqdoc.xml | 63 + .../scopedenum/expected/docbook/whatsnew.xml | 11 + .../scopedenum/expected/html/autolinking.html | 36 + .../scopedenum/expected/html/cpptypes.html | 24 + .../expected/html/crossmoduleref.html | 47 + .../expected/html/obsolete-classes.html | 32 + .../expected/html/scoped-enum-linking.html | 18 + .../expected/html/testcpp-module.html | 55 + .../scopedenum/expected/html/testcpp.index | 91 + .../expected/html/testqdoc-test-members.html | 30 + .../expected/html/testqdoc-test-obsolete.html | 45 + .../expected/html/testqdoc-test.html | 173 + .../html/testqdoc-testderived-members.html | 38 + .../html/testqdoc-testderived-obsolete.html | 26 + .../expected/html/testqdoc-testderived.html | 81 + .../scopedenum/expected/html/testqdoc.html | 64 + .../scopedenum/expected/html/whatsnew.html | 37 + .../expected/webxml/autolinking.webxml | 36 + .../expected/webxml/cpptypes.webxml | 22 + .../expected/webxml/crossmoduleref.webxml | 15 + .../expected/webxml/obsolete-classes.webxml | 18 + .../webxml/scoped-enum-linking.webxml | 12 + .../expected/webxml/testcpp-module.webxml | 4 + .../scopedenum/expected/webxml/testcpp.index | 91 + .../expected/webxml/testqdoc-test.webxml | 160 + .../webxml/testqdoc-testderived.webxml | 33 + .../expected/webxml/testqdoc.webxml | 201 + .../expected/webxml/whatsnew.webxml | 10 + .../testdata/scopedenum/scopedenum.qdocconf | 42 + .../testdata/scopedenum/src/classlists.qdoc | 51 + .../testdata/scopedenum/src/scopedenum.qdoc | 43 + .../src/snippets/snippet_testcpp.cpp | 3 + .../testdata/scopedenum/src/testcpp.cpp | 402 + .../testdata/scopedenum/src/testcpp.h | 139 + .../expected/mod-module.html | 26 + .../expected/sharedcommentsinnamespace.index | 11 + .../expected/test.html | 42 + .../sharedcommentsinnamespace.qdocconf | 7 + .../sharedcommentsinnamespace/src/test.cpp | 17 + .../sharedcommentsinnamespace/src/test.h | 8 + .../testdata/singleexec/args.txt | 1 + .../singleexec/expected/project-a/a.html | 17 + .../expected/project-a/project-a.index | 7 + .../singleexec/expected/project-b/b.html | 16 + .../expected/project-b/project-b.index | 7 + .../testdata/singleexec/project-a.qdocconf | 8 + .../testdata/singleexec/project-b.qdocconf | 8 + .../testdata/singleexec/singleexec.qdocconf | 2 + .../testdata/singleexec/src/a/a.qdoc | 8 + .../testdata/singleexec/src/b/b.qdoc | 6 + .../expected/snippet-marker-indentation.html | 44 + .../snippet-marker-indentation.webxml | 39 + .../expected/snippet-marker-indentation.xml | 40 + .../expected/snippet_marker_indentation.index | 12 + .../snippet_marker_indentation.qdocconf | 13 + .../snippet_marker_indentation/src/test.cpp | 26 + .../snippet_marker_indentation/src/test.qdoc | 39 + .../expected/docbok/tableaftervalue.xml | 60 + .../html/tableaftervalue-members.html | 18 + .../expected/html/tableaftervalue.html | 56 + .../expected/html/tableaftervalue.index | 13 + .../expected/webxml/tableaftervalue.index | 13 + .../expected/webxml/tableaftervalue.webxml | 35 + .../tableaftervalue/src/table-after-value.cpp | 28 + .../tableaftervalue/src/table-after-value.h | 8 + .../tableaftervalue/tableaftervalue.qdocconf | 30 + .../docbook/templatealiasmodule-module.xml | 26 + .../expected/docbook/testaliases.xml | 40 + .../html/templatealiasdefaultparams.index | 12 + .../html/templatealiasmodule-module.html | 32 + .../expected/html/testaliases.html | 56 + .../webxml/templatealiasdefaultparams.index | 12 + .../webxml/templatealiasmodule-module.webxml | 4 + .../expected/webxml/testaliases.webxml | 28 + .../src/module.qdoc | 9 + .../src/test.cpp | 12 + .../template_alias_default_params/src/test.h | 45 + .../template_alias_default_params.qdocconf | 23 + .../docbook/templated-callables-h.xml | 65 + .../expected/docbook/templatedclass.xml | 66 + .../expected/html/templated-callables-h.html | 81 + .../expected/html/templatedcallables.index | 77 + .../expected/html/templatedclass-members.html | 26 + .../expected/html/templatedclass.html | 90 + .../webxml/templated-callables-h.webxml | 66 + .../expected/webxml/templatedcallables.index | 77 + .../expected/webxml/templatedclass.webxml | 66 + .../src/templated_callables.cpp | 149 + .../src/templated_callables.h | 73 + .../templatedcallables.qdocconf | 30 + .../expected/html/templated-callables-h.html | 81 + .../expected/html/templatedcallables.index | 77 + .../expected/html/templatedclass-members.html | 26 + .../expected/html/templatedclass.html | 90 + .../src/templated_callables.cpp | 19 + .../src/templated_callables.h | 143 + .../templatedcallables_headerdocs.qdocconf | 15 + .../expected/docbook/autolinking.xml | 32 + .../testtemplate/expected/docbook/bar.xml | 43 + .../testtemplate/expected/docbook/baz.xml | 42 + .../expected/docbook/cpptypes.xml | 30 + .../expected/docbook/crossmoduleref.xml | 49 + .../testtemplate/expected/docbook/foo.xml | 43 + .../expected/docbook/obsolete-classes.xml | 31 + .../expected/docbook/testcpp-module.xml | 107 + .../expected/docbook/testqdoc-test-struct.xml | 42 + .../expected/docbook/testqdoc-test.xml | 188 + .../expected/docbook/testqdoc-testderived.xml | 82 + .../expected/docbook/testqdoc-vec.xml | 43 + .../expected/docbook/testqdoc.xml | 67 + .../expected/html/autolinking.html | 36 + .../testtemplate/expected/html/bar.html | 38 + .../testtemplate/expected/html/baz.html | 38 + .../testtemplate/expected/html/cpptypes.html | 24 + .../expected/html/crossmoduleref.html | 47 + .../testtemplate/expected/html/foo.html | 38 + .../expected/html/obsolete-classes.html | 32 + .../expected/html/testcpp-module.html | 60 + .../expected/html/testqdoc-test-members.html | 31 + .../expected/html/testqdoc-test-obsolete.html | 45 + .../expected/html/testqdoc-test-struct.html | 32 + .../expected/html/testqdoc-test.html | 167 + .../html/testqdoc-testderived-members.html | 39 + .../html/testqdoc-testderived-obsolete.html | 26 + .../expected/html/testqdoc-testderived.html | 81 + .../expected/html/testqdoc-vec.html | 38 + .../testtemplate/expected/html/testqdoc.html | 68 + .../expected/html/testtemplate.index | 87 + .../expected/webxml/autolinking.webxml | 36 + .../testtemplate/expected/webxml/bar.webxml | 10 + .../testtemplate/expected/webxml/baz.webxml | 10 + .../expected/webxml/cpptypes.webxml | 22 + .../expected/webxml/crossmoduleref.webxml | 15 + .../testtemplate/expected/webxml/foo.webxml | 10 + .../expected/webxml/obsolete-classes.webxml | 18 + .../expected/webxml/testcpp-module.webxml | 4 + .../webxml/testqdoc-test-struct.webxml | 10 + .../expected/webxml/testqdoc-test.webxml | 130 + .../webxml/testqdoc-testderived.webxml | 33 + .../expected/webxml/testqdoc-vec.webxml | 10 + .../expected/webxml/testqdoc.webxml | 176 + .../expected/webxml/testtemplate.index | 87 + .../testdata/testtemplate/src/classlists.qdoc | 51 + .../src/snippets/snippet_testcpp.cpp | 3 + .../testdata/testtemplate/src/testcpp.cpp | 402 + .../testdata/testtemplate/src/testcpp.h | 137 + .../testtemplate/src/testtemplate.cpp | 23 + .../testdata/testtemplate/src/testtemplate.h | 28 + .../testtemplate/testtemplate.qdocconf | 43 + .../tocnavigation/expected/docbook/a.xml | 13 + .../tocnavigation/expected/docbook/c.xml | 13 + .../tocnavigation/expected/docbook/crash.xml | 14 + .../tocnavigation/expected/docbook/d.xml | 12 + .../tocnavigation/expected/docbook/e.xml | 13 + .../tocnavigation/expected/docbook/group2.xml | 22 + .../expected/docbook/images/01.png | Bin 0 -> 1142 bytes .../docbook/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../qdoctests-qdocfileoutput-exhaustive.xml | 131 + .../qdoctests-qdocfileoutput-linking.xml | 19 + .../docbook/qdoctests-qdocfileoutput.xml | 80 + .../qdoctests-qdocmanuallikefileoutput.xml | 27 + .../expected/docbook/toc-test.xml | 65 + .../tocnavigation/expected/docbook/toc.xml | 24 + .../tocnavigation/expected/html/a.html | 27 + .../tocnavigation/expected/html/c.html | 27 + .../tocnavigation/expected/html/crash.html | 26 + .../tocnavigation/expected/html/d.html | 26 + .../tocnavigation/expected/html/e.html | 29 + .../tocnavigation/expected/html/group2.html | 33 + .../tocnavigation/expected/html/images/01.png | Bin 0 -> 1142 bytes .../html/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/html/outputfromqdocfiles.index | 41 + .../expected/html/outputfromqdocfiles.qhp | 56 + .../qdoctests-qdocfileoutput-exhaustive.html | 91 + .../qdoctests-qdocfileoutput-linking.html | 39 + .../html/qdoctests-qdocfileoutput.html | 72 + .../qdoctests-qdocmanuallikefileoutput.html | 63 + .../tocnavigation/expected/html/toc-test.html | 43 + .../tocnavigation/expected/html/toc.html | 34 + .../tocnavigation/expected/webxml/a.webxml | 11 + .../tocnavigation/expected/webxml/c.webxml | 11 + .../expected/webxml/crash.webxml | 12 + .../tocnavigation/expected/webxml/d.webxml | 10 + .../tocnavigation/expected/webxml/e.webxml | 11 + .../expected/webxml/group2.webxml | 33 + .../expected/webxml/images/01.png | Bin 0 -> 1142 bytes .../webxml/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../expected/webxml/outputfromqdocfiles.index | 41 + ...qdoctests-qdocfileoutput-exhaustive.webxml | 92 + .../qdoctests-qdocfileoutput-linking.webxml | 20 + .../webxml/qdoctests-qdocfileoutput.webxml | 95 + .../qdoctests-qdocmanuallikefileoutput.webxml | 57 + .../expected/webxml/toc-test.webxml | 73 + .../tocnavigation/expected/webxml/toc.webxml | 25 + .../testdata/tocnavigation/src/group.qdoc | 37 + .../testdata/tocnavigation/src/images/01.png | Bin 0 -> 1142 bytes .../src/images/leonardo-da-vinci.png | Bin 0 -> 15076 bytes .../src/qdoctests-outputfromqdocfiles.qdoc | 241 + ...octests-outputfromqdocmanuallikefiles.qdoc | 59 + .../tocnavigation/src/snippets/main.cpp | 10 + .../testdata/tocnavigation/src/toc.qdoc | 27 + .../tocnavigation/tocnavigation.qdocconf | 63 + .../expected/docbook/trademark-test.xml | 15 + .../expected/docbook/trademarks.xml | 19 + .../expected/html/trademark-test.html | 26 + .../expected/html/trademarkcommand.index | 12 + .../expected/html/trademarks.html | 23 + .../expected/webxml/trademark-test.webxml | 15 + .../expected/webxml/trademarkcommand.index | 12 + .../expected/webxml/trademarks.webxml | 20 + .../testdata/trademark_command/src/test.qdoc | 32 + .../trademark_command.qdocconf | 21 + .../expected/docbook/struct.xml | 57 + .../expected/html/struct-members.html | 19 + .../expected/html/struct.html | 53 + .../expected/html/trailingbackslashes.index | 18 + .../expected/webxml/struct.webxml | 32 + .../expected/webxml/trailingbackslashes.index | 18 + .../src/trailing_backslashes.cpp | 32 + .../src/trailing_backslashes.h | 14 + .../trailing_backslashes.qdocconf | 30 + .../usingdirective/expected/docbook/space.xml | 29 + .../usingdirective/expected/html/space.html | 43 + .../expected/html/usingdirective.index | 15 + .../expected/webxml/space.webxml | 16 + .../expected/webxml/usingdirective.index | 15 + .../usingdirective/src/UsingDirective | 2 + .../testdata/usingdirective/src/alias.h | 10 + .../testdata/usingdirective/src/space.cpp | 21 + .../testdata/usingdirective/src/space.h | 10 + .../usingdirective/usingdirective.qdocconf | 29 + .../tst_validateqdocoutputfiles.cpp | 194 + src/qev/CMakeLists.txt | 14 + src/qev/README | 2 + src/qev/qev.cpp | 40 + src/qtattributionsscanner/CMakeLists.txt | 26 + src/qtattributionsscanner/jsongenerator.cpp | 68 + src/qtattributionsscanner/jsongenerator.h | 19 + src/qtattributionsscanner/logging.h | 19 + src/qtattributionsscanner/main.cpp | 186 + src/qtattributionsscanner/package.h | 42 + src/qtattributionsscanner/packagefilter.cpp | 30 + src/qtattributionsscanner/packagefilter.h | 22 + src/qtattributionsscanner/qdocgenerator.cpp | 154 + src/qtattributionsscanner/qdocgenerator.h | 20 + src/qtattributionsscanner/scanner.cpp | 660 + src/qtattributionsscanner/scanner.h | 33 + src/qtdiag/CMakeLists.txt | 51 + src/qtdiag/main.cpp | 50 + src/qtdiag/qtdiag.cpp | 902 + src/qtdiag/qtdiag.h | 23 + src/qtplugininfo/CMakeLists.txt | 14 + src/qtplugininfo/qtplugininfo.cpp | 144 + src/shared/deviceskin/deviceskin.cpp | 806 + src/shared/deviceskin/deviceskin_p.h | 148 + .../ClamshellPhone.skin/ClamshellPhone.skin | 30 + .../ClamshellPhone1-5-closed.png | Bin 0 -> 68200 bytes .../ClamshellPhone1-5-pressed.png | Bin 0 -> 113907 bytes .../ClamshellPhone.skin/ClamshellPhone1-5.png | Bin 0 -> 113450 bytes .../ClamshellPhone.skin/defaultbuttons.conf | 78 + .../PortableMedia.skin/PortableMedia.skin | 14 + .../PortableMedia.skin/defaultbuttons.conf | 23 + .../portablemedia-pressed.png | Bin 0 -> 6183 bytes .../PortableMedia.skin/portablemedia.png | Bin 0 -> 6182 bytes .../PortableMedia.skin/portablemedia.xcf | Bin 0 -> 41592 bytes .../S60-QVGA-Candybar-down.png | Bin 0 -> 161184 bytes .../S60-QVGA-Candybar.png | Bin 0 -> 156789 bytes .../S60-QVGA-Candybar.skin | 15 + .../defaultbuttons.conf | 78 + .../S60-nHD-Touchscreen-down.png | Bin 0 -> 241501 bytes .../S60-nHD-Touchscreen.png | Bin 0 -> 240615 bytes .../S60-nHD-Touchscreen.skin | 10 + .../defaultbuttons.conf | 53 + .../SmartPhone.skin/SmartPhone-pressed.png | Bin 0 -> 111515 bytes .../skins/SmartPhone.skin/SmartPhone.png | Bin 0 -> 101750 bytes .../skins/SmartPhone.skin/SmartPhone.skin | 28 + .../skins/SmartPhone.skin/defaultbuttons.conf | 78 + .../SmartPhone2.skin/SmartPhone2-pressed.png | Bin 0 -> 134749 bytes .../skins/SmartPhone2.skin/SmartPhone2.png | Bin 0 -> 121915 bytes .../skins/SmartPhone2.skin/SmartPhone2.skin | 25 + .../SmartPhone2.skin/defaultbuttons.conf | 52 + .../SmartPhoneWithButtons-pressed.png | Bin 0 -> 103838 bytes .../SmartPhoneWithButtons.png | Bin 0 -> 88470 bytes .../SmartPhoneWithButtons.skin | 31 + .../defaultbuttons.conf | 103 + .../TouchscreenPhone-pressed.png | Bin 0 -> 88599 bytes .../TouchscreenPhone.png | Bin 0 -> 61809 bytes .../TouchscreenPhone.skin | 16 + .../TouchscreenPhone.skin/defaultbuttons.conf | 45 + src/shared/findwidget/abstractfindwidget.cpp | 276 + src/shared/findwidget/abstractfindwidget_p.h | 90 + src/shared/findwidget/images/mac/closetab.png | Bin 0 -> 516 bytes src/shared/findwidget/images/mac/next.png | Bin 0 -> 1310 bytes src/shared/findwidget/images/mac/previous.png | Bin 0 -> 1080 bytes .../findwidget/images/mac/searchfind.png | Bin 0 -> 1836 bytes src/shared/findwidget/images/win/closetab.png | Bin 0 -> 375 bytes src/shared/findwidget/images/win/next.png | Bin 0 -> 1038 bytes src/shared/findwidget/images/win/previous.png | Bin 0 -> 898 bytes .../findwidget/images/win/searchfind.png | Bin 0 -> 1944 bytes src/shared/findwidget/images/wrap.png | Bin 0 -> 500 bytes src/shared/findwidget/itemviewfindwidget.cpp | 288 + src/shared/findwidget/itemviewfindwidget_p.h | 52 + src/shared/findwidget/texteditfindwidget.cpp | 131 + src/shared/findwidget/texteditfindwidget_p.h | 47 + src/shared/fontpanel/fontpanel.cpp | 273 + src/shared/fontpanel/fontpanel_p.h | 69 + src/shared/qtgradienteditor/images/down.png | Bin 0 -> 594 bytes src/shared/qtgradienteditor/images/edit.png | Bin 0 -> 503 bytes .../qtgradienteditor/images/editdelete.png | Bin 0 -> 831 bytes src/shared/qtgradienteditor/images/minus.png | Bin 0 -> 250 bytes src/shared/qtgradienteditor/images/plus.png | Bin 0 -> 462 bytes .../qtgradienteditor/images/spreadpad.png | Bin 0 -> 151 bytes .../qtgradienteditor/images/spreadreflect.png | Bin 0 -> 165 bytes .../qtgradienteditor/images/spreadrepeat.png | Bin 0 -> 156 bytes .../qtgradienteditor/images/typeconical.png | Bin 0 -> 937 bytes .../qtgradienteditor/images/typelinear.png | Bin 0 -> 145 bytes .../qtgradienteditor/images/typeradial.png | Bin 0 -> 583 bytes src/shared/qtgradienteditor/images/up.png | Bin 0 -> 692 bytes src/shared/qtgradienteditor/images/zoomin.png | Bin 0 -> 1208 bytes .../qtgradienteditor/images/zoomout.png | Bin 0 -> 1226 bytes src/shared/qtgradienteditor/qtcolorbutton.cpp | 236 + src/shared/qtgradienteditor/qtcolorbutton_p.h | 59 + src/shared/qtgradienteditor/qtcolorline.cpp | 1077 + src/shared/qtgradienteditor/qtcolorline_p.h | 93 + .../qtgradienteditor/qtgradientdialog.cpp | 315 + .../qtgradienteditor/qtgradientdialog.ui | 85 + .../qtgradienteditor/qtgradientdialog_p.h | 59 + .../qtgradienteditor/qtgradienteditor.cpp | 926 + .../qtgradienteditor/qtgradienteditor.ui | 1341 + .../qtgradienteditor/qtgradienteditor_p.h | 60 + .../qtgradienteditor/qtgradientmanager.cpp | 95 + .../qtgradienteditor/qtgradientmanager_p.h | 65 + .../qtgradientstopscontroller.cpp | 671 + .../qtgradientstopscontroller_p.h | 52 + .../qtgradienteditor/qtgradientstopsmodel.cpp | 421 + .../qtgradienteditor/qtgradientstopsmodel_p.h | 94 + .../qtgradientstopswidget.cpp | 1100 + .../qtgradientstopswidget_p.h | 73 + .../qtgradienteditor/qtgradientutils.cpp | 383 + .../qtgradienteditor/qtgradientutils_p.h | 39 + .../qtgradienteditor/qtgradientview.cpp | 256 + src/shared/qtgradienteditor/qtgradientview.ui | 135 + .../qtgradienteditor/qtgradientview_p.h | 72 + .../qtgradienteditor/qtgradientviewdialog.cpp | 51 + .../qtgradienteditor/qtgradientviewdialog.ui | 85 + .../qtgradienteditor/qtgradientviewdialog_p.h | 48 + .../qtgradienteditor/qtgradientwidget.cpp | 757 + .../qtgradienteditor/qtgradientwidget_p.h | 94 + .../qtpropertybrowser/images/cursor-arrow.png | Bin 0 -> 171 bytes .../qtpropertybrowser/images/cursor-busy.png | Bin 0 -> 201 bytes .../images/cursor-closedhand.png | Bin 0 -> 147 bytes .../qtpropertybrowser/images/cursor-cross.png | Bin 0 -> 130 bytes .../images/cursor-forbidden.png | Bin 0 -> 199 bytes .../qtpropertybrowser/images/cursor-hand.png | Bin 0 -> 159 bytes .../images/cursor-hsplit.png | Bin 0 -> 155 bytes .../qtpropertybrowser/images/cursor-ibeam.png | Bin 0 -> 124 bytes .../images/cursor-openhand.png | Bin 0 -> 160 bytes .../images/cursor-sizeall.png | Bin 0 -> 174 bytes .../qtpropertybrowser/images/cursor-sizeb.png | Bin 0 -> 161 bytes .../qtpropertybrowser/images/cursor-sizef.png | Bin 0 -> 161 bytes .../qtpropertybrowser/images/cursor-sizeh.png | Bin 0 -> 145 bytes .../qtpropertybrowser/images/cursor-sizev.png | Bin 0 -> 141 bytes .../images/cursor-uparrow.png | Bin 0 -> 132 bytes .../images/cursor-vsplit.png | Bin 0 -> 161 bytes .../qtpropertybrowser/images/cursor-wait.png | Bin 0 -> 172 bytes .../images/cursor-whatsthis.png | Bin 0 -> 191 bytes .../qtbuttonpropertybrowser.cpp | 572 + .../qtbuttonpropertybrowser_p.h | 51 + .../qtpropertybrowser/qteditorfactory.cpp | 2479 ++ .../qtpropertybrowser/qteditorfactory_p.h | 326 + .../qtgroupboxpropertybrowser.cpp | 445 + .../qtgroupboxpropertybrowser_p.h | 44 + .../qtpropertybrowser/qtpropertybrowser.cpp | 1910 ++ .../qtpropertybrowser/qtpropertybrowser_p.h | 268 + .../qtpropertybrowserutils.cpp | 287 + .../qtpropertybrowserutils_p.h | 97 + .../qtpropertybrowser/qtpropertymanager.cpp | 6399 +++++ .../qtpropertybrowser/qtpropertymanager_p.h | 693 + .../qttreepropertybrowser.cpp | 1007 + .../qttreepropertybrowser_p.h | 98 + .../qtpropertybrowser/qtvariantproperty.cpp | 2248 ++ .../qtpropertybrowser/qtvariantproperty_p.h | 112 + src/shared/qttoolbardialog/images/back.png | Bin 0 -> 678 bytes src/shared/qttoolbardialog/images/down.png | Bin 0 -> 594 bytes src/shared/qttoolbardialog/images/forward.png | Bin 0 -> 655 bytes src/shared/qttoolbardialog/images/minus.png | Bin 0 -> 250 bytes src/shared/qttoolbardialog/images/plus.png | Bin 0 -> 462 bytes src/shared/qttoolbardialog/images/up.png | Bin 0 -> 692 bytes .../qttoolbardialog/qttoolbardialog.cpp | 1766 ++ src/shared/qttoolbardialog/qttoolbardialog.ui | 207 + .../qttoolbardialog/qttoolbardialog_p.h | 84 + src/uiplugin/CMakeLists.txt | 27 + src/uiplugin/customwidget.h | 68 + src/uiplugin/customwidget.qdoc | 269 + src/uiplugin/qdesignerexportwidget.h | 26 + src/uitools/CMakeLists.txt | 49 + .../doc/images/textfinder-example-find.webp | Bin 0 -> 31170 bytes .../doc/images/textfinder-example-find2.webp | Bin 0 -> 31064 bytes src/uitools/doc/images/textfinder-example.png | Bin 0 -> 15424 bytes src/uitools/doc/images/uitools-examples.png | Bin 0 -> 12021 bytes src/uitools/doc/qtuitools.qdocconf | 52 + .../snippets/quiloader/doc_src_qtuiloader.pro | 6 + src/uitools/doc/snippets/quiloader/main.cpp | 35 + src/uitools/doc/snippets/quiloader/myform.ui | 130 + .../doc/snippets/quiloader/mywidget.cpp | 24 + src/uitools/doc/snippets/quiloader/mywidget.h | 15 + .../doc/snippets/quiloader/mywidget.qrc | 5 + .../doc/snippets/quiloader/quiloader.pro | 4 + src/uitools/doc/src/qtuitools-examples.qdoc | 26 + src/uitools/doc/src/qtuitools-index.qdoc | 54 + src/uitools/doc/src/qtuitools-module.qdoc | 32 + src/uitools/qtuitoolsglobal.h | 24 + src/uitools/quiloader.cpp | 932 + src/uitools/quiloader.h | 61 + src/uitools/quiloader_p.h | 77 + tests/CMakeLists.txt | 8 + tests/README | 18 + tests/auto/CMakeLists.txt | 25 + .../data/QtDesigner.6.0.0.linux-gcc-amd64.txt | 21454 ++++++++++++++ .../data/QtDesigner.6.1.0.linux-gcc-amd64.txt | 21466 ++++++++++++++ .../data/QtDesigner.6.2.0.linux-gcc-amd64.txt | 21876 +++++++++++++++ .../bic/data/QtHelp.6.0.0.linux-gcc-amd64.txt | 20252 ++++++++++++++ .../bic/data/QtHelp.6.1.0.linux-gcc-amd64.txt | 20259 ++++++++++++++ .../bic/data/QtHelp.6.2.0.linux-gcc-amd64.txt | 20669 ++++++++++++++ .../data/QtUiTools.6.0.0.linux-gcc-amd64.txt | 19797 +++++++++++++ .../data/QtUiTools.6.1.0.linux-gcc-amd64.txt | 19804 +++++++++++++ .../data/QtUiTools.6.2.0.linux-gcc-amd64.txt | 20214 ++++++++++++++ tests/auto/cmake/CMakeLists.txt | 68 + tests/auto/cmake/linguist/CMakeLists.txt | 96 + .../cmake/linguist/build_time_checks.cmake | 28 + .../test_add_translation_macro/CMakeLists.txt | 17 + .../myi18nobject.cpp | 28 + .../test_add_translation_macro/myobject_de.ts | 12 + .../some_dir/some_include.h | 9 + .../CMakeLists.txt | 17 + .../myi18nobject.cpp | 26 + .../myobject_de.ts | 12 + .../some_dir/some_include.h | 9 + .../CMakeLists.txt | 22 + .../de/myobject.ts | 12 + .../fr/myobject.ts | 12 + .../myi18nobject.cpp | 32 + .../CMakeLists.txt | 142 + .../test_i18n_auto_ts_file_names/lib.cpp | 1 + .../subdir/CMakeLists.txt | 13 + .../test_i18n_exclusion/CMakeLists.txt | 29 + .../test_i18n_exclusion/apps/CMakeLists.txt | 4 + .../apps/app1/CMakeLists.txt | 25 + .../apps/app1/excluded1.cpp | 9 + .../test_i18n_exclusion/apps/app1/main.cpp | 20 + .../apps/app1/subdir/excluded2.cpp | 9 + .../apps/app1/subdir/subdir3/excluded5.cpp | 9 + .../apps/app1/subdir2/excluded4.cpp | 14 + .../apps/shared/excluded3.cpp | 9 + .../test_i18n_exclusion/check_ts_file.cmake | 38 + .../test_i18n_exclusion/libs/CMakeLists.txt | 5 + .../libs/lib1/CMakeLists.txt | 6 + .../test_i18n_exclusion/libs/lib1/lib1.cpp | 14 + .../test_i18n_exclusion/libs/lib1/lib1.h | 17 + .../libs/lib2/CMakeLists.txt | 7 + .../test_i18n_exclusion/libs/lib2/lib2.cpp | 14 + .../test_i18n_exclusion/libs/lib2/lib2.h | 17 + .../test_i18n_exclusion_de.ts.in | 4 + .../test_i18n_exclusion/tests/CMakeLists.txt | 5 + .../tests/test1/CMakeLists.txt | 5 + .../test_i18n_exclusion/tests/test1/test1.cpp | 12 + .../CMakeLists.txt | 30 + .../app1/CMakeLists.txt | 9 + .../app1/main.cpp | 27 + .../app1/mainwindow.ui | 31 + .../app2/CMakeLists.txt | 12 + .../app2/main.cpp | 27 + .../app2/mainwindow.ui | 31 + .../check_ts_file.cmake | 31 + .../CMakeLists.txt | 7 + .../subdir/CMakeLists.txt | 10 + .../subdir/app1_de.ts | 12 + .../subdir/main.cpp | 32 + .../CMakeLists.txt | 50 + .../fake_translations/qtbase_de.ts | 12 + .../test_i18n_merge_qt_translations/main.cpp | 40 + .../myapp_de.ts | 12 + .../CMakeLists.txt | 61 + .../fake_translations/qtbase_de.ts | 12 + .../main.cpp | 40 + .../myapp_de.ts | 12 + .../myapp_de_TSFOO_tag.ts | 4 + .../myapp_de_language_after_TS.ts | 3 + .../myapp_de_newlines.ts | 6 + .../CMakeLists.txt | 53 + .../fake_translations/greenphone_de.ts | 11 + .../fake_translations/qtbase_de.ts | 12 + .../main.cpp | 48 + .../myapp_de.ts | 12 + .../test_i18n_source_language/CMakeLists.txt | 68 + .../test_i18n_source_language/lib.cpp | 12 + .../test_i18n_source_language/lib2_en.ts.in | 18 + .../post_build_check.cmake | 56 + .../linguist/test_i18n_subdir/CMakeLists.txt | 9 + .../test_i18n_subdir/subdir/CMakeLists.txt | 13 + .../test_i18n_subdir/subdir/app1_de.ts | 12 + .../linguist/test_i18n_subdir/subdir/main.cpp | 32 + .../linguist/test_i18n_subdir2/CMakeLists.txt | 9 + .../test_i18n_subdir2/subdir/CMakeLists.txt | 17 + .../test_i18n_subdir2/subdir/app1_fr.ts | 12 + .../test_i18n_subdir2/subdir/main.cpp | 32 + .../test_translation_api/CMakeLists.txt | 217 + .../test_translation_api/myi18nobject.cpp | 24 + .../test_translation_api/myobject_de.ts | 12 + .../post_build_check.cmake | 12 + .../some_dir/some_include.h | 12 + .../cmake/test_uiplugin_module/CMakeLists.txt | 13 + .../my_designer_plugin.cpp | 45 + .../test_uiplugin_via_designer/CMakeLists.txt | 15 + .../my_designer_plugin.cpp | 51 + tests/auto/helpengineplugin/CMakeLists.txt | 25 + tests/auto/helpengineplugin/data/elements.qml | 19 + tests/auto/helpengineplugin/data/qtqml.qch | Bin 0 -> 1306624 bytes .../helpengineplugin/tst_helpengineplugin.cpp | 84 + tests/auto/linguist/CMakeLists.txt | 6 + tests/auto/linguist/lconvert/CMakeLists.txt | 17 + tests/auto/linguist/lconvert/data/REUSE.toml | 31 + .../auto/linguist/lconvert/data/codec-utf8.ts | 27 + tests/auto/linguist/lconvert/data/emptymsg.ts | 15 + .../linguist/lconvert/data/endless-po-loop.ts | 16 + .../linguist/lconvert/data/idxmerge-add.ts | 21 + tests/auto/linguist/lconvert/data/idxmerge.ts | 22 + .../linguist/lconvert/data/idxmerge.ts.out | 26 + tests/auto/linguist/lconvert/data/msgid.ts | 30 + .../linguist/lconvert/data/phrasebook.qph | 21 + .../auto/linguist/lconvert/data/plurals-cn.ts | 17 + .../auto/linguist/lconvert/data/plurals-de.ts | 18 + tests/auto/linguist/lconvert/data/relative.ts | 74 + tests/auto/linguist/lconvert/data/singular.po | 42 + .../lconvert/data/test-broken-utf8.po | 9 + .../lconvert/data/test-broken-utf8.po.out | 12 + .../lconvert/data/test-developer-comment.po | 23 + .../lconvert/data/test-empty-comment.po | 24 + .../linguist/lconvert/data/test-escapes.po | 11 + .../lconvert/data/test-escapes.po.out | 16 + .../linguist/lconvert/data/test-kde-ctxt.po | 25 + .../linguist/lconvert/data/test-kde-fuzzy.po | 43 + .../lconvert/data/test-kde-multiline.po | 32 + .../lconvert/data/test-kde-plurals.po | 27 + .../auto/linguist/lconvert/data/test-refs.po | 23 + .../auto/linguist/lconvert/data/test-slurp.po | 19 + .../linguist/lconvert/data/test-slurp.po.out | 22 + .../lconvert/data/test-trans_seg.ts.out | 29 + .../linguist/lconvert/data/test-trans_seg.xlf | 42 + .../lconvert/data/test-translator-comment.po | 41 + tests/auto/linguist/lconvert/data/test1-cn.po | 67 + tests/auto/linguist/lconvert/data/test1-de.po | 75 + tests/auto/linguist/lconvert/data/test20.ts | 171 + .../linguist/lconvert/data/untranslated.qm | Bin 0 -> 222 bytes .../linguist/lconvert/data/untranslated.ts | 25 + .../lconvert/data/untranslated.ts.out | 20 + tests/auto/linguist/lconvert/data/variants.ts | 24 + .../auto/linguist/lconvert/data/whitespace.ts | 12 + tests/auto/linguist/lconvert/data/wrapping.po | 66 + tests/auto/linguist/lconvert/tst_lconvert.cpp | 374 + tests/auto/linguist/lrelease/CMakeLists.txt | 17 + .../linguist/lrelease/testdata/compressed.ts | 46 + .../linguist/lrelease/testdata/dupes.errors | 6 + .../auto/linguist/lrelease/testdata/dupes.ts | 21 + .../linguist/lrelease/testdata/idbased.ts | 28 + .../lrelease/testdata/no-translations.pro | 1 + .../linguist/lrelease/testdata/translate.ts | 141 + tests/auto/linguist/lrelease/tst_lrelease.cpp | 199 + tests/auto/linguist/lupdate/CMakeLists.txt | 20 + .../testdata/good/backslashes/lupdatecmd | 1 + .../testdata/good/backslashes/project.pro | 3 + .../testdata/good/backslashes/src/main.cpp | 44 + .../good/backslashes/ts/project.ts.result | 13 + .../testdata/good/cmdline_deeppath/lupdatecmd | 2 + .../good/cmdline_deeppath/project.ts.result | 27 + .../lupdate/testdata/good/cmdline_order/a.h | 29 + .../lupdate/testdata/good/cmdline_order/b.h | 31 + .../testdata/good/cmdline_order/lupdatecmd | 1 + .../good/cmdline_order/project.ts.result | 20 + .../testdata/good/cmdline_recurse/lupdatecmd | 2 + .../good/cmdline_recurse/project.ts.result | 115 + .../testdata/good/codecforsrc/main.cpp | 55 + .../testdata/good/codecforsrc/project.pro | 6 + .../good/codecforsrc/project.ts.result | 37 + .../testdata/good/from_subdir/lupdatecmd | 1 + .../good/from_subdir/project.ts.result | 17 + .../testdata/good/from_subdir/src/main.cpp | 38 + .../testdata/good/from_subdir/src/main.h | 32 + .../from_subdir/translations/translations.pro | 7 + .../good/heuristics/expectedoutput.txt | 4 + .../testdata/good/heuristics/lupdatecmd | 2 + .../lupdate/testdata/good/heuristics/main.cpp | 46 + .../testdata/good/heuristics/project.pro | 3 + .../good/heuristics/project.ts.before | 30 + .../good/heuristics/project.ts.result | 17 + .../good/lacksqobject/expectedoutput.txt | 4 + .../testdata/good/lacksqobject/main.cpp | 75 + .../testdata/good/lacksqobject/project.pro | 3 + .../good/lacksqobject/project.ts.result | 40 + .../lupdate/testdata/good/language/main.cpp | 6 + .../testdata/good/language/project.pro | 2 + .../testdata/good/language/project.ts.before | 15 + .../testdata/good/language/project.ts.result | 15 + .../testdata/good/merge_ordering/foo.cpp | 56 + .../testdata/good/merge_ordering/lupdatecmd | 1 + .../testdata/good/merge_ordering/project.pro | 3 + .../good/merge_ordering/project.ts.before | 74 + .../good/merge_ordering/project.ts.result | 82 + .../testdata/good/merge_versions/project.pro | 3 + .../good/merge_versions/project.ts.before | 14 + .../good/merge_versions/project.ts.result | 17 + .../testdata/good/merge_versions/project.ui | 35 + .../testdata/good/merge_whitespace/main.cpp | 51 + .../good/merge_whitespace/project.pro | 3 + .../good/merge_whitespace/project.ts.before | 70 + .../good/merge_whitespace/project.ts.result | 71 + .../testdata/good/mergecpp/finddialog.cpp | 74 + .../testdata/good/mergecpp/project.pro | 3 + .../testdata/good/mergecpp/project.ts.before | 71 + .../testdata/good/mergecpp/project.ts.result | 74 + .../good/mergecpp_noobsolete/finddialog.cpp | 141 + .../good/mergecpp_noobsolete/finddialog.h | 66 + .../good/mergecpp_noobsolete/lupdatecmd | 1 + .../good/mergecpp_noobsolete/project.pro | 3 + .../mergecpp_noobsolete/project.ts.before | 44 + .../mergecpp_noobsolete/project.ts.result | 35 + .../good/mergecpp_obsolete/finddialog.cpp | 145 + .../good/mergecpp_obsolete/finddialog.h | 66 + .../good/mergecpp_obsolete/project.pro | 3 + .../good/mergecpp_obsolete/project.ts.before | 39 + .../good/mergecpp_obsolete/project.ts.result | 43 + .../lupdate/testdata/good/mergeui/project.pro | 3 + .../testdata/good/mergeui/project.ts.before | 22 + .../testdata/good/mergeui/project.ts.result | 26 + .../lupdate/testdata/good/mergeui/project.ui | 39 + .../good/mergeui_obsolete/project.pro | 3 + .../good/mergeui_obsolete/project.ts.before | 16 + .../good/mergeui_obsolete/project.ts.result | 22 + .../testdata/good/mergeui_obsolete/project.ui | 26 + .../good/multiple_locations/finddialog.cpp | 42 + .../testdata/good/multiple_locations/main.cpp | 63 + .../good/multiple_locations/project.pro | 4 + .../good/multiple_locations/project.ts.result | 41 + .../lupdate/testdata/good/namespaces/main.cpp | 585 + .../testdata/good/namespaces/project.pro | 3 + .../good/namespaces/project.ts.result | 319 + .../testdata/good/notargetlanguage/main.cpp | 42 + .../good/notargetlanguage/project.pro | 3 + .../good/notargetlanguage/project.ts.before | 15 + .../good/notargetlanguage/project.ts.result | 15 + .../good/parse_escaped_chars/main.cpp | 17 + .../good/parse_escaped_chars/project.pro | 3 + .../parse_escaped_chars/project.ts.result | 22 + .../good/parse_special_chars/main.cpp | 46 + .../good/parse_special_chars/project.pro | 3 + .../parse_special_chars/project.ts.result | 17 + .../good/parsecontexts/external_types.h | 38 + .../testdata/good/parsecontexts/main.cpp | 352 + .../testdata/good/parsecontexts/project.pro | 3 + .../good/parsecontexts/project.ts.result | 249 + .../testdata/good/parsecpp/excluded.cpp | 32 + .../testdata/good/parsecpp/expectedoutput.txt | 10 + .../testdata/good/parsecpp/finddialog.cpp | 171 + .../testdata/good/parsecpp/included.cpp | 31 + .../lupdate/testdata/good/parsecpp/main.cpp | 758 + .../testdata/good/parsecpp/notincluded.cpp | 31 + .../testdata/good/parsecpp/project.pro | 7 + .../testdata/good/parsecpp/project.ts.result | 731 + .../good/parsecpp2/expectedoutput.txt | 8 + .../lupdate/testdata/good/parsecpp2/main.cpp | 232 + .../lupdate/testdata/good/parsecpp2/main.h | 38 + .../testdata/good/parsecpp2/project.pro | 3 + .../testdata/good/parsecpp2/project.ts.result | 183 + .../parsecpp_attributes/expectedoutput.txt | 0 .../good/parsecpp_attributes/main.cpp | 339 + .../good/parsecpp_attributes/project.pro | 2 + .../parsecpp_attributes/project.ts.result | 260 + .../parsecpp_expression/expectedoutput.txt | 0 .../good/parsecpp_expression/main.cpp | 176 + .../good/parsecpp_expression/project.pro | 2 + .../parsecpp_expression/project.ts.result | 527 + .../good/parsecpp_template/expectedoutput | 0 .../good/parsecpp_template/project.pro | 5 + .../good/parsecpp_template/project.ts.result | 679 + .../parsecpp_template/template_classes.cpp | 432 + .../parsecpp_typed_enum/expectedoutput.txt | 0 .../good/parsecpp_typed_enum/my_class.cpp | 21 + .../good/parsecpp_typed_enum/my_class.h | 8 + .../good/parsecpp_typed_enum/project.pro | 7 + .../parsecpp_typed_enum/project.ts.result | 17 + .../testdata/good/parseidbasedui/project.pro | 3 + .../good/parseidbasedui/project.ts.result | 17 + .../testdata/good/parseidbasedui/project.ui | 35 + .../lupdate/testdata/good/parsejava/main.java | 84 + .../testdata/good/parsejava/project.pro | 3 + .../testdata/good/parsejava/project.ts.result | 120 + .../lupdate/testdata/good/parsejs/main.js | 96 + .../lupdate/testdata/good/parsejs/project.pro | 3 + .../testdata/good/parsejs/project.ts.result | 207 + .../testdata/good/parsejs2/expectedoutput.txt | 20 + .../lupdate/testdata/good/parsejs2/main.js | 56 + .../testdata/good/parsejs2/project.pro | 3 + .../testdata/good/parsejs2/project.ts.result | 30 + .../lupdate/testdata/good/parsejs3/main.js | 40 + .../testdata/good/parsejs3/project.pro | 3 + .../testdata/good/parsejs3/project.ts.result | 90 + .../testdata/good/parsejs4/expectedoutput.txt | 20 + .../lupdate/testdata/good/parsejs4/main.js | 85 + .../testdata/good/parsejs4/project.pro | 3 + .../testdata/good/parsejs4/project.ts.result | 30 + .../testdata/good/parsejscontexts/main.js | 32 + .../testdata/good/parsejscontexts/project.pro | 3 + .../good/parsejscontexts/project.ts.result | 69 + .../testdata/good/parsemjs/expectedoutput.txt | 20 + .../lupdate/testdata/good/parsemjs/main.mjs | 50 + .../testdata/good/parsemjs/project.pro | 3 + .../testdata/good/parsemjs/project.ts.result | 30 + .../lupdate/testdata/good/parseobjc/main.mm | 51 + .../testdata/good/parseobjc/project.pro | 3 + .../testdata/good/parseobjc/project.ts.result | 20 + .../lupdate/testdata/good/parsepython/main.py | 132 + .../testdata/good/parsepython/project.pro | 3 + .../good/parsepython/project.ts.result | 176 + .../lupdate/testdata/good/parseqml/main.qml | 131 + .../good/parseqml/main_test_pragma.qml | 104 + .../testdata/good/parseqml/project.pro | 4 + .../testdata/good/parseqml/project.ts.result | 346 + .../good/parseqml2/expectedoutput.txt | 20 + .../lupdate/testdata/good/parseqml2/main.qml | 88 + .../testdata/good/parseqml2/project.pro | 3 + .../testdata/good/parseqml2/project.ts.result | 30 + .../good/parseqml3/expectedoutput.txt | 1 + .../lupdate/testdata/good/parseqml3/main.qml | 23 + .../testdata/good/parseqml3/project.pro | 3 + .../testdata/good/parseqml3/project.ts.result | 22 + .../lupdate/testdata/good/parseqrc/main.cpp | 37 + .../lupdate/testdata/good/parseqrc/main.js | 1 + .../lupdate/testdata/good/parseqrc/main.qml | 35 + .../testdata/good/parseqrc/project.pro | 9 + .../testdata/good/parseqrc/project.qrc | 5 + .../testdata/good/parseqrc/project.ts.result | 25 + .../testdata/good/parseqrc_json/main.cpp | 10 + .../testdata/good/parseqrc_json/main.js | 1 + .../testdata/good/parseqrc_json/main.qml | 10 + .../testdata/good/parseqrc_json/project.json | 16 + .../testdata/good/parseqrc_json/project.qrc | 5 + .../good/parseqrc_json/project.ts.result | 25 + .../lupdate/testdata/good/parseui/project.pro | 3 + .../testdata/good/parseui/project.ts.result | 17 + .../lupdate/testdata/good/parseui/project.ui | 35 + .../lupdate/testdata/good/prefix/main.cpp | 56 + .../lupdate/testdata/good/prefix/main.h | 48 + .../lupdate/testdata/good/prefix/project.pro | 3 + .../testdata/good/prefix/project.ts.result | 47 + .../lupdate/testdata/good/preprocess/main.cpp | 65 + .../testdata/good/preprocess/project.pro | 3 + .../good/preprocess/project.ts.result | 35 + .../good/proparsing/features/default_pre.prf | 2 + .../lupdate/testdata/good/proparsing/main.cpp | 37 + .../lupdate/testdata/good/proparsing/main.qml | 35 + .../testdata/good/proparsing/main_mac.cpp | 35 + .../testdata/good/proparsing/main_unix.cpp | 38 + .../testdata/good/proparsing/main_win.cpp | 38 + .../testdata/good/proparsing/project.pro | 36 + .../good/proparsing/project.ts.result | 80 + .../testdata/good/proparsing/qml/excluded.cpp | 32 + .../testdata/good/proparsing/qml/notmain.qml | 35 + .../vpaths/dependpath/main_dependpath.cpp | 36 + .../good/proparsing/wildcard/main1.cpp | 37 + .../good/proparsing/wildcard/mainfile.cpp | 37 + .../testdata/good/proparsing/wildcard1.cpp | 35 + .../testdata/good/proparsing/wildcard99.cpp | 35 + .../lupdate/testdata/good/proparsing2/a | 32 + .../lupdate/testdata/good/proparsing2/a.cpp | 32 + .../lupdate/testdata/good/proparsing2/b | 32 + .../lupdate/testdata/good/proparsing2/b.cpp | 32 + .../lupdate/testdata/good/proparsing2/e | 32 + .../lupdate/testdata/good/proparsing2/f/g.cpp | 32 + .../testdata/good/proparsing2/files-cc.txt | 1 + .../testdata/good/proparsing2/include.h | 2 + .../testdata/good/proparsing2/project.pro | 33 + .../good/proparsing2/project.ts.result | 62 + .../testdata/good/proparsing2/spaces/z | 32 + .../good/proparsing2/variable_with_spaces | 32 + .../lupdate/testdata/good/proparsing2/with | 32 + .../lupdate/testdata/good/proparsing2/x/d | 32 + .../testdata/good/proparsing2/x/variable | 32 + .../testdata/good/proparsingpaths/file1.cpp | 37 + .../testdata/good/proparsingpaths/filter.cpp | 37 + .../testdata/good/proparsingpaths/project.pro | 5 + .../good/proparsingpaths/project.ts.result | 31 + .../testdata/good/proparsingpaths/sub/sub.pri | 3 + .../good/proparsingpaths/sub/subfile1.cpp | 37 + .../good/proparsingpaths/sub/subfilter.cpp | 37 + .../good/proparsingpri/common/common.pri | 1 + .../good/proparsingpri/common/main.cpp | 37 + .../good/proparsingpri/common/main.pri | 5 + .../testdata/good/proparsingpri/mac/mac.pri | 5 + .../good/proparsingpri/mac/main_mac.cpp | 38 + .../testdata/good/proparsingpri/project.pro | 10 + .../good/proparsingpri/project.ts.result | 37 + .../proparsingpri/relativity/relativity.cpp | 37 + .../proparsingpri/relativity/relativity.pri | 3 + .../good/proparsingpri/relativity/sub/sub.pri | 1 + .../proparsingpri/relativity/sub2/sub2.pri | 2 + .../good/proparsingpri/unix/main_unix.cpp | 38 + .../testdata/good/proparsingpri/unix/unix.pri | 5 + .../good/proparsingpri/win/main_win.cpp | 38 + .../testdata/good/proparsingpri/win/win.pri | 5 + .../good/proparsingsubdirs/project.pro | 2 + .../good/proparsingsubdirs/project.ts.result | 13 + .../good/proparsingsubdirs/sub1/main.cpp | 37 + .../good/proparsingsubdirs/sub1/sub1.pro | 3 + .../good/proparsingsubs/common/common.pro | 1 + .../good/proparsingsubs/common/main.cpp | 37 + .../good/proparsingsubs/excluded/excluded.pro | 1 + .../good/proparsingsubs/excluded/main.cpp | 37 + .../testdata/good/proparsingsubs/lupdatecmd | 1 + .../testdata/good/proparsingsubs/mac/mac.pro | 1 + .../good/proparsingsubs/mac/main_mac.cpp | 38 + .../testdata/good/proparsingsubs/project.pro | 8 + .../good/proparsingsubs/project.ts.result | 39 + .../good/proparsingsubs/sub/include/test.h | 43 + .../good/proparsingsubs/sub/src/test.cpp | 39 + .../testdata/good/proparsingsubs/sub/sub.pro | 5 + .../good/proparsingsubs/unix/main_unix.cpp | 38 + .../good/proparsingsubs/unix/unix.pro | 1 + .../good/proparsingsubs/win/main_win.cpp | 38 + .../testdata/good/proparsingsubs/win/win.pro | 1 + .../good/recurse_full/expectedoutput.txt | 0 .../testdata/good/recurse_full/lupdatecmd | 2 + .../good/recurse_full/project.ts.result | 20 + .../good/recurse_full/project_sub.ts.result | 12 + .../good/recurse_full_ts/expectedoutput.txt | 2 + .../testdata/good/recurse_full_ts/lupdatecmd | 3 + .../good/recurse_full_ts/project.ts.result | 20 + .../recurse_full_ts/project_sub.ts.before | 0 .../recurse_full_ts/project_sub.ts.result | 0 .../recurse_full_ts_join/expectedoutput.txt | 0 .../good/recurse_full_ts_join/lupdatecmd | 2 + .../recurse_full_ts_join/project.ts.result | 20 + .../good/recurse_part/expectedoutput.txt | 1 + .../testdata/good/recurse_part/lupdatecmd | 2 + .../good/recurse_part/project.ts.before | 0 .../good/recurse_part/project.ts.result | 0 .../good/recurse_part/project_sub.ts.result | 12 + .../good/recurse_part_ts/expectedoutput.txt | 1 + .../testdata/good/recurse_part_ts/lupdatecmd | 3 + .../good/recurse_part_ts/project.ts.result | 20 + .../recurse_part_ts/project_sub.ts.before | 0 .../recurse_part_ts/project_sub.ts.result | 0 .../testdata/good/reloutput/lupdatecmd | 1 + .../lupdate/testdata/good/reloutput/main.cpp | 37 + .../testdata/good/reloutput/project.pro | 3 + .../reloutput/translations/project.ts.result | 12 + .../good/resources_cmdline/lupdatecmd | 1 + .../testdata/good/resources_cmdline/main.js | 1 + .../testdata/good/resources_cmdline/main.qml | 35 + .../good/resources_cmdline/project.qrc | 6 + .../good/resources_cmdline/project.ts.result | 17 + .../lupdate/testdata/good/respfile/lupdatecmd | 2 + .../testdata/good/respfile/project.ts.result | 17 + .../testdata/good/respfile/source1.cpp | 36 + .../testdata/good/respfile/source2.cpp | 36 + .../testdata/good/respfile/sources.lst | 2 + .../testdata/good/respfile/tsfiles.lst | 1 + .../testdata/good/textsimilarity/project.pro | 3 + .../good/textsimilarity/project.ts.before | 16 + .../good/textsimilarity/project.ts.result | 22 + .../testdata/good/textsimilarity/project.ui | 26 + .../good/tr_function_alias/lupdatecmd | 1 + .../testdata/good/tr_function_alias/main.cpp | 89 + .../testdata/good/tr_function_alias/main.qml | 4 + .../good/tr_function_alias/project.pro | 4 + .../good/tr_function_alias/project.ts.result | 187 + .../testdata/good/using_namespaces/file.cpp | 20 + .../testdata/good/using_namespaces/file.h | 14 + .../good/using_namespaces/project.pro | 4 + .../good/using_namespaces/project.ts.result | 12 + .../lupdate/testdata/recursivescan/main.cpp | 50 + .../lupdate/testdata/recursivescan/project.ui | 36 + .../recursivescan/sub/filetypes/main.c++ | 36 + .../recursivescan/sub/filetypes/main.cpp | 36 + .../recursivescan/sub/filetypes/main.cxx | 36 + .../testdata/recursivescan/sub/finddialog.cpp | 60 + .../lupdate/testdata/subdirs_full/project.pro | 4 + .../testdata/subdirs_full/subdir1/main.cpp | 33 + .../testdata/subdirs_full/subdir1/subdir1.pro | 1 + .../testdata/subdirs_full/subdir2/subdir2.pro | 2 + .../subdirs_full/subdir2/subsub1/main.cpp | 33 + .../subdirs_full/subdir2/subsub1/subsub1.pro | 1 + .../subdirs_full/subdir2/subsub2/main.cpp | 33 + .../subdirs_full/subdir2/subsub2/subsub2.pro | 3 + .../lupdate/testdata/subdirs_part/project.pro | 2 + .../testdata/subdirs_part/subdir1/main.cpp | 33 + .../testdata/subdirs_part/subdir1/subdir1.pro | 1 + .../testdata/subdirs_part/subdir2/subdir2.pro | 2 + .../subdirs_part/subdir2/subsub1/main.cpp | 33 + .../subdirs_part/subdir2/subsub1/subsub1.pro | 1 + .../subdirs_part/subdir2/subsub2/main.cpp | 33 + .../subdirs_part/subdir2/subsub2/subsub2.pro | 3 + tests/auto/linguist/lupdate/tst_lupdate.cpp | 398 + tests/auto/qdoc/CMakeLists.txt | 47 + tests/auto/qhelpcontentmodel/CMakeLists.txt | 23 + .../qhelpcontentmodel/data/collection.qhc | Bin 0 -> 10240 bytes .../qhelpcontentmodel/data/qmake-3.3.8.qch | Bin 0 -> 61440 bytes .../qhelpcontentmodel/data/qmake-4.3.0.qch | Bin 0 -> 93184 bytes tests/auto/qhelpcontentmodel/data/test.qch | Bin 0 -> 20480 bytes .../tst_qhelpcontentmodel.cpp | 138 + tests/auto/qhelpenginecore/CMakeLists.txt | 25 + .../auto/qhelpenginecore/data/collection.qhc | Bin 0 -> 10240 bytes .../auto/qhelpenginecore/data/collection1.qhc | Bin 0 -> 10240 bytes .../qhelpenginecore/data/linguist-3.3.8.qch | Bin 0 -> 131072 bytes .../auto/qhelpenginecore/data/qmake-3.3.8.qch | Bin 0 -> 61440 bytes .../auto/qhelpenginecore/data/qmake-4.3.0.qch | Bin 0 -> 93184 bytes tests/auto/qhelpenginecore/data/test.html | 11 + tests/auto/qhelpenginecore/data/test.qch | Bin 0 -> 20480 bytes .../qhelpenginecore/tst_qhelpenginecore.cpp | 403 + tests/auto/qhelpgenerator/CMakeLists.txt | 27 + tests/auto/qhelpgenerator/data/cars.html | 11 + tests/auto/qhelpgenerator/data/classic.css | 92 + tests/auto/qhelpgenerator/data/fancy.html | 11 + tests/auto/qhelpgenerator/data/people.html | 11 + tests/auto/qhelpgenerator/data/sub/about.html | 11 + tests/auto/qhelpgenerator/data/test.html | 11 + tests/auto/qhelpgenerator/data/test.qhp | 71 + .../qhelpgenerator/tst_qhelpgenerator.cpp | 200 + tests/auto/qhelpindexmodel/CMakeLists.txt | 24 + .../auto/qhelpindexmodel/data/collection.qhc | Bin 0 -> 10240 bytes .../auto/qhelpindexmodel/data/collection1.qhc | Bin 0 -> 10240 bytes .../qhelpindexmodel/data/linguist-3.3.8.qch | Bin 0 -> 131072 bytes .../auto/qhelpindexmodel/data/qmake-3.3.8.qch | Bin 0 -> 61440 bytes .../auto/qhelpindexmodel/data/qmake-4.3.0.qch | Bin 0 -> 93184 bytes tests/auto/qhelpindexmodel/data/test.html | 11 + tests/auto/qhelpindexmodel/data/test.qch | Bin 0 -> 20480 bytes .../qhelpindexmodel/tst_qhelpindexmodel.cpp | 130 + tests/auto/qhelpprojectdata/CMakeLists.txt | 25 + tests/auto/qhelpprojectdata/data/test.qhp | 72 + .../qhelpprojectdata/tst_qhelpprojectdata.cpp | 153 + .../auto/qtattributionsscanner/CMakeLists.txt | 21 + .../good/chromium/README_test.chromium | 9 + .../good/complete/qt_attribution_test.json | 33 + .../testdata/good/expected.error | 0 .../testdata/good/expected.json | 156 + .../testdata/good/licenses-dir/expected.error | 0 .../testdata/good/licenses-dir/expected.json | 26 + .../licenses-dir/qt_attribution_test.json | 9 + .../good/local_license/LICENSE.Id1.txt | 1 + .../good/local_license/LICENSE.Id2.txt | 1 + .../good/local_license/expected.error | 0 .../testdata/good/local_license/expected.json | 29 + .../local_license/qt_attribution_test.json | 10 + .../testdata/good/minimal/expected.error | 0 .../testdata/good/minimal/expected.json | 26 + .../good/minimal/qt_attribution_test.json | 11 + .../testdata/good/variants/COPYRIGHT.txt | 1 + .../testdata/good/variants/LICENSE.1.txt | 1 + .../testdata/good/variants/LICENSE.2.txt | 1 + .../testdata/good/variants/expected.error | 0 .../testdata/good/variants/expected.json | 29 + .../good/variants/qt_attribution_test.json | 11 + .../warnings/incomplete/expected.error | 3 + .../incomplete/qt_attribution_test.json | 2 + .../testdata/warnings/unknown/expected.error | 1 + .../warnings/unknown/qt_attribution_test.json | 10 + .../tst_qtattributionsscanner.cpp | 137 + tests/auto/qtdiag/CMakeLists.txt | 19 + tests/auto/qtdiag/tst_qtdiag.cpp | 57 + tests/global/global.cfg | 7 + tests/manual/CMakeLists.txt | 4 + tests/manual/manual.pro | 3 + .../qtattributionsscanner/CMakeLists.txt | 1 + .../manual/qtattributionsscanner/data/LICENSE | 686 + .../data/qt_attribution_test.json | 11 + .../qtattributionsscanner/overview.qdoc | 13 + .../qtattributionsscanner.pro | 14 + .../qtattributionsscanner/test.qdocconf | 6 + util/recolordocsicons/README.md | 32 + util/recolordocsicons/recolordocsicons.py | 76 + 5275 files changed, 749202 insertions(+) create mode 100644 .cmake.conf create mode 100644 .gitmodules create mode 100644 .gitreview create mode 100644 .tag create mode 100644 CMakeLists.txt create mode 100644 LICENSES/Apache-2.0.txt create mode 100644 LICENSES/BSD-3-Clause.txt create mode 100644 LICENSES/BSL-1.0.txt create mode 100644 LICENSES/CC0-1.0.txt create mode 100644 LICENSES/GFDL-1.3-no-invariants-only.txt create mode 100644 LICENSES/GPL-2.0-only.txt create mode 100644 LICENSES/GPL-3.0-only.txt create mode 100644 LICENSES/LGPL-3.0-only.txt create mode 100644 LICENSES/LLVM-exception.txt create mode 100644 LICENSES/LicenseRef-Qt-Commercial.txt create mode 100644 LICENSES/Qt-GPL-exception-1.0.txt create mode 100644 REUSE.toml create mode 100644 cmake/FindWrapLibClang.cmake create mode 100644 coin/axivion/ci_config_linux.json create mode 100755 coin/axivion/start_analysis.sh create mode 100644 coin/module_config.yaml create mode 100644 configure.cmake create mode 100644 dependencies.yaml create mode 100644 dist/REUSE.toml create mode 100644 dist/changes-5.0.1 create mode 100644 dist/changes-5.0.2 create mode 100644 dist/changes-5.1.0 create mode 100644 dist/changes-5.1.1 create mode 100644 dist/changes-5.10.0 create mode 100644 dist/changes-5.10.1 create mode 100644 dist/changes-5.11.0 create mode 100644 dist/changes-5.11.1 create mode 100644 dist/changes-5.11.2 create mode 100644 dist/changes-5.11.3 create mode 100644 dist/changes-5.12.0 create mode 100644 dist/changes-5.12.1 create mode 100644 dist/changes-5.12.10 create mode 100644 dist/changes-5.12.2 create mode 100644 dist/changes-5.12.3 create mode 100644 dist/changes-5.12.4 create mode 100644 dist/changes-5.12.5 create mode 100644 dist/changes-5.13.0 create mode 100644 dist/changes-5.13.1 create mode 100644 dist/changes-5.13.2 create mode 100644 dist/changes-5.14.0 create mode 100644 dist/changes-5.14.1 create mode 100644 dist/changes-5.14.2 create mode 100644 dist/changes-5.15.0 create mode 100644 dist/changes-5.15.1 create mode 100644 dist/changes-5.15.2 create mode 100644 dist/changes-5.2.0 create mode 100644 dist/changes-5.2.1 create mode 100644 dist/changes-5.6.3 create mode 100644 dist/changes-5.7.0 create mode 100644 dist/changes-5.8.0 create mode 100644 dist/changes-5.9.0 create mode 100644 dist/changes-5.9.1 create mode 100644 dist/changes-5.9.2 create mode 100644 dist/changes-5.9.3 create mode 100644 dist/changes-5.9.4 create mode 100644 dist/changes-5.9.5 create mode 100644 dist/changes-5.9.6 create mode 100644 dist/changes-6.0.0 create mode 100644 examples/CMakeLists.txt create mode 100644 examples/assistant/CMakeLists.txt create mode 100644 examples/assistant/assistant.pro create mode 100644 examples/assistant/doc/images/remotecontrol-example.png create mode 100644 examples/assistant/doc/images/simpletextviewer-example.png create mode 100644 examples/assistant/doc/images/simpletextviewer-findfiledialog.png create mode 100644 examples/assistant/doc/images/simpletextviewer-mainwindow.png create mode 100644 examples/assistant/doc/src/remotecontrol.qdoc create mode 100644 examples/assistant/doc/src/simpletextviewer.qdoc create mode 100644 examples/assistant/remotecontrol/CMakeLists.txt create mode 100644 examples/assistant/remotecontrol/enter.png create mode 100644 examples/assistant/remotecontrol/main.cpp create mode 100644 examples/assistant/remotecontrol/remotecontrol.cpp create mode 100644 examples/assistant/remotecontrol/remotecontrol.h create mode 100644 examples/assistant/remotecontrol/remotecontrol.pro create mode 100644 examples/assistant/remotecontrol/remotecontrol.qrc create mode 100644 examples/assistant/remotecontrol/remotecontrol.ui create mode 100644 examples/assistant/simpletextviewer/CMakeLists.txt create mode 100644 examples/assistant/simpletextviewer/assistant.cpp create mode 100644 examples/assistant/simpletextviewer/assistant.h create mode 100644 examples/assistant/simpletextviewer/documentation/about.txt create mode 100644 examples/assistant/simpletextviewer/documentation/browse.html create mode 100644 examples/assistant/simpletextviewer/documentation/filedialog.html create mode 100644 examples/assistant/simpletextviewer/documentation/findfile.html create mode 100644 examples/assistant/simpletextviewer/documentation/images/browse.png create mode 100644 examples/assistant/simpletextviewer/documentation/images/fadedfilemenu.png create mode 100644 examples/assistant/simpletextviewer/documentation/images/filedialog.png create mode 100644 examples/assistant/simpletextviewer/documentation/images/handbook.png create mode 100644 examples/assistant/simpletextviewer/documentation/images/icon.png create mode 100644 examples/assistant/simpletextviewer/documentation/images/mainwindow.png create mode 100644 examples/assistant/simpletextviewer/documentation/images/open.png create mode 100644 examples/assistant/simpletextviewer/documentation/images/wildcard.png create mode 100644 examples/assistant/simpletextviewer/documentation/index.html create mode 100644 examples/assistant/simpletextviewer/documentation/intro.html create mode 100644 examples/assistant/simpletextviewer/documentation/openfile.html create mode 100644 examples/assistant/simpletextviewer/documentation/simpletextviewer.qch create mode 100644 examples/assistant/simpletextviewer/documentation/simpletextviewer.qhc create mode 100644 examples/assistant/simpletextviewer/documentation/simpletextviewer.qhcp create mode 100644 examples/assistant/simpletextviewer/documentation/simpletextviewer.qhp create mode 100644 examples/assistant/simpletextviewer/documentation/wildcardmatching.html create mode 100644 examples/assistant/simpletextviewer/findfiledialog.cpp create mode 100644 examples/assistant/simpletextviewer/findfiledialog.h create mode 100644 examples/assistant/simpletextviewer/main.cpp create mode 100644 examples/assistant/simpletextviewer/mainwindow.cpp create mode 100644 examples/assistant/simpletextviewer/mainwindow.h create mode 100644 examples/assistant/simpletextviewer/simpletextviewer.pro create mode 100644 examples/assistant/simpletextviewer/textedit.cpp create mode 100644 examples/assistant/simpletextviewer/textedit.h create mode 100644 examples/designer/CMakeLists.txt create mode 100644 examples/designer/README create mode 100644 examples/designer/calculatorbuilder/CMakeLists.txt create mode 100644 examples/designer/calculatorbuilder/calculatorbuilder.pro create mode 100644 examples/designer/calculatorbuilder/calculatorbuilder.qrc create mode 100644 examples/designer/calculatorbuilder/calculatorform.ui create mode 100644 examples/designer/calculatorbuilder/main.cpp create mode 100644 examples/designer/calculatorform/CMakeLists.txt create mode 100644 examples/designer/calculatorform/calculatorform.cpp create mode 100644 examples/designer/calculatorform/calculatorform.h create mode 100644 examples/designer/calculatorform/calculatorform.pro create mode 100644 examples/designer/calculatorform/calculatorform.ui create mode 100644 examples/designer/calculatorform/main.cpp create mode 100644 examples/designer/calculatorform_mi/CMakeLists.txt create mode 100644 examples/designer/calculatorform_mi/calculatorform.cpp create mode 100644 examples/designer/calculatorform_mi/calculatorform.h create mode 100644 examples/designer/calculatorform_mi/calculatorform.ui create mode 100644 examples/designer/calculatorform_mi/calculatorform_mi.pro create mode 100644 examples/designer/calculatorform_mi/main.cpp create mode 100644 examples/designer/containerextension/CMakeLists.txt create mode 100644 examples/designer/containerextension/containerextension.pro create mode 100644 examples/designer/containerextension/multipagewidget.cpp create mode 100644 examples/designer/containerextension/multipagewidget.h create mode 100644 examples/designer/containerextension/multipagewidgetcontainerextension.cpp create mode 100644 examples/designer/containerextension/multipagewidgetcontainerextension.h create mode 100644 examples/designer/containerextension/multipagewidgetextensionfactory.cpp create mode 100644 examples/designer/containerextension/multipagewidgetextensionfactory.h create mode 100644 examples/designer/containerextension/multipagewidgetplugin.cpp create mode 100644 examples/designer/containerextension/multipagewidgetplugin.h create mode 100644 examples/designer/customwidgetplugin/CMakeLists.txt create mode 100644 examples/designer/customwidgetplugin/analogclock.cpp create mode 100644 examples/designer/customwidgetplugin/analogclock.h create mode 100644 examples/designer/customwidgetplugin/customwidgetplugin.cpp create mode 100644 examples/designer/customwidgetplugin/customwidgetplugin.h create mode 100644 examples/designer/customwidgetplugin/customwidgetplugin.pro create mode 100644 examples/designer/designer.pro create mode 100644 examples/designer/doc/images/calculatorbuilder-example.webp create mode 100644 examples/designer/doc/images/calculatorform-example.webp create mode 100644 examples/designer/doc/images/containerextension-example.webp create mode 100644 examples/designer/doc/images/customwidgetplugin-example.webp create mode 100644 examples/designer/doc/images/taskmenuextension-dialog.webp create mode 100644 examples/designer/doc/images/taskmenuextension-example.webp create mode 100644 examples/designer/doc/images/taskmenuextension-menu.webp create mode 100644 examples/designer/doc/snippets/doc_src_examples_containerextension.pro create mode 100644 examples/designer/doc/snippets/doc_src_examples_customwidgetplugin.pro create mode 100644 examples/designer/doc/snippets/doc_src_examples_taskmenuextension.pro create mode 100644 examples/designer/doc/src/calculatorbuilder.qdoc create mode 100644 examples/designer/doc/src/calculatorform.qdoc create mode 100644 examples/designer/doc/src/calculatorform_mi.qdoc create mode 100644 examples/designer/doc/src/containerextension.qdoc create mode 100644 examples/designer/doc/src/customwidgetplugin.qdoc create mode 100644 examples/designer/doc/src/taskmenuextension.qdoc create mode 100644 examples/designer/taskmenuextension/CMakeLists.txt create mode 100644 examples/designer/taskmenuextension/taskmenuextension.pro create mode 100644 examples/designer/taskmenuextension/tictactoe.cpp create mode 100644 examples/designer/taskmenuextension/tictactoe.h create mode 100644 examples/designer/taskmenuextension/tictactoedialog.cpp create mode 100644 examples/designer/taskmenuextension/tictactoedialog.h create mode 100644 examples/designer/taskmenuextension/tictactoeplugin.cpp create mode 100644 examples/designer/taskmenuextension/tictactoeplugin.h create mode 100644 examples/designer/taskmenuextension/tictactoetaskmenu.cpp create mode 100644 examples/designer/taskmenuextension/tictactoetaskmenu.h create mode 100644 examples/examples.pro create mode 100644 examples/help/CMakeLists.txt create mode 100644 examples/help/README create mode 100644 examples/help/contextsensitivehelp/CMakeLists.txt create mode 100644 examples/help/contextsensitivehelp/contextsensitivehelp.pro create mode 100644 examples/help/contextsensitivehelp/docs/amount.html create mode 100644 examples/help/contextsensitivehelp/docs/filter.html create mode 100644 examples/help/contextsensitivehelp/docs/plants.html create mode 100644 examples/help/contextsensitivehelp/docs/rain.html create mode 100644 examples/help/contextsensitivehelp/docs/source.html create mode 100644 examples/help/contextsensitivehelp/docs/temperature.html create mode 100644 examples/help/contextsensitivehelp/docs/time.html create mode 100644 examples/help/contextsensitivehelp/docs/wateringmachine.qch create mode 100644 examples/help/contextsensitivehelp/docs/wateringmachine.qhc create mode 100644 examples/help/contextsensitivehelp/docs/wateringmachine.qhcp create mode 100644 examples/help/contextsensitivehelp/docs/wateringmachine.qhp create mode 100644 examples/help/contextsensitivehelp/helpbrowser.cpp create mode 100644 examples/help/contextsensitivehelp/helpbrowser.h create mode 100644 examples/help/contextsensitivehelp/main.cpp create mode 100644 examples/help/contextsensitivehelp/wateringconfigdialog.cpp create mode 100644 examples/help/contextsensitivehelp/wateringconfigdialog.h create mode 100644 examples/help/contextsensitivehelp/wateringconfigdialog.ui create mode 100644 examples/help/doc/images/context-sensitive-help-example.png create mode 100644 examples/help/doc/src/contextsensitivehelp.qdoc create mode 100644 examples/help/help.pro create mode 100644 examples/linguist/CMakeLists.txt create mode 100644 examples/linguist/README create mode 100644 examples/linguist/arrowpad/CMakeLists.txt create mode 100644 examples/linguist/arrowpad/arrowpad.cpp create mode 100644 examples/linguist/arrowpad/arrowpad.h create mode 100644 examples/linguist/arrowpad/arrowpad.pro create mode 100644 examples/linguist/arrowpad/arrowpad_en.ts create mode 100644 examples/linguist/arrowpad/arrowpad_fr.ts create mode 100644 examples/linguist/arrowpad/arrowpad_nl.ts create mode 100644 examples/linguist/arrowpad/main.cpp create mode 100644 examples/linguist/arrowpad/mainwindow.cpp create mode 100644 examples/linguist/arrowpad/mainwindow.h create mode 100644 examples/linguist/doc/images/linguist-arrowpad_en.png create mode 100644 examples/linguist/doc/images/linguist-arrowpad_fr.png create mode 100644 examples/linguist/doc/images/linguist-arrowpad_nl.png create mode 100644 examples/linguist/doc/images/linguist-hellotr_en.png create mode 100644 examples/linguist/doc/images/linguist-hellotr_la.png create mode 100644 examples/linguist/doc/images/linguist-i18n.png create mode 100644 examples/linguist/doc/images/linguist-localizedclock-idbased-dialog_de.webp create mode 100644 examples/linguist/doc/images/linguist-localizedclock-idbased-dialog_en.webp create mode 100644 examples/linguist/doc/images/linguist-localizedclock-idbased_de.webp create mode 100644 examples/linguist/doc/images/linguist-localizedclock-idbased_en.webp create mode 100644 examples/linguist/doc/images/linguist-localizedclock_de_DE.webp create mode 100644 examples/linguist/doc/images/linguist-localizedclock_en_GB.webp create mode 100644 examples/linguist/doc/images/linguist-localizedclock_en_US.webp create mode 100644 examples/linguist/doc/images/linguist-localizedclock_switchlang_de.webp create mode 100644 examples/linguist/doc/images/linguist-localizedclock_switchlang_en.webp create mode 100644 examples/linguist/doc/images/linguist-trollprint_10_en.png create mode 100644 examples/linguist/doc/images/linguist-trollprint_10_pt_bad.png create mode 100644 examples/linguist/doc/images/linguist-trollprint_10_pt_good.png create mode 100644 examples/linguist/doc/images/linguist-trollprint_11_en.png create mode 100644 examples/linguist/doc/images/linguist-trollprint_11_pt.png create mode 100644 examples/linguist/doc/snippets/doc_src_examples_arrowpad.cpp create mode 100644 examples/linguist/doc/snippets/doc_src_examples_arrowpad.qdoc create mode 100644 examples/linguist/doc/snippets/doc_src_examples_hellotr.qdoc create mode 100644 examples/linguist/doc/snippets/doc_src_examples_trollprint.cpp create mode 100644 examples/linguist/doc/src/arrowpad.qdoc create mode 100644 examples/linguist/doc/src/hellotr.qdoc create mode 100644 examples/linguist/doc/src/i18n.qdoc create mode 100644 examples/linguist/doc/src/localizedclock-idbased.qdoc create mode 100644 examples/linguist/doc/src/localizedclock-switchlang.qdoc create mode 100644 examples/linguist/doc/src/localizedclock.qdoc create mode 100644 examples/linguist/doc/src/trollprint.qdoc create mode 100644 examples/linguist/hellotr/CMakeLists.txt create mode 100644 examples/linguist/hellotr/hellotr.pro create mode 100644 examples/linguist/hellotr/hellotr_en.ts create mode 100644 examples/linguist/hellotr/hellotr_la.ts create mode 100644 examples/linguist/hellotr/main.cpp create mode 100644 examples/linguist/i18n/CMakeLists.txt create mode 100644 examples/linguist/i18n/i18n.pro create mode 100644 examples/linguist/i18n/languagechooser.cpp create mode 100644 examples/linguist/i18n/languagechooser.h create mode 100644 examples/linguist/i18n/main.cpp create mode 100644 examples/linguist/i18n/mainwindow.cpp create mode 100644 examples/linguist/i18n/mainwindow.h create mode 100644 examples/linguist/i18n/translations/i18n_ar.ts create mode 100644 examples/linguist/i18n/translations/i18n_cs.ts create mode 100644 examples/linguist/i18n/translations/i18n_de.ts create mode 100644 examples/linguist/i18n/translations/i18n_el.ts create mode 100644 examples/linguist/i18n/translations/i18n_en.ts create mode 100644 examples/linguist/i18n/translations/i18n_eo.ts create mode 100644 examples/linguist/i18n/translations/i18n_fr.ts create mode 100644 examples/linguist/i18n/translations/i18n_it.ts create mode 100644 examples/linguist/i18n/translations/i18n_ja.ts create mode 100644 examples/linguist/i18n/translations/i18n_ko.ts create mode 100644 examples/linguist/i18n/translations/i18n_nb.ts create mode 100644 examples/linguist/i18n/translations/i18n_ru.ts create mode 100644 examples/linguist/i18n/translations/i18n_sv.ts create mode 100644 examples/linguist/i18n/translations/i18n_zh.ts create mode 100644 examples/linguist/linguist.pro create mode 100644 examples/linguist/localizedclock-idbased/CMakeLists.txt create mode 100644 examples/linguist/localizedclock-idbased/Main.qml create mode 100644 examples/linguist/localizedclock-idbased/dialog.cpp create mode 100644 examples/linguist/localizedclock-idbased/dialog.h create mode 100644 examples/linguist/localizedclock-idbased/dialog.ui create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_ar.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_de.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_en.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_es.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_fr.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_it.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_ja.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_ko.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_pt.ts create mode 100644 examples/linguist/localizedclock-idbased/i18n/clock_zh.ts create mode 100644 examples/linguist/localizedclock-idbased/main.cpp create mode 100644 examples/linguist/localizedclock-idbased/timezonemanager.cpp create mode 100644 examples/linguist/localizedclock-idbased/timezonemanager.h create mode 100644 examples/linguist/localizedclock-switchlocale/CMakeLists.txt create mode 100644 examples/linguist/localizedclock-switchlocale/Main.qml create mode 100644 examples/linguist/localizedclock-switchlocale/globe.png create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_ar.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_de.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_en.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_es.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_fr.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_it.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_ja.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_ko.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_pt.ts create mode 100644 examples/linguist/localizedclock-switchlocale/i18n/clock_zh.ts create mode 100644 examples/linguist/localizedclock-switchlocale/main.cpp create mode 100644 examples/linguist/localizedclock-switchlocale/translatormanager.cpp create mode 100644 examples/linguist/localizedclock-switchlocale/translatormanager.h create mode 100644 examples/linguist/localizedclock/CMakeLists.txt create mode 100644 examples/linguist/localizedclock/Main.qml create mode 100644 examples/linguist/localizedclock/i18n/clock_ar.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_de.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_en.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_es.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_fr.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_it.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_ja.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_ko.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_pt.ts create mode 100644 examples/linguist/localizedclock/i18n/clock_zh.ts create mode 100644 examples/linguist/localizedclock/main.cpp create mode 100644 examples/linguist/trollprint/CMakeLists.txt create mode 100644 examples/linguist/trollprint/main.cpp create mode 100644 examples/linguist/trollprint/mainwindow.cpp create mode 100644 examples/linguist/trollprint/mainwindow.h create mode 100644 examples/linguist/trollprint/printpanel.cpp create mode 100644 examples/linguist/trollprint/printpanel.h create mode 100644 examples/linguist/trollprint/trollprint.pro create mode 100644 examples/linguist/trollprint/trollprint_en.ts create mode 100644 examples/linguist/trollprint/trollprint_pt.ts create mode 100644 examples/uitools/CMakeLists.txt create mode 100644 examples/uitools/doc/images/textfinder-example-userinterface.webp create mode 100644 examples/uitools/doc/src/textfinder.qdoc create mode 100644 examples/uitools/textfinder/CMakeLists.txt create mode 100644 examples/uitools/textfinder/forms/input.txt create mode 100644 examples/uitools/textfinder/forms/textfinder.ui create mode 100644 examples/uitools/textfinder/main.cpp create mode 100644 examples/uitools/textfinder/textfinder.cpp create mode 100644 examples/uitools/textfinder/textfinder.h create mode 100644 examples/uitools/textfinder/textfinder.pro create mode 100644 examples/uitools/textfinder/textfinder.qrc create mode 100644 examples/uitools/uitools.pro create mode 100644 licenseRule.json create mode 100644 qt_cmdline.cmake create mode 100644 src/CMakeLists.txt create mode 100644 src/assistant/CMakeLists.txt create mode 100644 src/assistant/assistant/CMakeLists.txt create mode 100644 src/assistant/assistant/Info_mac.plist create mode 100644 src/assistant/assistant/aboutdialog.cpp create mode 100644 src/assistant/assistant/aboutdialog.h create mode 100644 src/assistant/assistant/assistant.desktop create mode 100644 src/assistant/assistant/assistant.icns create mode 100644 src/assistant/assistant/assistant.ico create mode 100644 src/assistant/assistant/assistant.metainfo.xml create mode 100644 src/assistant/assistant/bookmarkdialog.cpp create mode 100644 src/assistant/assistant/bookmarkdialog.h create mode 100644 src/assistant/assistant/bookmarkdialog.ui create mode 100644 src/assistant/assistant/bookmarkfiltermodel.cpp create mode 100644 src/assistant/assistant/bookmarkfiltermodel.h create mode 100644 src/assistant/assistant/bookmarkitem.cpp create mode 100644 src/assistant/assistant/bookmarkitem.h create mode 100644 src/assistant/assistant/bookmarkmanager.cpp create mode 100644 src/assistant/assistant/bookmarkmanager.h create mode 100644 src/assistant/assistant/bookmarkmanagerwidget.cpp create mode 100644 src/assistant/assistant/bookmarkmanagerwidget.h create mode 100644 src/assistant/assistant/bookmarkmanagerwidget.ui create mode 100644 src/assistant/assistant/bookmarkmodel.cpp create mode 100644 src/assistant/assistant/bookmarkmodel.h create mode 100644 src/assistant/assistant/bookmarkwidget.ui create mode 100644 src/assistant/assistant/centralwidget.cpp create mode 100644 src/assistant/assistant/centralwidget.h create mode 100644 src/assistant/assistant/cmdlineparser.cpp create mode 100644 src/assistant/assistant/cmdlineparser.h create mode 100644 src/assistant/assistant/contentwindow.cpp create mode 100644 src/assistant/assistant/contentwindow.h create mode 100644 src/assistant/assistant/doc/images/assistant-assistant.png create mode 100644 src/assistant/assistant/doc/images/assistant-bookmarks.png create mode 100644 src/assistant/assistant/doc/images/assistant-dockwidgets.png create mode 100644 src/assistant/assistant/doc/images/assistant-examples.png create mode 100644 src/assistant/assistant/doc/images/assistant-index.png create mode 100644 src/assistant/assistant/doc/images/assistant-preferences-documentation.png create mode 100644 src/assistant/assistant/doc/images/assistant-preferences-filters.png create mode 100644 src/assistant/assistant/doc/images/assistant-preferences-fonts.png create mode 100644 src/assistant/assistant/doc/images/assistant-preferences-options.png create mode 100644 src/assistant/assistant/doc/images/assistant-search.png create mode 100644 src/assistant/assistant/doc/internal/README create mode 100644 src/assistant/assistant/doc/internal/assistant.qdocconf create mode 100644 src/assistant/assistant/doc/internal/internal.pro create mode 100644 src/assistant/assistant/doc/qtassistant.qdocconf create mode 100644 src/assistant/assistant/doc/snippets/doc_src_assistant-manual.qdoc create mode 100644 src/assistant/assistant/doc/src/assistant-example.qdoc create mode 100644 src/assistant/assistant/doc/src/assistant-manual.qdoc create mode 100644 src/assistant/assistant/doc/src/assistant-quick-guide.qdoc create mode 100644 src/assistant/assistant/findwidget.cpp create mode 100644 src/assistant/assistant/findwidget.h create mode 100644 src/assistant/assistant/globalactions.cpp create mode 100644 src/assistant/assistant/globalactions.h create mode 100644 src/assistant/assistant/helpbrowsersupport.cpp create mode 100644 src/assistant/assistant/helpbrowsersupport.h create mode 100644 src/assistant/assistant/helpdocsettings.cpp create mode 100644 src/assistant/assistant/helpdocsettings.h create mode 100644 src/assistant/assistant/helpdocsettingswidget.cpp create mode 100644 src/assistant/assistant/helpdocsettingswidget.h create mode 100644 src/assistant/assistant/helpdocsettingswidget.ui create mode 100644 src/assistant/assistant/helpenginewrapper.cpp create mode 100644 src/assistant/assistant/helpenginewrapper.h create mode 100644 src/assistant/assistant/helpviewer.cpp create mode 100644 src/assistant/assistant/helpviewer.h create mode 100644 src/assistant/assistant/helpviewerimpl.cpp create mode 100644 src/assistant/assistant/helpviewerimpl.h create mode 100644 src/assistant/assistant/helpviewerimpl_p.h create mode 100644 src/assistant/assistant/helpviewerimpl_qtb.cpp create mode 100644 src/assistant/assistant/helpviewerimpl_qwv.cpp create mode 100644 src/assistant/assistant/images/assistant-128.png create mode 100644 src/assistant/assistant/images/assistant.png create mode 100644 src/assistant/assistant/images/bookmark.png create mode 100644 src/assistant/assistant/images/closebutton.png create mode 100644 src/assistant/assistant/images/darkclosebutton.png create mode 100644 src/assistant/assistant/images/mac/book.png create mode 100644 src/assistant/assistant/images/mac/closetab.png create mode 100644 src/assistant/assistant/images/mac/editcopy.png create mode 100644 src/assistant/assistant/images/mac/find.png create mode 100644 src/assistant/assistant/images/mac/home.png create mode 100644 src/assistant/assistant/images/mac/next.png create mode 100644 src/assistant/assistant/images/mac/previous.png create mode 100644 src/assistant/assistant/images/mac/print.png create mode 100644 src/assistant/assistant/images/mac/resetzoom.png create mode 100644 src/assistant/assistant/images/mac/synctoc.png create mode 100644 src/assistant/assistant/images/mac/zoomin.png create mode 100644 src/assistant/assistant/images/mac/zoomout.png create mode 100644 src/assistant/assistant/images/win/book.png create mode 100644 src/assistant/assistant/images/win/closetab.png create mode 100644 src/assistant/assistant/images/win/editcopy.png create mode 100644 src/assistant/assistant/images/win/find.png create mode 100644 src/assistant/assistant/images/win/home.png create mode 100644 src/assistant/assistant/images/win/next.png create mode 100644 src/assistant/assistant/images/win/previous.png create mode 100644 src/assistant/assistant/images/win/print.png create mode 100644 src/assistant/assistant/images/win/resetzoom.png create mode 100644 src/assistant/assistant/images/win/synctoc.png create mode 100644 src/assistant/assistant/images/win/zoomin.png create mode 100644 src/assistant/assistant/images/win/zoomout.png create mode 100644 src/assistant/assistant/images/wrap.png create mode 100644 src/assistant/assistant/indexwindow.cpp create mode 100644 src/assistant/assistant/indexwindow.h create mode 100644 src/assistant/assistant/main.cpp create mode 100644 src/assistant/assistant/mainwindow.cpp create mode 100644 src/assistant/assistant/mainwindow.h create mode 100644 src/assistant/assistant/openpagesmanager.cpp create mode 100644 src/assistant/assistant/openpagesmanager.h create mode 100644 src/assistant/assistant/openpagesmodel.cpp create mode 100644 src/assistant/assistant/openpagesmodel.h create mode 100644 src/assistant/assistant/openpagesswitcher.cpp create mode 100644 src/assistant/assistant/openpagesswitcher.h create mode 100644 src/assistant/assistant/openpageswidget.cpp create mode 100644 src/assistant/assistant/openpageswidget.h create mode 100644 src/assistant/assistant/preferencesdialog.cpp create mode 100644 src/assistant/assistant/preferencesdialog.h create mode 100644 src/assistant/assistant/preferencesdialog.ui create mode 100644 src/assistant/assistant/qtdocinstaller.cpp create mode 100644 src/assistant/assistant/qtdocinstaller.h create mode 100644 src/assistant/assistant/remotecontrol.cpp create mode 100644 src/assistant/assistant/remotecontrol.h create mode 100644 src/assistant/assistant/searchwidget.cpp create mode 100644 src/assistant/assistant/searchwidget.h create mode 100644 src/assistant/assistant/stdinlistener.cpp create mode 100644 src/assistant/assistant/stdinlistener.h create mode 100644 src/assistant/assistant/stdinlistener_win.cpp create mode 100644 src/assistant/assistant/stdinlistener_win.h create mode 100644 src/assistant/assistant/topicchooser.cpp create mode 100644 src/assistant/assistant/topicchooser.h create mode 100644 src/assistant/assistant/topicchooser.ui create mode 100644 src/assistant/assistant/tracer.h create mode 100644 src/assistant/assistant/xbelsupport.cpp create mode 100644 src/assistant/assistant/xbelsupport.h create mode 100644 src/assistant/help/CMakeLists.txt create mode 100644 src/assistant/help/doc/qthelp.qdocconf create mode 100644 src/assistant/help/doc/snippets/doc_src_qthelp.cpp create mode 100644 src/assistant/help/doc/snippets/doc_src_qthelp.qdoc create mode 100644 src/assistant/help/doc/src/qthelp-examples.qdoc create mode 100644 src/assistant/help/doc/src/qthelp-index.qdoc create mode 100644 src/assistant/help/doc/src/qthelp-module.qdoc create mode 100644 src/assistant/help/doc/src/qthelp-toc.qdoc create mode 100644 src/assistant/help/doc/src/qthelp.qdoc create mode 100644 src/assistant/help/images/1leftarrow.png create mode 100644 src/assistant/help/images/1rightarrow.png create mode 100644 src/assistant/help/images/3leftarrow.png create mode 100644 src/assistant/help/images/3rightarrow.png create mode 100644 src/assistant/help/images/mac/minus.png create mode 100644 src/assistant/help/images/mac/plus.png create mode 100644 src/assistant/help/images/win/minus.png create mode 100644 src/assistant/help/images/win/plus.png create mode 100644 src/assistant/help/qcompressedhelpinfo.cpp create mode 100644 src/assistant/help/qcompressedhelpinfo.h create mode 100644 src/assistant/help/qfilternamedialog.cpp create mode 100644 src/assistant/help/qfilternamedialog.ui create mode 100644 src/assistant/help/qfilternamedialog_p.h create mode 100644 src/assistant/help/qhelp_global.cpp create mode 100644 src/assistant/help/qhelp_global.h create mode 100644 src/assistant/help/qhelpcollectionhandler.cpp create mode 100644 src/assistant/help/qhelpcollectionhandler_p.h create mode 100644 src/assistant/help/qhelpcontentitem.cpp create mode 100644 src/assistant/help/qhelpcontentitem.h create mode 100644 src/assistant/help/qhelpcontentwidget.cpp create mode 100644 src/assistant/help/qhelpcontentwidget.h create mode 100644 src/assistant/help/qhelpdbreader.cpp create mode 100644 src/assistant/help/qhelpdbreader_p.h create mode 100644 src/assistant/help/qhelpengine.cpp create mode 100644 src/assistant/help/qhelpengine.h create mode 100644 src/assistant/help/qhelpenginecore.cpp create mode 100644 src/assistant/help/qhelpenginecore.h create mode 100644 src/assistant/help/qhelpfilterdata.cpp create mode 100644 src/assistant/help/qhelpfilterdata.h create mode 100644 src/assistant/help/qhelpfilterengine.cpp create mode 100644 src/assistant/help/qhelpfilterengine.h create mode 100644 src/assistant/help/qhelpfiltersettingswidget.cpp create mode 100644 src/assistant/help/qhelpfiltersettingswidget.h create mode 100644 src/assistant/help/qhelpfiltersettingswidget.ui create mode 100644 src/assistant/help/qhelpindexwidget.cpp create mode 100644 src/assistant/help/qhelpindexwidget.h create mode 100644 src/assistant/help/qhelplink.cpp create mode 100644 src/assistant/help/qhelplink.h create mode 100644 src/assistant/help/qhelpsearchengine.cpp create mode 100644 src/assistant/help/qhelpsearchengine.h create mode 100644 src/assistant/help/qhelpsearchenginecore.cpp create mode 100644 src/assistant/help/qhelpsearchenginecore.h create mode 100644 src/assistant/help/qhelpsearchindexreader.cpp create mode 100644 src/assistant/help/qhelpsearchindexreader_p.h create mode 100644 src/assistant/help/qhelpsearchindexwriter.cpp create mode 100644 src/assistant/help/qhelpsearchindexwriter_p.h create mode 100644 src/assistant/help/qhelpsearchquerywidget.cpp create mode 100644 src/assistant/help/qhelpsearchquerywidget.h create mode 100644 src/assistant/help/qhelpsearchresult.cpp create mode 100644 src/assistant/help/qhelpsearchresult.h create mode 100644 src/assistant/help/qhelpsearchresultwidget.cpp create mode 100644 src/assistant/help/qhelpsearchresultwidget.h create mode 100644 src/assistant/help/qoptionswidget.cpp create mode 100644 src/assistant/help/qoptionswidget_p.h create mode 100644 src/assistant/plugins/CMakeLists.txt create mode 100644 src/assistant/plugins/help/CMakeLists.txt create mode 100644 src/assistant/plugins/help/qhelpengineplugin.cpp create mode 100644 src/assistant/plugins/help/qhelpengineplugin.h create mode 100644 src/assistant/qhelpgenerator/CMakeLists.txt create mode 100644 src/assistant/qhelpgenerator/collectionconfigreader.cpp create mode 100644 src/assistant/qhelpgenerator/collectionconfigreader.h create mode 100644 src/assistant/qhelpgenerator/helpgenerator.cpp create mode 100644 src/assistant/qhelpgenerator/helpgenerator.h create mode 100644 src/assistant/qhelpgenerator/main.cpp create mode 100644 src/assistant/qhelpgenerator/qhelpdatainterface.cpp create mode 100644 src/assistant/qhelpgenerator/qhelpdatainterface_p.h create mode 100644 src/assistant/qhelpgenerator/qhelpprojectdata.cpp create mode 100644 src/assistant/qhelpgenerator/qhelpprojectdata_p.h create mode 100644 src/assistant/qlitehtml/.clang-format create mode 100644 src/assistant/qlitehtml/.gitmodules create mode 100644 src/assistant/qlitehtml/.tag create mode 100644 src/assistant/qlitehtml/CMakeLists.txt create mode 100644 src/assistant/qlitehtml/LICENSES/Apache-2.0.txt create mode 100644 src/assistant/qlitehtml/LICENSES/BSD-3-Clause.txt create mode 100644 src/assistant/qlitehtml/LICENSES/GPL-3.0-only.txt create mode 100644 src/assistant/qlitehtml/LICENSES/LicenseRef-Qt-Commercial.txt create mode 100644 src/assistant/qlitehtml/LICENSES/MIT.txt create mode 100644 src/assistant/qlitehtml/LICENSES/Qt-GPL-exception-1.0.txt create mode 100644 src/assistant/qlitehtml/README.md create mode 100644 src/assistant/qlitehtml/src/3rdparty/GUMBO-AUTHORS.txt create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/.github/FUNDING.yml create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/.github/workflows/cmake.yml create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/.gitignore create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/CMakeLists.txt create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/LICENSE create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/README.md create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/cmake/litehtmlConfig.cmake create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_container.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_container.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_font.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_font.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/gdiplus/gdiplus_container.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/gdiplus/gdiplus_container.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/haiku/container_haiku.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/haiku/container_haiku.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/linux/container_linux.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/linux/container_linux.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Bitmap.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Bitmap.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Font.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Font.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/OFL.txt create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/readme.txt create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-12px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-14px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-16px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-18px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-20px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-22px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-24px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-28px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-32px.yaff create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/lodepng.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/lodepng.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/test_container.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/test_container.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/win32/win32_container.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/containers/win32/win32_container.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/background.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/borders.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/codepoint.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_length.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_margins.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_offsets.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_position.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_properties.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_selector.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/document.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/document_container.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_anchor.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_base.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_before_after.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_body.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_break.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_cdata.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_comment.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_div.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_font.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_image.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_link.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_para.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_script.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_space.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_style.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_table.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_td.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_text.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_title.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_tr.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/element.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/flex_item.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/flex_line.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/formatting_context.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/html.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/html_tag.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/iterators.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/line_box.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/master_css.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/media_query.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/num_cvt.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/os_types.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_block.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_block_context.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_flex.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_image.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_inline.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_inline_context.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_item.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_table.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/string_id.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/style.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/stylesheet.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/table.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/tstring_view.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/types.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/url.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/url_path.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/utf8_strings.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/web_color.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/litehtml.vcxproj create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/litehtml.vcxproj.filters create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/codepoint.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/css_borders.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/css_length.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/css_properties.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/css_selector.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/document.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/document_container.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_anchor.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_base.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_before_after.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_body.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_break.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_cdata.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_comment.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_div.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_font.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_image.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_link.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_para.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_script.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_space.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_style.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_table.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_td.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_text.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_title.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/el_tr.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/element.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/flex_item.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/flex_line.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/formatting_context.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/CMakeLists.txt create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/LICENSE create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/attribute.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/char_ref.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/char_ref.rl create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/error.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/attribute.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/char_ref.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/error.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/insertion_mode.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/parser.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/string_buffer.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/string_piece.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/tag_enum.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/tag_gperf.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/tag_sizes.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/tag_strings.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/token_type.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/tokenizer.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/tokenizer_states.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/utf8.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/util.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/include/gumbo/vector.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/parser.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/string_buffer.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/string_piece.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/tag.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/tag.in create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/tokenizer.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/utf8.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/util.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/vector.c create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/gumbo/visualc/include/strings.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/html.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/html_tag.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/iterators.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/line_box.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/media_query.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/num_cvt.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/render_block.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/render_block_context.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/render_flex.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/render_image.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/render_inline_context.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/render_item.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/render_table.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/string_id.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/strtod.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/style.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/stylesheet.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/table.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/tstring_view.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/url.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/url_path.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/utf8_strings.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/src/web_color.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/codepoint_test.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/cssTest.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/dirent.h create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/mediaQueryTest.cpp create mode 100755 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/-table-caption.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/acid1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/acid1.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/counter.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/counter.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-1-line-height.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-1-line-height.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-2-invalid-color.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-2-invalid-color.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flex-minimum-height-flex-items-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-baseline-single-item-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-baseline-single-item-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-002a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-002b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-003a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-003b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-004a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-gap-position-absolute.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-items-as-stacking-contexts-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-mbp-horiz-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-paint-ordering-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-paint-ordering-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-single-line-clamp-3.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-vert-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-with-pseudo-elements-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_fbfc.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_fbfc2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-formatting-interop.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-natural-mixed-basis.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-natural-variable-zero-basis.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-none-wrappable-content.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline-abspos.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline-float.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_order-abspos-space-around.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_stf-table-singleline-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_table-fixed-layout.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_width-overflow.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--multiline-shrink-to-fit.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--percentage-heights-008.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--percentage-heights-010.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-contain-layout-baseline-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-contain-layout-suppress-baseline-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-content-height-with-scrollbars.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-cross-axis-scrollbar.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-box-justify-content.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse-wrap-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse-wrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-wrap-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-wrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-test1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-direction-upright-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-019.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-020.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-021.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-022.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-direction-row-002-visual.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-direction-row-vertical.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-inline.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-height-flex-items-022.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-009.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-abspos-child-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-abspos-child-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-baseline-horiz-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-baseline-horiz-008.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-horiz-001-table.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-anonymous-items-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-basic-block-horiz-001v.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-basic-block-vert-001v.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-baseline-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-004b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-default.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-vert-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-wrap-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-justify-content-wmvert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-001-rtl-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-001-rtl.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-002v.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-003v.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002c.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-overflow-horiz-001.htm.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-whitespace-handling-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-whitespace-handling-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-007.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-008.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-009.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-010.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-011.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-012.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-013.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-014.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-015.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-016.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_align-items-stretch-writing-modes.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_block.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_columns-flexitems-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_columns-flexitems.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan-overflow-automatic.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan-overflow.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-direction.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-flow-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-flow.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-order.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_stf-table-singleline.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_writing_mode_vertical_lays_out_contents_from_top_to_bottom.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-negative-flex-margins-crash.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-nested-orthogonal-flexbox-relayout.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-percentage-size-subitems-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-fixed-min-width-3.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-flex-cross-size.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-column-1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-column-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-row-1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-row-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-narrow-content-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-percent-width-cell-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-specified-height.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-specified-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size-5.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-ltr.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-ltr.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-rtl.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-rtl.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-ltr.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-ltr.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-rtl.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-rtl.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-ltr.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-ltr.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-rtl.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-rtl.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-baseline.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-baseline.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_center.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_center.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-end.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-end.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-start.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-start.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-around.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-around.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-between.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-between.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_stretch.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_stretch.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-007.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-007.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-008.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-008.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-009.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-009.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-010.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-010.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-011.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-011.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-012.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-012.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-013.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-013.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/anonymous-block.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/anonymous-block.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-column-with-border-and-padding.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-column-with-border-and-padding.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-with-flex.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-with-flex.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/contain-layout-baseline-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/css-box-justify-content.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/css-box-justify-content.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/display-flex-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/display-flex-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-center.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-center.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-end.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-end.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-around.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-around.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-between.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-between.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-start.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-start.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-base.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-base.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-007.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-007.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-008.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-008.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-010.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-010.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-011.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-011.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-box-wrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-box-wrap.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-container-margin.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-container-margin.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-001-visual.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-001-visual.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-001-visual.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-001-visual.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-002-visual.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-002-visual.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-001-visual.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-001-visual.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-001-visual.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-001-visual.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-002-visual.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-002-visual.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-childmargin.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-childmargin.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-percentage-prescation.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-percentage-prescation.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-007.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-007.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-008.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-008.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-009.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-009.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-010.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-010.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-011.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-011.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-012.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-012.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-007.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-007.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-margin-no-collapse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-margin-no-collapse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-011.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-011.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-width-flex-items-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-width-flex-items-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-order.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-order.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-007.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-007.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-008.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-008.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-abspos-child-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-abspos-child-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-items-center-nested-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-items-center-nested-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-007.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-007.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-001-block.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-001-block.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-stretch-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-stretch-vert-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-stretch-vert-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-stretch-vert-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-anonymous-items-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-anonymous-items-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-horiz-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-vert-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-vert-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-vert-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-vert-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-vert-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-horiz-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-vert-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-default.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-default.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-default.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-default.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-flexing.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-flexing.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-nowrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-nowrap.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-wrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-wrap.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-from-lowest.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-from-lowest.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-only-flexitems.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-only-flexitems.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-005.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-005.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-paint-ordering-003.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-paint-ordering-003.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001a.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001a.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001b.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001b.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-1.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-table-fixup-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-table-fixup-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-whitespace-handling-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-whitespace-handling-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-with-pseudo-elements-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-with-pseudo-elements-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-with-pseudo-elements-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-with-pseudo-elements-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_absolute-atomic.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_absolute-atomic.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-center.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-center.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexend.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexend.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexstart.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexstart.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacearound.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacearound.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacebetween.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacebetween.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-baseline.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-baseline.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-baseline.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-baseline.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-center.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-center.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexend.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexend.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexstart.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexstart.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-stretch.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-stretch.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_box-clear.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_box-clear.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_columns.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_columns.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-row-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-row-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_display.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_display.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_first-line.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_first-line.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-1-unitless-basis.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-1-unitless-basis.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-unitless-basis.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-unitless-basis.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-1-unitless-basis.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-1-unitless-basis.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-unitless-basis.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-unitless-basis.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0-unitless.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0-unitless.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis-shrink.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis-shrink.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-mixed-basis-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-mixed-basis-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-variable-auto-basis.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-variable-auto-basis.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-none.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-none.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-flex.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-flex.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-nested-flex.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-nested-flex.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-bottom-float.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-bottom-float.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-clear.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-clear.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-float.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-float.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-top-float.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-top-float.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-vertical-align.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-vertical-align.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-center.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-center.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-end.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-end.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-start.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-start.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-negative.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-negative.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-only.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-only.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-negative.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-negative.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-only.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-only.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto-overflow.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto-overflow.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-left-ex.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-left-ex.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_nested-flex.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_nested-flex.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_object.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_object.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-box.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-box.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-noninteger-invalid.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-noninteger-invalid.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-abspos.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-abspos.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-fixpos.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-fixpos.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-float.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-float.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-inline-block.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-inline-block.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-caption.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-caption.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-cell.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-cell.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-row-group.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-row-group.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-row.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-row.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_visibility-collapse-line-wrapping.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_visibility-collapse-line-wrapping.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_visibility-collapse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_visibility-collapse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_width-overflow.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-long.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-long.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-box-float.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-box-float.htm.chrome.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-box-float.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-order.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-order.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/item-with-table-with-infinite-max-intrinsic-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/item-with-table-with-infinite-max-intrinsic-width.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_center.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_center.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-end.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-end.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-start.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-start.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_space-around.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_space-around.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_space-between-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_space-between-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-002.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-002.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-reverse-column-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-reverse-column-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-reverse-row-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-reverse-row-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-column-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-column-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-row-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-row-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multiline-reverse-wrap-baseline.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multiline-reverse-wrap-baseline.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-flex-margins-crash.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-margins-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-margins-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-column-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-column-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-row-reverse.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-row-reverse.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/padding-overflow-crash.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/padding-overflow-crash.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-004.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-004.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-006.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-006.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-007.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-007.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-009.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-009.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/space-evenly-001.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/space-evenly-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/support/a-green.css create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/support/b-green.css create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/support/c-red.css create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/support/flexbox.css create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/support/import-green.css create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/support/import-red.css create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/support/test-style.css create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-auto-min-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-auto-min-width.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-fixed-min-width-2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-fixed-min-width-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-fixed-min-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-fixed-min-width.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-narrow-content-2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-narrow-content.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-narrow-content.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-percent-width-cell-001.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-3.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-3.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-4.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-4.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-with-infinite-max-intrinsic-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-with-infinite-max-intrinsic-width.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/zero-content-size-with-scrollbar-crash.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/zero-content-size-with-scrollbar-crash.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/render-1-inline.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/render-1-inline.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-1-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-1-width.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-2-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-2-width.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-3-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-3-width.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-4-td-width.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-4-td-width.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test1.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test1.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test10.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test10.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test11.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test11.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test12.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test12.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test13.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test13.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test14.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test14.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test15.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test15.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test16.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test16.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test17.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test17.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test18.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test18.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test19.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test19.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test2.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test2.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test20.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test20.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test21.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test21.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test22.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test22.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test23.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test23.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test24.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test24.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test25.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test25.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test26.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test26.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test27.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test27.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test28.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test28.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test29.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test29.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test3.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test3.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test30.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test30.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test31.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test31.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test32.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test32.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test33.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test33.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test34.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test34.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test35.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test35.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test36.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test36.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test37.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test37.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test38.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test38.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test39.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test39.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test4.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test4.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test5.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test5.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test6.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test6.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test7.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test7.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test8.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test8.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test9.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test9.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/text-before-after.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/text-before-after.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/text-justify.htm create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/text-justify.htm.png create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/render_test.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/tstring_view_test.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/url_path_test.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/litehtml/test/url_test.cpp create mode 100644 src/assistant/qlitehtml/src/3rdparty/qt_attribution.json create mode 100644 src/assistant/qlitehtml/src/CMakeLists.txt create mode 100644 src/assistant/qlitehtml/src/container_qpainter.cpp create mode 100644 src/assistant/qlitehtml/src/container_qpainter.h create mode 100644 src/assistant/qlitehtml/src/container_qpainter_p.h create mode 100644 src/assistant/qlitehtml/src/qlitehtml.pri create mode 100644 src/assistant/qlitehtml/src/qlitehtml.qbs create mode 100644 src/assistant/qlitehtml/src/qlitehtml_global.h create mode 100644 src/assistant/qlitehtml/src/qlitehtmlwidget.cpp create mode 100644 src/assistant/qlitehtml/src/qlitehtmlwidget.h create mode 100644 src/assistant/qlitehtml/tests/manual/browser/CMakeLists.txt create mode 100644 src/assistant/qlitehtml/tests/manual/browser/main.cpp create mode 100644 src/assistant/shared/collectionconfiguration.cpp create mode 100644 src/assistant/shared/collectionconfiguration.h create mode 100644 src/designer/CMakeLists.txt create mode 100644 src/designer/data/README create mode 100644 src/designer/data/generate_header.xsl create mode 100644 src/designer/data/generate_impl.xsl create mode 100644 src/designer/data/generate_shared.xsl create mode 100755 src/designer/data/generate_ui.py create mode 100644 src/designer/data/ui3.xsd create mode 100644 src/designer/data/ui4.xsd create mode 100644 src/designer/src/CMakeLists.txt create mode 100644 src/designer/src/components/CMakeLists.txt create mode 100644 src/designer/src/components/buddyeditor/buddyeditor.cpp create mode 100644 src/designer/src/components/buddyeditor/buddyeditor.h create mode 100644 src/designer/src/components/buddyeditor/buddyeditor.json create mode 100644 src/designer/src/components/buddyeditor/buddyeditor_global.h create mode 100644 src/designer/src/components/buddyeditor/buddyeditor_plugin.cpp create mode 100644 src/designer/src/components/buddyeditor/buddyeditor_plugin.h create mode 100644 src/designer/src/components/buddyeditor/buddyeditor_tool.cpp create mode 100644 src/designer/src/components/buddyeditor/buddyeditor_tool.h create mode 100644 src/designer/src/components/formeditor/default_actionprovider.cpp create mode 100644 src/designer/src/components/formeditor/default_actionprovider.h create mode 100644 src/designer/src/components/formeditor/default_container.cpp create mode 100644 src/designer/src/components/formeditor/default_container.h create mode 100644 src/designer/src/components/formeditor/default_layoutdecoration.cpp create mode 100644 src/designer/src/components/formeditor/default_layoutdecoration.h create mode 100644 src/designer/src/components/formeditor/defaultbrushes.xml create mode 100644 src/designer/src/components/formeditor/deviceprofiledialog.cpp create mode 100644 src/designer/src/components/formeditor/deviceprofiledialog.h create mode 100644 src/designer/src/components/formeditor/deviceprofiledialog.ui create mode 100644 src/designer/src/components/formeditor/dpi_chooser.cpp create mode 100644 src/designer/src/components/formeditor/dpi_chooser.h create mode 100644 src/designer/src/components/formeditor/embeddedoptionspage.cpp create mode 100644 src/designer/src/components/formeditor/embeddedoptionspage.h create mode 100644 src/designer/src/components/formeditor/formeditor.cpp create mode 100644 src/designer/src/components/formeditor/formeditor.h create mode 100644 src/designer/src/components/formeditor/formeditor_global.h create mode 100644 src/designer/src/components/formeditor/formeditor_optionspage.cpp create mode 100644 src/designer/src/components/formeditor/formeditor_optionspage.h create mode 100644 src/designer/src/components/formeditor/formwindow.cpp create mode 100644 src/designer/src/components/formeditor/formwindow.h create mode 100644 src/designer/src/components/formeditor/formwindow_dnditem.cpp create mode 100644 src/designer/src/components/formeditor/formwindow_dnditem.h create mode 100644 src/designer/src/components/formeditor/formwindow_widgetstack.cpp create mode 100644 src/designer/src/components/formeditor/formwindow_widgetstack.h create mode 100644 src/designer/src/components/formeditor/formwindowcursor.cpp create mode 100644 src/designer/src/components/formeditor/formwindowcursor.h create mode 100644 src/designer/src/components/formeditor/formwindowmanager.cpp create mode 100644 src/designer/src/components/formeditor/formwindowmanager.h create mode 100644 src/designer/src/components/formeditor/formwindowsettings.cpp create mode 100644 src/designer/src/components/formeditor/formwindowsettings.h create mode 100644 src/designer/src/components/formeditor/formwindowsettings.ui create mode 100644 src/designer/src/components/formeditor/images/color.png create mode 100644 src/designer/src/components/formeditor/images/configure.png create mode 100644 src/designer/src/components/formeditor/images/downplus.png create mode 100644 src/designer/src/components/formeditor/images/dropdownbutton.png create mode 100644 src/designer/src/components/formeditor/images/edit.png create mode 100644 src/designer/src/components/formeditor/images/editdelete-16.png create mode 100644 src/designer/src/components/formeditor/images/emptyicon.png create mode 100644 src/designer/src/components/formeditor/images/filenew-16.png create mode 100644 src/designer/src/components/formeditor/images/fileopen-16.png create mode 100644 src/designer/src/components/formeditor/images/leveldown.png create mode 100644 src/designer/src/components/formeditor/images/levelup.png create mode 100644 src/designer/src/components/formeditor/images/mac/adjustsize.png create mode 100644 src/designer/src/components/formeditor/images/mac/back.png create mode 100644 src/designer/src/components/formeditor/images/mac/buddytool.png create mode 100644 src/designer/src/components/formeditor/images/mac/down.png create mode 100644 src/designer/src/components/formeditor/images/mac/editbreaklayout.png create mode 100644 src/designer/src/components/formeditor/images/mac/editcopy.png create mode 100644 src/designer/src/components/formeditor/images/mac/editcut.png create mode 100644 src/designer/src/components/formeditor/images/mac/editdelete.png create mode 100644 src/designer/src/components/formeditor/images/mac/editform.png create mode 100644 src/designer/src/components/formeditor/images/mac/editgrid.png create mode 100644 src/designer/src/components/formeditor/images/mac/edithlayout.png create mode 100644 src/designer/src/components/formeditor/images/mac/edithlayoutsplit.png create mode 100644 src/designer/src/components/formeditor/images/mac/editlower.png create mode 100644 src/designer/src/components/formeditor/images/mac/editpaste.png create mode 100644 src/designer/src/components/formeditor/images/mac/editraise.png create mode 100644 src/designer/src/components/formeditor/images/mac/editvlayout.png create mode 100644 src/designer/src/components/formeditor/images/mac/editvlayoutsplit.png create mode 100644 src/designer/src/components/formeditor/images/mac/filenew.png create mode 100644 src/designer/src/components/formeditor/images/mac/fileopen.png create mode 100644 src/designer/src/components/formeditor/images/mac/filesave.png create mode 100644 src/designer/src/components/formeditor/images/mac/forward.png create mode 100644 src/designer/src/components/formeditor/images/mac/insertimage.png create mode 100644 src/designer/src/components/formeditor/images/mac/minus.png create mode 100644 src/designer/src/components/formeditor/images/mac/plus.png create mode 100644 src/designer/src/components/formeditor/images/mac/redo.png create mode 100644 src/designer/src/components/formeditor/images/mac/resetproperty.png create mode 100644 src/designer/src/components/formeditor/images/mac/signalslottool.png create mode 100644 src/designer/src/components/formeditor/images/mac/simplifyrichtext.png create mode 100644 src/designer/src/components/formeditor/images/mac/tabordertool.png create mode 100644 src/designer/src/components/formeditor/images/mac/textanchor.png create mode 100644 src/designer/src/components/formeditor/images/mac/textbold.png create mode 100644 src/designer/src/components/formeditor/images/mac/textcenter.png create mode 100644 src/designer/src/components/formeditor/images/mac/textitalic.png create mode 100644 src/designer/src/components/formeditor/images/mac/textjustify.png create mode 100644 src/designer/src/components/formeditor/images/mac/textleft.png create mode 100644 src/designer/src/components/formeditor/images/mac/textright.png create mode 100644 src/designer/src/components/formeditor/images/mac/textsubscript.png create mode 100644 src/designer/src/components/formeditor/images/mac/textsuperscript.png create mode 100644 src/designer/src/components/formeditor/images/mac/textunder.png create mode 100644 src/designer/src/components/formeditor/images/mac/undo.png create mode 100644 src/designer/src/components/formeditor/images/mac/up.png create mode 100644 src/designer/src/components/formeditor/images/mac/widgettool.png create mode 100644 src/designer/src/components/formeditor/images/minus-16.png create mode 100644 src/designer/src/components/formeditor/images/prefix-add.png create mode 100644 src/designer/src/components/formeditor/images/qtlogo128x128.png create mode 100644 src/designer/src/components/formeditor/images/qtlogo16x16.png create mode 100644 src/designer/src/components/formeditor/images/qtlogo24x24.png create mode 100644 src/designer/src/components/formeditor/images/qtlogo32x32.png create mode 100644 src/designer/src/components/formeditor/images/qtlogo64x64.png create mode 100644 src/designer/src/components/formeditor/images/reload.png create mode 100644 src/designer/src/components/formeditor/images/resetproperty.png create mode 100644 src/designer/src/components/formeditor/images/righttoleft.png create mode 100644 src/designer/src/components/formeditor/images/sort.png create mode 100644 src/designer/src/components/formeditor/images/submenu.png create mode 100644 src/designer/src/components/formeditor/images/widgets/calendarwidget.png create mode 100644 src/designer/src/components/formeditor/images/widgets/checkbox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/columnview.png create mode 100644 src/designer/src/components/formeditor/images/widgets/combobox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/commandlinkbutton.png create mode 100644 src/designer/src/components/formeditor/images/widgets/dateedit.png create mode 100644 src/designer/src/components/formeditor/images/widgets/datetimeedit.png create mode 100644 src/designer/src/components/formeditor/images/widgets/dial.png create mode 100644 src/designer/src/components/formeditor/images/widgets/dialogbuttonbox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/dockwidget.png create mode 100644 src/designer/src/components/formeditor/images/widgets/doublespinbox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/fontcombobox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/frame.png create mode 100644 src/designer/src/components/formeditor/images/widgets/graphicsview.png create mode 100644 src/designer/src/components/formeditor/images/widgets/groupbox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/hscrollbar.png create mode 100644 src/designer/src/components/formeditor/images/widgets/hslider.png create mode 100644 src/designer/src/components/formeditor/images/widgets/label.png create mode 100644 src/designer/src/components/formeditor/images/widgets/lcdnumber.png create mode 100644 src/designer/src/components/formeditor/images/widgets/line.png create mode 100644 src/designer/src/components/formeditor/images/widgets/lineedit.png create mode 100644 src/designer/src/components/formeditor/images/widgets/listbox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/listview.png create mode 100644 src/designer/src/components/formeditor/images/widgets/mdiarea.png create mode 100644 src/designer/src/components/formeditor/images/widgets/plaintextedit.png create mode 100644 src/designer/src/components/formeditor/images/widgets/progress.png create mode 100644 src/designer/src/components/formeditor/images/widgets/pushbutton.png create mode 100644 src/designer/src/components/formeditor/images/widgets/radiobutton.png create mode 100644 src/designer/src/components/formeditor/images/widgets/scrollarea.png create mode 100644 src/designer/src/components/formeditor/images/widgets/spacer.png create mode 100644 src/designer/src/components/formeditor/images/widgets/spinbox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/table.png create mode 100644 src/designer/src/components/formeditor/images/widgets/tabwidget.png create mode 100644 src/designer/src/components/formeditor/images/widgets/textedit.png create mode 100644 src/designer/src/components/formeditor/images/widgets/timeedit.png create mode 100644 src/designer/src/components/formeditor/images/widgets/toolbox.png create mode 100644 src/designer/src/components/formeditor/images/widgets/toolbutton.png create mode 100644 src/designer/src/components/formeditor/images/widgets/vline.png create mode 100644 src/designer/src/components/formeditor/images/widgets/vscrollbar.png create mode 100644 src/designer/src/components/formeditor/images/widgets/vslider.png create mode 100644 src/designer/src/components/formeditor/images/widgets/vspacer.png create mode 100644 src/designer/src/components/formeditor/images/widgets/widget.png create mode 100644 src/designer/src/components/formeditor/images/widgets/widgetstack.png create mode 100644 src/designer/src/components/formeditor/images/win/adjustsize.png create mode 100644 src/designer/src/components/formeditor/images/win/back.png create mode 100644 src/designer/src/components/formeditor/images/win/buddytool.png create mode 100644 src/designer/src/components/formeditor/images/win/down.png create mode 100644 src/designer/src/components/formeditor/images/win/editbreaklayout.png create mode 100644 src/designer/src/components/formeditor/images/win/editcopy.png create mode 100644 src/designer/src/components/formeditor/images/win/editcut.png create mode 100644 src/designer/src/components/formeditor/images/win/editdelete.png create mode 100644 src/designer/src/components/formeditor/images/win/editform.png create mode 100644 src/designer/src/components/formeditor/images/win/editgrid.png create mode 100644 src/designer/src/components/formeditor/images/win/edithlayout.png create mode 100644 src/designer/src/components/formeditor/images/win/edithlayoutsplit.png create mode 100644 src/designer/src/components/formeditor/images/win/editlower.png create mode 100644 src/designer/src/components/formeditor/images/win/editpaste.png create mode 100644 src/designer/src/components/formeditor/images/win/editraise.png create mode 100644 src/designer/src/components/formeditor/images/win/editvlayout.png create mode 100644 src/designer/src/components/formeditor/images/win/editvlayoutsplit.png create mode 100644 src/designer/src/components/formeditor/images/win/filenew.png create mode 100644 src/designer/src/components/formeditor/images/win/fileopen.png create mode 100644 src/designer/src/components/formeditor/images/win/filesave.png create mode 100644 src/designer/src/components/formeditor/images/win/forward.png create mode 100644 src/designer/src/components/formeditor/images/win/insertimage.png create mode 100644 src/designer/src/components/formeditor/images/win/minus.png create mode 100644 src/designer/src/components/formeditor/images/win/plus.png create mode 100644 src/designer/src/components/formeditor/images/win/redo.png create mode 100644 src/designer/src/components/formeditor/images/win/signalslottool.png create mode 100644 src/designer/src/components/formeditor/images/win/simplifyrichtext.png create mode 100644 src/designer/src/components/formeditor/images/win/tabordertool.png create mode 100644 src/designer/src/components/formeditor/images/win/textanchor.png create mode 100644 src/designer/src/components/formeditor/images/win/textbold.png create mode 100644 src/designer/src/components/formeditor/images/win/textcenter.png create mode 100644 src/designer/src/components/formeditor/images/win/textitalic.png create mode 100644 src/designer/src/components/formeditor/images/win/textjustify.png create mode 100644 src/designer/src/components/formeditor/images/win/textleft.png create mode 100644 src/designer/src/components/formeditor/images/win/textright.png create mode 100644 src/designer/src/components/formeditor/images/win/textsubscript.png create mode 100644 src/designer/src/components/formeditor/images/win/textsuperscript.png create mode 100644 src/designer/src/components/formeditor/images/win/textunder.png create mode 100644 src/designer/src/components/formeditor/images/win/undo.png create mode 100644 src/designer/src/components/formeditor/images/win/up.png create mode 100644 src/designer/src/components/formeditor/images/win/widgettool.png create mode 100644 src/designer/src/components/formeditor/itemview_propertysheet.cpp create mode 100644 src/designer/src/components/formeditor/itemview_propertysheet.h create mode 100644 src/designer/src/components/formeditor/layout_propertysheet.cpp create mode 100644 src/designer/src/components/formeditor/layout_propertysheet.h create mode 100644 src/designer/src/components/formeditor/line_propertysheet.cpp create mode 100644 src/designer/src/components/formeditor/line_propertysheet.h create mode 100644 src/designer/src/components/formeditor/previewactiongroup.cpp create mode 100644 src/designer/src/components/formeditor/previewactiongroup.h create mode 100644 src/designer/src/components/formeditor/qdesigner_resource.cpp create mode 100644 src/designer/src/components/formeditor/qdesigner_resource.h create mode 100644 src/designer/src/components/formeditor/qlayoutwidget_propertysheet.cpp create mode 100644 src/designer/src/components/formeditor/qlayoutwidget_propertysheet.h create mode 100644 src/designer/src/components/formeditor/qmainwindow_container.cpp create mode 100644 src/designer/src/components/formeditor/qmainwindow_container.h create mode 100644 src/designer/src/components/formeditor/qmdiarea_container.cpp create mode 100644 src/designer/src/components/formeditor/qmdiarea_container.h create mode 100644 src/designer/src/components/formeditor/qwizard_container.cpp create mode 100644 src/designer/src/components/formeditor/qwizard_container.h create mode 100644 src/designer/src/components/formeditor/spacer_propertysheet.cpp create mode 100644 src/designer/src/components/formeditor/spacer_propertysheet.h create mode 100644 src/designer/src/components/formeditor/templateoptionspage.cpp create mode 100644 src/designer/src/components/formeditor/templateoptionspage.h create mode 100644 src/designer/src/components/formeditor/templateoptionspage.ui create mode 100644 src/designer/src/components/formeditor/tool_widgeteditor.cpp create mode 100644 src/designer/src/components/formeditor/tool_widgeteditor.h create mode 100644 src/designer/src/components/formeditor/widgetselection.cpp create mode 100644 src/designer/src/components/formeditor/widgetselection.h create mode 100644 src/designer/src/components/lib/CMakeLists.txt create mode 100644 src/designer/src/components/lib/lib_pch.h create mode 100644 src/designer/src/components/lib/qdesigner_components.cpp create mode 100644 src/designer/src/components/objectinspector/objectinspector.cpp create mode 100644 src/designer/src/components/objectinspector/objectinspector.h create mode 100644 src/designer/src/components/objectinspector/objectinspector_global.h create mode 100644 src/designer/src/components/objectinspector/objectinspectormodel.cpp create mode 100644 src/designer/src/components/objectinspector/objectinspectormodel_p.h create mode 100644 src/designer/src/components/propertyeditor/brushpropertymanager.cpp create mode 100644 src/designer/src/components/propertyeditor/brushpropertymanager.h create mode 100644 src/designer/src/components/propertyeditor/designerpropertymanager.cpp create mode 100644 src/designer/src/components/propertyeditor/designerpropertymanager.h create mode 100644 src/designer/src/components/propertyeditor/fontmapping.xml create mode 100644 src/designer/src/components/propertyeditor/fontpropertymanager.cpp create mode 100644 src/designer/src/components/propertyeditor/fontpropertymanager.h create mode 100644 src/designer/src/components/propertyeditor/newdynamicpropertydialog.cpp create mode 100644 src/designer/src/components/propertyeditor/newdynamicpropertydialog.h create mode 100644 src/designer/src/components/propertyeditor/newdynamicpropertydialog.ui create mode 100644 src/designer/src/components/propertyeditor/paletteeditor.cpp create mode 100644 src/designer/src/components/propertyeditor/paletteeditor.h create mode 100644 src/designer/src/components/propertyeditor/paletteeditor.ui create mode 100644 src/designer/src/components/propertyeditor/paletteeditorbutton.cpp create mode 100644 src/designer/src/components/propertyeditor/paletteeditorbutton.h create mode 100644 src/designer/src/components/propertyeditor/pixmapeditor.cpp create mode 100644 src/designer/src/components/propertyeditor/pixmapeditor.h create mode 100644 src/designer/src/components/propertyeditor/previewframe.cpp create mode 100644 src/designer/src/components/propertyeditor/previewframe.h create mode 100644 src/designer/src/components/propertyeditor/previewwidget.cpp create mode 100644 src/designer/src/components/propertyeditor/previewwidget.h create mode 100644 src/designer/src/components/propertyeditor/previewwidget.ui create mode 100644 src/designer/src/components/propertyeditor/propertyeditor.cpp create mode 100644 src/designer/src/components/propertyeditor/propertyeditor.h create mode 100644 src/designer/src/components/propertyeditor/propertyeditor_global.h create mode 100644 src/designer/src/components/propertyeditor/qlonglongvalidator.cpp create mode 100644 src/designer/src/components/propertyeditor/qlonglongvalidator.h create mode 100644 src/designer/src/components/propertyeditor/resetdecorator.cpp create mode 100644 src/designer/src/components/propertyeditor/resetdecorator.h create mode 100644 src/designer/src/components/propertyeditor/stringlisteditor.cpp create mode 100644 src/designer/src/components/propertyeditor/stringlisteditor.h create mode 100644 src/designer/src/components/propertyeditor/stringlisteditor.ui create mode 100644 src/designer/src/components/propertyeditor/stringlisteditorbutton.cpp create mode 100644 src/designer/src/components/propertyeditor/stringlisteditorbutton.h create mode 100644 src/designer/src/components/propertyeditor/texteditor.cpp create mode 100644 src/designer/src/components/propertyeditor/texteditor.h create mode 100644 src/designer/src/components/signalsloteditor/connectdialog.cpp create mode 100644 src/designer/src/components/signalsloteditor/connectdialog.ui create mode 100644 src/designer/src/components/signalsloteditor/connectdialog_p.h create mode 100644 src/designer/src/components/signalsloteditor/signalslot_utils.cpp create mode 100644 src/designer/src/components/signalsloteditor/signalslot_utils_p.h create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor.cpp create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor.h create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor.json create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor_global.h create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor_p.h create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor_plugin.cpp create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor_plugin.h create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor_tool.cpp create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditor_tool.h create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditorwindow.cpp create mode 100644 src/designer/src/components/signalsloteditor/signalsloteditorwindow.h create mode 100644 src/designer/src/components/tabordereditor/tabordereditor.cpp create mode 100644 src/designer/src/components/tabordereditor/tabordereditor.h create mode 100644 src/designer/src/components/tabordereditor/tabordereditor.json create mode 100644 src/designer/src/components/tabordereditor/tabordereditor_global.h create mode 100644 src/designer/src/components/tabordereditor/tabordereditor_plugin.cpp create mode 100644 src/designer/src/components/tabordereditor/tabordereditor_plugin.h create mode 100644 src/designer/src/components/tabordereditor/tabordereditor_tool.cpp create mode 100644 src/designer/src/components/tabordereditor/tabordereditor_tool.h create mode 100644 src/designer/src/components/taskmenu/button_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/button_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/combobox_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/combobox_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/containerwidget_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/containerwidget_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/groupbox_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/groupbox_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/inplace_editor.cpp create mode 100644 src/designer/src/components/taskmenu/inplace_editor.h create mode 100644 src/designer/src/components/taskmenu/inplace_widget_helper.cpp create mode 100644 src/designer/src/components/taskmenu/inplace_widget_helper.h create mode 100644 src/designer/src/components/taskmenu/itemlisteditor.cpp create mode 100644 src/designer/src/components/taskmenu/itemlisteditor.h create mode 100644 src/designer/src/components/taskmenu/itemlisteditor.ui create mode 100644 src/designer/src/components/taskmenu/label_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/label_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/layouttaskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/layouttaskmenu.h create mode 100644 src/designer/src/components/taskmenu/lineedit_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/lineedit_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/listwidget_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/listwidget_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/listwidgeteditor.cpp create mode 100644 src/designer/src/components/taskmenu/listwidgeteditor.h create mode 100644 src/designer/src/components/taskmenu/menutaskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/menutaskmenu.h create mode 100644 src/designer/src/components/taskmenu/tablewidget_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/tablewidget_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/tablewidgeteditor.cpp create mode 100644 src/designer/src/components/taskmenu/tablewidgeteditor.h create mode 100644 src/designer/src/components/taskmenu/tablewidgeteditor.ui create mode 100644 src/designer/src/components/taskmenu/taskmenu_component.cpp create mode 100644 src/designer/src/components/taskmenu/taskmenu_component.h create mode 100644 src/designer/src/components/taskmenu/taskmenu_global.h create mode 100644 src/designer/src/components/taskmenu/textedit_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/textedit_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/toolbar_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/toolbar_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/treewidget_taskmenu.cpp create mode 100644 src/designer/src/components/taskmenu/treewidget_taskmenu.h create mode 100644 src/designer/src/components/taskmenu/treewidgeteditor.cpp create mode 100644 src/designer/src/components/taskmenu/treewidgeteditor.h create mode 100644 src/designer/src/components/taskmenu/treewidgeteditor.ui create mode 100644 src/designer/src/components/widgetbox/widgetbox.cpp create mode 100644 src/designer/src/components/widgetbox/widgetbox.h create mode 100644 src/designer/src/components/widgetbox/widgetbox.xml create mode 100644 src/designer/src/components/widgetbox/widgetbox_dnditem.cpp create mode 100644 src/designer/src/components/widgetbox/widgetbox_dnditem.h create mode 100644 src/designer/src/components/widgetbox/widgetbox_global.h create mode 100644 src/designer/src/components/widgetbox/widgetboxcategorylistview.cpp create mode 100644 src/designer/src/components/widgetbox/widgetboxcategorylistview.h create mode 100644 src/designer/src/components/widgetbox/widgetboxtreewidget.cpp create mode 100644 src/designer/src/components/widgetbox/widgetboxtreewidget.h create mode 100644 src/designer/src/designer/CMakeLists.txt create mode 100644 src/designer/src/designer/Designer.entitlements create mode 100644 src/designer/src/designer/Info_mac.plist create mode 100644 src/designer/src/designer/appfontdialog.cpp create mode 100644 src/designer/src/designer/appfontdialog.h create mode 100644 src/designer/src/designer/assistantclient.cpp create mode 100644 src/designer/src/designer/assistantclient.h create mode 100644 src/designer/src/designer/designer.desktop create mode 100644 src/designer/src/designer/designer.icns create mode 100644 src/designer/src/designer/designer.ico create mode 100644 src/designer/src/designer/designer.metainfo.xml create mode 100644 src/designer/src/designer/designer_enums.h create mode 100644 src/designer/src/designer/doc/images/addressbook-tutorial-part3-labeled-layout.png create mode 100644 src/designer/src/designer/doc/images/designer-action-editor.png create mode 100644 src/designer/src/designer/doc/images/designer-add-custom-toolbar.png create mode 100644 src/designer/src/designer/doc/images/designer-add-files-button.png create mode 100644 src/designer/src/designer/doc/images/designer-add-resource-entry-button.png create mode 100644 src/designer/src/designer/doc/images/designer-adding-dockwidget.png create mode 100644 src/designer/src/designer/doc/images/designer-adding-dynamic-property.png create mode 100644 src/designer/src/designer/doc/images/designer-adding-menu-action.png create mode 100644 src/designer/src/designer/doc/images/designer-adding-toolbar-action.png create mode 100644 src/designer/src/designer/doc/images/designer-buddy-making.png create mode 100644 src/designer/src/designer/doc/images/designer-buddy-mode.png create mode 100644 src/designer/src/designer/doc/images/designer-buddy-tool.png create mode 100644 src/designer/src/designer/doc/images/designer-choosing-form.png create mode 100644 src/designer/src/designer/doc/images/designer-code-viewer.png create mode 100644 src/designer/src/designer/doc/images/designer-connection-dialog.png create mode 100644 src/designer/src/designer/doc/images/designer-connection-editing.png create mode 100644 src/designer/src/designer/doc/images/designer-connection-editor.png create mode 100644 src/designer/src/designer/doc/images/designer-connection-highlight.png create mode 100644 src/designer/src/designer/doc/images/designer-connection-making.png create mode 100644 src/designer/src/designer/doc/images/designer-connection-mode.png create mode 100644 src/designer/src/designer/doc/images/designer-connection-to-form.png create mode 100644 src/designer/src/designer/doc/images/designer-connection-tool.png create mode 100644 src/designer/src/designer/doc/images/designer-containers-dockwidget.png create mode 100644 src/designer/src/designer/doc/images/designer-containers-frame.png create mode 100644 src/designer/src/designer/doc/images/designer-containers-groupbox.png create mode 100644 src/designer/src/designer/doc/images/designer-containers-stackedwidget.png create mode 100644 src/designer/src/designer/doc/images/designer-containers-tabwidget.png create mode 100644 src/designer/src/designer/doc/images/designer-containers-toolbox.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-dynamic-property.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu-entry1.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu-entry2.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu-entry3.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu-entry4.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu1.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu2.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu3.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menu4.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-menubar.png create mode 100644 src/designer/src/designer/doc/images/designer-creating-toolbar.png create mode 100644 src/designer/src/designer/doc/images/designer-custom-widget-box.png create mode 100644 src/designer/src/designer/doc/images/designer-customize-toolbar.png create mode 100644 src/designer/src/designer/doc/images/designer-dialog-final.png create mode 100644 src/designer/src/designer/doc/images/designer-dialog-initial.png create mode 100644 src/designer/src/designer/doc/images/designer-dialog-layout.png create mode 100644 src/designer/src/designer/doc/images/designer-dialog-preview.png create mode 100644 src/designer/src/designer/doc/images/designer-disambiguation.png create mode 100644 src/designer/src/designer/doc/images/designer-dragging-onto-form.png create mode 100644 src/designer/src/designer/doc/images/designer-edit-resource.png create mode 100644 src/designer/src/designer/doc/images/designer-edit-resources-button.png create mode 100644 src/designer/src/designer/doc/images/designer-editing-mode.png create mode 100644 src/designer/src/designer/doc/images/designer-embedded-preview.png create mode 100644 src/designer/src/designer/doc/images/designer-english-dialog.png create mode 100644 src/designer/src/designer/doc/images/designer-examples.png create mode 100644 src/designer/src/designer/doc/images/designer-file-menu.png create mode 100644 src/designer/src/designer/doc/images/designer-find-icon.png create mode 100644 src/designer/src/designer/doc/images/designer-form-layout-cleanlooks.png create mode 100644 src/designer/src/designer/doc/images/designer-form-layout-macintosh.png create mode 100644 src/designer/src/designer/doc/images/designer-form-layout-windowsXP.png create mode 100644 src/designer/src/designer/doc/images/designer-form-layout.png create mode 100644 src/designer/src/designer/doc/images/designer-form-layoutfunction.png create mode 100644 src/designer/src/designer/doc/images/designer-form-settings.png create mode 100644 src/designer/src/designer/doc/images/designer-form-viewcode.png create mode 100644 src/designer/src/designer/doc/images/designer-french-dialog.png create mode 100644 src/designer/src/designer/doc/images/designer-getting-started.png create mode 100644 src/designer/src/designer/doc/images/designer-layout-inserting.png create mode 100644 src/designer/src/designer/doc/images/designer-main-window.png create mode 100644 src/designer/src/designer/doc/images/designer-making-connection.png create mode 100644 src/designer/src/designer/doc/images/designer-manual-containerextension.png create mode 100644 src/designer/src/designer/doc/images/designer-manual-membersheetextension.png create mode 100644 src/designer/src/designer/doc/images/designer-manual-propertysheetextension.png create mode 100644 src/designer/src/designer/doc/images/designer-manual-taskmenuextension.png create mode 100644 src/designer/src/designer/doc/images/designer-multiple-screenshot.png create mode 100644 src/designer/src/designer/doc/images/designer-object-inspector.png create mode 100644 src/designer/src/designer/doc/images/designer-palette-brush-editor.png create mode 100644 src/designer/src/designer/doc/images/designer-palette-editor.png create mode 100644 src/designer/src/designer/doc/images/designer-palette-gradient-editor.png create mode 100644 src/designer/src/designer/doc/images/designer-palette-pattern-editor.png create mode 100644 src/designer/src/designer/doc/images/designer-preview-device-skin.png create mode 100644 src/designer/src/designer/doc/images/designer-preview-deviceskin-selection.png create mode 100644 src/designer/src/designer/doc/images/designer-preview-style-selection.png create mode 100644 src/designer/src/designer/doc/images/designer-preview-style.png create mode 100644 src/designer/src/designer/doc/images/designer-preview-stylesheet.png create mode 100644 src/designer/src/designer/doc/images/designer-promoting-widgets.png create mode 100644 src/designer/src/designer/doc/images/designer-property-editor-add-dynamic.png create mode 100644 src/designer/src/designer/doc/images/designer-property-editor-configure.png create mode 100644 src/designer/src/designer/doc/images/designer-property-editor-link.png create mode 100644 src/designer/src/designer/doc/images/designer-property-editor-remove-dynamic.png create mode 100644 src/designer/src/designer/doc/images/designer-property-editor-toolbar.png create mode 100644 src/designer/src/designer/doc/images/designer-property-editor.png create mode 100644 src/designer/src/designer/doc/images/designer-reload-resources-button.png create mode 100644 src/designer/src/designer/doc/images/designer-remove-custom-toolbar.png create mode 100644 src/designer/src/designer/doc/images/designer-remove-resource-entry-button.png create mode 100644 src/designer/src/designer/doc/images/designer-removing-toolbar-action.png create mode 100644 src/designer/src/designer/doc/images/designer-removing-toolbar.png create mode 100644 src/designer/src/designer/doc/images/designer-resource-browser.png create mode 100644 src/designer/src/designer/doc/images/designer-resource-selector.png create mode 100644 src/designer/src/designer/doc/images/designer-resource-tool.png create mode 100644 src/designer/src/designer/doc/images/designer-resources-adding.png create mode 100644 src/designer/src/designer/doc/images/designer-resources-editing.png create mode 100644 src/designer/src/designer/doc/images/designer-resources-empty.png create mode 100644 src/designer/src/designer/doc/images/designer-resources-using.png create mode 100644 src/designer/src/designer/doc/images/designer-screenshot.png create mode 100644 src/designer/src/designer/doc/images/designer-selecting-widget.png create mode 100644 src/designer/src/designer/doc/images/designer-selecting-widgets.png create mode 100644 src/designer/src/designer/doc/images/designer-set-layout.png create mode 100644 src/designer/src/designer/doc/images/designer-set-layout2.png create mode 100644 src/designer/src/designer/doc/images/designer-splitter-layout.png create mode 100644 src/designer/src/designer/doc/images/designer-stylesheet-options.png create mode 100644 src/designer/src/designer/doc/images/designer-stylesheet-usage.png create mode 100644 src/designer/src/designer/doc/images/designer-tab-order-mode.png create mode 100644 src/designer/src/designer/doc/images/designer-tab-order-tool.png create mode 100644 src/designer/src/designer/doc/images/designer-widget-box.png create mode 100644 src/designer/src/designer/doc/images/designer-widget-filter.png create mode 100644 src/designer/src/designer/doc/images/designer-widget-final.png create mode 100644 src/designer/src/designer/doc/images/designer-widget-initial.png create mode 100644 src/designer/src/designer/doc/images/designer-widget-layout.png create mode 100644 src/designer/src/designer/doc/images/designer-widget-morph.png create mode 100644 src/designer/src/designer/doc/images/designer-widget-preview.png create mode 100644 src/designer/src/designer/doc/images/designer-widget-tool.png create mode 100644 src/designer/src/designer/doc/images/directapproach-calculatorform.png create mode 100644 src/designer/src/designer/doc/images/qtdesignerextensions.png create mode 100644 src/designer/src/designer/doc/images/qtdesignerscreenshot.png create mode 100644 src/designer/src/designer/doc/images/rgbController-arrangement.png create mode 100644 src/designer/src/designer/doc/images/rgbController-configure-connection1.png create mode 100644 src/designer/src/designer/doc/images/rgbController-configure-connection2.png create mode 100644 src/designer/src/designer/doc/images/rgbController-final-layout.png create mode 100644 src/designer/src/designer/doc/images/rgbController-form-gridLayout.png create mode 100644 src/designer/src/designer/doc/images/rgbController-no-toplevel-layout.png create mode 100644 src/designer/src/designer/doc/images/rgbController-property-editing.png create mode 100644 src/designer/src/designer/doc/images/rgbController-screenshot.png create mode 100644 src/designer/src/designer/doc/images/rgbController-selectForLayout.png create mode 100644 src/designer/src/designer/doc/images/rgbController-signalsAndSlots.png create mode 100644 src/designer/src/designer/doc/images/worldtimeclockplugin-example.png create mode 100644 src/designer/src/designer/doc/qtdesigner.qdocconf create mode 100644 src/designer/src/designer/doc/snippets/CMakeLists.txt create mode 100644 src/designer/src/designer/doc/snippets/autoconnection/CMakeLists.txt create mode 100644 src/designer/src/designer/doc/snippets/autoconnection/autoconnection.pro create mode 100644 src/designer/src/designer/doc/snippets/autoconnection/imagedialog.cpp create mode 100644 src/designer/src/designer/doc/snippets/autoconnection/imagedialog.h create mode 100644 src/designer/src/designer/doc/snippets/autoconnection/imagedialog.ui create mode 100644 src/designer/src/designer/doc/snippets/autoconnection/main.cpp create mode 100644 src/designer/src/designer/doc/snippets/designer.pro create mode 100644 src/designer/src/designer/doc/snippets/imagedialog/CMakeLists.txt create mode 100644 src/designer/src/designer/doc/snippets/imagedialog/imagedialog.pro create mode 100644 src/designer/src/designer/doc/snippets/imagedialog/imagedialog.ui create mode 100644 src/designer/src/designer/doc/snippets/imagedialog/main.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_default_extensionfactory.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_extension.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_qextensionmanager.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindow.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp create mode 100644 src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_uilib_formbuilder.cpp create mode 100644 src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.cpp create mode 100644 src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.js create mode 100644 src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.pro create mode 100644 src/designer/src/designer/doc/snippets/multipleinheritance/CMakeLists.txt create mode 100644 src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.cpp create mode 100644 src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.h create mode 100644 src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.ui create mode 100644 src/designer/src/designer/doc/snippets/multipleinheritance/main.cpp create mode 100644 src/designer/src/designer/doc/snippets/multipleinheritance/multipleinheritance.pro create mode 100644 src/designer/src/designer/doc/snippets/noautoconnection/CMakeLists.txt create mode 100644 src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.cpp create mode 100644 src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.h create mode 100644 src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.ui create mode 100644 src/designer/src/designer/doc/snippets/noautoconnection/main.cpp create mode 100644 src/designer/src/designer/doc/snippets/noautoconnection/noautoconnection.pro create mode 100644 src/designer/src/designer/doc/snippets/plugins/doc_src_qtdesigner.cpp create mode 100644 src/designer/src/designer/doc/snippets/singleinheritance/CMakeLists.txt create mode 100644 src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.cpp create mode 100644 src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.h create mode 100644 src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.ui create mode 100644 src/designer/src/designer/doc/snippets/singleinheritance/main.cpp create mode 100644 src/designer/src/designer/doc/snippets/singleinheritance/singleinheritance.pro create mode 100644 src/designer/src/designer/doc/snippets/uitools/calculatorform/CMakeLists.txt create mode 100644 src/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.pro create mode 100644 src/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.ui create mode 100644 src/designer/src/designer/doc/snippets/uitools/calculatorform/main.cpp create mode 100644 src/designer/src/designer/doc/src/designer-custom-widgets.qdoc create mode 100644 src/designer/src/designer/doc/src/designer-examples.qdoc create mode 100644 src/designer/src/designer/doc/src/designer-manual.qdoc create mode 100644 src/designer/src/designer/doc/src/qtdesigner-index.qdoc create mode 100644 src/designer/src/designer/doc/src/qtdesigner-module.qdoc create mode 100644 src/designer/src/designer/doc/src/qtdesigner-toc.qdoc create mode 100644 src/designer/src/designer/images/designer.png create mode 100644 src/designer/src/designer/main.cpp create mode 100644 src/designer/src/designer/mainwindow.cpp create mode 100644 src/designer/src/designer/mainwindow.h create mode 100644 src/designer/src/designer/newform.cpp create mode 100644 src/designer/src/designer/newform.h create mode 100644 src/designer/src/designer/preferencesdialog.cpp create mode 100644 src/designer/src/designer/preferencesdialog.h create mode 100644 src/designer/src/designer/preferencesdialog.ui create mode 100644 src/designer/src/designer/qdesigner.cpp create mode 100644 src/designer/src/designer/qdesigner.h create mode 100644 src/designer/src/designer/qdesigner_actions.cpp create mode 100644 src/designer/src/designer/qdesigner_actions.h create mode 100644 src/designer/src/designer/qdesigner_appearanceoptions.cpp create mode 100644 src/designer/src/designer/qdesigner_appearanceoptions.h create mode 100644 src/designer/src/designer/qdesigner_appearanceoptions.ui create mode 100644 src/designer/src/designer/qdesigner_formwindow.cpp create mode 100644 src/designer/src/designer/qdesigner_formwindow.h create mode 100644 src/designer/src/designer/qdesigner_pch.h create mode 100644 src/designer/src/designer/qdesigner_server.cpp create mode 100644 src/designer/src/designer/qdesigner_server.h create mode 100644 src/designer/src/designer/qdesigner_settings.cpp create mode 100644 src/designer/src/designer/qdesigner_settings.h create mode 100644 src/designer/src/designer/qdesigner_toolwindow.cpp create mode 100644 src/designer/src/designer/qdesigner_toolwindow.h create mode 100644 src/designer/src/designer/qdesigner_workbench.cpp create mode 100644 src/designer/src/designer/qdesigner_workbench.h create mode 100644 src/designer/src/designer/saveformastemplate.cpp create mode 100644 src/designer/src/designer/saveformastemplate.h create mode 100644 src/designer/src/designer/saveformastemplate.ui create mode 100644 src/designer/src/designer/uifile.icns create mode 100644 src/designer/src/designer/versiondialog.cpp create mode 100644 src/designer/src/designer/versiondialog.h create mode 100644 src/designer/src/lib/CMakeLists.txt create mode 100644 src/designer/src/lib/components/qdesigner_components.h create mode 100644 src/designer/src/lib/components/qdesigner_components_global.h create mode 100644 src/designer/src/lib/extension/default_extensionfactory.cpp create mode 100644 src/designer/src/lib/extension/default_extensionfactory.h create mode 100644 src/designer/src/lib/extension/extension.cpp create mode 100644 src/designer/src/lib/extension/extension.h create mode 100644 src/designer/src/lib/extension/extension_global.h create mode 100644 src/designer/src/lib/extension/qextensionmanager.cpp create mode 100644 src/designer/src/lib/extension/qextensionmanager.h create mode 100644 src/designer/src/lib/lib_pch.h create mode 100644 src/designer/src/lib/sdk/abstractactioneditor.cpp create mode 100644 src/designer/src/lib/sdk/abstractactioneditor.h create mode 100644 src/designer/src/lib/sdk/abstractdialoggui.cpp create mode 100644 src/designer/src/lib/sdk/abstractdialoggui_p.h create mode 100644 src/designer/src/lib/sdk/abstractdnditem.h create mode 100644 src/designer/src/lib/sdk/abstractdnditem.qdoc create mode 100644 src/designer/src/lib/sdk/abstractformeditor.cpp create mode 100644 src/designer/src/lib/sdk/abstractformeditor.h create mode 100644 src/designer/src/lib/sdk/abstractformeditorplugin.cpp create mode 100644 src/designer/src/lib/sdk/abstractformeditorplugin.h create mode 100644 src/designer/src/lib/sdk/abstractformwindow.cpp create mode 100644 src/designer/src/lib/sdk/abstractformwindow.h create mode 100644 src/designer/src/lib/sdk/abstractformwindowcursor.cpp create mode 100644 src/designer/src/lib/sdk/abstractformwindowcursor.h create mode 100644 src/designer/src/lib/sdk/abstractformwindowmanager.cpp create mode 100644 src/designer/src/lib/sdk/abstractformwindowmanager.h create mode 100644 src/designer/src/lib/sdk/abstractformwindowtool.cpp create mode 100644 src/designer/src/lib/sdk/abstractformwindowtool.h create mode 100644 src/designer/src/lib/sdk/abstractintegration.cpp create mode 100644 src/designer/src/lib/sdk/abstractintegration.h create mode 100644 src/designer/src/lib/sdk/abstractintrospection.cpp create mode 100644 src/designer/src/lib/sdk/abstractintrospection_p.h create mode 100644 src/designer/src/lib/sdk/abstractlanguage.h create mode 100644 src/designer/src/lib/sdk/abstractmetadatabase.cpp create mode 100644 src/designer/src/lib/sdk/abstractmetadatabase.h create mode 100644 src/designer/src/lib/sdk/abstractnewformwidget.cpp create mode 100644 src/designer/src/lib/sdk/abstractnewformwidget.h create mode 100644 src/designer/src/lib/sdk/abstractobjectinspector.cpp create mode 100644 src/designer/src/lib/sdk/abstractobjectinspector.h create mode 100644 src/designer/src/lib/sdk/abstractoptionspage.h create mode 100644 src/designer/src/lib/sdk/abstractoptionspage.qdoc create mode 100644 src/designer/src/lib/sdk/abstractpromotioninterface.cpp create mode 100644 src/designer/src/lib/sdk/abstractpromotioninterface.h create mode 100644 src/designer/src/lib/sdk/abstractpropertyeditor.cpp create mode 100644 src/designer/src/lib/sdk/abstractpropertyeditor.h create mode 100644 src/designer/src/lib/sdk/abstractresourcebrowser.cpp create mode 100644 src/designer/src/lib/sdk/abstractresourcebrowser.h create mode 100644 src/designer/src/lib/sdk/abstractsettings.h create mode 100644 src/designer/src/lib/sdk/abstractsettings.qdoc create mode 100644 src/designer/src/lib/sdk/abstractwidgetbox.cpp create mode 100644 src/designer/src/lib/sdk/abstractwidgetbox.h create mode 100644 src/designer/src/lib/sdk/abstractwidgetdatabase.cpp create mode 100644 src/designer/src/lib/sdk/abstractwidgetdatabase.h create mode 100644 src/designer/src/lib/sdk/abstractwidgetfactory.cpp create mode 100644 src/designer/src/lib/sdk/abstractwidgetfactory.h create mode 100644 src/designer/src/lib/sdk/container.h create mode 100644 src/designer/src/lib/sdk/container.qdoc create mode 100644 src/designer/src/lib/sdk/dynamicpropertysheet.h create mode 100644 src/designer/src/lib/sdk/dynamicpropertysheet.qdoc create mode 100644 src/designer/src/lib/sdk/extrainfo.cpp create mode 100644 src/designer/src/lib/sdk/extrainfo.h create mode 100644 src/designer/src/lib/sdk/layoutdecoration.h create mode 100644 src/designer/src/lib/sdk/layoutdecoration.qdoc create mode 100644 src/designer/src/lib/sdk/membersheet.h create mode 100644 src/designer/src/lib/sdk/membersheet.qdoc create mode 100644 src/designer/src/lib/sdk/propertysheet.h create mode 100644 src/designer/src/lib/sdk/propertysheet.qdoc create mode 100644 src/designer/src/lib/sdk/sdk_global.h create mode 100644 src/designer/src/lib/sdk/taskmenu.cpp create mode 100644 src/designer/src/lib/sdk/taskmenu.h create mode 100644 src/designer/src/lib/sdk/taskmenu.qdoc create mode 100644 src/designer/src/lib/shared/actioneditor.cpp create mode 100644 src/designer/src/lib/shared/actioneditor_p.h create mode 100644 src/designer/src/lib/shared/actionprovider_p.h create mode 100644 src/designer/src/lib/shared/actionrepository.cpp create mode 100644 src/designer/src/lib/shared/actionrepository_p.h create mode 100644 src/designer/src/lib/shared/addlinkdialog.ui create mode 100644 src/designer/src/lib/shared/codedialog.cpp create mode 100644 src/designer/src/lib/shared/codedialog_p.h create mode 100644 src/designer/src/lib/shared/connectionedit.cpp create mode 100644 src/designer/src/lib/shared/connectionedit_p.h create mode 100644 src/designer/src/lib/shared/csshighlighter.cpp create mode 100644 src/designer/src/lib/shared/csshighlighter_p.h create mode 100644 src/designer/src/lib/shared/defaultgradients.xml create mode 100644 src/designer/src/lib/shared/deviceprofile.cpp create mode 100644 src/designer/src/lib/shared/deviceprofile_p.h create mode 100644 src/designer/src/lib/shared/dialoggui.cpp create mode 100644 src/designer/src/lib/shared/dialoggui_p.h create mode 100644 src/designer/src/lib/shared/extensionfactory_p.h create mode 100644 src/designer/src/lib/shared/formlayoutmenu.cpp create mode 100644 src/designer/src/lib/shared/formlayoutmenu_p.h create mode 100644 src/designer/src/lib/shared/formlayoutrowdialog.ui create mode 100644 src/designer/src/lib/shared/formwindowbase.cpp create mode 100644 src/designer/src/lib/shared/formwindowbase_p.h create mode 100644 src/designer/src/lib/shared/grid.cpp create mode 100644 src/designer/src/lib/shared/grid_p.h create mode 100644 src/designer/src/lib/shared/gridpanel.cpp create mode 100644 src/designer/src/lib/shared/gridpanel.ui create mode 100644 src/designer/src/lib/shared/gridpanel_p.h create mode 100644 src/designer/src/lib/shared/htmlhighlighter.cpp create mode 100644 src/designer/src/lib/shared/htmlhighlighter_p.h create mode 100644 src/designer/src/lib/shared/icon-naming-spec.txt create mode 100644 src/designer/src/lib/shared/iconloader.cpp create mode 100644 src/designer/src/lib/shared/iconloader_p.h create mode 100644 src/designer/src/lib/shared/iconselector.cpp create mode 100644 src/designer/src/lib/shared/iconselector_p.h create mode 100644 src/designer/src/lib/shared/invisible_widget.cpp create mode 100644 src/designer/src/lib/shared/invisible_widget_p.h create mode 100644 src/designer/src/lib/shared/layout.cpp create mode 100644 src/designer/src/lib/shared/layout_p.h create mode 100644 src/designer/src/lib/shared/layoutinfo.cpp create mode 100644 src/designer/src/lib/shared/layoutinfo_p.h create mode 100644 src/designer/src/lib/shared/metadatabase.cpp create mode 100644 src/designer/src/lib/shared/metadatabase_p.h create mode 100644 src/designer/src/lib/shared/morphmenu.cpp create mode 100644 src/designer/src/lib/shared/morphmenu_p.h create mode 100644 src/designer/src/lib/shared/newactiondialog.cpp create mode 100644 src/designer/src/lib/shared/newactiondialog.ui create mode 100644 src/designer/src/lib/shared/newactiondialog_p.h create mode 100644 src/designer/src/lib/shared/newformwidget.cpp create mode 100644 src/designer/src/lib/shared/newformwidget.ui create mode 100644 src/designer/src/lib/shared/newformwidget_p.h create mode 100644 src/designer/src/lib/shared/orderdialog.cpp create mode 100644 src/designer/src/lib/shared/orderdialog.ui create mode 100644 src/designer/src/lib/shared/orderdialog_p.h create mode 100644 src/designer/src/lib/shared/plaintexteditor.cpp create mode 100644 src/designer/src/lib/shared/plaintexteditor_p.h create mode 100644 src/designer/src/lib/shared/plugindialog.cpp create mode 100644 src/designer/src/lib/shared/plugindialog.ui create mode 100644 src/designer/src/lib/shared/plugindialog_p.h create mode 100644 src/designer/src/lib/shared/pluginmanager.cpp create mode 100644 src/designer/src/lib/shared/pluginmanager_p.h create mode 100644 src/designer/src/lib/shared/previewconfigurationwidget.cpp create mode 100644 src/designer/src/lib/shared/previewconfigurationwidget.ui create mode 100644 src/designer/src/lib/shared/previewconfigurationwidget_p.h create mode 100644 src/designer/src/lib/shared/previewmanager.cpp create mode 100644 src/designer/src/lib/shared/previewmanager_p.h create mode 100644 src/designer/src/lib/shared/promotionmodel.cpp create mode 100644 src/designer/src/lib/shared/promotionmodel_p.h create mode 100644 src/designer/src/lib/shared/promotiontaskmenu.cpp create mode 100644 src/designer/src/lib/shared/promotiontaskmenu_p.h create mode 100644 src/designer/src/lib/shared/propertylineedit.cpp create mode 100644 src/designer/src/lib/shared/propertylineedit_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_command.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_command2.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_command2_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_command_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_dnditem.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_dnditem_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_dockwidget.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_dockwidget_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_formbuilder.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_formbuilder_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_formeditorcommand.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_formeditorcommand_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_formwindowcommand.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_formwindowcommand_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_formwindowmanager.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_formwindowmanager_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_introspection.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_introspection_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_membersheet.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_membersheet_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_menu.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_menu_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_menubar.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_menubar_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_objectinspector.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_objectinspector_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_promotion.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_promotion_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_promotiondialog.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_promotiondialog_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_propertycommand.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_propertycommand_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_propertyeditor.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_propertyeditor_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_propertysheet.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_propertysheet_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_qsettings.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_qsettings_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_stackedbox.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_stackedbox_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_tabwidget.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_tabwidget_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_taskmenu.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_taskmenu_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_toolbar.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_toolbar_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_toolbox.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_toolbox_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_utils.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_utils_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_widget.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_widget_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_widgetbox.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_widgetbox_p.h create mode 100644 src/designer/src/lib/shared/qdesigner_widgetitem.cpp create mode 100644 src/designer/src/lib/shared/qdesigner_widgetitem_p.h create mode 100644 src/designer/src/lib/shared/qlayout_widget.cpp create mode 100644 src/designer/src/lib/shared/qlayout_widget_p.h create mode 100644 src/designer/src/lib/shared/qsimpleresource.cpp create mode 100644 src/designer/src/lib/shared/qsimpleresource_p.h create mode 100644 src/designer/src/lib/shared/qtresourceeditordialog.cpp create mode 100644 src/designer/src/lib/shared/qtresourceeditordialog.ui create mode 100644 src/designer/src/lib/shared/qtresourceeditordialog_p.h create mode 100644 src/designer/src/lib/shared/qtresourcemodel.cpp create mode 100644 src/designer/src/lib/shared/qtresourcemodel_p.h create mode 100644 src/designer/src/lib/shared/qtresourceview.cpp create mode 100644 src/designer/src/lib/shared/qtresourceview_p.h create mode 100644 src/designer/src/lib/shared/rcc.cpp create mode 100644 src/designer/src/lib/shared/rcc_p.h create mode 100644 src/designer/src/lib/shared/richtexteditor.cpp create mode 100644 src/designer/src/lib/shared/richtexteditor_p.h create mode 100644 src/designer/src/lib/shared/selectsignaldialog.cpp create mode 100644 src/designer/src/lib/shared/selectsignaldialog.ui create mode 100644 src/designer/src/lib/shared/selectsignaldialog_p.h create mode 100644 src/designer/src/lib/shared/shared_enums_p.h create mode 100644 src/designer/src/lib/shared/shared_global_p.h create mode 100644 src/designer/src/lib/shared/shared_settings.cpp create mode 100644 src/designer/src/lib/shared/shared_settings_p.h create mode 100644 src/designer/src/lib/shared/sheet_delegate.cpp create mode 100644 src/designer/src/lib/shared/sheet_delegate_p.h create mode 100644 src/designer/src/lib/shared/signalslotdialog.cpp create mode 100644 src/designer/src/lib/shared/signalslotdialog.ui create mode 100644 src/designer/src/lib/shared/signalslotdialog_p.h create mode 100644 src/designer/src/lib/shared/spacer_widget.cpp create mode 100644 src/designer/src/lib/shared/spacer_widget_p.h create mode 100644 src/designer/src/lib/shared/stylesheeteditor.cpp create mode 100644 src/designer/src/lib/shared/stylesheeteditor_p.h create mode 100644 src/designer/src/lib/shared/templates/forms/240x320/Dialog_with_Buttons_Bottom.ui create mode 100644 src/designer/src/lib/shared/templates/forms/240x320/Dialog_with_Buttons_Right.ui create mode 100644 src/designer/src/lib/shared/templates/forms/320x240/Dialog_with_Buttons_Bottom.ui create mode 100644 src/designer/src/lib/shared/templates/forms/320x240/Dialog_with_Buttons_Right.ui create mode 100644 src/designer/src/lib/shared/templates/forms/480x640/Dialog_with_Buttons_Bottom.ui create mode 100644 src/designer/src/lib/shared/templates/forms/480x640/Dialog_with_Buttons_Right.ui create mode 100644 src/designer/src/lib/shared/templates/forms/640x480/Dialog_with_Buttons_Bottom.ui create mode 100644 src/designer/src/lib/shared/templates/forms/640x480/Dialog_with_Buttons_Right.ui create mode 100644 src/designer/src/lib/shared/templates/forms/Dialog_with_Buttons_Bottom.ui create mode 100644 src/designer/src/lib/shared/templates/forms/Dialog_with_Buttons_Right.ui create mode 100644 src/designer/src/lib/shared/templates/forms/Dialog_without_Buttons.ui create mode 100644 src/designer/src/lib/shared/templates/forms/Main_Window.ui create mode 100644 src/designer/src/lib/shared/templates/forms/Widget.ui create mode 100644 src/designer/src/lib/shared/textpropertyeditor.cpp create mode 100644 src/designer/src/lib/shared/textpropertyeditor_p.h create mode 100644 src/designer/src/lib/shared/widgetdatabase.cpp create mode 100644 src/designer/src/lib/shared/widgetdatabase_p.h create mode 100644 src/designer/src/lib/shared/widgetfactory.cpp create mode 100644 src/designer/src/lib/shared/widgetfactory_p.h create mode 100644 src/designer/src/lib/shared/zoomwidget.cpp create mode 100644 src/designer/src/lib/shared/zoomwidget_p.h create mode 100644 src/designer/src/lib/uilib/abstractformbuilder.cpp create mode 100644 src/designer/src/lib/uilib/abstractformbuilder.h create mode 100644 src/designer/src/lib/uilib/formbuilder.cpp create mode 100644 src/designer/src/lib/uilib/formbuilder.h create mode 100644 src/designer/src/lib/uilib/formbuilderextra.cpp create mode 100644 src/designer/src/lib/uilib/formbuilderextra_p.h create mode 100644 src/designer/src/lib/uilib/properties.cpp create mode 100644 src/designer/src/lib/uilib/properties_p.h create mode 100644 src/designer/src/lib/uilib/resourcebuilder.cpp create mode 100644 src/designer/src/lib/uilib/resourcebuilder_p.h create mode 100644 src/designer/src/lib/uilib/textbuilder.cpp create mode 100644 src/designer/src/lib/uilib/textbuilder_p.h create mode 100644 src/designer/src/lib/uilib/ui4.cpp create mode 100644 src/designer/src/lib/uilib/ui4_p.h create mode 100644 src/designer/src/lib/uilib/uilib_global.h create mode 100644 src/designer/src/lib/uilib/widgets.table create mode 100644 src/designer/src/plugins/CMakeLists.txt create mode 100644 src/designer/src/plugins/activeqt/CMakeLists.txt create mode 100644 src/designer/src/plugins/activeqt/activeqt.json create mode 100644 src/designer/src/plugins/activeqt/qaxwidgetextrainfo.cpp create mode 100644 src/designer/src/plugins/activeqt/qaxwidgetextrainfo.h create mode 100644 src/designer/src/plugins/activeqt/qaxwidgetplugin.cpp create mode 100644 src/designer/src/plugins/activeqt/qaxwidgetplugin.h create mode 100644 src/designer/src/plugins/activeqt/qaxwidgetpropertysheet.cpp create mode 100644 src/designer/src/plugins/activeqt/qaxwidgetpropertysheet.h create mode 100644 src/designer/src/plugins/activeqt/qaxwidgettaskmenu.cpp create mode 100644 src/designer/src/plugins/activeqt/qaxwidgettaskmenu.h create mode 100644 src/designer/src/plugins/activeqt/qdesigneraxwidget.cpp create mode 100644 src/designer/src/plugins/activeqt/qdesigneraxwidget.h create mode 100644 src/designer/src/plugins/qquickwidget/CMakeLists.txt create mode 100644 src/designer/src/plugins/qquickwidget/images/qquickwidget.png create mode 100644 src/designer/src/plugins/qquickwidget/qquickwidget_plugin.cpp create mode 100644 src/designer/src/plugins/qquickwidget/qquickwidget_plugin.h create mode 100644 src/designer/src/plugins/qwebview/CMakeLists.txt create mode 100644 src/designer/src/plugins/qwebview/images/qwebview.png create mode 100644 src/designer/src/plugins/qwebview/qwebview.json create mode 100644 src/designer/src/plugins/qwebview/qwebview_plugin.cpp create mode 100644 src/designer/src/plugins/qwebview/qwebview_plugin.h create mode 100644 src/designer/src/plugins/tools/view3d/CMakeLists.txt create mode 100644 src/designer/src/plugins/tools/view3d/view3d.cpp create mode 100644 src/designer/src/plugins/tools/view3d/view3d.h create mode 100644 src/designer/src/plugins/tools/view3d/view3d_global.h create mode 100644 src/designer/src/plugins/tools/view3d/view3d_plugin.cpp create mode 100644 src/designer/src/plugins/tools/view3d/view3d_plugin.h create mode 100644 src/designer/src/plugins/tools/view3d/view3d_tool.cpp create mode 100644 src/designer/src/plugins/tools/view3d/view3d_tool.h create mode 100644 src/distancefieldgenerator/CMakeLists.txt create mode 100644 src/distancefieldgenerator/distancefieldmodel.cpp create mode 100644 src/distancefieldgenerator/distancefieldmodel.h create mode 100644 src/distancefieldgenerator/distancefieldmodelworker.cpp create mode 100644 src/distancefieldgenerator/distancefieldmodelworker.h create mode 100644 src/distancefieldgenerator/doc/images/distancefieldgenerator.png create mode 100644 src/distancefieldgenerator/doc/qtdistancefieldgenerator.qdocconf create mode 100644 src/distancefieldgenerator/doc/src/distancefieldgenerator-manual.qdoc create mode 100644 src/distancefieldgenerator/main.cpp create mode 100644 src/distancefieldgenerator/mainwindow.cpp create mode 100644 src/distancefieldgenerator/mainwindow.h create mode 100644 src/distancefieldgenerator/mainwindow.ui create mode 100644 src/global/CMakeLists.txt create mode 100644 src/kmap2qmap/CMakeLists.txt create mode 100644 src/kmap2qmap/main.cpp create mode 100644 src/linguist/CMakeLists.txt create mode 100644 src/linguist/GenerateLUpdateProject.cmake create mode 100644 src/linguist/Qt6LinguistToolsMacros.cmake create mode 100644 src/linguist/lconvert/CMakeLists.txt create mode 100644 src/linguist/lconvert/main.cpp create mode 100644 src/linguist/linguist/CMakeLists.txt create mode 100644 src/linguist/linguist/Info_mac.plist create mode 100644 src/linguist/linguist/batchtranslation.ui create mode 100644 src/linguist/linguist/batchtranslationdialog.cpp create mode 100644 src/linguist/linguist/batchtranslationdialog.h create mode 100644 src/linguist/linguist/doc/cmake-macros.qdoc create mode 100644 src/linguist/linguist/doc/cmake-properties.qdoc create mode 100644 src/linguist/linguist/doc/images/front-coding.png create mode 100644 src/linguist/linguist/doc/images/front-publishing.png create mode 100644 src/linguist/linguist/doc/images/front-ui.png create mode 100644 src/linguist/linguist/doc/images/linguist-batchtranslation.png create mode 100644 src/linguist/linguist/doc/images/linguist-check-empty.png create mode 100644 src/linguist/linguist/doc/images/linguist-check-obsolete.png create mode 100644 src/linguist/linguist/doc/images/linguist-check-off.png create mode 100644 src/linguist/linguist/doc/images/linguist-check-on.png create mode 100644 src/linguist/linguist/doc/images/linguist-check-warning.png create mode 100644 src/linguist/linguist/doc/images/linguist-context-view.webp create mode 100644 src/linguist/linguist/doc/images/linguist-danger.png create mode 100644 src/linguist/linguist/doc/images/linguist-doneandnext.png create mode 100644 src/linguist/linguist/doc/images/linguist-examples.png create mode 100644 src/linguist/linguist/doc/images/linguist-linguist_2.webp create mode 100644 src/linguist/linguist/doc/images/linguist-phrasebookdialog.png create mode 100644 src/linguist/linguist/doc/images/linguist-strings-view.webp create mode 100644 src/linguist/linguist/doc/images/linguist-translationfilesettings.png create mode 100644 src/linguist/linguist/doc/images/linguist-ui.webp create mode 100644 src/linguist/linguist/doc/images/linguist.webp create mode 100644 src/linguist/linguist/doc/images/next.png create mode 100644 src/linguist/linguist/doc/images/nextunfinished.png create mode 100644 src/linguist/linguist/doc/images/xNIz78IPBu0.jpg create mode 100644 src/linguist/linguist/doc/includes/cmake-find-package-linguisttools.qdocinc create mode 100644 src/linguist/linguist/doc/qtlinguist.qdocconf create mode 100644 src/linguist/linguist/doc/snippets/cmake-macros/examples.cmake create mode 100644 src/linguist/linguist/doc/snippets/doc_src_linguist-manual.cpp create mode 100644 src/linguist/linguist/doc/snippets/doc_src_linguist-manual.pro create mode 100644 src/linguist/linguist/doc/src/linguist-examples.qdoc create mode 100644 src/linguist/linguist/doc/src/linguist-manual.qdoc create mode 100644 src/linguist/linguist/errorsview.cpp create mode 100644 src/linguist/linguist/errorsview.h create mode 100644 src/linguist/linguist/finddialog.cpp create mode 100644 src/linguist/linguist/finddialog.h create mode 100644 src/linguist/linguist/finddialog.ui create mode 100644 src/linguist/linguist/globals.cpp create mode 100644 src/linguist/linguist/globals.h create mode 100644 src/linguist/linguist/images/alert.png create mode 100644 src/linguist/linguist/images/appicon.png create mode 100644 src/linguist/linguist/images/check.png create mode 100644 src/linguist/linguist/images/darkicons/check-ampersands-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/check-ampersands.png create mode 100644 src/linguist/linguist/images/darkicons/check-ending-pontuation-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/check-ending-pontuation.png create mode 100644 src/linguist/linguist/images/darkicons/check-phrase-suggestions-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/check-phrase-suggestions.png create mode 100644 src/linguist/linguist/images/darkicons/check-place-markers-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/check-place-markers.png create mode 100644 src/linguist/linguist/images/darkicons/check-white-spaces-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/check-white-spaces.png create mode 100644 src/linguist/linguist/images/darkicons/copy-general-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/copy-general.png create mode 100644 src/linguist/linguist/images/darkicons/cut-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/cut.png create mode 100644 src/linguist/linguist/images/darkicons/hit-help-chosen-option-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/hit-help-chosen-option.png create mode 100644 src/linguist/linguist/images/darkicons/library-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/library.png create mode 100644 src/linguist/linguist/images/darkicons/mark-current-translation-done-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/mark-current-translation-done-move-to-next-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/mark-current-translation-done-move-to-next.png create mode 100644 src/linguist/linguist/images/darkicons/mark-current-translation-done.png create mode 100644 src/linguist/linguist/images/darkicons/minus-square-fill-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/minus-square-fill.png create mode 100644 src/linguist/linguist/images/darkicons/next-translation-item-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/next-translation-item.png create mode 100644 src/linguist/linguist/images/darkicons/next-unfinished-translation-item-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/next-unfinished-translation-item.png create mode 100644 src/linguist/linguist/images/darkicons/open-new-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/open-new.png create mode 100644 src/linguist/linguist/images/darkicons/paste-general-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/paste-general.png create mode 100644 src/linguist/linguist/images/darkicons/plus-square-fill-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/plus-square-fill.png create mode 100644 src/linguist/linguist/images/darkicons/previous-translation-item-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/previous-translation-item.png create mode 100644 src/linguist/linguist/images/darkicons/previous-unfinished-translation-item-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/previous-unfinished-translation-item.png create mode 100644 src/linguist/linguist/images/darkicons/print-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/print.png create mode 100644 src/linguist/linguist/images/darkicons/redo-arrow-right-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/redo-arrow-right.png create mode 100644 src/linguist/linguist/images/darkicons/save-fl-disk-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/save-fl-disk.png create mode 100644 src/linguist/linguist/images/darkicons/search-magnifier-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/search-magnifier.png create mode 100644 src/linguist/linguist/images/darkicons/undo-arrow-left-disabled.png create mode 100644 src/linguist/linguist/images/darkicons/undo-arrow-left.png create mode 100644 src/linguist/linguist/images/darkmarks/danger-mark.png create mode 100644 src/linguist/linguist/images/darkmarks/empty-mark.png create mode 100644 src/linguist/linguist/images/darkmarks/obsolete-mark.png create mode 100644 src/linguist/linguist/images/darkmarks/off-mark.png create mode 100644 src/linguist/linguist/images/darkmarks/on-mark.png create mode 100644 src/linguist/linguist/images/darkmarks/warning-mark.png create mode 100755 src/linguist/linguist/images/generate-marks.sh create mode 100644 src/linguist/linguist/images/icons/linguist-128-32.png create mode 100644 src/linguist/linguist/images/lighticons/check-ampersands-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/check-ampersands.png create mode 100644 src/linguist/linguist/images/lighticons/check-ending-pontuation-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/check-ending-pontuation.png create mode 100644 src/linguist/linguist/images/lighticons/check-phrase-suggestions-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/check-phrase-suggestions.png create mode 100644 src/linguist/linguist/images/lighticons/check-place-markers-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/check-place-markers.png create mode 100644 src/linguist/linguist/images/lighticons/check-white-spaces-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/check-white-spaces.png create mode 100644 src/linguist/linguist/images/lighticons/copy-general-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/copy-general.png create mode 100644 src/linguist/linguist/images/lighticons/cut-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/cut.png create mode 100644 src/linguist/linguist/images/lighticons/hit-help-chosen-option-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/hit-help-chosen-option.png create mode 100644 src/linguist/linguist/images/lighticons/library-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/library.png create mode 100644 src/linguist/linguist/images/lighticons/mark-current-translation-done-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/mark-current-translation-done-move-to-next-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/mark-current-translation-done-move-to-next.png create mode 100644 src/linguist/linguist/images/lighticons/mark-current-translation-done.png create mode 100644 src/linguist/linguist/images/lighticons/minus-square-fill-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/minus-square-fill.png create mode 100644 src/linguist/linguist/images/lighticons/next-translation-item-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/next-translation-item.png create mode 100644 src/linguist/linguist/images/lighticons/next-unfinished-translation-item-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/next-unfinished-translation-item.png create mode 100644 src/linguist/linguist/images/lighticons/open-new-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/open-new.png create mode 100644 src/linguist/linguist/images/lighticons/paste-general-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/paste-general.png create mode 100644 src/linguist/linguist/images/lighticons/plus-square-fill-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/plus-square-fill.png create mode 100644 src/linguist/linguist/images/lighticons/previous-translation-item-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/previous-translation-item.png create mode 100644 src/linguist/linguist/images/lighticons/previous-unfinished-translation-item-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/previous-unfinished-translation-item.png create mode 100644 src/linguist/linguist/images/lighticons/print-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/print.png create mode 100644 src/linguist/linguist/images/lighticons/redo-arrow-right-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/redo-arrow-right.png create mode 100644 src/linguist/linguist/images/lighticons/save-fl-disk-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/save-fl-disk.png create mode 100644 src/linguist/linguist/images/lighticons/search-magnifier-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/search-magnifier.png create mode 100644 src/linguist/linguist/images/lighticons/undo-arrow-left-disabled.png create mode 100644 src/linguist/linguist/images/lighticons/undo-arrow-left.png create mode 100644 src/linguist/linguist/images/lightmarks/danger-mark.png create mode 100644 src/linguist/linguist/images/lightmarks/empty-mark.png create mode 100644 src/linguist/linguist/images/lightmarks/obsolete-mark.png create mode 100644 src/linguist/linguist/images/lightmarks/off-mark.png create mode 100644 src/linguist/linguist/images/lightmarks/on-mark.png create mode 100644 src/linguist/linguist/images/lightmarks/warning-mark.png create mode 100644 src/linguist/linguist/images/menu-dots.png create mode 100644 src/linguist/linguist/linguist.desktop create mode 100644 src/linguist/linguist/linguist.icns create mode 100644 src/linguist/linguist/linguist.ico create mode 100644 src/linguist/linguist/linguist.metainfo.xml create mode 100644 src/linguist/linguist/main.cpp create mode 100644 src/linguist/linguist/mainwindow.cpp create mode 100644 src/linguist/linguist/mainwindow.h create mode 100644 src/linguist/linguist/mainwindow.ui create mode 100644 src/linguist/linguist/messageeditor.cpp create mode 100644 src/linguist/linguist/messageeditor.h create mode 100644 src/linguist/linguist/messageeditorwidgets.cpp create mode 100644 src/linguist/linguist/messageeditorwidgets.h create mode 100644 src/linguist/linguist/messagehighlighter.cpp create mode 100644 src/linguist/linguist/messagehighlighter.h create mode 100644 src/linguist/linguist/messagemodel.cpp create mode 100644 src/linguist/linguist/messagemodel.h create mode 100644 src/linguist/linguist/phrase.cpp create mode 100644 src/linguist/linguist/phrase.h create mode 100644 src/linguist/linguist/phrasebookbox.cpp create mode 100644 src/linguist/linguist/phrasebookbox.h create mode 100644 src/linguist/linguist/phrasebookbox.ui create mode 100644 src/linguist/linguist/phrasemodel.cpp create mode 100644 src/linguist/linguist/phrasemodel.h create mode 100644 src/linguist/linguist/phraseview.cpp create mode 100644 src/linguist/linguist/phraseview.h create mode 100644 src/linguist/linguist/printout.cpp create mode 100644 src/linguist/linguist/printout.h create mode 100644 src/linguist/linguist/qmlformpreviewview.cpp create mode 100644 src/linguist/linguist/qmlformpreviewview.h create mode 100644 src/linguist/linguist/recentfiles.cpp create mode 100644 src/linguist/linguist/recentfiles.h create mode 100644 src/linguist/linguist/sourcecodeview.cpp create mode 100644 src/linguist/linguist/sourcecodeview.h create mode 100644 src/linguist/linguist/statistics.cpp create mode 100644 src/linguist/linguist/statistics.h create mode 100644 src/linguist/linguist/statistics.ui create mode 100644 src/linguist/linguist/translatedialog.cpp create mode 100644 src/linguist/linguist/translatedialog.h create mode 100644 src/linguist/linguist/translatedialog.ui create mode 100644 src/linguist/linguist/translationsettings.ui create mode 100644 src/linguist/linguist/translationsettingsdialog.cpp create mode 100644 src/linguist/linguist/translationsettingsdialog.h create mode 100644 src/linguist/linguist/uiformpreviewview.cpp create mode 100644 src/linguist/linguist/uiformpreviewview.h create mode 100644 src/linguist/lprodump/CMakeLists.txt create mode 100644 src/linguist/lprodump/main.cpp create mode 100644 src/linguist/lrelease-pro/CMakeLists.txt create mode 100644 src/linguist/lrelease-pro/main.cpp create mode 100644 src/linguist/lrelease/CMakeLists.txt create mode 100644 src/linguist/lrelease/main.cpp create mode 100644 src/linguist/lupdate-pro/CMakeLists.txt create mode 100644 src/linguist/lupdate-pro/lupdate-pro.exe.manifest create mode 100644 src/linguist/lupdate-pro/lupdate-pro.rc create mode 100644 src/linguist/lupdate-pro/main.cpp create mode 100644 src/linguist/lupdate/CMakeLists.txt create mode 100644 src/linguist/lupdate/cpp.cpp create mode 100644 src/linguist/lupdate/cpp.h create mode 100644 src/linguist/lupdate/java.cpp create mode 100644 src/linguist/lupdate/lupdate.exe.manifest create mode 100644 src/linguist/lupdate/lupdate.h create mode 100644 src/linguist/lupdate/lupdate.rc create mode 100644 src/linguist/lupdate/main.cpp create mode 100644 src/linguist/lupdate/merge.cpp create mode 100644 src/linguist/lupdate/metastrings.cpp create mode 100644 src/linguist/lupdate/metastrings.h create mode 100644 src/linguist/lupdate/python.cpp create mode 100644 src/linguist/lupdate/qdeclarative.cpp create mode 100644 src/linguist/lupdate/synchronized.h create mode 100644 src/linguist/lupdate/ui.cpp create mode 100644 src/linguist/phrasebooks/danish.qph create mode 100644 src/linguist/phrasebooks/dutch.qph create mode 100644 src/linguist/phrasebooks/finnish.qph create mode 100644 src/linguist/phrasebooks/french.qph create mode 100644 src/linguist/phrasebooks/german.qph create mode 100644 src/linguist/phrasebooks/hungarian.qph create mode 100644 src/linguist/phrasebooks/italian.qph create mode 100644 src/linguist/phrasebooks/japanese.qph create mode 100644 src/linguist/phrasebooks/norwegian.qph create mode 100644 src/linguist/phrasebooks/polish.qph create mode 100644 src/linguist/phrasebooks/russian.qph create mode 100644 src/linguist/phrasebooks/spanish.qph create mode 100644 src/linguist/phrasebooks/swedish.qph create mode 100644 src/linguist/shared/exclusive_builds.prf create mode 100644 src/linguist/shared/fmt.h create mode 100644 src/linguist/shared/ioutils.cpp create mode 100644 src/linguist/shared/ioutils.h create mode 100644 src/linguist/shared/numerus.cpp create mode 100644 src/linguist/shared/po.cpp create mode 100644 src/linguist/shared/profileevaluator.cpp create mode 100644 src/linguist/shared/profileevaluator.h create mode 100644 src/linguist/shared/profileutils.h create mode 100644 src/linguist/shared/proitems.cpp create mode 100644 src/linguist/shared/proitems.h create mode 100644 src/linguist/shared/projectdescriptionreader.cpp create mode 100644 src/linguist/shared/projectdescriptionreader.h create mode 100644 src/linguist/shared/qm.cpp create mode 100644 src/linguist/shared/qmake_global.h create mode 100644 src/linguist/shared/qmakebuiltins.cpp create mode 100644 src/linguist/shared/qmakeevaluator.cpp create mode 100644 src/linguist/shared/qmakeevaluator.h create mode 100644 src/linguist/shared/qmakeevaluator_p.h create mode 100644 src/linguist/shared/qmakeglobals.cpp create mode 100644 src/linguist/shared/qmakeglobals.h create mode 100644 src/linguist/shared/qmakeparser.cpp create mode 100644 src/linguist/shared/qmakeparser.h create mode 100644 src/linguist/shared/qmakevfs.cpp create mode 100644 src/linguist/shared/qmakevfs.h create mode 100644 src/linguist/shared/qph.cpp create mode 100644 src/linguist/shared/qrcreader.cpp create mode 100644 src/linguist/shared/qrcreader.h create mode 100644 src/linguist/shared/registry.cpp create mode 100644 src/linguist/shared/registry_p.h create mode 100644 src/linguist/shared/runqttool.cpp create mode 100644 src/linguist/shared/runqttool.h create mode 100644 src/linguist/shared/simtexth.cpp create mode 100644 src/linguist/shared/simtexth.h create mode 100644 src/linguist/shared/translator.cpp create mode 100644 src/linguist/shared/translator.h create mode 100644 src/linguist/shared/translatormessage.cpp create mode 100644 src/linguist/shared/translatormessage.h create mode 100644 src/linguist/shared/ts.cpp create mode 100644 src/linguist/shared/ts.xsd create mode 100644 src/linguist/shared/xliff.cpp create mode 100644 src/linguist/shared/xmlparser.cpp create mode 100644 src/linguist/shared/xmlparser.h create mode 100644 src/pixeltool/CMakeLists.txt create mode 100644 src/pixeltool/Info_mac.plist create mode 100644 src/pixeltool/main.cpp create mode 100644 src/pixeltool/qpixeltool.cpp create mode 100644 src/pixeltool/qpixeltool.h create mode 100644 src/qdbus/CMakeLists.txt create mode 100644 src/qdbus/qdbus/CMakeLists.txt create mode 100644 src/qdbus/qdbus/qdbus.cpp create mode 100644 src/qdbus/qdbusviewer/CMakeLists.txt create mode 100644 src/qdbus/qdbusviewer/Info_mac.plist create mode 100644 src/qdbus/qdbusviewer/images/qdbusviewer-128.png create mode 100644 src/qdbus/qdbusviewer/images/qdbusviewer.icns create mode 100644 src/qdbus/qdbusviewer/images/qdbusviewer.ico create mode 100644 src/qdbus/qdbusviewer/images/qdbusviewer.png create mode 100644 src/qdbus/qdbusviewer/logviewer.cpp create mode 100644 src/qdbus/qdbusviewer/logviewer.h create mode 100644 src/qdbus/qdbusviewer/main.cpp create mode 100644 src/qdbus/qdbusviewer/mainwindow.cpp create mode 100644 src/qdbus/qdbusviewer/mainwindow.h create mode 100644 src/qdbus/qdbusviewer/propertydialog.cpp create mode 100644 src/qdbus/qdbusviewer/propertydialog.h create mode 100644 src/qdbus/qdbusviewer/qdbusmodel.cpp create mode 100644 src/qdbus/qdbusviewer/qdbusmodel.h create mode 100644 src/qdbus/qdbusviewer/qdbusviewer.cpp create mode 100644 src/qdbus/qdbusviewer/qdbusviewer.desktop create mode 100644 src/qdbus/qdbusviewer/qdbusviewer.h create mode 100644 src/qdbus/qdbusviewer/qdbusviewer.metainfo.xml create mode 100644 src/qdbus/qdbusviewer/servicesproxymodel.cpp create mode 100644 src/qdbus/qdbusviewer/servicesproxymodel.h create mode 100644 src/qdoc/CMakeLists.txt create mode 100644 src/qdoc/catch/CMakeLists.txt create mode 100644 src/qdoc/catch/LICENSE.CATCH.txt create mode 100644 src/qdoc/catch/REUSE.toml create mode 100644 src/qdoc/catch/include/catch/catch.hpp create mode 100644 src/qdoc/catch/qt_attribution.json create mode 100644 src/qdoc/catch_conversions/CMakeLists.txt create mode 100644 src/qdoc/catch_conversions/src/catch_conversions/qdoc_catch_conversions.h create mode 100644 src/qdoc/catch_conversions/src/catch_conversions/qt_catch_conversions.h create mode 100644 src/qdoc/catch_conversions/src/catch_conversions/std_catch_conversions.h create mode 100644 src/qdoc/catch_generators/CMakeLists.txt create mode 100644 src/qdoc/catch_generators/src/catch_generators/generators/combinators/cycle_generator.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/generators/combinators/oneof_generator.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/generators/k_partition_of_r_generator.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/generators/path_generator.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/generators/qchar_generator.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/generators/qstring_generator.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/namespaces.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/utilities/semantics/copy_value.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/utilities/semantics/generator_handler.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/utilities/semantics/move_into_vector.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/utilities/statistics/distribution.h create mode 100644 src/qdoc/catch_generators/src/catch_generators/utilities/statistics/percentages.h create mode 100644 src/qdoc/catch_generators/tests/CMakeLists.txt create mode 100644 src/qdoc/catch_generators/tests/generators/catch_k_partition_of_r_generator.cpp create mode 100644 src/qdoc/catch_generators/tests/generators/catch_path_generator.cpp create mode 100644 src/qdoc/catch_generators/tests/generators/catch_qchar_generator.cpp create mode 100644 src/qdoc/catch_generators/tests/generators/catch_qstring_generator.cpp create mode 100644 src/qdoc/catch_generators/tests/generators/combinators/catch_cycle_generator.cpp create mode 100644 src/qdoc/catch_generators/tests/generators/combinators/catch_oneof_generator.cpp create mode 100644 src/qdoc/catch_generators/tests/main.cpp create mode 100644 src/qdoc/catch_generators/tests/utilities/semantics/catch_generator_handler.cpp create mode 100644 src/qdoc/cmake/QDocConfiguration.cmake create mode 100644 src/qdoc/cmake/QDocConfigureMessages.cmake create mode 100644 src/qdoc/qdoc/CMakeLists.txt create mode 100644 src/qdoc/qdoc/doc/config/qdoc.qdocconf create mode 100644 src/qdoc/qdoc/doc/examples/cpp.qdoc.sample create mode 100644 src/qdoc/qdoc/doc/examples/layoutmanagement.qdocinc create mode 100644 src/qdoc/qdoc/doc/examples/main.cpp create mode 100644 src/qdoc/qdoc/doc/examples/mainwindow.cpp create mode 100644 src/qdoc/qdoc/doc/examples/minimum.qdocconf create mode 100644 src/qdoc/qdoc/doc/examples/objectmodel.qdocinc create mode 100644 src/qdoc/qdoc/doc/examples/qml.qdoc.sample create mode 100644 src/qdoc/qdoc/doc/examples/samples.qdocinc create mode 100644 src/qdoc/qdoc/doc/examples/signalandslots.qdocinc create mode 100644 src/qdoc/qdoc/doc/files/basicqt.qdoc.sample create mode 100644 src/qdoc/qdoc/doc/files/compat.qdocconf create mode 100644 src/qdoc/qdoc/doc/files/qtgui.qdocconf create mode 100644 src/qdoc/qdoc/doc/images/happyguy.jpg create mode 100644 src/qdoc/qdoc/doc/images/link-to-qquickitem.png create mode 100644 src/qdoc/qdoc/doc/images/links-to-broken-links.png create mode 100644 src/qdoc/qdoc/doc/images/links-to-links.png create mode 100644 src/qdoc/qdoc/doc/images/qt-logo.png create mode 100644 src/qdoc/qdoc/doc/images/training.jpg create mode 100644 src/qdoc/qdoc/doc/images/windows-pushbutton.png create mode 100644 src/qdoc/qdoc/doc/images/windows-toolbutton.png create mode 100644 src/qdoc/qdoc/doc/qdoc-guide/qdoc-guide.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-cpp.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-qml.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-manual-cmdindex.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-manual-contextcmds.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-manual-intro.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-manual-macros.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-manual-markupcmds.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-manual-qdocconf.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-manual-topiccmds.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-manual.qdoc create mode 100644 src/qdoc/qdoc/doc/qdoc-warnings.qdoc create mode 100644 src/qdoc/qdoc/doc/qtgui-qdocconf.qdoc create mode 100644 src/qdoc/qdoc/src/qdoc/access.h create mode 100644 src/qdoc/qdoc/src/qdoc/aggregate.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/aggregate.h create mode 100644 src/qdoc/qdoc/src/qdoc/atom.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/atom.h create mode 100644 src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h create mode 100644 src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h create mode 100644 src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h create mode 100644 src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h create mode 100644 src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc create mode 100644 src/qdoc/qdoc/src/qdoc/clang/AST/LICENSE.LLVM.txt create mode 100644 src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h create mode 100644 src/qdoc/qdoc/src/qdoc/clang/AST/REUSE.toml create mode 100644 src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json create mode 100644 src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/clangcodeparser.h create mode 100644 src/qdoc/qdoc/src/qdoc/classnode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/classnode.h create mode 100644 src/qdoc/qdoc/src/qdoc/codechunk.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/codechunk.h create mode 100644 src/qdoc/qdoc/src/qdoc/codemarker.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/codemarker.h create mode 100644 src/qdoc/qdoc/src/qdoc/codeparser.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/codeparser.h create mode 100644 src/qdoc/qdoc/src/qdoc/collectionnode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/collectionnode.h create mode 100644 src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/comparisoncategory.h create mode 100644 src/qdoc/qdoc/src/qdoc/config.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/config.h create mode 100644 src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/cppcodemarker.h create mode 100644 src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/cppcodeparser.h create mode 100644 src/qdoc/qdoc/src/qdoc/doc.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/doc.h create mode 100644 src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/docbookgenerator.h create mode 100644 src/qdoc/qdoc/src/qdoc/docparser.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/docparser.h create mode 100644 src/qdoc/qdoc/src/qdoc/docprivate.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/docprivate.h create mode 100644 src/qdoc/qdoc/src/qdoc/docutilities.h create mode 100644 src/qdoc/qdoc/src/qdoc/editdistance.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/editdistance.h create mode 100644 src/qdoc/qdoc/src/qdoc/enumitem.h create mode 100644 src/qdoc/qdoc/src/qdoc/enumnode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/enumnode.h create mode 100644 src/qdoc/qdoc/src/qdoc/examplenode.h create mode 100644 src/qdoc/qdoc/src/qdoc/externalpagenode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/externalpagenode.h create mode 100644 src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h create mode 100644 src/qdoc/qdoc/src/qdoc/functionnode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/functionnode.h create mode 100644 src/qdoc/qdoc/src/qdoc/generator.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/generator.h create mode 100644 src/qdoc/qdoc/src/qdoc/genustypes.h create mode 100644 src/qdoc/qdoc/src/qdoc/headernode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/headernode.h create mode 100644 src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/helpprojectwriter.h create mode 100644 src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/htmlgenerator.h create mode 100644 src/qdoc/qdoc/src/qdoc/importrec.h create mode 100644 src/qdoc/qdoc/src/qdoc/inode.h create mode 100644 src/qdoc/qdoc/src/qdoc/location.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/location.h create mode 100644 src/qdoc/qdoc/src/qdoc/macro.h create mode 100644 src/qdoc/qdoc/src/qdoc/main.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/manifestwriter.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/manifestwriter.h create mode 100644 src/qdoc/qdoc/src/qdoc/namespacenode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/namespacenode.h create mode 100644 src/qdoc/qdoc/src/qdoc/nativeenum.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/nativeenum.h create mode 100644 src/qdoc/qdoc/src/qdoc/node.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/node.h create mode 100644 src/qdoc/qdoc/src/qdoc/openedlist.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/openedlist.h create mode 100644 src/qdoc/qdoc/src/qdoc/pagenode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/pagenode.h create mode 100644 src/qdoc/qdoc/src/qdoc/parameters.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/parameters.h create mode 100644 src/qdoc/qdoc/src/qdoc/parsererror.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/parsererror.h create mode 100644 src/qdoc/qdoc/src/qdoc/propertynode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/propertynode.h create mode 100644 src/qdoc/qdoc/src/qdoc/proxynode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/proxynode.h create mode 100644 src/qdoc/qdoc/src/qdoc/puredocparser.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/puredocparser.h create mode 100644 src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h create mode 100644 src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qdocdatabase.h create mode 100644 src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qdocindexfiles.h create mode 100644 src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qmlcodemarker.h create mode 100644 src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qmlcodeparser.h create mode 100644 src/qdoc/qdoc/src/qdoc/qmlenumnode.h create mode 100644 src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h create mode 100644 src/qdoc/qdoc/src/qdoc/qmlpropertyarguments.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qmlpropertyarguments.h create mode 100644 src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qmlpropertynode.h create mode 100644 src/qdoc/qdoc/src/qdoc/qmltypenode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qmltypenode.h create mode 100644 src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/qmlvisitor.h create mode 100644 src/qdoc/qdoc/src/qdoc/quoter.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/quoter.h create mode 100644 src/qdoc/qdoc/src/qdoc/relatedclass.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/relatedclass.h create mode 100644 src/qdoc/qdoc/src/qdoc/sections.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/sections.h create mode 100644 src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/sharedcommentnode.h create mode 100644 src/qdoc/qdoc/src/qdoc/singleton.h create mode 100644 src/qdoc/qdoc/src/qdoc/sourcefileparser.h create mode 100644 src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/tagfilewriter.h create mode 100644 src/qdoc/qdoc/src/qdoc/template_declaration.h create mode 100644 src/qdoc/qdoc/src/qdoc/text.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/text.h create mode 100644 src/qdoc/qdoc/src/qdoc/tokenizer.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/tokenizer.h create mode 100644 src/qdoc/qdoc/src/qdoc/topic.h create mode 100644 src/qdoc/qdoc/src/qdoc/tree.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/tree.h create mode 100644 src/qdoc/qdoc/src/qdoc/typedefnode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/typedefnode.h create mode 100644 src/qdoc/qdoc/src/qdoc/utilities.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/utilities.h create mode 100644 src/qdoc/qdoc/src/qdoc/variablenode.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/variablenode.h create mode 100644 src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/webxmlgenerator.h create mode 100644 src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp create mode 100644 src/qdoc/qdoc/src/qdoc/xmlgenerator.h create mode 100644 src/qdoc/qdoc/tests/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/config/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/config/testdata/configs/exampletest.qdocconf create mode 100644 src/qdoc/qdoc/tests/config/testdata/configs/expandvars.qdocconf create mode 100644 src/qdoc/qdoc/tests/config/testdata/configs/includepaths.qdocconf create mode 100644 src/qdoc/qdoc/tests/config/testdata/configs/includes/test.qdoc create mode 100644 src/qdoc/qdoc/tests/config/testdata/configs/paths.qdocconf create mode 100644 src/qdoc/qdoc/tests/config/testdata/configs/sourcelink.qdocconf create mode 100644 src/qdoc/qdoc/tests/config/testdata/configs/vars.qdocconf create mode 100644 src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/empty/test.pro create mode 100644 src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example1/example1.pro create mode 100644 src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example2/example2.qmlproject create mode 100644 src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example3/example3.pyproject create mode 100644 src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/example4.pro create mode 100644 src/qdoc/qdoc/tests/config/testdata/includepaths/include/framework/ignore.h create mode 100644 src/qdoc/qdoc/tests/config/testdata/includepaths/include/more/ignore.h create mode 100644 src/qdoc/qdoc/tests/config/testdata/includepaths/include/purpose.h create mode 100644 src/qdoc/qdoc/tests/config/testdata/includepaths/include/system/ignore.h create mode 100644 src/qdoc/qdoc/tests/config/testdata/includepaths/includepaths.qdocconf create mode 100644 src/qdoc/qdoc/tests/config/testdata/paths/includes/test.qdoc create mode 100644 src/qdoc/qdoc/tests/config/testdata/paths/paths.qdocconf create mode 100644 src/qdoc/qdoc/tests/config/tst_config.cpp create mode 100644 src/qdoc/qdoc/tests/generatedoutput/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/autolinking.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/cpptypes.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/all-namespaces.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype-members.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmoduleref-sub-crossmodule.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype-members.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/index.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent-members.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/index-linking.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/qdoc-test-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/test-componentset-example.xml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/testcpp-module.xml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/qdoc-test-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/test-componentset-example.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/testcpp-module.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/obsolete-classes.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/qml-linkmodule-grandchild-members.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups-docbook/qml-qdoc-test-parent.xml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-anotherchild-members.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-parent.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp-module.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp.index create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/crossmoduleref.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testcpp-module.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test-members.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-members.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-obsolete.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-members.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-obsolete.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc.html create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/configs/noautolist.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp_singleexec.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/CrossModule create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule_singleexec.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/namespaces.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.cpp create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.h create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/TestCPP create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/dontdocument.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.cpp create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.pro create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/doc/src/demo.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/dontxclude/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/excludes/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/doc/src/hidden.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/hidden.pro create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/images/01.png create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/anotherindex.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/parentinclude.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/includefromexampledirs.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/includefromparent.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/parent.qdocinc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/indexlinking.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/linking.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/DocTest.qml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/Item.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/doc/src/cmaketest.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/main.cpp create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/ProgressBar.qml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/Switch.qml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/TabWidget.qml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.pro create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.qml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/examples.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/uicomponents.qdoc.sample create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/doctest/DocTest.qml create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/modules.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/parent.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qml/type.cpp create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/parent.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/qmlpropertygroups.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/singleexec/singleexec.qdocconf create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/TestCPP create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/classlists.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/properties.qdoc create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/snippets/snippet_testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.h create mode 100644 src/qdoc/qdoc/tests/generatedoutput/tst_generatedoutput.cpp create mode 100644 src/qdoc/qdoc/tests/inode/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/inode/tst_inodeinterface.cpp create mode 100644 src/qdoc/qdoc/tests/nativeenum/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/nativeenum/tst_nativeenum.cpp create mode 100644 src/qdoc/qdoc/tests/qdoc/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_directorypath.cpp create mode 100644 src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_filepath.cpp create mode 100644 src/qdoc/qdoc/tests/qdoc/filesystem/catch_fileresolver.cpp create mode 100644 src/qdoc/qdoc/tests/qdoc/main.cpp create mode 100644 src/qdoc/qdoc/tests/qdoc/node/catch_genustypes.cpp create mode 100644 src/qdoc/qdoc/tests/qdoccommandlineparser/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/qdoccommandlineparser/tst_arguments.txt create mode 100644 src/qdoc/qdoc/tests/qdoccommandlineparser/tst_qdoccommandlineparser.cpp create mode 100644 src/qdoc/qdoc/tests/utilities/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/utilities/tst_utilities.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/README.md create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/attributions.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/docbook/attributions-test.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/docbook/some-attribution-1.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/docbook/some-attribution-2.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/html/attributions-test.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/html/attributions.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/html/some-attribution-1.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/html/some-attribution-2.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/webxml/attributions-test.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/webxml/attributions.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/webxml/some-attribution-1.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/expected/webxml/some-attribution-2.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/attributions/src/test.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/auto_as_return_type.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/docbook/qdoctest-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/docbook/qdoctests-numbers.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/docbook/qdoctests.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/html/auto-as-return-type.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/html/qdoctest-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/html/qdoctests-numbers-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/html/qdoctests-numbers.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/html/qdoctests.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/webxml/auto-as-return-type.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/webxml/qdoctest-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/webxml/qdoctests-numbers.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/expected/webxml/qdoctests.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/src/numbers.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/auto_as_return_type/src/numbers.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/bug80259.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-nested.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/index.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/second.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/testmodule.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/third.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first-nested.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/index.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/second.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/testmodule.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/third.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/TestModule.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/aaa.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/bbb.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/ccc.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/main.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/qdoc/index.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/clashing_qmltypes.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/expected/html/barmod-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/expected/html/clashingqmltypes.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/expected/html/foomod-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/expected/html/qml-barmod-foo-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/expected/html/qml-barmod-foo.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/expected/html/qml-foomod-foo-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/expected/html/qml-foomod-foo.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/expected/html/qmltypes-overview.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/clashing_qmltypes/src/test.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/class_relates/class_relates.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/class_relates/expected/html/classrelates.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/class_relates/expected/html/foo-bar.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/class_relates/expected/html/foo-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/class_relates/expected/html/foo.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/class_relates/src/bar.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/class_relates/src/foo.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/class_relates/src/foo.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/cmakedocumentation.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/car.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/engine.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcar-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcarprivate-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/car.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/cmakedocumentation.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/engine.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcar-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcarprivate-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/car.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/cmakedocumentation.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/engine.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcar-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcarprivate-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/compiler_generated_member_functions.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-compilergeneratedmemberfunctions.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/compiler-generated-member-functions.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/compiler-generated-member-functions.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-compilergeneratedmemberfunctions.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/comprehensiveproject.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/autolinking.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/classes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/cpptypes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/crossmoduleref.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/obsolete-classes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qdoc-test-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-int.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-abstractparent.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-child.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-doctest.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-item.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-oldtype.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-type.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-yetanotherchild.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-doctest.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-typenoversion.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-themodule-thetype.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-progressbar.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-switch.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-tabwidget.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qmlmodules.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/seenclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-cmakelists-txt.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-main-cpp.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-pro.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-qml.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-progressbar-qml.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-switch-qml.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-tabwidget-qml.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-cpp.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-pro.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-dontxclude-cmakelists-txt.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-hidden-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-empty-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-nover-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testcpp-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-test.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-testderived.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/themodule-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/uicomponents-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/autolinking.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/classes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/cpptypes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/crossmoduleref.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/obsolete-classes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qdoc-test-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-int.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-item-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-item.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qmlmodules.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/seenclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-cmakelists-txt.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-main-cpp.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-pro.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-qml.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-progressbar-qml.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-switch-qml.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-tabwidget-qml.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-cpp.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-pro.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-dontxclude-cmakelists-txt.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-hidden-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-empty-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-nover-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.qhp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testcpp-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testtagfile.tags create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/themodule-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/uicomponents-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/autolinking.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/classes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/cpptypes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/crossmoduleref.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/obsolete-classes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qdoc-test-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qmlmodules.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/seenclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-cmakelists-txt.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-main-cpp.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-pro.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-qml.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-progressbar-qml.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-switch-qml.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-tabwidget-qml.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-cpp.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-pro.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-dontxclude-cmakelists-txt.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-hidden-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-empty-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-nover-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testcpp-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-test.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-testderived.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testtagfile.tags create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/themodule-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/uicomponents-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/TestCPP create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/classlists.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.pro create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/doc/src/demo.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/dontxclude/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/excludes/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/doc/src/hidden.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/hidden.pro create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/properties.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/DocTest.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/Item.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/doc/src/cmaketest.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/main.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/ProgressBar.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/Switch.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/TabWidget.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.pro create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/examples.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/uicomponents.qdoc.sample create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/doctest/DocTest.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/modules.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/parent.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/type.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/snippets/snippet_testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/unseenclass.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/comprehensiveproject_headerdocs.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/autolinking.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/classes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/cpptypes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/crossmoduleref.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/obsolete-classes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qdoc-test-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-int.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-qdoc-test-abstractparent.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-qdoc-test-child.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-qdoc-test-doctest.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-qdoc-test-oldtype.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-qdoc-test-type.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-qdoc-test-yetanotherchild.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-test-nover-doctest.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-test-nover-typenoversion.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-themodule-thetype.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-uicomponents-progressbar.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-uicomponents-switch.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qml-uicomponents-tabwidget.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/qmlmodules.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/seenclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-cmaketest-cmakelists-txt.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-cmaketest-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-cmaketest-main-cpp.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-componentset-componentset-pro.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-componentset-componentset-qml.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-componentset-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-componentset-progressbar-qml.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-componentset-switch-qml.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-componentset-tabwidget-qml.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-demos-demo-demo-cpp.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-demos-demo-demo-pro.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-demos-demo-dontxclude-cmakelists-txt.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-demos-demo-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-demos-hidden-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-empty-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/test-nover-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/testcpp-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/testqdoc-test-struct.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/testqdoc-test.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/testqdoc-testderived.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/testqdoc-vec.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/testqdoc.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/themodule-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/docbook/uicomponents-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/autolinking.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/classes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/cpptypes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/crossmoduleref.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/obsolete-classes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qdoc-test-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-int.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-abstractparent-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-abstractparent.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-child-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-child.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-doctest-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-doctest.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-oldtype-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-oldtype.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-type-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-type-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-type.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-yetanotherchild-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-qdoc-test-yetanotherchild.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-test-nover-doctest-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-test-nover-doctest.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-test-nover-typenoversion-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-test-nover-typenoversion.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-themodule-thetype-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-themodule-thetype.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-uicomponents-progressbar-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-uicomponents-progressbar.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-uicomponents-switch-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-uicomponents-switch.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-uicomponents-tabwidget-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qml-uicomponents-tabwidget.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/qmlmodules.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/seenclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-cmaketest-cmakelists-txt.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-cmaketest-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-cmaketest-main-cpp.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-componentset-componentset-pro.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-componentset-componentset-qml.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-componentset-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-componentset-progressbar-qml.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-componentset-switch-qml.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-componentset-tabwidget-qml.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-demos-demo-demo-cpp.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-demos-demo-demo-pro.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-demos-demo-dontxclude-cmakelists-txt.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-demos-demo-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-demos-hidden-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-empty-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test-nover-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/test.qhp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testcpp-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc-test-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc-test-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc-test-struct.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc-test.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc-testderived-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc-testderived-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc-testderived.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc-vec.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testqdoc.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/testtagfile.tags create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/themodule-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/html/uicomponents-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/autolinking.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/classes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/cpptypes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/crossmoduleref.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/obsolete-classes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/qdoc-test-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/qmlmodules.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/seenclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-cmaketest-cmakelists-txt.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-cmaketest-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-cmaketest-main-cpp.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-componentset-componentset-pro.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-componentset-componentset-qml.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-componentset-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-componentset-progressbar-qml.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-componentset-switch-qml.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-componentset-tabwidget-qml.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-demos-demo-demo-cpp.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-demos-demo-demo-pro.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-demos-demo-dontxclude-cmakelists-txt.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-demos-demo-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-demos-hidden-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-empty-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test-nover-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/test.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/testcpp-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/testqdoc-test-struct.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/testqdoc-test.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/testqdoc-testderived.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/testqdoc-vec.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/testqdoc.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/testtagfile.tags create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/themodule-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/expected/webxml/uicomponents-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/TestCPP create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/classlists.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/dont.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/dont.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/examples/demos/demo/demo.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/examples/demos/demo/demo.pro create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/examples/demos/demo/doc/src/demo.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/examples/demos/demo/dontxclude/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/examples/demos/demo/excludes/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/examples/demos/hidden/doc/src/hidden.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/examples/demos/hidden/hidden.pro create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/properties.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/DocTest.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/cmaketest/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/cmaketest/doc/src/cmaketest.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/cmaketest/main.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/componentset/ProgressBar.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/componentset/Switch.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/componentset/TabWidget.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/componentset/componentset.pro create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/componentset/componentset.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/componentset/examples.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/componentset/uicomponents.qdoc.sample create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/doctest/DocTest.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/modules.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/parent.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/qml/type.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/snippets/snippet_testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/testcpp.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/testcpp.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject_headerdocs/src/unseenclass.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/customsortedlists.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/docbook/a.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/docbook/b.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/docbook/c.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/docbook/d.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/docbook/lists.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/html/a.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/html/b.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/html/c.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/html/customsortedlists.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/html/d.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/html/lists.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/webxml/a.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/webxml/b.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/webxml/c.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/webxml/customsortedlists.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/webxml/d.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/expected/webxml/lists.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/customsortedlists/src/test.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/cxx20.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/bar.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/baz.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithoneclassandpartiallywithanother.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclasses.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclassesacrossmultiplelines.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithtwoclasses.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/equalitycomparableclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/foo.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/partiallyorderedclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/stronglyorderedclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/weaklyorderedclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/bar.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/baz.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithoneclassandpartiallywithanother.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclasses.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclassesacrossmultiplelines.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithtwoclasses.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/cxx20.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/equalitycomparableclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/foo.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/partiallyorderedclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/stronglyorderedclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/weaklyorderedclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/bar.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/baz.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithoneclassandpartiallywithanother.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclasses.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclassesacrossmultiplelines.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithtwoclasses.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/cxx20.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/equalitycomparableclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/foo.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/partiallyorderedclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/stronglyorderedclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/weaklyorderedclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/duplicate_section_titles_have_unique_anchors.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/docbook/page-one.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/duplicate-section-titles-have-unique-anchors.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/page-one.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/duplicate-section-titles-have-unique-anchors.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/page-one.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/src/page_one.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/externalpage_hyphens/expected/html/external-page-hyphens.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/externalpage_hyphens/expected/html/qdoctest-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/externalpage_hyphens/externalpage_hyphens.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/externalpage_hyphens/src/test.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/globals.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/globalfunc.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/TestGlobals create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/headers.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/testheader.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/tests.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headerfile.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headers.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/testheader.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/tests.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headerfile.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headers.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/testheader.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/tests.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/headerfile.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/another-page-with-comments-in-the-brief.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/brief-adventures.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatted-examples.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation-someexample-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-an-image-at-the-top.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-after-brief.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-in-brief.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/another-page-with-comments-in-the-brief.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/brief-adventures.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatted-examples.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation-someexample-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-an-image-at-the-top.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-after-brief.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-in-brief.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/illformatted_documentation.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/brief_adventures.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/illformatted-examples.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/some_example.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/image_alt_as_title/expected/html/imagealtastitle.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/image_alt_as_title/expected/html/images/leonardo-da-vinci-small.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/image_alt_as_title/expected/html/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/image_alt_as_title/expected/html/page-with-an-image-that-has-a-title-attribute.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/image_alt_as_title/image_alt_as_title.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/image_alt_as_title/src/image_alt_as_title.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/image_alt_as_title/src/images/leonardo-da-vinci-small.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/image_alt_as_title/src/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/a-page-with-a-line-comment-in-the-see-also-command.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/another-page-with-an-image-at-the-top.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/line-comment-adventures.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/a-page-with-a-line-comment-in-the-see-also-command.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/another-page-with-an-image-at-the-top.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/line-comment-adventures.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/linecomment.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/a-page-with-a-line-comment-in-the-see-also-command.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/another-page-with-an-image-at-the-top.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/line-comment-adventures.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/linecomment.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/line_comments.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/line_comment_adventures.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/expected/docbook/linkparentheses.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/expected/html/linkparentheses.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/expected/html/linkparentheses.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/expected/webxml/linkparentheses.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/expected/webxml/linkparentheses.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/html/linkparentheses.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/html/linkparentheses.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/linkparentheses.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/linkparentheses/src/main.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/macro_expansion_in_links/expected/another-page.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/macro_expansion_in_links/expected/macro-expansion-in-links.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/macro_expansion_in_links/expected/testpage.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/macro_expansion_in_links/macro_expansion_in_links.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/macro_expansion_in_links/src/anotherpage.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/macro_expansion_in_links/src/testpage.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/a-minimal-qdoc-configuration.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/readme.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/minimal_configuration.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/src/README.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/cppmodule-module-suffix.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/group.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/modifiedoutputfilenames-test-example.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/page.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-header-suffix.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-class-suffix.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-suffix.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qml-qmlmodule-suffix-type.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qmlmodule-qmlmodule-suffix.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/cppmodule-module-suffix.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/group.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames-test-example.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/page.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-header-suffix.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-class-suffix.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-suffix.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qmlmodule-qmlmodule-suffix.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/cppmodule-module-suffix.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/group.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames-test-example.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/page.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-header-suffix.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-class-suffix.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-suffix.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/qmlmodule-qmlmodule-suffix.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/modifiedoutputfilenames.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/example/test/CMakeLists.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/boringclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/excitingclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/moduleinstate-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/boringclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/excitingclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/moduleinstate-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/modulestate.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/boringclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/excitingclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/moduleinstate-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/modulestate.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/modulestate.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/classes_in_stateful_module.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/module_in_a_state.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/docbook/testaction.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/docbook/testmodule-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/html/multisignaltest.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/html/testaction-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/html/testaction.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/html/testmodule-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/webxml/multisignaltest.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/webxml/testaction.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/expected/webxml/testmodule-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/multisignal.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/src/testaction.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/multisignal/src/testaction.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/expected/docbook/testclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/expected/html/noexceptnotes.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/expected/html/testclass-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/expected/html/testclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/expected/webxml/noexceptnotes.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/expected/webxml/testclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/noexcept_notes.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/src/testclass.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/noexcept_notes/src/testclass.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/8b5c72eb.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/adventures-with-non-ascii-characters.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/e85685de.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/8b5c72eb.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/adventures-with-non-ascii-characters.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/e85685de.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/mozzarella-7c883eff.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/nonasciicharacterinput.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/santa-14209312.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/trademarks-test2.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/trademarks.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/mozzarella-7c883eff.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/nonasciicharacterinput.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/santa-14209312.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/trademarks-test2.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/trademarks.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/non_ascii_character_input.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/src/adventures_with_non_ascii_characters.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/crash.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-linking.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/toc.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/crash.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/outputfromqdocfiles.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-exhaustive.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-linking.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocmanuallikefileoutput.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/toc.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/crash.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/outputfromqdocfiles.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-linking.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/toc.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/outputfromqdocfiles.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocfiles.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocmanuallikefiles.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/snippets/main.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overload_command/expected/overload-command-test.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overload_command/expected/test.qhp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overload_command/expected/testclass-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overload_command/expected/testclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overload_command/overload_command.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overload_command/src/classwithoverloads.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overload_command/src/classwithoverloads.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/docbook/overloadedemptytargets-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/docbook/testclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/html/overloadedemptytargets-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/html/overloadedemptytargets.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/html/testclass-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/html/testclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/overloadedemptytargets-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/overloadedemptytargets.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/expected/testclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/overloaded_empty_targets.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/src/testclass.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_empty_targets/src/testclass.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/docbook/overloadedsignalsslots-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/docbook/testclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/html/overloadedsignalsslots-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/html/overloadedsignalsslots.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/html/testclass-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/html/testclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/webxml/overloadedsignalsslots-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/webxml/overloadedsignalsslots.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/expected/webxml/testclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/overloaded_signals_slots.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/src/testclass.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/overloaded_signals_slots/src/testclass.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/docbook/stdpair-proxypage-proxy.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/proxypage.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/stdpair-proxypage-proxy.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/proxypage.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qdoctest-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-a-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-a.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-abstractapplicationwindow-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-abstractapplicationwindow.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-applicationwindow-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-applicationwindow.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-b-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-b.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-c-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-c.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-selfinheriting-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qml-qdoctest-selfinheriting.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/expected/html/qmlmutualdefs.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/qml_mutual_definitions.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/src/A.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/src/AbstractApplicationWindow.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/src/ApplicationWindow.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/src/B.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/src/C.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/src/SelfInheriting.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/src/foo.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_mutual_definitions/src/foo.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/docbook/qml-qtquick-controls-abstractapplicationwindow.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/docbook/qml-qtquick-controls-applicationwindow.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/docbook/qml-some-module-abstractapplicationwindow.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/docbook/qml-some-module-applicationwindow.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/docbook/qtquick-controls-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/docbook/some-module-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-namespaces.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-qtquick-controls-abstractapplicationwindow-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-qtquick-controls-abstractapplicationwindow.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-qtquick-controls-applicationwindow-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-qtquick-controls-applicationwindow.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-some-module-abstractapplicationwindow-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-some-module-abstractapplicationwindow.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-some-module-applicationwindow-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qml-some-module-applicationwindow.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/qtquick-controls-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/expected/html/some-module-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/qml_namespaces.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/src/AbstractApplicationWindow.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/src/ApplicationWindow.qml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_namespaces/src/modules.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/cppcar.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-car.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-truck.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qmlnativetypesynopsis-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/cppcar.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-nativetype-synopsis-test.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qmlnativetypesynopsis-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/cppcar.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qml-nativetype-synopsis-test.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qmlnativetypesynopsis-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/qml_nativetype_synopsis.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/class.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/module-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qml-qmlmodule-type.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qmlmodule-qmlmodule.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/module-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlenumvaluesfromcpp.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlenumvaluesfromcpp.qhp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlmodule-qmlmodule.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/test.tag create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/class.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/module-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlenumvaluesfromcpp.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlmodule-qmlmodule.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/test.tag create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/qmlenumvaluesfromcpp.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/qmltype.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/b.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/bar.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/module-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/relatesordering.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/relatesordering.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/autolinking.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/cpptypes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/crossmoduleref.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/obsolete-classes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/scoped-enum-linking.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testcpp-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-test.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-testderived.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/whatsnew.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/autolinking.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/cpptypes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/crossmoduleref.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/obsolete-classes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/scoped-enum-linking.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/whatsnew.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/autolinking.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/cpptypes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/crossmoduleref.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/obsolete-classes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/scoped-enum-linking.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-test.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-testderived.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/whatsnew.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/scopedenum.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/classlists.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/scopedenum.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/snippets/snippet_testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/sharedcommentsinnamespace/expected/mod-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/sharedcommentsinnamespace/expected/sharedcommentsinnamespace.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/sharedcommentsinnamespace/expected/test.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/sharedcommentsinnamespace/sharedcommentsinnamespace.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/sharedcommentsinnamespace/src/test.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/sharedcommentsinnamespace/src/test.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/args.txt create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/expected/project-a/a.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/expected/project-a/project-a.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/expected/project-b/b.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/expected/project-b/project-b.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/project-a.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/project-b.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/singleexec.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/src/a/a.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/singleexec/src/b/b.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/snippet_marker_indentation/expected/snippet-marker-indentation.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/snippet_marker_indentation/expected/snippet-marker-indentation.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/snippet_marker_indentation/expected/snippet-marker-indentation.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/snippet_marker_indentation/expected/snippet_marker_indentation.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/snippet_marker_indentation/snippet_marker_indentation.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/snippet_marker_indentation/src/test.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/snippet_marker_indentation/src/test.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/docbok/tableaftervalue.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/tableaftervalue.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/expected/docbook/templatealiasmodule-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/expected/docbook/testaliases.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/expected/html/templatealiasdefaultparams.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/expected/html/templatealiasmodule-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/expected/html/testaliases.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/expected/webxml/templatealiasdefaultparams.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/expected/webxml/templatealiasmodule-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/expected/webxml/testaliases.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/src/module.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/src/test.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/src/test.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/template_alias_default_params/template_alias_default_params.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templated-callables-h.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templatedclass.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templated-callables-h.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedcallables.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templated-callables-h.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedcallables.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedclass.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/templatedcallables.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables_headerdocs/expected/html/templated-callables-h.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables_headerdocs/expected/html/templatedcallables.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables_headerdocs/expected/html/templatedclass-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables_headerdocs/expected/html/templatedclass.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables_headerdocs/src/templated_callables.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables_headerdocs/src/templated_callables.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables_headerdocs/templatedcallables_headerdocs.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/autolinking.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/bar.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/baz.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/cpptypes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/crossmoduleref.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/foo.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/obsolete-classes.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testcpp-module.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test-struct.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-testderived.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-vec.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/autolinking.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/bar.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/baz.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/cpptypes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/crossmoduleref.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/foo.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/obsolete-classes.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testcpp-module.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-struct.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-obsolete.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-vec.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testtemplate.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/autolinking.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/bar.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/baz.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/cpptypes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/crossmoduleref.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/foo.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/obsolete-classes.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testcpp-module.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test-struct.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-testderived.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-vec.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testtemplate.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/classlists.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/snippets/snippet_testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/testtemplate.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/a.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/c.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/crash.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/d.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/e.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/group2.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-linking.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc-test.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/a.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/c.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/crash.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/d.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/e.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/group2.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/outputfromqdocfiles.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/outputfromqdocfiles.qhp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-exhaustive.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-linking.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocmanuallikefileoutput.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc-test.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/a.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/c.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/crash.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/d.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/e.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/group2.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/outputfromqdocfiles.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-linking.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc-test.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/group.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/01.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/leonardo-da-vinci.png create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocfiles.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocmanuallikefiles.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/snippets/main.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/toc.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/tocnavigation.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademark-test.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademarks.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademark-test.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarkcommand.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarks.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademark-test.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarkcommand.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarks.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/src/test.qdoc create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/trademark_command.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/docbook/struct.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct-members.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/trailingbackslashes.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/struct.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/trailingbackslashes.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/trailing_backslashes.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/docbook/space.xml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/space.html create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/usingdirective.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/space.webxml create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/usingdirective.index create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/UsingDirective create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/alias.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.cpp create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.h create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/usingdirective.qdocconf create mode 100644 src/qdoc/qdoc/tests/validateqdocoutputfiles/tst_validateqdocoutputfiles.cpp create mode 100644 src/qev/CMakeLists.txt create mode 100644 src/qev/README create mode 100644 src/qev/qev.cpp create mode 100644 src/qtattributionsscanner/CMakeLists.txt create mode 100644 src/qtattributionsscanner/jsongenerator.cpp create mode 100644 src/qtattributionsscanner/jsongenerator.h create mode 100644 src/qtattributionsscanner/logging.h create mode 100644 src/qtattributionsscanner/main.cpp create mode 100644 src/qtattributionsscanner/package.h create mode 100644 src/qtattributionsscanner/packagefilter.cpp create mode 100644 src/qtattributionsscanner/packagefilter.h create mode 100644 src/qtattributionsscanner/qdocgenerator.cpp create mode 100644 src/qtattributionsscanner/qdocgenerator.h create mode 100644 src/qtattributionsscanner/scanner.cpp create mode 100644 src/qtattributionsscanner/scanner.h create mode 100644 src/qtdiag/CMakeLists.txt create mode 100644 src/qtdiag/main.cpp create mode 100644 src/qtdiag/qtdiag.cpp create mode 100644 src/qtdiag/qtdiag.h create mode 100644 src/qtplugininfo/CMakeLists.txt create mode 100644 src/qtplugininfo/qtplugininfo.cpp create mode 100644 src/shared/deviceskin/deviceskin.cpp create mode 100644 src/shared/deviceskin/deviceskin_p.h create mode 100644 src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone.skin create mode 100644 src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5-closed.png create mode 100644 src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5-pressed.png create mode 100644 src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5.png create mode 100644 src/shared/deviceskin/skins/ClamshellPhone.skin/defaultbuttons.conf create mode 100644 src/shared/deviceskin/skins/PortableMedia.skin/PortableMedia.skin create mode 100644 src/shared/deviceskin/skins/PortableMedia.skin/defaultbuttons.conf create mode 100644 src/shared/deviceskin/skins/PortableMedia.skin/portablemedia-pressed.png create mode 100644 src/shared/deviceskin/skins/PortableMedia.skin/portablemedia.png create mode 100644 src/shared/deviceskin/skins/PortableMedia.skin/portablemedia.xcf create mode 100644 src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar-down.png create mode 100644 src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar.png create mode 100644 src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar.skin create mode 100644 src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/defaultbuttons.conf create mode 100644 src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen-down.png create mode 100644 src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen.png create mode 100644 src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen.skin create mode 100644 src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/defaultbuttons.conf create mode 100644 src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone-pressed.png create mode 100644 src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone.png create mode 100644 src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone.skin create mode 100644 src/shared/deviceskin/skins/SmartPhone.skin/defaultbuttons.conf create mode 100644 src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2-pressed.png create mode 100644 src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2.png create mode 100644 src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2.skin create mode 100644 src/shared/deviceskin/skins/SmartPhone2.skin/defaultbuttons.conf create mode 100644 src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons-pressed.png create mode 100644 src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons.png create mode 100644 src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons.skin create mode 100644 src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/defaultbuttons.conf create mode 100644 src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone-pressed.png create mode 100644 src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone.png create mode 100644 src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone.skin create mode 100644 src/shared/deviceskin/skins/TouchscreenPhone.skin/defaultbuttons.conf create mode 100644 src/shared/findwidget/abstractfindwidget.cpp create mode 100644 src/shared/findwidget/abstractfindwidget_p.h create mode 100644 src/shared/findwidget/images/mac/closetab.png create mode 100644 src/shared/findwidget/images/mac/next.png create mode 100644 src/shared/findwidget/images/mac/previous.png create mode 100644 src/shared/findwidget/images/mac/searchfind.png create mode 100644 src/shared/findwidget/images/win/closetab.png create mode 100644 src/shared/findwidget/images/win/next.png create mode 100644 src/shared/findwidget/images/win/previous.png create mode 100644 src/shared/findwidget/images/win/searchfind.png create mode 100644 src/shared/findwidget/images/wrap.png create mode 100644 src/shared/findwidget/itemviewfindwidget.cpp create mode 100644 src/shared/findwidget/itemviewfindwidget_p.h create mode 100644 src/shared/findwidget/texteditfindwidget.cpp create mode 100644 src/shared/findwidget/texteditfindwidget_p.h create mode 100644 src/shared/fontpanel/fontpanel.cpp create mode 100644 src/shared/fontpanel/fontpanel_p.h create mode 100644 src/shared/qtgradienteditor/images/down.png create mode 100644 src/shared/qtgradienteditor/images/edit.png create mode 100644 src/shared/qtgradienteditor/images/editdelete.png create mode 100644 src/shared/qtgradienteditor/images/minus.png create mode 100644 src/shared/qtgradienteditor/images/plus.png create mode 100644 src/shared/qtgradienteditor/images/spreadpad.png create mode 100644 src/shared/qtgradienteditor/images/spreadreflect.png create mode 100644 src/shared/qtgradienteditor/images/spreadrepeat.png create mode 100644 src/shared/qtgradienteditor/images/typeconical.png create mode 100644 src/shared/qtgradienteditor/images/typelinear.png create mode 100644 src/shared/qtgradienteditor/images/typeradial.png create mode 100644 src/shared/qtgradienteditor/images/up.png create mode 100644 src/shared/qtgradienteditor/images/zoomin.png create mode 100644 src/shared/qtgradienteditor/images/zoomout.png create mode 100644 src/shared/qtgradienteditor/qtcolorbutton.cpp create mode 100644 src/shared/qtgradienteditor/qtcolorbutton_p.h create mode 100644 src/shared/qtgradienteditor/qtcolorline.cpp create mode 100644 src/shared/qtgradienteditor/qtcolorline_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientdialog.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientdialog.ui create mode 100644 src/shared/qtgradienteditor/qtgradientdialog_p.h create mode 100644 src/shared/qtgradienteditor/qtgradienteditor.cpp create mode 100644 src/shared/qtgradienteditor/qtgradienteditor.ui create mode 100644 src/shared/qtgradienteditor/qtgradienteditor_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientmanager.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientmanager_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientstopscontroller.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientstopscontroller_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientstopsmodel.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientstopsmodel_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientstopswidget.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientstopswidget_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientutils.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientutils_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientview.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientview.ui create mode 100644 src/shared/qtgradienteditor/qtgradientview_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientviewdialog.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientviewdialog.ui create mode 100644 src/shared/qtgradienteditor/qtgradientviewdialog_p.h create mode 100644 src/shared/qtgradienteditor/qtgradientwidget.cpp create mode 100644 src/shared/qtgradienteditor/qtgradientwidget_p.h create mode 100644 src/shared/qtpropertybrowser/images/cursor-arrow.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-busy.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-closedhand.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-cross.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-forbidden.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-hand.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-hsplit.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-ibeam.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-openhand.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-sizeall.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-sizeb.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-sizef.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-sizeh.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-sizev.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-uparrow.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-vsplit.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-wait.png create mode 100644 src/shared/qtpropertybrowser/images/cursor-whatsthis.png create mode 100644 src/shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp create mode 100644 src/shared/qtpropertybrowser/qtbuttonpropertybrowser_p.h create mode 100644 src/shared/qtpropertybrowser/qteditorfactory.cpp create mode 100644 src/shared/qtpropertybrowser/qteditorfactory_p.h create mode 100644 src/shared/qtpropertybrowser/qtgroupboxpropertybrowser.cpp create mode 100644 src/shared/qtpropertybrowser/qtgroupboxpropertybrowser_p.h create mode 100644 src/shared/qtpropertybrowser/qtpropertybrowser.cpp create mode 100644 src/shared/qtpropertybrowser/qtpropertybrowser_p.h create mode 100644 src/shared/qtpropertybrowser/qtpropertybrowserutils.cpp create mode 100644 src/shared/qtpropertybrowser/qtpropertybrowserutils_p.h create mode 100644 src/shared/qtpropertybrowser/qtpropertymanager.cpp create mode 100644 src/shared/qtpropertybrowser/qtpropertymanager_p.h create mode 100644 src/shared/qtpropertybrowser/qttreepropertybrowser.cpp create mode 100644 src/shared/qtpropertybrowser/qttreepropertybrowser_p.h create mode 100644 src/shared/qtpropertybrowser/qtvariantproperty.cpp create mode 100644 src/shared/qtpropertybrowser/qtvariantproperty_p.h create mode 100644 src/shared/qttoolbardialog/images/back.png create mode 100644 src/shared/qttoolbardialog/images/down.png create mode 100644 src/shared/qttoolbardialog/images/forward.png create mode 100644 src/shared/qttoolbardialog/images/minus.png create mode 100644 src/shared/qttoolbardialog/images/plus.png create mode 100644 src/shared/qttoolbardialog/images/up.png create mode 100644 src/shared/qttoolbardialog/qttoolbardialog.cpp create mode 100644 src/shared/qttoolbardialog/qttoolbardialog.ui create mode 100644 src/shared/qttoolbardialog/qttoolbardialog_p.h create mode 100644 src/uiplugin/CMakeLists.txt create mode 100644 src/uiplugin/customwidget.h create mode 100644 src/uiplugin/customwidget.qdoc create mode 100644 src/uiplugin/qdesignerexportwidget.h create mode 100644 src/uitools/CMakeLists.txt create mode 100644 src/uitools/doc/images/textfinder-example-find.webp create mode 100644 src/uitools/doc/images/textfinder-example-find2.webp create mode 100644 src/uitools/doc/images/textfinder-example.png create mode 100644 src/uitools/doc/images/uitools-examples.png create mode 100644 src/uitools/doc/qtuitools.qdocconf create mode 100644 src/uitools/doc/snippets/quiloader/doc_src_qtuiloader.pro create mode 100644 src/uitools/doc/snippets/quiloader/main.cpp create mode 100644 src/uitools/doc/snippets/quiloader/myform.ui create mode 100644 src/uitools/doc/snippets/quiloader/mywidget.cpp create mode 100644 src/uitools/doc/snippets/quiloader/mywidget.h create mode 100644 src/uitools/doc/snippets/quiloader/mywidget.qrc create mode 100644 src/uitools/doc/snippets/quiloader/quiloader.pro create mode 100644 src/uitools/doc/src/qtuitools-examples.qdoc create mode 100644 src/uitools/doc/src/qtuitools-index.qdoc create mode 100644 src/uitools/doc/src/qtuitools-module.qdoc create mode 100644 src/uitools/qtuitoolsglobal.h create mode 100644 src/uitools/quiloader.cpp create mode 100644 src/uitools/quiloader.h create mode 100644 src/uitools/quiloader_p.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/README create mode 100644 tests/auto/CMakeLists.txt create mode 100644 tests/auto/bic/data/QtDesigner.6.0.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtDesigner.6.1.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtDesigner.6.2.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtHelp.6.0.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtHelp.6.1.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtHelp.6.2.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtUiTools.6.0.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtUiTools.6.1.0.linux-gcc-amd64.txt create mode 100644 tests/auto/bic/data/QtUiTools.6.2.0.linux-gcc-amd64.txt create mode 100644 tests/auto/cmake/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/build_time_checks.cmake create mode 100644 tests/auto/cmake/linguist/test_add_translation_macro/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_add_translation_macro/myi18nobject.cpp create mode 100644 tests/auto/cmake/linguist/test_add_translation_macro/myobject_de.ts create mode 100644 tests/auto/cmake/linguist/test_add_translation_macro/some_dir/some_include.h create mode 100644 tests/auto/cmake/linguist/test_create_translation_macro/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_create_translation_macro/myi18nobject.cpp create mode 100644 tests/auto/cmake/linguist/test_create_translation_macro/myobject_de.ts create mode 100644 tests/auto/cmake/linguist/test_create_translation_macro/some_dir/some_include.h create mode 100644 tests/auto/cmake/linguist/test_create_translation_same_base_names/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_create_translation_same_base_names/de/myobject.ts create mode 100644 tests/auto/cmake/linguist/test_create_translation_same_base_names/fr/myobject.ts create mode 100644 tests/auto/cmake/linguist/test_create_translation_same_base_names/myi18nobject.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_auto_ts_file_names/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_auto_ts_file_names/lib.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_auto_ts_file_names/subdir/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/apps/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/apps/app1/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/apps/app1/excluded1.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/apps/app1/main.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/apps/app1/subdir/excluded2.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/apps/app1/subdir/subdir3/excluded5.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/apps/app1/subdir2/excluded4.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/apps/shared/excluded3.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/check_ts_file.cmake create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/libs/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/libs/lib1/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/libs/lib1/lib1.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/libs/lib1/lib1.h create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/libs/lib2/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/libs/lib2/lib2.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/libs/lib2/lib2.h create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/test_i18n_exclusion_de.ts.in create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/tests/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/tests/test1/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_exclusion/tests/test1/test1.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_filter_autogen_files/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/main.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app1/mainwindow.ui create mode 100644 tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/main.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_filter_autogen_files/app2/mainwindow.ui create mode 100644 tests/auto/cmake/linguist/test_i18n_filter_autogen_files/check_ts_file.cmake create mode 100644 tests/auto/cmake/linguist/test_i18n_find_package_in_subdir/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_find_package_in_subdir/subdir/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_find_package_in_subdir/subdir/app1_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_find_package_in_subdir/subdir/main.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations/fake_translations/qtbase_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations/main.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations/myapp_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_language_match/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_language_match/fake_translations/qtbase_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_language_match/main.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_language_match/myapp_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_language_match/myapp_de_TSFOO_tag.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_language_match/myapp_de_language_after_TS.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_language_match/myapp_de_newlines.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_select_catalogs/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_select_catalogs/fake_translations/greenphone_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_select_catalogs/fake_translations/qtbase_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_select_catalogs/main.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_merge_qt_translations_select_catalogs/myapp_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_source_language/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_source_language/lib.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_source_language/lib2_en.ts.in create mode 100644 tests/auto/cmake/linguist/test_i18n_source_language/post_build_check.cmake create mode 100644 tests/auto/cmake/linguist/test_i18n_subdir/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_subdir/subdir/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_subdir/subdir/app1_de.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_subdir/subdir/main.cpp create mode 100644 tests/auto/cmake/linguist/test_i18n_subdir2/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_subdir2/subdir/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_i18n_subdir2/subdir/app1_fr.ts create mode 100644 tests/auto/cmake/linguist/test_i18n_subdir2/subdir/main.cpp create mode 100644 tests/auto/cmake/linguist/test_translation_api/CMakeLists.txt create mode 100644 tests/auto/cmake/linguist/test_translation_api/myi18nobject.cpp create mode 100644 tests/auto/cmake/linguist/test_translation_api/myobject_de.ts create mode 100644 tests/auto/cmake/linguist/test_translation_api/post_build_check.cmake create mode 100644 tests/auto/cmake/linguist/test_translation_api/some_dir/some_include.h create mode 100644 tests/auto/cmake/test_uiplugin_module/CMakeLists.txt create mode 100644 tests/auto/cmake/test_uiplugin_module/my_designer_plugin.cpp create mode 100644 tests/auto/cmake/test_uiplugin_via_designer/CMakeLists.txt create mode 100644 tests/auto/cmake/test_uiplugin_via_designer/my_designer_plugin.cpp create mode 100644 tests/auto/helpengineplugin/CMakeLists.txt create mode 100644 tests/auto/helpengineplugin/data/elements.qml create mode 100644 tests/auto/helpengineplugin/data/qtqml.qch create mode 100644 tests/auto/helpengineplugin/tst_helpengineplugin.cpp create mode 100644 tests/auto/linguist/CMakeLists.txt create mode 100644 tests/auto/linguist/lconvert/CMakeLists.txt create mode 100644 tests/auto/linguist/lconvert/data/REUSE.toml create mode 100644 tests/auto/linguist/lconvert/data/codec-utf8.ts create mode 100644 tests/auto/linguist/lconvert/data/emptymsg.ts create mode 100644 tests/auto/linguist/lconvert/data/endless-po-loop.ts create mode 100644 tests/auto/linguist/lconvert/data/idxmerge-add.ts create mode 100644 tests/auto/linguist/lconvert/data/idxmerge.ts create mode 100644 tests/auto/linguist/lconvert/data/idxmerge.ts.out create mode 100644 tests/auto/linguist/lconvert/data/msgid.ts create mode 100644 tests/auto/linguist/lconvert/data/phrasebook.qph create mode 100644 tests/auto/linguist/lconvert/data/plurals-cn.ts create mode 100644 tests/auto/linguist/lconvert/data/plurals-de.ts create mode 100644 tests/auto/linguist/lconvert/data/relative.ts create mode 100644 tests/auto/linguist/lconvert/data/singular.po create mode 100644 tests/auto/linguist/lconvert/data/test-broken-utf8.po create mode 100644 tests/auto/linguist/lconvert/data/test-broken-utf8.po.out create mode 100644 tests/auto/linguist/lconvert/data/test-developer-comment.po create mode 100644 tests/auto/linguist/lconvert/data/test-empty-comment.po create mode 100644 tests/auto/linguist/lconvert/data/test-escapes.po create mode 100644 tests/auto/linguist/lconvert/data/test-escapes.po.out create mode 100644 tests/auto/linguist/lconvert/data/test-kde-ctxt.po create mode 100644 tests/auto/linguist/lconvert/data/test-kde-fuzzy.po create mode 100644 tests/auto/linguist/lconvert/data/test-kde-multiline.po create mode 100644 tests/auto/linguist/lconvert/data/test-kde-plurals.po create mode 100644 tests/auto/linguist/lconvert/data/test-refs.po create mode 100644 tests/auto/linguist/lconvert/data/test-slurp.po create mode 100644 tests/auto/linguist/lconvert/data/test-slurp.po.out create mode 100644 tests/auto/linguist/lconvert/data/test-trans_seg.ts.out create mode 100644 tests/auto/linguist/lconvert/data/test-trans_seg.xlf create mode 100644 tests/auto/linguist/lconvert/data/test-translator-comment.po create mode 100644 tests/auto/linguist/lconvert/data/test1-cn.po create mode 100644 tests/auto/linguist/lconvert/data/test1-de.po create mode 100644 tests/auto/linguist/lconvert/data/test20.ts create mode 100644 tests/auto/linguist/lconvert/data/untranslated.qm create mode 100644 tests/auto/linguist/lconvert/data/untranslated.ts create mode 100644 tests/auto/linguist/lconvert/data/untranslated.ts.out create mode 100644 tests/auto/linguist/lconvert/data/variants.ts create mode 100644 tests/auto/linguist/lconvert/data/whitespace.ts create mode 100644 tests/auto/linguist/lconvert/data/wrapping.po create mode 100644 tests/auto/linguist/lconvert/tst_lconvert.cpp create mode 100644 tests/auto/linguist/lrelease/CMakeLists.txt create mode 100644 tests/auto/linguist/lrelease/testdata/compressed.ts create mode 100644 tests/auto/linguist/lrelease/testdata/dupes.errors create mode 100644 tests/auto/linguist/lrelease/testdata/dupes.ts create mode 100644 tests/auto/linguist/lrelease/testdata/idbased.ts create mode 100644 tests/auto/linguist/lrelease/testdata/no-translations.pro create mode 100644 tests/auto/linguist/lrelease/testdata/translate.ts create mode 100644 tests/auto/linguist/lrelease/tst_lrelease.cpp create mode 100644 tests/auto/linguist/lupdate/CMakeLists.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/backslashes/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/backslashes/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/backslashes/src/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/backslashes/ts/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/cmdline_deeppath/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/cmdline_deeppath/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/cmdline_order/a.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/cmdline_order/b.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/cmdline_order/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/cmdline_order/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/cmdline_recurse/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/cmdline_recurse/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/codecforsrc/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/codecforsrc/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/codecforsrc/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/from_subdir/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/from_subdir/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/from_subdir/src/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/from_subdir/src/main.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/from_subdir/translations/translations.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/heuristics/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/heuristics/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/heuristics/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/heuristics/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/heuristics/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/heuristics/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/lacksqobject/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/lacksqobject/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/lacksqobject/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/lacksqobject/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/language/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/language/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/language/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/language/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_ordering/foo.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_ordering/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_ordering/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_ordering/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_ordering/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_versions/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_versions/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_versions/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_versions/project.ui create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_whitespace/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_whitespace/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_whitespace/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/merge_whitespace/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp/finddialog.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_noobsolete/finddialog.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_noobsolete/finddialog.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_noobsolete/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_noobsolete/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_noobsolete/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_noobsolete/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_obsolete/finddialog.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_obsolete/finddialog.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_obsolete/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_obsolete/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergecpp_obsolete/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergeui/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergeui/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergeui/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergeui/project.ui create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergeui_obsolete/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergeui_obsolete/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergeui_obsolete/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/mergeui_obsolete/project.ui create mode 100644 tests/auto/linguist/lupdate/testdata/good/multiple_locations/finddialog.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/multiple_locations/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/multiple_locations/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/multiple_locations/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/namespaces/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/namespaces/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/namespaces/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/notargetlanguage/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/notargetlanguage/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/notargetlanguage/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/notargetlanguage/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parse_escaped_chars/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parse_escaped_chars/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parse_escaped_chars/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parse_special_chars/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parse_special_chars/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parse_special_chars/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecontexts/external_types.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecontexts/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecontexts/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecontexts/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp/excluded.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp/finddialog.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp/included.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp/notincluded.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp2/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp2/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp2/main.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp2/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp2/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_attributes/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_attributes/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_attributes/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_attributes/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_expression/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_expression/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_expression/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_expression/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_template/expectedoutput create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_template/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_template/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_template/template_classes.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_typed_enum/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_typed_enum/my_class.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_typed_enum/my_class.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_typed_enum/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsecpp_typed_enum/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseidbasedui/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseidbasedui/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseidbasedui/project.ui create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejava/main.java create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejava/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejava/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs/main.js create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs2/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs2/main.js create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs2/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs2/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs3/main.js create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs3/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs3/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs4/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs4/main.js create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs4/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejs4/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejscontexts/main.js create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejscontexts/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsejscontexts/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsemjs/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsemjs/main.mjs create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsemjs/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsemjs/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseobjc/main.mm create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseobjc/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseobjc/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsepython/main.py create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsepython/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parsepython/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml/main.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml/main_test_pragma.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml2/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml2/main.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml2/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml2/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml3/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml3/main.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml3/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqml3/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc/main.js create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc/main.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc/project.qrc create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc_json/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc_json/main.js create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc_json/main.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc_json/project.json create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc_json/project.qrc create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseqrc_json/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseui/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseui/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/parseui/project.ui create mode 100644 tests/auto/linguist/lupdate/testdata/good/prefix/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/prefix/main.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/prefix/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/prefix/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/preprocess/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/preprocess/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/preprocess/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/features/default_pre.prf create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/main.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/main_mac.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/main_unix.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/main_win.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/qml/excluded.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/qml/notmain.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/vpaths/dependpath/main_dependpath.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/wildcard/main1.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/wildcard/mainfile.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/wildcard1.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing/wildcard99.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/a create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/a.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/b create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/b.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/e create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/f/g.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/files-cc.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/include.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/spaces/z create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/variable_with_spaces create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/with create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/x/d create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsing2/x/variable create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpaths/file1.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpaths/filter.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpaths/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpaths/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpaths/sub/sub.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpaths/sub/subfile1.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpaths/sub/subfilter.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/common/common.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/common/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/common/main.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/mac/mac.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/mac/main_mac.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/relativity/relativity.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/relativity/relativity.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/relativity/sub/sub.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/relativity/sub2/sub2.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/unix/main_unix.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/unix/unix.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/win/main_win.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingpri/win/win.pri create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubdirs/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubdirs/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubdirs/sub1/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubdirs/sub1/sub1.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/common/common.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/common/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/excluded/excluded.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/excluded/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/mac/mac.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/mac/main_mac.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/sub/include/test.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/sub/src/test.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/sub/sub.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/unix/main_unix.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/unix/unix.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/win/main_win.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/proparsingsubs/win/win.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full/project_sub.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full_ts/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full_ts/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full_ts/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full_ts/project_sub.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full_ts/project_sub.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full_ts_join/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full_ts_join/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_full_ts_join/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part/project_sub.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part_ts/expectedoutput.txt create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part_ts/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part_ts/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part_ts/project_sub.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/recurse_part_ts/project_sub.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/reloutput/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/reloutput/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/reloutput/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/reloutput/translations/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/resources_cmdline/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/resources_cmdline/main.js create mode 100644 tests/auto/linguist/lupdate/testdata/good/resources_cmdline/main.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/resources_cmdline/project.qrc create mode 100644 tests/auto/linguist/lupdate/testdata/good/resources_cmdline/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/respfile/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/respfile/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/respfile/source1.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/respfile/source2.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/respfile/sources.lst create mode 100644 tests/auto/linguist/lupdate/testdata/good/respfile/tsfiles.lst create mode 100644 tests/auto/linguist/lupdate/testdata/good/textsimilarity/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/textsimilarity/project.ts.before create mode 100644 tests/auto/linguist/lupdate/testdata/good/textsimilarity/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/textsimilarity/project.ui create mode 100644 tests/auto/linguist/lupdate/testdata/good/tr_function_alias/lupdatecmd create mode 100644 tests/auto/linguist/lupdate/testdata/good/tr_function_alias/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/tr_function_alias/main.qml create mode 100644 tests/auto/linguist/lupdate/testdata/good/tr_function_alias/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/tr_function_alias/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/good/using_namespaces/file.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/good/using_namespaces/file.h create mode 100644 tests/auto/linguist/lupdate/testdata/good/using_namespaces/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/good/using_namespaces/project.ts.result create mode 100644 tests/auto/linguist/lupdate/testdata/recursivescan/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/recursivescan/project.ui create mode 100644 tests/auto/linguist/lupdate/testdata/recursivescan/sub/filetypes/main.c++ create mode 100644 tests/auto/linguist/lupdate/testdata/recursivescan/sub/filetypes/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/recursivescan/sub/filetypes/main.cxx create mode 100644 tests/auto/linguist/lupdate/testdata/recursivescan/sub/finddialog.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_full/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_full/subdir1/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_full/subdir1/subdir1.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_full/subdir2/subdir2.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_full/subdir2/subsub1/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_full/subdir2/subsub1/subsub1.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_full/subdir2/subsub2/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_full/subdir2/subsub2/subsub2.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_part/project.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_part/subdir1/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_part/subdir1/subdir1.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_part/subdir2/subdir2.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_part/subdir2/subsub1/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_part/subdir2/subsub1/subsub1.pro create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_part/subdir2/subsub2/main.cpp create mode 100644 tests/auto/linguist/lupdate/testdata/subdirs_part/subdir2/subsub2/subsub2.pro create mode 100644 tests/auto/linguist/lupdate/tst_lupdate.cpp create mode 100644 tests/auto/qdoc/CMakeLists.txt create mode 100644 tests/auto/qhelpcontentmodel/CMakeLists.txt create mode 100644 tests/auto/qhelpcontentmodel/data/collection.qhc create mode 100644 tests/auto/qhelpcontentmodel/data/qmake-3.3.8.qch create mode 100644 tests/auto/qhelpcontentmodel/data/qmake-4.3.0.qch create mode 100644 tests/auto/qhelpcontentmodel/data/test.qch create mode 100644 tests/auto/qhelpcontentmodel/tst_qhelpcontentmodel.cpp create mode 100644 tests/auto/qhelpenginecore/CMakeLists.txt create mode 100644 tests/auto/qhelpenginecore/data/collection.qhc create mode 100644 tests/auto/qhelpenginecore/data/collection1.qhc create mode 100644 tests/auto/qhelpenginecore/data/linguist-3.3.8.qch create mode 100644 tests/auto/qhelpenginecore/data/qmake-3.3.8.qch create mode 100644 tests/auto/qhelpenginecore/data/qmake-4.3.0.qch create mode 100644 tests/auto/qhelpenginecore/data/test.html create mode 100644 tests/auto/qhelpenginecore/data/test.qch create mode 100644 tests/auto/qhelpenginecore/tst_qhelpenginecore.cpp create mode 100644 tests/auto/qhelpgenerator/CMakeLists.txt create mode 100644 tests/auto/qhelpgenerator/data/cars.html create mode 100644 tests/auto/qhelpgenerator/data/classic.css create mode 100644 tests/auto/qhelpgenerator/data/fancy.html create mode 100644 tests/auto/qhelpgenerator/data/people.html create mode 100644 tests/auto/qhelpgenerator/data/sub/about.html create mode 100644 tests/auto/qhelpgenerator/data/test.html create mode 100644 tests/auto/qhelpgenerator/data/test.qhp create mode 100644 tests/auto/qhelpgenerator/tst_qhelpgenerator.cpp create mode 100644 tests/auto/qhelpindexmodel/CMakeLists.txt create mode 100644 tests/auto/qhelpindexmodel/data/collection.qhc create mode 100644 tests/auto/qhelpindexmodel/data/collection1.qhc create mode 100644 tests/auto/qhelpindexmodel/data/linguist-3.3.8.qch create mode 100644 tests/auto/qhelpindexmodel/data/qmake-3.3.8.qch create mode 100644 tests/auto/qhelpindexmodel/data/qmake-4.3.0.qch create mode 100644 tests/auto/qhelpindexmodel/data/test.html create mode 100644 tests/auto/qhelpindexmodel/data/test.qch create mode 100644 tests/auto/qhelpindexmodel/tst_qhelpindexmodel.cpp create mode 100644 tests/auto/qhelpprojectdata/CMakeLists.txt create mode 100644 tests/auto/qhelpprojectdata/data/test.qhp create mode 100644 tests/auto/qhelpprojectdata/tst_qhelpprojectdata.cpp create mode 100644 tests/auto/qtattributionsscanner/CMakeLists.txt create mode 100644 tests/auto/qtattributionsscanner/testdata/good/chromium/README_test.chromium create mode 100644 tests/auto/qtattributionsscanner/testdata/good/complete/qt_attribution_test.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/expected.error create mode 100644 tests/auto/qtattributionsscanner/testdata/good/expected.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/licenses-dir/expected.error create mode 100644 tests/auto/qtattributionsscanner/testdata/good/licenses-dir/expected.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/licenses-dir/qt_attribution_test.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/local_license/LICENSE.Id1.txt create mode 100644 tests/auto/qtattributionsscanner/testdata/good/local_license/LICENSE.Id2.txt create mode 100644 tests/auto/qtattributionsscanner/testdata/good/local_license/expected.error create mode 100644 tests/auto/qtattributionsscanner/testdata/good/local_license/expected.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/local_license/qt_attribution_test.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/minimal/expected.error create mode 100644 tests/auto/qtattributionsscanner/testdata/good/minimal/expected.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/minimal/qt_attribution_test.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/variants/COPYRIGHT.txt create mode 100644 tests/auto/qtattributionsscanner/testdata/good/variants/LICENSE.1.txt create mode 100644 tests/auto/qtattributionsscanner/testdata/good/variants/LICENSE.2.txt create mode 100644 tests/auto/qtattributionsscanner/testdata/good/variants/expected.error create mode 100644 tests/auto/qtattributionsscanner/testdata/good/variants/expected.json create mode 100644 tests/auto/qtattributionsscanner/testdata/good/variants/qt_attribution_test.json create mode 100644 tests/auto/qtattributionsscanner/testdata/warnings/incomplete/expected.error create mode 100644 tests/auto/qtattributionsscanner/testdata/warnings/incomplete/qt_attribution_test.json create mode 100644 tests/auto/qtattributionsscanner/testdata/warnings/unknown/expected.error create mode 100644 tests/auto/qtattributionsscanner/testdata/warnings/unknown/qt_attribution_test.json create mode 100644 tests/auto/qtattributionsscanner/tst_qtattributionsscanner.cpp create mode 100644 tests/auto/qtdiag/CMakeLists.txt create mode 100644 tests/auto/qtdiag/tst_qtdiag.cpp create mode 100644 tests/global/global.cfg create mode 100644 tests/manual/CMakeLists.txt create mode 100644 tests/manual/manual.pro create mode 100644 tests/manual/qtattributionsscanner/CMakeLists.txt create mode 100644 tests/manual/qtattributionsscanner/data/LICENSE create mode 100644 tests/manual/qtattributionsscanner/data/qt_attribution_test.json create mode 100644 tests/manual/qtattributionsscanner/overview.qdoc create mode 100644 tests/manual/qtattributionsscanner/qtattributionsscanner.pro create mode 100644 tests/manual/qtattributionsscanner/test.qdocconf create mode 100644 util/recolordocsicons/README.md create mode 100644 util/recolordocsicons/recolordocsicons.py diff --git a/.cmake.conf b/.cmake.conf new file mode 100644 index 0000000..04c152f --- /dev/null +++ b/.cmake.conf @@ -0,0 +1,7 @@ +set(QT_REPO_MODULE_VERSION "6.10.2") +set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") +set(QT_EXTRA_INTERNAL_TARGET_DEFINES + "QT_NO_QASCONST=1" + "QT_NO_CONTEXTLESS_CONNECT=1" + "QT_NO_FOREACH=1" +) diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3b0405e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/assistant/qlitehtml"] + path = src/assistant/qlitehtml + url = https://code.qt.io/playground/qlitehtml.git diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..57ae536 --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=codereview.qt-project.org +project=qt/qttools +defaultbranch=dev diff --git a/.tag b/.tag new file mode 100644 index 0000000..bac71ac --- /dev/null +++ b/.tag @@ -0,0 +1 @@ +171ae9df0d84ee5133193cd3e27848fd73601c53 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..39ea4e4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +cmake_minimum_required(VERSION 3.16) + +include(.cmake.conf) +project(QtTools + VERSION "${QT_REPO_MODULE_VERSION}" + DESCRIPTION "Qt Tools" + HOMEPAGE_URL "https://qt.io/" + LANGUAGES CXX C +) + +# add platform specific compontents +set(optional_components "") +if(WIN32) + list(APPEND optional_components AxContainer) +endif() + +find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core) +find_package(Qt6 ${PROJECT_VERSION} QUIET CONFIG OPTIONAL_COMPONENTS + DBus Network Xml Widgets Quick QuickWidgets Qml QmlLSPrivate + Sql PrintSupport OpenGL OpenGLWidgets QuickLayouts ${optional_components}) +qt_internal_project_setup() + +qt_build_repo() + +# Add tool dependencies that were deferred by qt_internal_add_docs. +qt_internal_add_deferred_dependencies() diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt new file mode 100644 index 0000000..136d900 --- /dev/null +++ b/LICENSES/Apache-2.0.txt @@ -0,0 +1,61 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 0000000..b91bbd8 --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,9 @@ +Copyright (c) . + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/BSL-1.0.txt b/LICENSES/BSL-1.0.txt new file mode 100644 index 0000000..9cc4741 --- /dev/null +++ b/LICENSES/BSL-1.0.txt @@ -0,0 +1,9 @@ + + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/LICENSES/GFDL-1.3-no-invariants-only.txt b/LICENSES/GFDL-1.3-no-invariants-only.txt new file mode 100644 index 0000000..857214d --- /dev/null +++ b/LICENSES/GFDL-1.3-no-invariants-only.txt @@ -0,0 +1,451 @@ + + GNU Free Documentation License + Version 1.3, 3 November 2008 + + + Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The "publisher" means any person or entity that distributes copies of +the Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no +other conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to +give them a chance to provide you with an updated version of the +Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other +documents released under this License, and replace the individual +copies of this License in the various documents with a single copy +that is included in the collection, provided that you follow the rules +of this License for verbatim copying of each of the documents in all +other respects. + +You may extract a single document from such a collection, and +distribute it individually under this License, provided you insert a +copy of this License into the extracted document, and follow this +License in all other respects regarding verbatim copying of that +document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the +GNU Free Documentation License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. See +https://www.gnu.org/licenses/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +"Massive Multiauthor Collaboration" (or "MMC") contained in the site +means any set of copyrightable works thus published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in +part, as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole or +in part into the MMC, (1) had no cover texts or invariant sections, and +(2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/LICENSES/GPL-2.0-only.txt b/LICENSES/GPL-2.0-only.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSES/GPL-2.0-only.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LICENSES/GPL-3.0-only.txt b/LICENSES/GPL-3.0-only.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSES/GPL-3.0-only.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/LICENSES/LGPL-3.0-only.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/LICENSES/LLVM-exception.txt b/LICENSES/LLVM-exception.txt new file mode 100644 index 0000000..733e9b0 --- /dev/null +++ b/LICENSES/LLVM-exception.txt @@ -0,0 +1,14 @@ +LLVM Exceptions to the Apache 2.0 License + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you may +redistribute such embedded portions in such Object form without complying with +the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a court +of competent jurisdiction determines that the patent provision (Section 3), +the indemnity provision (Section 9) or other Section of the License conflicts +with the conditions of the GPLv2, you may retroactively and prospectively +choose to deem waived or otherwise exclude such Section(s) of the License, +but only in their entirety and only with respect to the Combined Software. diff --git a/LICENSES/LicenseRef-Qt-Commercial.txt b/LICENSES/LicenseRef-Qt-Commercial.txt new file mode 100644 index 0000000..825b1f3 --- /dev/null +++ b/LICENSES/LicenseRef-Qt-Commercial.txt @@ -0,0 +1,8 @@ +Licensees holding valid commercial Qt licenses may use this software in +accordance with the the terms contained in a written agreement between +you and The Qt Company. Alternatively, the terms and conditions that were +accepted by the licensee when buying and/or downloading the +software do apply. + +For the latest licensing terms and conditions, see https://www.qt.io/terms-conditions. +For further information use the contact form at https://www.qt.io/contact-us. diff --git a/LICENSES/Qt-GPL-exception-1.0.txt b/LICENSES/Qt-GPL-exception-1.0.txt new file mode 100644 index 0000000..d0322bf --- /dev/null +++ b/LICENSES/Qt-GPL-exception-1.0.txt @@ -0,0 +1,22 @@ +The Qt Company GPL Exception 1.0 + +Exception 1: + +As a special exception you may create a larger work which contains the +output of this application and distribute that work under terms of your +choice, so long as the work is not otherwise derived from or based on +this application and so long as the work does not in itself generate +output that contains the output from this application in its original +or modified form. + +Exception 2: + +As a special exception, you have permission to combine this application +with Plugins licensed under the terms of your choice, to produce an +executable, and to copy and distribute the resulting executable under +the terms of your choice. However, the executable must be accompanied +by a prominent notice offering all users of the executable the entire +source code to this application, excluding the source code of the +independent modules, but including any changes you have made to this +application, under the terms of this license. + diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..ec25c51 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,164 @@ +version = 1 + +[[annotations]] +path = ["src/qdbus/**", + "src/linguist/**", + "src/distancefieldgenerator/mainwindow.ui", + "src/designer/src/designer/**", + "src/designer/src/components/**", + "src/designer/data/**", + "src/assistant/assistant/**"] +precedence = "closest" +comment = "tool, according to licenseRule.json" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0" + +[[annotations]] +path = ["src/shared/**", + "src/designer/src/lib/**", + "src/designer/src/plugins/**", + "src/assistant/help/**"] +precedence = "closest" +comment = "module and plugin, according to licenseRule.json" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only" + +[[annotations]] +path = ["src/designer/**"] +precedence = "closest" +comment = "tools, according to licenseRule.json" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0" + +[[annotations]] +path = ["src/designer/src/lib/uilib/**", + "src/designer/src/plugins/**"] +precedence = "closest" +comment = "module and plugin, according to licenseRule.json" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only" + +[[annotations]] +path = ["tests/**.ts*", + "tests/**.ui", + "tests/**.html", + "tests/**.qch", + "tests/**.qhc", + "tests/**.qhp", + "tests/**.qph", + "tests/**.css", + "tests/**.qml", + "tests/**.txt", + "tests/**cmd", + "tests/**.po*", + "tests/**.js", + "tests/**.json", + "tests/**.lst", + "tests/**.py", + "tests/**expected.error", + "tests/**main.mjs", + "tests/auto/cmake/linguist/test_i18n_auto_ts_file_names/lib.cpp", + "tests/auto/linguist/lconvert/data/test-trans_seg.xlf", + "tests/auto/linguist/lconvert/data/untranslated.qm", + "tests/auto/linguist/lrelease/testdata/dupes.errors", + "tests/auto/linguist/lupdate/testdata/good/language/main.cpp", + "tests/auto/linguist/lupdate/testdata/good/proparsing2/include.h", + "src/qdoc/qdoc/tests/**xml", + "src/qdoc/qdoc/tests/**.html", + "src/qdoc/qdoc/tests/**.png", + "src/qdoc/qdoc/tests/**.index", + "src/qdoc/qdoc/tests/generatedoutput/testdata/**", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**", + "src/qdoc/qdoc/tests/config/testdata/**", + "src/qdoc/qdoc/tests/qdoccommandlineparser/tst_arguments.txt", + "tests/auto/linguist/lupdate/testdata/good/parsecpp_typed_enum/my_class.cpp", + "tests/auto/linguist/lupdate/testdata/good/parsecpp_typed_enum/my_class.h", + "tests/auto/linguist/lupdate/testdata/good/parsecpp_template/template_classes.cpp"] +precedence = "closest" +comment = "test" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only" + +[[annotations]] +path = ["**.pro", "**.qrc", "**CMakeLists.txt", ".cmake.conf", "**.yaml", + "coin/axivion/ci_config_linux.json", + "**.cfg", "**.plist", "**.pri", "**.prf", ".tag"] +precedence = "closest" +comment = "build system" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "BSD-3-Clause" + +[[annotations]] +path = ["**/.gitattributes", "**.gitignore", ".gitmodules", "**.gitreview"] +precedence = "closest" +comment = "version control system. Infrastructure" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" + +[[annotations]] +path = ["**/doc/snippets/**", "examples/**", "src/qdoc/qdoc/doc/examples/**", + "src/qdoc/qdoc/tests/**/testdata/**/examples/**"] +comment = "this must be after the build system table because example and snippets take precedence over build system" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" + +[[annotations]] +path = ["**/doc/images/**", "**/README*", "**.qdocconf", "**.qdoc.sample", "**.qdoc", "**.qdocinc"] +comment = "documentation" +precedence = "closest" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only" + +[[annotations]] +path = ["src/qdoc/qdoc/**/testdata/**.qdocconf", + "src/qdoc/qdoc/**/testdata/**.qdoc", + "src/qdoc/qdoc/**/testdata/**.qdocinc"] +precedence = "closest" +comment = "test" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only" + + +[[annotations]] +path = ["**.toml", "licenseRule.json"] +comment = "infrastructure" +precedence = "override" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" + +[[annotations]] +path = ["**/qt_attribution.json"] +comment = "documentation" +precedence = "override" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only" + +[[annotations]] +path = "examples/linguist/trollprint/trollprint_pt.ts" +precedence = "override" +comment = "example. Copyright in file needs to be ignored" +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" + +[[annotations]] +path = ["src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.webxml", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.html", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.cpp", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.h", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.qml", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.qdoc", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.qdoc.sample", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.qdocconf", + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/**.xml"] +precedence = "override" +comment = ["reuse ignore or have trouble reading some of those webxml or html files", + "files in this directory are for file generation within testing", + "the licensing does not necessary correspond to the file", + "Licensing for those files is test licensing"] +SPDX-FileCopyrightText = "Copyright (C) 2016 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only" + + + + diff --git a/cmake/FindWrapLibClang.cmake b/cmake/FindWrapLibClang.cmake new file mode 100644 index 0000000..b0312c9 --- /dev/null +++ b/cmake/FindWrapLibClang.cmake @@ -0,0 +1,182 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(TARGET WrapLibClang::WrapLibClang) + set(WrapLibClang_FOUND TRUE) + return() +endif() + +if(DEFINED ENV{LLVM_INSTALL_DIR}) + set(__qt_wrap_clang_backup_prefix "${CMAKE_PREFIX_PATH}") + list(PREPEND CMAKE_PREFIX_PATH "$ENV{LLVM_INSTALL_DIR}") +elseif(DEFINED CACHE{LLVM_INSTALL_DIR}) + set(__qt_wrap_clang_backup_prefix "${CMAKE_PREFIX_PATH}") + list(PREPEND CMAKE_PREFIX_PATH "${LLVM_INSTALL_DIR}") +endif() + +include(FindPackageHandleStandardArgs) +set(WrapLibClang_FOUND FALSE) + +# Extract major.minor.patch version from version string for developer builds. +function(normalize_version_for_dev_build IN OUT) + if(QT_FEATURE_developer_build) + string(REGEX MATCH "^([0-9]+\\.[0-9]+\\.[0-9]+)" _clean "${${IN}}") + if(_clean STREQUAL "") + set(_clean "${${IN}}") + endif() + set(${OUT} "${_clean}" PARENT_SCOPE) + else() + set(${OUT} "${${IN}}" PARENT_SCOPE) + endif() +endfunction() + +# Find the zstd package before llvm gets a chance to plant its Findzstd.cmake on us. That find +# module is most likely inconsistent with your system-provided llvmConfig.cmake, leading to +# configuration errors. Disable find_package(zstd) within llvm if FindWrapZSTD.cmake was successful. +# Upstream issue: https://github.com/llvm/llvm-project/issues/139666 +if(QT_FEATURE_zstd) + find_package(WrapZSTD QUIET) + set(__qt_wraplibclang_CMAKE_DISABLE_FIND_PACKAGE_zstd ${CMAKE_DISABLE_FIND_PACKAGE_zstd}) + if(WrapZSTD_FOUND) + set(CMAKE_DISABLE_FIND_PACKAGE_zstd TRUE) + endif() +endif() + +if(QT_NO_FIND_PACKAGE_CLANG_WORKAROUND) + set(Clang_FOUND FALSE) + foreach(VERSION ${QDOC_SUPPORTED_CLANG_VERSIONS}) + if(NOT Clang_FOUND) + normalize_version_for_dev_build(VERSION VERSION_CLEAN) + find_package(Clang ${VERSION_CLEAN} CONFIG QUIET) + endif() + endforeach() +else() + set(__qt_wraplibclang_message + "This probably means that one or more packages necessary for find_package(Clang) are not" + "installed. See below for more information. You can turn off this pre-check by setting the" + "CMake variable QT_NO_FIND_PACKAGE_CLANG_WORKAROUND to ON." + ) + + # Try to find the LLVM package. ClangConfig.cmake has a find_package(LLVM REQUIRED) call, which + # will break if clang is installed but the LLVM CMake files are not installed. + set(LLVM_FOUND FALSE) + foreach(VERSION ${QDOC_SUPPORTED_CLANG_VERSIONS}) + if(NOT LLVM_FOUND) + normalize_version_for_dev_build(VERSION VERSION_CLEAN) + find_package(LLVM ${VERSION_CLEAN} CONFIG QUIET) + endif() + endforeach() + if(NOT LLVM_FOUND) + list(PREPEND __qt_wraplibclang_message "The LLVM package could not be found.") + string(REPLACE ";" " " __qt_wraplibclang_message "${__qt_wraplibclang_message}") + find_package_handle_standard_args(WrapLibClang + REQUIRED_VARS WrapLibClang_FOUND + REASON_FAILURE_MESSAGE "${__qt_wraplibclang_message}") + unset(__qt_wraplibclang_message) + return() + endif() + + # Try to find libClang libraries - either one of the static libs or the whole shared object. + # ClangTargets.cmake checks for the presence of these libraries. + find_library(__qt_wraplibclang clangBasic HINTS ${LLVM_LIBRARY_DIRS}) + if(__qt_wraplibclang STREQUAL "__qt_wraplibclang-NOTFOUND") + unset(__qt_wraplibclang CACHE) + find_library(__qt_wraplibclang clang HINTS ${LLVM_LIBRARY_DIRS}) + endif() + if(__qt_wraplibclang STREQUAL "__qt_wraplibclang-NOTFOUND") + unset(__qt_wraplibclang CACHE) + list(PREPEND __qt_wraplibclang_message "The clang libraries could not be located.") + string(REPLACE ";" " " __qt_wraplibclang_message "${__qt_wraplibclang_message}") + find_package_handle_standard_args(WrapLibClang + REQUIRED_VARS WrapLibClang_FOUND + REASON_FAILURE_MESSAGE "${__qt_wraplibclang_message}") + unset(__qt_wraplibclang_message) + return() + endif() + unset(__qt_wraplibclang CACHE) + + # Now, we're pretty certain that we can find the 'Clang' package without running into errors. + normalize_version_for_dev_build(LLVM_VERSION LLVM_VERSION_CLEAN) + find_package(Clang ${LLVM_VERSION_CLEAN} EXACT CONFIG) +endif() + +if(QT_FEATURE_zstd) + set(CMAKE_DISABLE_FIND_PACKAGE_zstd ${__qt_wraplibclang_CMAKE_DISABLE_FIND_PACKAGE_zstd}) +endif() + +# LLVM versions >= 16 come with Findzstd.cmake that creates a target for libzstd. +# Disable its global promotion to prevent interference with FindWrapZSTD.cmake. +if(TARGET zstd::libzstd) + qt_internal_disable_find_package_global_promotion(zstd::libzstd) +endif() +if(TARGET zstd::libzstd_shared) + qt_internal_disable_find_package_global_promotion(zstd::libzstd_shared) +endif() +if(TARGET zstd::libzstd_static) + qt_internal_disable_find_package_global_promotion(zstd::libzstd_static) +endif() + +if(__qt_wrap_clang_backup_prefix) + set(CMAKE_PREFIX_PATH "${__qt_wrap_clang_backup_prefix}") + unset(__qt_wrap_clang_backup_prefix) +endif() + +set(__wrap_lib_clang_requested_version_found FALSE) + +# Need to explicitly handle the version check, because the Clang package doesn't. +if(WrapLibClang_FIND_VERSION AND LLVM_PACKAGE_VERSION + AND LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL "${WrapLibClang_FIND_VERSION}") + set(__wrap_lib_clang_requested_version_found TRUE) +endif() + +if(TARGET libclang AND ((TARGET clang-cpp AND TARGET LLVM) OR TARGET clangHandleCXX) AND __wrap_lib_clang_requested_version_found) + set(WrapLibClang_FOUND TRUE) + + get_target_property(type libclang TYPE) + if (MSVC AND type STREQUAL "STATIC_LIBRARY") + get_property(__wrap_lib_clang_multi_config + GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(__wrap_lib_clang_multi_config) + set(__wrap_lib_clang_configs ${CMAKE_CONFIGURATION_TYPES}) + else() + set(__wrap_lib_clang_configs ${CMAKE_BUILD_TYPE}) + endif() + set(__wrap_lib_clang_non_release_configs ${configs}) + list(REMOVE_ITEM __wrap_lib_clang_non_release_configs + Release MinSizeRel RelWithDebInfo) + if(__wrap_lib_clang_non_release_configs STREQUAL __wrap_lib_clang_configs) + message(STATUS "Static linkage against libclang with MSVC was requested, but the build is not a release build, therefore libclang cannot be used.") + set(WrapLibClang_FOUND FALSE) + endif() + endif() + + if(WrapLibClang_FOUND) + add_library(WrapLibClang::WrapLibClang IMPORTED INTERFACE) + + target_include_directories(WrapLibClang::WrapLibClang INTERFACE ${CLANG_INCLUDE_DIRS}) + if (NOT TARGET Threads::Threads) + find_package(Threads) + endif() + qt_internal_disable_find_package_global_promotion(Threads::Threads) + # lupdate must also link to LLVM when using clang-cpp + set(__qt_clang_genex_condition "$,$>") + set(__qt_clang_genex "$") + target_link_libraries(WrapLibClang::WrapLibClang + INTERFACE libclang + "${__qt_clang_genex}" + Threads::Threads + ) + + foreach(version MAJOR MINOR PATCH) + set(QT_LIB_CLANG_VERSION_${version} ${LLVM_VERSION_${version}} CACHE STRING "" FORCE) + endforeach() + set(QT_LIB_CLANG_VERSION ${LLVM_PACKAGE_VERSION} CACHE STRING "" FORCE) + set(QT_LIB_CLANG_LIBDIR "${LLVM_LIBRARY_DIRS}" CACHE STRING "" FORCE) + set(QT_LIBCLANG_RESOURCE_DIR + "\"${QT_LIB_CLANG_LIBDIR}/clang/${QT_LIB_CLANG_VERSION}/include\"" CACHE STRING "" FORCE) + endif() +endif() + +find_package_handle_standard_args(WrapLibClang + REQUIRED_VARS WrapLibClang_FOUND + VERSION_VAR LLVM_PACKAGE_VERSION) diff --git a/coin/axivion/ci_config_linux.json b/coin/axivion/ci_config_linux.json new file mode 100644 index 0000000..566ee44 --- /dev/null +++ b/coin/axivion/ci_config_linux.json @@ -0,0 +1,25 @@ +{ + "Project": { + "BuildSystemIntegration": { + "child_order": [ + "LinkLibraries" + ] + }, + "LinkLibraries": { + "_active": true, + "_copy_from": "AxivionLinker", + "input_files": [ + "$(splitpath:TARGET_NAME)" + ], + "ir": "$(env:IRNAME)" + } + }, + "_Format": "1.0", + "_Version": "7.6.2", + "_VersionNum": [ + 7, + 6, + 2, + 12725 + ] +} diff --git a/coin/axivion/start_analysis.sh b/coin/axivion/start_analysis.sh new file mode 100755 index 0000000..9cff1ee --- /dev/null +++ b/coin/axivion/start_analysis.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +$HOME/bauhaus-suite/setup.sh --non-interactive +export PATH=/home/qt/bauhaus-suite/bin:$PATH +export BAUHAUS_CONFIG=$(cd $(dirname $(readlink -f $0)) && pwd) +export AXIVION_VERSION_NAME=$(git rev-parse HEAD) +export EXCLUDE_FILES="build/*:src/3rdparty/*" +export CAFECC_BASEPATH="/home/qt/work/qt/$TESTED_MODULE_COIN" +gccsetup --cc gcc --cxx g++ --config "$BAUHAUS_CONFIG" +cd "$CAFECC_BASEPATH" +BAUHAUS_IR_COMPRESSION=none COMPILE_ONLY=1 cmake -G Ninja -DAXIVION_ANALYSIS_TOOLCHAIN_FILE=/home/qt/bauhaus-suite/profiles/cmake/axivion-launcher-toolchain.cmake -DCMAKE_PREFIX_PATH=/home/qt/work/qt/qttools/build -DCMAKE_PROJECT_INCLUDE_BEFORE=/home/qt/bauhaus-suite/profiles/cmake/axivion-before-project-hook.cmake -B build -S . --fresh +cmake --build build -j4 +for MODULE in qtassistant qtdesigner qthelp qtuitools linguist; do + export MODULE + export PLUGINS="" + export IRNAME=build/$MODULE.ir + if [ "$MODULE" == "qtassistant" ] + then + export TARGET_NAME="build/bin/assistant.ir" + export PACKAGE="Add-ons" + elif [ "$MODULE" == "qtdesigner" ] + then + export TARGET_NAME="build/bin/designer.ir" + export PACKAGE="Add-ons" + elif [ "$MODULE" == "qthelp" ] + then + export TARGET_NAME="build/lib/libQt6Help.so.*.ir" + export PACKAGE="Add-ons" + elif [ "$MODULE" == "qtuitools" ] + then + export TARGET_NAME="build/lib/libQt6UiTools.so.*.ir" + export PACKAGE="Add-ons" + elif [ "$MODULE" == "linguist" ] + then + export TARGET_NAME="build/bin/linguist.ir" + export PACKAGE="Add-ons" + fi + axivion_ci "$@" +done diff --git a/coin/module_config.yaml b/coin/module_config.yaml new file mode 100644 index 0000000..6614eb6 --- /dev/null +++ b/coin/module_config.yaml @@ -0,0 +1,23 @@ +version: 2 + +tags: [git] + +accept_configuration: + condition: property + property: features + not_contains_value: Disable + +machine_type: + Build: + cores: 4 + +instructions: + Build: + - type: EnvironmentVariable + variableName: VERIFY_SOURCE_SBOM + variableValue: "ON" + - !include "{{qt/qtbase}}/coin_module_build_template_v2.yaml" + + Test: + - !include "{{qt/qtbase}}/coin_module_test_template_v3.yaml" + - !include "{{qt/qtbase}}/coin_module_test_docs.yaml" diff --git a/configure.cmake b/configure.cmake new file mode 100644 index 0000000..f0f8b18 --- /dev/null +++ b/configure.cmake @@ -0,0 +1,157 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Include QDoc-specific configuration early (needed for feature definitions) +include(${CMAKE_CURRENT_LIST_DIR}/src/qdoc/cmake/QDocConfiguration.cmake) + +#### Tests + +qt_find_package(WrapLibClang 8 PROVIDED_TARGETS WrapLibClang::WrapLibClang) + +if(TARGET WrapLibClang::WrapLibClang) + set(TEST_libclang "ON" CACHE BOOL "Required libclang version found." FORCE) +endif() + + + +#### Features + +# Check whether the sqlite plugin is available. +set(sqlite_plugin_available FALSE) +if(NOT QT_CONFIGURE_RUNNING) + if(TARGET ${QT_CMAKE_EXPORT_NAMESPACE}::Sql) + get_target_property(sql_plugins ${QT_CMAKE_EXPORT_NAMESPACE}::Sql QT_PLUGINS) + if(QSQLiteDriverPlugin IN_LIST sql_plugins) + set(sqlite_plugin_available TRUE) + endif() + endif() +endif() + +qt_feature("assistant" PRIVATE + LABEL "Qt Assistant" + PURPOSE "Qt Assistant is a tool for viewing on-line documentation in Qt help file format." + CONDITION sqlite_plugin_available OR QT_BUILD_SHARED_LIBS +) +qt_feature("clang" PRIVATE + LABEL "libclang found" + CONDITION TEST_libclang +) +qt_feature("clang-rtti" PRIVATE + LABEL "libclang has RTTI support" + CONDITION QT_FEATURE_clang AND LLVM_ENABLE_RTTI +) +qt_feature("qdoc" PRIVATE + LABEL "QDoc" + PURPOSE "QDoc is Qt's documentation generator for C++ and QML projects." + CONDITION TARGET Qt::QmlPrivate AND QT_FEATURE_clang AND QT_FEATURE_commandlineparser AND QT_FEATURE_thread AND QT_LIB_CLANG_VERSION VERSION_GREATER_EQUAL QDOC_MINIMUM_CLANG_VERSION +) +qt_feature("designer" PRIVATE + LABEL "Qt Widgets Designer" + PURPOSE "Qt Widgets Designer is the Qt tool for designing and building graphical user interfaces (GUIs) with Qt Widgets. You can compose and customize your windows or dialogs in a what-you-see-is-what-you-get (WYSIWYG) manner, and test them using different styles and resolutions." + CONDITION TARGET Qt::Widgets AND TARGET Qt::Network AND QT_FEATURE_png AND QT_FEATURE_pushbutton AND QT_FEATURE_toolbutton +) +qt_feature("distancefieldgenerator" PRIVATE + LABEL "Qt Distance Field Generator" + PURPOSE "The Qt Distance Field Generator tool can be used to pregenerate the font cache in order to optimize startup performance." + CONDITION TARGET Qt::Widgets AND QT_FEATURE_png AND QT_FEATURE_thread AND QT_FEATURE_toolbutton AND TARGET Qt::Quick +) +qt_feature("kmap2qmap" PRIVATE + LABEL "kmap2qmap" + PURPOSE "kmap2qmap is a tool to generate keymaps for use on Embedded Linux. The source files have to be in standard Linux kmap format that is e.g. understood by the kernel's loadkeys command." +) +qt_feature("linguist" PRIVATE + LABEL "Qt Linguist" + PURPOSE "Qt Linguist can be used by translator to translate text in Qt applications." +) +qt_feature("pixeltool" PRIVATE + LABEL "pixeltool" + PURPOSE "The Qt Pixel Zooming Tool is a graphical application that magnifies the screen around the mouse pointer so you can look more closely at individual pixels." + CONDITION TARGET Qt::Widgets AND QT_FEATURE_png AND QT_FEATURE_pushbutton AND QT_FEATURE_toolbutton +) +qt_feature("qdbus" PRIVATE + LABEL "qdbus" + PURPOSE "qdbus is a communication interface for Qt-based applications." + CONDITION TARGET Qt::DBus +) +qt_feature("qev" PRIVATE + LABEL "qev" + PURPOSE "qev allows introspection of incoming events for a QWidget, similar to the X11 xev tool." +) +qt_feature("qtattributionsscanner" PRIVATE + LABEL "Qt Attributions Scanner" + PURPOSE "Qt Attributions Scanner generates attribution documents for third-party code in Qt." + CONDITION QT_FEATURE_commandlineparser +) +qt_feature("qtdiag" PRIVATE + LABEL "qtdiag" + PURPOSE "qtdiag outputs information about the Qt installation it was built with." + CONDITION QT_FEATURE_commandlineparser AND TARGET Qt::Gui AND NOT ANDROID AND NOT QNX AND NOT UIKIT AND NOT WASM +) +qt_feature("qtplugininfo" PRIVATE + LABEL "qtplugininfo" + PURPOSE "qtplugininfo dumps metadata about Qt plugins in JSON format." + CONDITION QT_FEATURE_commandlineparser AND QT_FEATURE_library AND (android_app OR NOT ANDROID) +) +qt_feature("fullqthelp" PUBLIC + LABEL "fullqthelp" + PURPOSE "Builds Help with Gui and Widget dependency." + CONDITION (TARGET Qt::Widgets) AND (TARGET Qt::Network) AND QT_FEATURE_png AND + QT_FEATURE_pushbutton AND QT_FEATURE_toolbutton +) +qt_configure_add_summary_section(NAME "Qt Tools") +qt_configure_add_summary_entry(ARGS "assistant") +qt_configure_add_summary_entry(ARGS "clang") +qt_configure_add_summary_entry(ARGS "designer") +qt_configure_add_summary_entry(ARGS "distancefieldgenerator") +#qt_configure_add_summary_entry(ARGS "kmap2qmap") +qt_configure_add_summary_entry(ARGS "linguist") +qt_configure_add_summary_entry(ARGS "pixeltool") +qt_configure_add_summary_entry(ARGS "qdbus") +qt_configure_add_summary_entry(ARGS "qdoc") +#qt_configure_add_summary_entry(ARGS "qev") +qt_configure_add_summary_entry(ARGS "qtattributionsscanner") +qt_configure_add_summary_entry(ARGS "qtdiag") +qt_configure_add_summary_entry(ARGS "qtplugininfo") +qt_configure_end_summary_section() # end of "Qt Tools" section + +# Generate QDoc-specific warning messages +if(NOT QT_CONFIGURE_RUNNING) + include(${CMAKE_CURRENT_LIST_DIR}/src/qdoc/cmake/QDocConfigureMessages.cmake) + qdoc_generate_clang_warning_message(QDOC_CLANG_WARNING) + qdoc_generate_qmlprivate_warning_message(QDOC_QMLPRIVATE_WARNING) + qdoc_generate_missing_features_warning_message(QDOC_MISSING_FEATURES_WARNING) + qdoc_generate_clang_version_warning_message(QDOC_CLANG_VERSION_WARNING) +endif() + +qt_configure_add_report_entry( + TYPE WARNING + MESSAGE "${QDOC_CLANG_WARNING}" + CONDITION NOT QT_FEATURE_clang +) +qt_configure_add_report_entry( + TYPE WARNING + MESSAGE "${QDOC_QMLPRIVATE_WARNING}" + CONDITION NOT TARGET Qt::QmlPrivate +) +qt_configure_add_report_entry( + TYPE WARNING + MESSAGE "${QDOC_MISSING_FEATURES_WARNING}" + CONDITION NOT QT_FEATURE_commandlineparser OR NOT QT_FEATURE_thread +) +qt_configure_add_report_entry( + TYPE WARNING + MESSAGE "${QDOC_CLANG_VERSION_WARNING}" + CONDITION QT_LIB_CLANG_VERSION VERSION_LESS QDOC_MINIMUM_CLANG_VERSION +) + +qt_configure_add_report_entry( + TYPE WARNING + MESSAGE "Qt Assistant will not be compiled because it requires Qt Network." + CONDITION NOT QT_FEATURE_assistant AND NOT TARGET Qt::Network +) + +qt_configure_add_report_entry( + TYPE WARNING + MESSAGE "Qt Designer will not be compiled because it requires Qt Network." + CONDITION NOT QT_FEATURE_designer AND NOT TARGET Qt::Network +) diff --git a/dependencies.yaml b/dependencies.yaml new file mode 100644 index 0000000..3d0d15c --- /dev/null +++ b/dependencies.yaml @@ -0,0 +1,10 @@ +dependencies: + ../qtactiveqt: + ref: 1e4419eeaf6d3f69387c15f64c9132d4ded049b6 + required: false + ../qtbase: + ref: 000d6c62f7880bb8d3054724e8da0b8ae244130e + required: true + ../qtdeclarative: + ref: 09c70541c76659bcd8c49f05841b0e778c9ffd4c + required: false diff --git a/dist/REUSE.toml b/dist/REUSE.toml new file mode 100644 index 0000000..f14104b --- /dev/null +++ b/dist/REUSE.toml @@ -0,0 +1,8 @@ +version = 1 + +[[annotations]] +path = ["*"] +precedence = "override" +comment = "Licensed as documentation." +SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." +SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only" diff --git a/dist/changes-5.0.1 b/dist/changes-5.0.1 new file mode 100644 index 0000000..f476e80 --- /dev/null +++ b/dist/changes-5.0.1 @@ -0,0 +1,71 @@ +Qt 5.0.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.0.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.0/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + +**************************************************************************** +* General * +**************************************************************************** + +General Improvements +-------------------- + + - [QTBUG-28633] Fix bogus include paths in some pkg-config files + - [QTBUG-28689] Fix help URLs in tools. + - [QTBUG-28579] Doc: Correcting qhp links to the Linguist and Assistant examples + +Third party components +---------------------- + +**************************************************************************** +* Platform Specific Changes * +**************************************************************************** + + +**************************************************************************** +* Compiler Specific Changes * +**************************************************************************** + + +**************************************************************************** +* Tools * +**************************************************************************** + +Qt Assistant +------------ + + - Fix warnings found by the headersclean test + - Move functions from an anonymous namespace in a header + +Qt DBus Viewer +-------------- + + - Try harder in matching the method signature + +Qt Designer +----------- + + - Fix warning about hidden overloaded virtual, found by Clang + +Qt Help +------- + +Qt UI Tools +----------- + +**************************************************************************** +* Plugins * +**************************************************************************** + diff --git a/dist/changes-5.0.2 b/dist/changes-5.0.2 new file mode 100644 index 0000000..f29fa49 --- /dev/null +++ b/dist/changes-5.0.2 @@ -0,0 +1,25 @@ +Qt 5.0.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.0.0 and 5.0.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.0/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + +**************************************************************************** +* General * +**************************************************************************** + +General Improvements +-------------------- + + - This release contains only minor code improvements. diff --git a/dist/changes-5.1.0 b/dist/changes-5.1.0 new file mode 100644 index 0000000..74f9fa7 --- /dev/null +++ b/dist/changes-5.1.0 @@ -0,0 +1,48 @@ +Qt 5.1 introduces many new features and improvements as well as bugfixes +over the 5.0.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.1 + +The Qt version 5.1 series is binary compatible with the 5.0.x series. +Applications compiled for 5.0 will continue to run with 5.1. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* General * +**************************************************************************** + +Qt Assistant +------------ + + - [QTBUG-30110] List supported file formats in search indexing. + +Qt Designer +----------- + + - [QTBUG-9502] Rich text editor: Add support for Right-to-Left blocks. + - [QTBUG-25872] Fix moving widgets in horizontal layouts in RTL mode. + - [QTBUG-26394] Use multiline validation for dynamic string properties. + - [QTBUG-29234] ActiveQt plugin: Load control string correctly. + - [QTBUG-29234] ActiveQt plugin: Fix setting a control by task menu. + - [QTBUG-29904] Aero-Style-QWizard: Remove special handling in Qt Designer. + +Qt Linguist +----------- + + - make linguist tools suitable for cross-building + - purge TS 1.1 support from linguist tools + - purge CODECFORTR & -codecfortr support from linguist tools + - [QTBUG-27238] Make linguist's source viewer read all files as UTF-8. + + - lupdate + + * purge ui3 support from lupdate + diff --git a/dist/changes-5.1.1 b/dist/changes-5.1.1 new file mode 100644 index 0000000..e233af4 --- /dev/null +++ b/dist/changes-5.1.1 @@ -0,0 +1,25 @@ +Qt 5.1.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.1.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.1/ + +The Qt version 5.1 series is binary compatible with the 5.0.x series. +Applications compiled for 5.0 will continue to run with 5.1. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + + +**************************************************************************** +* General * +**************************************************************************** + + - This release contains only minor code improvements. diff --git a/dist/changes-5.10.0 b/dist/changes-5.10.0 new file mode 100644 index 0000000..8b71dea --- /dev/null +++ b/dist/changes-5.10.0 @@ -0,0 +1,27 @@ +Qt 5.10 introduces many new features and improvements as well as bugfixes +over the 5.9.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.10 series is binary compatible with the 5.9.x series. +Applications compiled for 5.9 will continue to run with 5.10. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.10.0 Changes * +**************************************************************************** + + - This release contains only minor code improvements. + +UNSPECIFIED +----------- + + - Added option "-no-untranslated" to lconvert diff --git a/dist/changes-5.10.1 b/dist/changes-5.10.1 new file mode 100644 index 0000000..2a6f51c --- /dev/null +++ b/dist/changes-5.10.1 @@ -0,0 +1,35 @@ +Qt 5.10.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.10.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.10 series is binary compatible with the 5.9.x series. +Applications compiled for 5.9 will continue to run with 5.10. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +This release contains all fixes included in the Qt 5.9.4 release. + +**************************************************************************** +* Qt 5.10.1 Changes * +**************************************************************************** + +windeployqt +------------ + + - The Qt3DAnimation dll is now deployed when the module is used. + +macdeployqt +------------ + + - QtLocation and Qt Positioning plugins are now deployed along the + frameworks. diff --git a/dist/changes-5.11.0 b/dist/changes-5.11.0 new file mode 100644 index 0000000..ad2d7c0 --- /dev/null +++ b/dist/changes-5.11.0 @@ -0,0 +1,65 @@ +Qt 5.11 introduces many new features and improvements as well as bugfixes +over the 5.10.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.11 series is binary compatible with the 5.10.x series. +Applications compiled for 5.10 will continue to run with 5.11. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.11.0 Changes * +**************************************************************************** + +Licensing +--------- + + - The Qt UI Tools library got relicensed to commercial + BSD. + +Qt Designer +----------- + + - [QTBUG-34610] Qt Designer now supports id-based translations. + +Qt Linguist +----------- + + - [QTBUG-35652] Fixed special whitespace characters getting lost. + - [QTBUG-43519] Language labels are now country-qualified when necessary. + - [QTBUG-56376] The language selection dialog now displays each country + and language in both english and the language itself. + - Language labels are now displayed in the target language itself. + - The phrase view now shows the source context of guesses. + - The maximal number of guesses in the phrase view can be changed now. + - The message count fields gained tooltips that show the number of + remaining unfinished messages. + - It is now possible to mark a message as done without jumping to the + next unfinished one. + - The search dialog now supports regular expressions. + +lupdate +------- + + - [QTBUG-67278] -tr-function-alias may now specify QML member expressions. + +lrelease +-------- + + - [QTBUG-64317][CMake] Fixed qt5_add_translation() mishandling of filenames + with multiple dots. + - [QTBUG-44323][CMake] Added OPTIONS parameter to qt5_add_translation(). + +macdeployqt +----------- + + - [QTBUG-65844] Added support for selecting the file system type to use + when building a .dmg file. Defaults to HFS+ to support a wider range + of macOS versions. diff --git a/dist/changes-5.11.1 b/dist/changes-5.11.1 new file mode 100644 index 0000000..030ea3f --- /dev/null +++ b/dist/changes-5.11.1 @@ -0,0 +1,24 @@ +Qt 5.11.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.11.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.11 series is binary compatible with the 5.10.x series. +Applications compiled for 5.10 will continue to run with 5.11. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.11.1 Changes * +**************************************************************************** + + - This release contains only minor code improvements. diff --git a/dist/changes-5.11.2 b/dist/changes-5.11.2 new file mode 100644 index 0000000..8efdf5e --- /dev/null +++ b/dist/changes-5.11.2 @@ -0,0 +1,24 @@ +Qt 5.11.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.11.0 through 5.11.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.11 series is binary compatible with the 5.10.x series. +Applications compiled for 5.10 will continue to run with 5.11. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.11.2 Changes * +**************************************************************************** + + - This release contains only minor code improvements. diff --git a/dist/changes-5.11.3 b/dist/changes-5.11.3 new file mode 100644 index 0000000..4e0a0b1 --- /dev/null +++ b/dist/changes-5.11.3 @@ -0,0 +1,32 @@ +Qt 5.11.3 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.11.0 through 5.11.2. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.11 series is binary compatible with the 5.10.x series. +Applications compiled for 5.10 will continue to run with 5.11. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* macdeployqt * +**************************************************************************** + + - [QTBUG-68823] Now deploys plugins when Qt is configured with + -no-framework + +**************************************************************************** +* Assistant * +**************************************************************************** + + - [QTBUG-71399] Fix a crash when removing multiple documentation files + inside preferences dialog diff --git a/dist/changes-5.12.0 b/dist/changes-5.12.0 new file mode 100644 index 0000000..7cbcf1d --- /dev/null +++ b/dist/changes-5.12.0 @@ -0,0 +1,60 @@ +Qt 5.12 introduces many new features and improvements as well as bugfixes +over the 5.11.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* macdeployqt * +**************************************************************************** + + - [QTBUG-68823] Now deploys plugins when Qt is configured with + -no-framework + +**************************************************************************** +* winrtrunner * +**************************************************************************** + + - Loopback exemption for UWP applications: + * Added ability to enable loopback exemption for clients. This basically + enables UWP applications to connect to a local server for debugging + purposes. + * Added ability to enable loopback exemption for servers. This basically + enables UWP applications to react on socket connections that are made + from the same machine for debugging purposes. + +**************************************************************************** +* Qt Help * +**************************************************************************** + + - [QTCREATORBUG-18242] Fixed the issue with too many open files, when + many documentation files were registered. + - [QTBUG-59363] Fixed jumping to the proper documentation version + when activating the link that refers to another qch file. + - [QTBUG-21357] Items are now sorted alphabetically in Contents view. + - Removed qhelpconverter tool. + - Merged qcollectiongenerator tool into the qhelpgenerator tool. + +**************************************************************************** +* Qt Linguist * +**************************************************************************** + + - Added "Go to" action to context menu in "Phrases and guesses" pane. + +lupdate +------- + + - [QTBUG-63364] Add support for C++17 nested namespaces. + - [QTBUG-62478] Fixed premature termination on qmake .prf execution errors. + - Updated qmake project handling to newer functionality in qmake. diff --git a/dist/changes-5.12.1 b/dist/changes-5.12.1 new file mode 100644 index 0000000..ca51580 --- /dev/null +++ b/dist/changes-5.12.1 @@ -0,0 +1,59 @@ +Qt 5.12.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* qdoc * +**************************************************************************** + + - [QTBUG-69484] Fixed link resolution for types under namespaces. + - [QTBUG-70801] Added linking of buildversion field to + navigation.landingpage. + - [QTBUG-72357] Fixed regression in including inherited members in + all-members list. + - [QTBUG-72723] Don't include overridden functions in all-members list + anymore. + - [QTBUG-71792] Section headers in C++ reference pages for sections + without content are now skipped. + +**************************************************************************** +* Qt Assistant * +**************************************************************************** + + - [QTBUG-72174] Fixed an issue with opening a read-only qhc file. + +**************************************************************************** +* Qt Help * +**************************************************************************** + + - Re-added the qcollectiongenerator tool, which was removed in Qt 5.12.0 + release. It redirects to the qhelpgenerator tool now. + +**************************************************************************** +* windeployqt * +**************************************************************************** + + - Added deployment of Qt Virtual Keyboard plugins. + - Added support for packaging arm64 desktop apps. + +**************************************************************************** +* winrtrunner * +**************************************************************************** + + - winrtrunner now forwards the QT_LOGGING_RULES environment variable to + the actual application. diff --git a/dist/changes-5.12.10 b/dist/changes-5.12.10 new file mode 100644 index 0000000..95c6a0b --- /dev/null +++ b/dist/changes-5.12.10 @@ -0,0 +1,26 @@ +Qt 5.12.10 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.9. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + https://doc.qt.io/qt-5.12/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Important Behavior Changes * +**************************************************************************** + +**************************************************************************** +* Library * +**************************************************************************** diff --git a/dist/changes-5.12.2 b/dist/changes-5.12.2 new file mode 100644 index 0000000..e83f600 --- /dev/null +++ b/dist/changes-5.12.2 @@ -0,0 +1,24 @@ +Qt 5.12.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* lupdate * +**************************************************************************** + + - [QTBUG-42736] Added support for parsing C++11 raw string literals diff --git a/dist/changes-5.12.3 b/dist/changes-5.12.3 new file mode 100644 index 0000000..b1ea2fe --- /dev/null +++ b/dist/changes-5.12.3 @@ -0,0 +1,24 @@ +Qt 5.12.3 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.2. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* makeqpf * +**************************************************************************** + + - Fix assert when rendering font using debug build of Qt diff --git a/dist/changes-5.12.4 b/dist/changes-5.12.4 new file mode 100644 index 0000000..4796a38 --- /dev/null +++ b/dist/changes-5.12.4 @@ -0,0 +1,33 @@ +Qt 5.12.4 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.3. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* lupdate * +**************************************************************************** + + - lupdate will now generate an error if it is asked to update a .ts file + with translations, but without a target language. This is to ensure that + plural translations are not destroyed. + +**************************************************************************** +* qdoc * +**************************************************************************** + + - [QTBUG-73058] qdoc now uses #! as a snippet marker in .cmake, + CMakeLists.txt files. diff --git a/dist/changes-5.12.5 b/dist/changes-5.12.5 new file mode 100644 index 0000000..379f390 --- /dev/null +++ b/dist/changes-5.12.5 @@ -0,0 +1,37 @@ +Qt 5.12.5 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.12.0 through 5.12.4. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.12 series is binary compatible with the 5.11.x series. +Applications compiled for 5.11 will continue to run with 5.12. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* distancefieldgenerator * +**************************************************************************** + + - [QTBUG-76188] Fixed bug where the tool would fail for valid fonts with + the message "end of cmap table reached when parsing subtable". + - [QTBUG-76528] Fixed a bug where the generated textures might exceed the + maximum height. + - [QTBUG-76188][QTBUG-76528] Fixed possible crash when generating large + number of glyphs with a small texture size. + - [QTBUG-77501] Fixed broken text rendering when generating large glyph + sets. + +**************************************************************************** +* Qt Help * +**************************************************************************** + + - [QDS-779] Fixed possible application freeze when using QtHelp module. diff --git a/dist/changes-5.13.0 b/dist/changes-5.13.0 new file mode 100644 index 0000000..2859e92 --- /dev/null +++ b/dist/changes-5.13.0 @@ -0,0 +1,37 @@ +Qt 5.13 introduces many new features and improvements as well as bugfixes +over the 5.12.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.13 series is binary compatible with the 5.12.x series. +Applications compiled for 5.12 will continue to run with 5.13. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Designer * +**************************************************************************** + + - Added a filter line edit to the object inspector. + +**************************************************************************** +* lupdate * +**************************************************************************** + + - lupdate will now generate an error if it is asked to update a .ts file + with translations, but without a target language. This is to ensure that + plural translations are not destroyed. + +**************************************************************************** +* qdoc * +**************************************************************************** + + - [QTBUG-73058] qdoc now uses #! as a snippet marker in .cmake, + CMakeLists.txt files. diff --git a/dist/changes-5.13.1 b/dist/changes-5.13.1 new file mode 100644 index 0000000..557d702 --- /dev/null +++ b/dist/changes-5.13.1 @@ -0,0 +1,29 @@ +Qt 5.13.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.13.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.13 series is binary compatible with the 5.12.x series. +Applications compiled for 5.12 will continue to run with 5.13. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* distancefieldgenerator * +**************************************************************************** + + - [QTBUG-76188] Fixed bug where the tool would fail for valid fonts with + the message "end of cmap table reached when parsing subtable". + - [QTBUG-76528] Fixed a bug where the generated textures might exceed the + maximum height. + - [QTBUG-76188][QTBUG-76528] Fixed possible crash when generating large + number of glyphs with a small texture size. diff --git a/dist/changes-5.13.2 b/dist/changes-5.13.2 new file mode 100644 index 0000000..f1a1f9d --- /dev/null +++ b/dist/changes-5.13.2 @@ -0,0 +1,27 @@ +Qt 5.13.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.13.0 through 5.13.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.13 series is binary compatible with the 5.12.x series. +Applications compiled for 5.12 will continue to run with 5.13. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Distance Field Generator * +**************************************************************************** + + - [QTBUG-77499] Improved performance when selecting unicode ranges in + large fonts. + - [QTBUG-77501] Fixed broken text rendering when generating large glyph + sets. diff --git a/dist/changes-5.14.0 b/dist/changes-5.14.0 new file mode 100644 index 0000000..de2bb6f --- /dev/null +++ b/dist/changes-5.14.0 @@ -0,0 +1,63 @@ +Qt 5.14 introduces many new features and improvements as well as bugfixes +over the 5.13.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.14 series is binary compatible with the 5.13.x series. +Applications compiled for 5.13 will continue to run with 5.14. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* pixeltool * +**************************************************************************** + + - 'c' now copies color under cursor, and shows it in the tool window title. + +**************************************************************************** +* Qt Designer * +**************************************************************************** + + - [QTBUG-76375] A per-form setting for disabling generating calls to + QObject::connectSlotsByName() has been added to support migrating forms + to Qt 5 connection syntax. + - The multiselection-modifier of the buddy/signal slot editors has been + changed to Control instead of (historical) Shift. + +**************************************************************************** +* Qt Linguist * +**************************************************************************** + + - [QTBUG-76265] lupdate now warns about qsTr() calls with template literals + in .qml files. + - [QTBUG-67908] Extra \n when reading translator comment from .po file now + gets removed. + - [QTBUG-76723] CMake: qt5_create_translation was creating a warning when + translation files had the same prefix, separated by a dot. This is now + fixed. + +**************************************************************************** +* windeployqt * +**************************************************************************** + + - [QTBUG-15234] windeployqt does not patch paths in Qt5Core anymore if Qt + is configured with -feature-relocatable. + - [QTBUG-75272] Added option -no-virtualkeyboard to disable deployment of + Qt Virtual Keyboard. + +**************************************************************************** +* qdoc * +**************************************************************************** + + - QDoc no longer attempts to run if 'project' configuration variable is + not set; it now fails with an appropriate error message. + + - [QTBUG-80051] 'depends' configuration now accepts '*' as a value, + instructing QDoc to load all available index files as dependencies. diff --git a/dist/changes-5.14.1 b/dist/changes-5.14.1 new file mode 100644 index 0000000..ac11cec --- /dev/null +++ b/dist/changes-5.14.1 @@ -0,0 +1,26 @@ +Qt 5.14.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.14.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.14 series is binary compatible with the 5.13.x series. +Applications compiled for 5.13 will continue to run with 5.14. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* windeployqt * +**************************************************************************** + + - [QTBUG-80806] windeployqt has been fixed to work with MinGW, again. + - [QTBUG-78146] Fixed possible empty content and index widgets when using + Qt Help module. diff --git a/dist/changes-5.14.2 b/dist/changes-5.14.2 new file mode 100644 index 0000000..f90f453 --- /dev/null +++ b/dist/changes-5.14.2 @@ -0,0 +1,24 @@ +Qt 5.14.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.14.0 through 5.14.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.14 series is binary compatible with the 5.13.x series. +Applications compiled for 5.13 will continue to run with 5.14. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* qdoc * +**************************************************************************** + + - [QTBUG-82252] Fixed "-F" option on macOS diff --git a/dist/changes-5.15.0 b/dist/changes-5.15.0 new file mode 100644 index 0000000..b061a69 --- /dev/null +++ b/dist/changes-5.15.0 @@ -0,0 +1,58 @@ +Qt 5.15 introduces many new features and improvements as well as bugfixes +over the 5.14.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.15 series is binary compatible with the 5.14.x series. +Applications compiled for 5.14 will continue to run with 5.15. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* QtHelp * +**************************************************************************** + + - QHelpLink data structure has been introduced and used in newly added + documentsForIdentifier() and documentsForKeyword() methods. + - Deprecated linksForIdentifier() and linksForKeyword() methods + in favor of the methods above. + - The QHelpFilterSettings class has been added, describing all + the available filter details. + - The QHelpFilterSettingsWidget class has been added, serving + as an editor for all available filters. + +**************************************************************************** +* Qt Designer * +**************************************************************************** + + - The palette editor dialog has been overhauled. + +**************************************************************************** +* macdeployqt * +**************************************************************************** + + - Added "-hardened-runtime" option to support app notarization. + +**************************************************************************** +* qdoc * +**************************************************************************** + + - QDoc now supports DocBook as an output format. + - Introduced configuration variable 'locationinfo' to drop host-specific + paths from the generated output. + - Added capability to display class/method template parameters in the + generated documentation. + - QDoc now generates correct documentation for enum classes. + - QDoc is now aware of the namespace scope of an \fn command without + requiring fully qualified paths. + - Generate output for arguments passed to the \obsolete command. + - [QTBUG-37355] QDoc now generates a note for the name of the + corresponding handler in \qmlsignal documentation. + - Added support for CMake-based example projects. diff --git a/dist/changes-5.15.1 b/dist/changes-5.15.1 new file mode 100644 index 0000000..018a8c4 --- /dev/null +++ b/dist/changes-5.15.1 @@ -0,0 +1,47 @@ +Qt 5.15.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.15.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qt-5/index.html + +The Qt version 5.15 series is binary compatible with the 5.14.x series. +Applications compiled for 5.14 will continue to run with 5.15. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt Designer * +**************************************************************************** + + - New command line option --no-scaling allows to disable high-dpi + scaling. + +**************************************************************************** +* qdoc * +**************************************************************************** + + - QDoc now correctly handles templated structs, unions, and type aliases. + +**************************************************************************** +* windeployqt * +**************************************************************************** + + - New command line option --translations allows to pass a comma-separated + list of languages (.qm files) to deploy. + - Fixed an issue where the MSVC Runtime variant (Debug, Release) was + detected incorrectly especially on WinRT. + - Qt3D renderer plugins added in 5.15 are now automatically deployed. + +**************************************************************************** +* macdeployqt * +**************************************************************************** + + - .prl files are not copied anymore to the Resources folder. diff --git a/dist/changes-5.15.2 b/dist/changes-5.15.2 new file mode 100644 index 0000000..fe9d837 --- /dev/null +++ b/dist/changes-5.15.2 @@ -0,0 +1,72 @@ +Qt 5.15.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.15.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + https://doc.qt.io/qt-5.15/index.html + +The Qt version 5.15 series is binary compatible with the 5.14.x series. +Applications compiled for 5.14 will continue to run with 5.15. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Important Behavior Changes * +**************************************************************************** + +**************************************************************************** +* Library * +**************************************************************************** + + - [QTBUG-87802] qdoc: Fix broken links generated by \sincelist + The combination of single-exec mode and outputting to subdirectories + (offline mode) made \sincelist generate href's to new classes and + functions that are missing the target subdirectory. This happened + because we did not pass the relative node to the function(s) that + generate that list - proper link resolution requires a check if the + page containing the \sincelist command comes from a different doc + module, and the relative node is used for that. + The bug was: qdoc: \sincelist generates broken links in certain + conditions + - [QTBUG-84703] qdoc: DocBook generator: Correctly handle enum classes + While commit 46f71fcc fixed handling of enum classes for the code + parser and marker, the DocBook generator has some dedicated code that + also needs to be amended. + The bug was: qdoc: DocBook generator: scoped enums not handled + correctly + - [QTBUG-86988] qdoc: DocBook generator: Remove code for encoding special characters + QXmlStreamWriter already handles this for us. + The bug was: qdoc: DocBook output format: XML special characters are + escaped twice + - [QTBUG-86759] Revert "macdeployqt: Don't copy .prl files into the Resources folder" + This was skipping other files from Resources/ as well, such as + “en-GB.pak” and “QtWebEngineProcess”. This reverts commit + aabba72f7965e06e2e6ed960d8cf8078249dac8c. + The bug was: Missing QtWebEngineProcess Application in application + frameworks + - [QTBUG-84727] QtHelp: Fix documentsFor() when not using filter engine + The bug was: QHelpIndexWidget does not emit documentActivated or + documentsActivated + - [QTBUG-86598] Assistant: fix build with QT_NO_CLIPBOARD + The bug was: Assistant fails to compile if QT_NO_CLIPBOARD is defined + - [QTBUG-86477] Fix static build of assistant + The bug was: [REG 5.15.0->5.15.1] assistant not build when building + whole Qt statically + - [QTBUG-86293] winrtrunner: Remove Windows Phone support + Windows Phone has not been supported for a while now. With newer + Windows versions the functionality causes issues. On build 1909 the + application hangs in CoUninitialize when called with --list-devices + and in build 2004 the needed classes are not registered at all. + The bug was: "winrtrunner --list-devices" hangs + - [QTBUG-86188] qdoc: Fix incorrect loop when processing tagged \fn parameters + This fixes ASSERT: "uint(i) < uint(size())" in file + qt5/qtbase/src/corelib/text/qstring.h, line 1067 + The bug was: qdoc: Crash when using a tagged \fn command + diff --git a/dist/changes-5.2.0 b/dist/changes-5.2.0 new file mode 100644 index 0000000..2beebcc --- /dev/null +++ b/dist/changes-5.2.0 @@ -0,0 +1,59 @@ +Qt 5.2 introduces many new features and improvements as well as bugfixes +over the 5.1.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.2 + +The Qt version 5.2 series is binary compatible with the 5.1.x series. +Applications compiled for 5.1 will continue to run with 5.2. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* General * +**************************************************************************** + +Qt Assistant +------------ + + +Qt Designer +----------- + + +Qt Linguist +----------- + + - [QTBUG-32196] Fixed search skipping some messages entirely + - [QTBUG-32218] Fixed search generally skipping plural forms + - Fixed search skipping translator comments in multi-language mode + - [QTBUG-14592] Fixed handling of country-less languages like Esperanto + + - lupdate + + * Made the functions lupdate looks for customizable + * Made TR_EXCLUDE work for SUBDIRS and #include statements + * [QTBUG-14056] Added collection of messages from INSTALL and DEPLOYMENT file lists + * [QTBUG-24587] Fixed INCLUDEPATH resolution relative to current project + * Fixed INCLUDEPATH erroneously contributing to VPATH for HEADERS + * [QTBUG-12948] Fixed handling of PO files with obsolete fuzzy messages. + This necessitated the introduction of a new message state, and thus an + increment of the TS file format version to 2.1. + * [QTBUG-27974] Fixed messages from included sources not being collected + * [QTBUG-29131][QTBUG-32725] Fixed handling of incomplete and contradictory meta data + * [QTBUG-18890] Fixed meta data changes being ignored + * [QTBUG-11866] Fixed too aggressive collection of message meta data + * [QTBUG-21876] Fixed numerous C++ parsing issues triggered by comments + * [QTBUG-29998] Fixed parsing of tr() inside brackets (used as a hash key) + * [QTBUG-9276] Fixed parsing of QT_TR_NOOP() in non-array static initializers + * [QTBUG-4393] Fixed parsing of constructors with namespaced parent classes + * Fixed long-standing performance regression relating to processing includes + * Fixed decoding of qml files in non-UTF-8 locales + * Made built-in qmake project parser more accurate and less noisy + * Added -pro-debug option equivalent to qmake -d diff --git a/dist/changes-5.2.1 b/dist/changes-5.2.1 new file mode 100644 index 0000000..430099f --- /dev/null +++ b/dist/changes-5.2.1 @@ -0,0 +1,42 @@ +Qt 5.2.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.2.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.2 + +The Qt version 5.2 series is binary compatible with the 5.1.x series. +Applications compiled for 5.1 will continue to run with 5.2. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Tools * +**************************************************************************** + +androiddeployqt +--------------- + + - [QTBUG-35401] Debug deployment is now much faster + - [QTBUG-35129] androiddeployqt now updates deployed plugins and + imports when the APK is updated on the device. + +Qt Assistant +------------ + + - mailto links now work again in Qt Assistant. + +Qt Linguist +----------- + + - [QTBUG-35094] TS files are now always written with normalized numerus forms + + - lupdate: + * Tr and TR won't be misinterpreted as tr any more diff --git a/dist/changes-5.6.3 b/dist/changes-5.6.3 new file mode 100644 index 0000000..c4694b0 --- /dev/null +++ b/dist/changes-5.6.3 @@ -0,0 +1,27 @@ +Qt 5.6.3 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.6.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5/index.html + +The Qt version 5.6 series is binary compatible with the 5.5.x series. +Applications compiled for 5.5 will continue to run with 5.6. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Library * +**************************************************************************** + +lupdate +------- + + - [QTBUG-53206] Added qrc resource file support diff --git a/dist/changes-5.7.0 b/dist/changes-5.7.0 new file mode 100644 index 0000000..02bb4b7 --- /dev/null +++ b/dist/changes-5.7.0 @@ -0,0 +1,32 @@ +Qt 5.7 introduces many new features and improvements as well as bugfixes +over the 5.6.x series. Also, there is a change in the licensing terms. +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://doc.qt.io/qt-5/index.html + +The Qt version 5.7 series is binary compatible with the 5.6.x series. +Applications compiled for 5.6 will continue to run with 5.7. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Important License Changes * +**************************************************************************** + + This module is no longer available under LGPLv2.1. The libraries are + now available under the following licenses: + * Commercial License + * GNU General Public License v2.0 (LICENSE.GPL2) and later + * GNU Lesser General Public License v3.0 (LICENSE.LGPL3) + + The tools are now available under the following licenses: + * Commercial License + * GNU General Public License 3.0 (LICENSE.GPL3) with exceptions + described in The Qt Company GPL Exception 1.0 (LICENSE.GPL3-EXCEPT) diff --git a/dist/changes-5.8.0 b/dist/changes-5.8.0 new file mode 100644 index 0000000..3c771f3 --- /dev/null +++ b/dist/changes-5.8.0 @@ -0,0 +1,41 @@ +Qt 5.8 introduces many new features and improvements as well as bugfixes +over the 5.7.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + + http://doc.qt.io/qt-5/index.html + +The Qt version 5.8 series is binary compatible with the 5.7.x series. +Applications compiled for 5.7 will continue to run with 5.8. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +androiddeployqt +--------------- + + - [QTBUG-54147] Fixed problem with ANDROID_DEPLOYMENT_DEPENDENCIES where + last entry in directory would be skipped. + +lupdate +------- + + - [QTBUG-34092] Fixed parsing of #includes without a space before the + file name + - [QTBUG-53206] Added qrc resource file support + +linguist +-------- + + - [QTBUG-35144] Translation validation now recognizes the %Ln marker + - [QTBUG-52020] Fixed widget alignment in length variants view + - [QTBUG-56374] Fixed descriptions of file formats not being translated + +macdeployqt +----------- + + - Added option '-libpath' to search libraries in a custom path. diff --git a/dist/changes-5.9.0 b/dist/changes-5.9.0 new file mode 100644 index 0000000..e113ee6 --- /dev/null +++ b/dist/changes-5.9.0 @@ -0,0 +1,34 @@ +Qt 5.9 introduces many new features and improvements as well as bugfixes +over the 5.8.x series. For more details, refer to the online documentation +included in this distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +Linguist +-------- + + - [QTBUG-16368] Added capability of checking that the whitespace + surrounding the translations' text matches that of the source strings. + +Assistant +--------- + + - Removed the clucene dependency, used FTS5 from sqlite instead. + - Search results show now the doc excerpt instead of the explicit link. + - Made search results more reliable (e.g.: charset, "UTF-8"). + +QtHelp +----------- + + - Moved the linksForKeyword() method from QHelpIndexModel into QHelpEngineCore. diff --git a/dist/changes-5.9.1 b/dist/changes-5.9.1 new file mode 100644 index 0000000..131dd63 --- /dev/null +++ b/dist/changes-5.9.1 @@ -0,0 +1,63 @@ +Qt 5.9.1 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.9.0. + +For more details, refer to the documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +General +------- + +- [QTBUG-60926] Fixed building of qdoc documentation with a static Qt. + +androiddeployqt +--------------- + +- [QTBUG-60918] Fixed deployment of QtQuick.Controls 1 and QtQuick.Dialogs. + +macdeployqt +----------- + +- [QTBUG-59609] Icon engines are now deployed. + +qtdiag +------ + +- Show more useful information about styles and platform themes. +- [QTBUG-60962] Changed logic to determine name of ANGLE libraries by + existence. + +Qt Assistant +------------ + +- Fixed the build with qtwebkit. +- Fixed availability of widget arrows in the search result. + +Qt Designer +----------- + +- [QTBUG-61009] Fixed crash in "Go to slot" dialog when no signal is + selected. + + +Qt Property Browser +------------------- + +- Fixed build breakage when qreal is defined as float. + +windeployqt +----------- + +- [QTBUG-61127] Added support of deploying Qt5GamePad with plugins. diff --git a/dist/changes-5.9.2 b/dist/changes-5.9.2 new file mode 100644 index 0000000..8fa99a5 --- /dev/null +++ b/dist/changes-5.9.2 @@ -0,0 +1,28 @@ +Qt 5.9.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.9.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.9.2 Changes * +**************************************************************************** + +androiddeployqt +--------------- + + - [QTBUG-61636] Fixed a crash when using ANDROID_DEPLOYMENT_DEPENDENCIES + to specify the files to deploy. diff --git a/dist/changes-5.9.3 b/dist/changes-5.9.3 new file mode 100644 index 0000000..bb5db63 --- /dev/null +++ b/dist/changes-5.9.3 @@ -0,0 +1,24 @@ +Qt 5.9.3 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.9.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.9.3 Changes * +**************************************************************************** + + - This release contains only minor code improvements. diff --git a/dist/changes-5.9.4 b/dist/changes-5.9.4 new file mode 100644 index 0000000..9a71366 --- /dev/null +++ b/dist/changes-5.9.4 @@ -0,0 +1,28 @@ +Qt 5.9.4 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.9.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.9.4 Changes * +**************************************************************************** + +Build system +------------ + + - [QTBUG-64317] Fixed qt5_add_translation() CMake macro for TS files whose + names contain multiple dots. diff --git a/dist/changes-5.9.5 b/dist/changes-5.9.5 new file mode 100644 index 0000000..eb9f826 --- /dev/null +++ b/dist/changes-5.9.5 @@ -0,0 +1,34 @@ +Qt 5.9.5 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.9.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.9.5 Changes * +**************************************************************************** + +windeployqt +----------- + + - The Qt3DAnimation dll is now deployed when the module is used. + +macdeployqt +----------- + + - [QTBUG-65844] Added support for selecting the file system type to use + when building a .dmg file. Defaults to HFS+ to support a wider range + of macOS versions. diff --git a/dist/changes-5.9.6 b/dist/changes-5.9.6 new file mode 100644 index 0000000..f0179a5 --- /dev/null +++ b/dist/changes-5.9.6 @@ -0,0 +1,24 @@ +Qt 5.9.6 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.9.0 through 5.9.5. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.9.6 Changes * +**************************************************************************** + + - This release contains only minor code improvements. diff --git a/dist/changes-6.0.0 b/dist/changes-6.0.0 new file mode 100644 index 0000000..c7d04ef --- /dev/null +++ b/dist/changes-6.0.0 @@ -0,0 +1,16 @@ +Qt 6.0.0 is a new major version release of Qt. It is not binary compatible with +earlier Qt releases. + +The goal has been to retain as much source compatibility with Qt 5.15 as +possible, but some changes were inevitable to make Qt a better framework. + +To make it easier to port to Qt 6.0, we have created a porting guide to +summarize those changes and provide guidance to handle them. In the guide, you +can find links to articles about changes that may affect your application and +help you transition from Qt 5.15 to Qt 6.0: + +https://doc.qt.io/qt-6/portingguide.html + +For more details refer to the online documentation of Qt 6.0: + +https://doc.qt.io/qt-6/index.html diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..0c7c5d6 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +qt_examples_build_begin(EXTERNAL_BUILD) + +qt_exclude_tool_directories_from_default_target( + assistant +) + +if(TARGET Qt::Widgets) + if(TARGET Qt::Help) + add_subdirectory(help) + endif() + if(QT_FEATURE_linguist) + add_subdirectory(linguist) + endif() + add_subdirectory(uitools) +endif() +if(QT_FEATURE_process AND TARGET Qt::Widgets) + if(QT_FEATURE_designer) + add_subdirectory(designer) + endif() + add_subdirectory(assistant) +endif() + +qt_examples_build_end() diff --git a/examples/assistant/CMakeLists.txt b/examples/assistant/CMakeLists.txt new file mode 100644 index 0000000..3d8c97d --- /dev/null +++ b/examples/assistant/CMakeLists.txt @@ -0,0 +1,2 @@ +qt_internal_add_example(simpletextviewer) +qt_internal_add_example(remotecontrol) diff --git a/examples/assistant/assistant.pro b/examples/assistant/assistant.pro new file mode 100644 index 0000000..578c7ff --- /dev/null +++ b/examples/assistant/assistant.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS += simpletextviewer \ + remotecontrol diff --git a/examples/assistant/doc/images/remotecontrol-example.png b/examples/assistant/doc/images/remotecontrol-example.png new file mode 100644 index 0000000000000000000000000000000000000000..045b2de4621c265626033fb09345af370e112df8 GIT binary patch literal 71508 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

RD=X80No{DD~ClJc=z91BjQ{ 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 PS{OWC{an^LB{Ts5sT2fT literal 0 HcmV?d00001 diff --git a/examples/assistant/doc/images/simpletextviewer-mainwindow.png b/examples/assistant/doc/images/simpletextviewer-mainwindow.png new file mode 100644 index 0000000000000000000000000000000000000000..31e3ccb7abbc9c3e705d9343ee7fec4d9beb3ab2 GIT binary patch literal 24247 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 + + org.qt-project.examples.simpletextviewer + doc + \endcode + + The next step is to define the filter section. A filter section + contains the table of contents, indices and a complete list of + all documentation files, and can have any number of filter attributes + assigned to it. A filter attribute is an ordinary string which can + be freely chosen. Later in \QA, users can then define a custom + filter referencing those attributes. If the attributes of a filter + section match the attributes of the custom filter the documentation + will be shown, otherwise \QA will hide the documentation. + + Again, since we'll only have one documentation set, we do not need + the filtering functionality of \QA and can therefore skip the + filter attributes. + + Now, we build up the table of contents. An item in the table is + defined by the \c section tag which contains the attributes for the + item title as well as link to the actual page. Section tags can be + nested infinitely, but for practical reasons it is not recommended + to nest them deeper than three or four levels. For our example we + want to use the following outline for the table of contents: + + \list + \li Simple Text Viewer + \list + \li Find File + \list + \li File Dialog + \li Wildcard Matching + \li Browse + \endlist + \li Open File + \endlist + \endlist + + In the help project file, the outline is represented by: + + \code + + +

+
+
+
+
+
+
+
+ + \endcode + + After the table of contents is defined, we will list all index keywords: + + \code + + + + + + + + + + + + + + + + + \endcode + + As the last step, we have to list all files making up the documentation. + An important point to note here is that all files have to listed, including + image files, and even stylesheets if they are used. + + \code + + browse.html + filedialog.html + findfile.html + index.html + intro.html + openfile.html + wildcardmatching.html + images/browse.png + images/fadedfilemenu.png + images/filedialog.png + images/handbook.png + images/mainwindow.png + images/open.png + images/wildcard.png + + + + \endcode + + The help project file is now finished. If you want to see the resulting + documentation in \QA, you have to generate a Qt compressed help file + out of it and register it with the default help collection of \QA. + + \code + qhelpgenerator simpletextviewer.qhp -o simpletextviewer.qch + assistant -register simpletextviewer.qch + \endcode + + If you start \QA now, you'll see the Simple Text Viewer documentation + beside the Qt documentation. This is OK for testing purposes, but + for the final version we want to only have the Simple Text Viewer + documentation in \QA. + + \section2 Customizing \QA + + The easiest way to make \QA only display the Simple Text Viewer + documentation is to create our own help collection file. A collection + file is stored in a binary format, similar to the compressed help file, + and generated from a help collection project file (*.qhcp). With + the help of a collection file, we can customize the appearance and even + some functionality offered by \QA. + + At first, we change the window title and icon. Instead of showing "\QA" + it will show "Simple Text Viewer", so it is much clearer for the user + that the help viewer actually belongs to our application. + + \code + + + + Simple Text Viewer + images/handbook.png + QtProject/SimpleTextViewer + \endcode + + The \c cacheDirectory tag specifies a subdirectory of the users + data directory (see the + \l{Customizing Qt Assistant#Qt Help Collection Files}{Qt Help Collection Files}) + where the cache file for the full text search or the settings file will + be stored. + + After this, we set the page displayed by \QA when launched for the very + first time in its new configuration. The URL consists of the namespace + and virtual folder defined in the Qt help project file, followed by the + actual page file name. + + \code + qthelp://org.qt-project.examples.simpletextviewer/doc/index.html + \endcode + + Next, we alter the name of the "About" menu item to "About Simple + Text Viewer". The contents of the \gui{About} dialog are also changed + by specifying a file where the about text or icon is taken from. + + \code + + About Simple Text Viewer + + + about.txt + images/icon.png + + \endcode + + \QA offers the possibility to add or remove documentation via its + preferences dialog. This functionality is helpful when using \QA + as the central help viewer for more applications, but in our case + we want to actually prevent the user from removing the documentation. + So, we hide the \gui Documentation tab in the \gui Preferences dialog. + + Since the address bar is not really relevant in such a small + documentation set we switch it off as well. By having just one filter + section, without any filter attributes, we can also disable the filter + functionality of \QA, which means that the filter page and the filter + toolbar will not be available. + + \code + false + false + false + + \endcode + + For testing purposes, we already generated the compressed help file + and registered it with \QA's default help collection. With the + following lines we achieve the same result. The only and important + difference is that we register the compressed help file, not in + the default collection, but in our own collection file. + + \code + + + + simpletextviewer.qhp + simpletextviewer.qch + + + + simpletextviewer.qch + + + + \endcode + + As the last step, we have to generate the binary collection file + out of the help collection project file. This is done by running the + \c qhelpgenerator tool. + + \code + qhelpgenerator simpletextviewer.qhcp -o simpletextviewer.qhc + \endcode + + To test all our customizations made to \QA, we add the collection + file name to the command line: + + \code + assistant -collectionFile simpletextviewer.qhc + \endcode + + \section1 Controlling \QA via the Assistant Class + + We will first take a look at how to start and operate \QA from a + remote application. For that purpose, we create a class called + \c Assistant. + + This class provides a public function that is used to show pages + of the documentation, and one private helper function to make sure + that \QA is up and running. + + Launching \QA is done in the function \c startAssistant() by simply + creating and starting a QProcess. If the process is already running, + the function returns immediately. Otherwise, the process has + to be set up and started. + + \snippet simpletextviewer/assistant.cpp 2 + + To start the process we need the executable name of \QA as well as the + command line arguments for running \QA in a customized mode. The + executable name is a little bit tricky since it depends on the + platform, but fortunately it is only different on \macos. + + The displayed documentation can be altered using the \c -collectionFile + command line argument when launching \QA. When started without any options, + \QA displays a default set of documentation. When Qt is installed, + the default documentation set in \QA contains the Qt reference + documentation as well as the tools that come with Qt, such as Qt + Designer and \c qmake. + + In our example, we replace the default documentation set with our + custom documentation by passing our application-specific collection + file to the process's command line options. + + As the last argument, we add \c -enableRemoteControl, which makes \QA + listen to its \c stdin channel for commands, such as those to display + a certain page in the documentation. + Then we start the process and wait until it is actually running. If, + for some reason \QA cannot be started, \c startAssistant() will return + false. + + The implementation for \c showDocumentation() is now straightforward. + Firstly, we ensure that \QA is running, then we send the request to + display the \a page via the \c stdin channel of the process. It is very + important here that the command is terminated by an end of line token + to flush the channel. + + \snippet simpletextviewer/assistant.cpp 1 + + Finally, we make sure that \QA is terminated properly in the case that + the application is shut down. The destructor of QProcess kills the + process, meaning that the application has no possibility to do things + like save user settings, which would result in corrupted settings files. + To avoid this, we ask \QA to terminate in the destructor of the + \c Assistant class. + + \snippet simpletextviewer/assistant.cpp 0 + + \section1 MainWindow Class + + \image simpletextviewer-mainwindow.png + + The \c MainWindow class provides the main application window with + two menus: the \gui File menu lets the user open and view an + existing file, while the \gui Help menu provides information about + the application and about Qt, and lets the user open \QA to + display the application's documentation. + + To be able to access the help functionality, we initialize the + \c Assistant object in the \c MainWindow's constructor. + + \snippet simpletextviewer/mainwindow.cpp 0 + \dots + \snippet simpletextviewer/mainwindow.cpp 1 + + Then we create all the actions for the Simple Text Viewer application. + Of special interest is the \c assistantAct action accessible + via the \key{F1} shortcut or the \gui Help > \gui {Help Contents} menu item. + This action is connected to the \c showDocumentation() slot of + the \c MainWindow class. + + \snippet simpletextviewer/mainwindow.cpp 4 + \dots + \snippet simpletextviewer/mainwindow.cpp 5 + + In the \c showDocumentation() slot, we call the \c showDocumentation() + function of the \c Assistant class with the URL of home page of the + documentation. + + \snippet simpletextviewer/mainwindow.cpp 3 + + Finally, we must reimplement the protected QWidget::closeEvent() + event handler to ensure that the application's \QA instance is + properly closed before we terminate the application. + + \snippet simpletextviewer/mainwindow.cpp 2 + + \section1 FindFileDialog Class + + \image simpletextviewer-findfiledialog.png + + The Simple Text Viewer application provides a find file dialog + allowing the user to search for files using wildcard matching. The + search is performed within the specified directory, and the user + is given an option to browse the existing file system to find the + relevant directory. + + In the constructor we save the references to the \c Assistant + and \c QTextEdit objects passed as arguments. The \c Assistant + object will be used in the \c FindFileDialog's \c help() slot, + as we will see shortly, while the QTextEdit will be used in the + dialog's \c openFile() slot to display the chosen file. + + \snippet simpletextviewer/findfiledialog.cpp 0 + \dots + \snippet simpletextviewer/findfiledialog.cpp 1 + + The most relevant member to observe in the \c FindFileDialog + class is the private \c help() slot. The slot is connected to the + dialog's \gui Help button, and brings the current \QA instance + to the foreground with the documentation for the dialog by + calling \c Assistant's \c showDocumentation() function. + + \snippet simpletextviewer/findfiledialog.cpp 2 + + \section1 Summary + + In order to make \QA act as a customized help tool for + your application, you must provide your application with a + process that controls \QA in addition to a custom help collection + file including Qt compressed help files. + + For more information about the options and settings available to + applications that use \QA as a custom help viewer, see + \l{Customizing Qt Assistant}. +*/ diff --git a/examples/assistant/remotecontrol/CMakeLists.txt b/examples/assistant/remotecontrol/CMakeLists.txt new file mode 100644 index 0000000..8afd643 --- /dev/null +++ b/examples/assistant/remotecontrol/CMakeLists.txt @@ -0,0 +1,50 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(remotecontrol LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/assistant/remotecontrol") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(remotecontrol + main.cpp + remotecontrol.cpp remotecontrol.h remotecontrol.ui +) + +set_target_properties(remotecontrol PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(remotecontrol PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) + +# Resources: +set(remotecontrol_resource_files + "enter.png" +) + +qt6_add_resources(remotecontrol "remotecontrol" + PREFIX + "/remotecontrol" + FILES + ${remotecontrol_resource_files} +) + +install(TARGETS remotecontrol + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/assistant/remotecontrol/enter.png b/examples/assistant/remotecontrol/enter.png new file mode 100644 index 0000000000000000000000000000000000000000..f397f4b9c421c1fbdf714b7c914e28d9797a96fc GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#I-3}epz!kr59)wIvL&t&CC>S|xv6<249-QVi6yBi3gww4844j8sS56%z5(x3 zRP%re*^<27T^Rm@;DWu&Cj&+1d%8G=Se#ByP>?fVGBg&L&0r|cEXlB>QcvJKbCPNi z55vu0@?Bp~8ajw|{CDL2pnRO6OyM85sltRtABN9P8h3dnEIh(6VNvOYAL=(75)}S1 z9+`cz&{4urj!?7eIcyB%~I za~OoDO{nMDnkl;TBd>AG1jZhNCc$>a+Ljz9hIcZiYx8O!`vING;OXk;vd$@?2>=~N BWt#v1 literal 0 HcmV?d00001 diff --git a/examples/assistant/remotecontrol/main.cpp b/examples/assistant/remotecontrol/main.cpp new file mode 100644 index 0000000..c4207e2 --- /dev/null +++ b/examples/assistant/remotecontrol/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "remotecontrol.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + RemoteControl w; + w.show(); + return a.exec(); +} diff --git a/examples/assistant/remotecontrol/remotecontrol.cpp b/examples/assistant/remotecontrol/remotecontrol.cpp new file mode 100644 index 0000000..b4d1fe0 --- /dev/null +++ b/examples/assistant/remotecontrol/remotecontrol.cpp @@ -0,0 +1,113 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "remotecontrol.h" + +#include +#include +#include +#include + +using namespace Qt::StringLiterals; + +RemoteControl::RemoteControl() +{ + ui.setupUi(this); + connect(ui.launchButton, &QPushButton::clicked, this, &RemoteControl::onLaunchClicked); + + connect(ui.indexButton, &QPushButton::clicked, this, &RemoteControl::onIndexClicked); + connect(ui.indexLineEdit, &QLineEdit::returnPressed, this, &RemoteControl::onIndexClicked); + + connect(ui.idButton, &QPushButton::clicked, this, &RemoteControl::onIdClicked); + connect(ui.idLineEdit, &QLineEdit::returnPressed, this, &RemoteControl::onIdClicked); + + connect(ui.urlButton, &QPushButton::clicked, this, &RemoteControl::onUrlClicked); + connect(ui.urlLineEdit, &QLineEdit::returnPressed, this, &RemoteControl::onUrlClicked); + + connect(ui.syncContentsButton, &QPushButton::clicked, this, + [this] { sendCommand("SyncContents"_L1); }); + + connect(ui.contentsCheckBox, &QCheckBox::toggled, this, [this](bool checked) { + sendCommand(checked ? "Show Contents"_L1 : "Hide Contents"_L1); + }); + connect(ui.indexCheckBox, &QCheckBox::toggled, this, + [this](bool checked) { sendCommand(checked ? "Show Index"_L1 : "Hide Index"_L1); }); + connect(ui.bookmarksCheckBox, &QCheckBox::toggled, this, [this](bool checked) { + sendCommand(checked ? "Show Bookmarks"_L1 : "Hide Bookmarks"_L1); + }); + + connect(ui.actionQuit, &QAction::triggered, this, &QMainWindow::close); + + const QString versionString = QString::number(QT_VERSION_MAJOR) + + QString::number(QT_VERSION_MINOR) + QString::number(QT_VERSION_PATCH); + ui.startUrlLineEdit->setText("qthelp://org.qt-project.qtdoc."_L1 + versionString + + "/qdoc/qdoc-index.html"_L1); + + process = new QProcess(this); + connect(process, &QProcess::finished, this, [this] { + ui.launchButton->setEnabled(true); + ui.startUrlLineEdit->setEnabled(true); + ui.actionGroupBox->setEnabled(false); + }); +} + +RemoteControl::~RemoteControl() +{ + if (process->state() == QProcess::Running) { + process->terminate(); + process->waitForFinished(3000); + } +} + +void RemoteControl::onLaunchClicked() +{ + if (process->state() == QProcess::Running) + return; + + QString app = QLibraryInfo::path(QLibraryInfo::BinariesPath); +#if !defined(Q_OS_MAC) + app += "/assistant"_L1; +#else + app += "/Assistant.app/Contents/MacOS/Assistant"_L1; +#endif + + process->start(app, {"-enableRemoteControl"_L1}); + if (!process->waitForStarted()) { + QMessageBox::critical(this, tr("Remote Control"), + tr("Could not start Qt Assistant from %1.").arg(QDir::toNativeSeparators(app))); + return; + } + + ui.contentsCheckBox->setChecked(true); + ui.indexCheckBox->setChecked(true); + ui.bookmarksCheckBox->setChecked(true); + + if (!ui.startUrlLineEdit->text().isEmpty()) + sendCommand("SetSource "_L1 + ui.startUrlLineEdit->text()); + + ui.launchButton->setEnabled(false); + ui.startUrlLineEdit->setEnabled(false); + ui.actionGroupBox->setEnabled(true); +} + +void RemoteControl::onIndexClicked() +{ + sendCommand("ActivateKeyword "_L1 + ui.indexLineEdit->text()); +} + +void RemoteControl::onIdClicked() +{ + sendCommand("ActivateIdentifier "_L1 + ui.idLineEdit->text()); +} + +void RemoteControl::onUrlClicked() +{ + sendCommand("SetSource "_L1 + ui.urlLineEdit->text()); +} + +void RemoteControl::sendCommand(const QString &cmd) +{ + if (process->state() != QProcess::Running) + return; + process->write(cmd.toLocal8Bit() + '\n'); +} diff --git a/examples/assistant/remotecontrol/remotecontrol.h b/examples/assistant/remotecontrol/remotecontrol.h new file mode 100644 index 0000000..ea707ba --- /dev/null +++ b/examples/assistant/remotecontrol/remotecontrol.h @@ -0,0 +1,36 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef REMOTECONTROL_H +#define REMOTECONTROL_H + +#include "ui_remotecontrol.h" + +#include + +QT_BEGIN_NAMESPACE +class QProcess; +QT_END_NAMESPACE + +class RemoteControl : public QMainWindow +{ + Q_OBJECT + +public: + RemoteControl(); + ~RemoteControl(); + +private: + Ui::RemoteControlClass ui; + QProcess *process; + +private: + void onLaunchClicked(); + void onIndexClicked(); + void onIdClicked(); + void onUrlClicked(); + + void sendCommand(const QString &cmd); +}; + +#endif // REMOTECONTROL_H diff --git a/examples/assistant/remotecontrol/remotecontrol.pro b/examples/assistant/remotecontrol/remotecontrol.pro new file mode 100644 index 0000000..fb9ce4d --- /dev/null +++ b/examples/assistant/remotecontrol/remotecontrol.pro @@ -0,0 +1,12 @@ +TEMPLATE = app +QT += widgets + +HEADERS += remotecontrol.h +SOURCES += main.cpp \ + remotecontrol.cpp +FORMS += remotecontrol.ui +RESOURCES += remotecontrol.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/assistant/remotecontrol +INSTALLS += target + diff --git a/examples/assistant/remotecontrol/remotecontrol.qrc b/examples/assistant/remotecontrol/remotecontrol.qrc new file mode 100644 index 0000000..9b4299d --- /dev/null +++ b/examples/assistant/remotecontrol/remotecontrol.qrc @@ -0,0 +1,5 @@ + + + enter.png + + diff --git a/examples/assistant/remotecontrol/remotecontrol.ui b/examples/assistant/remotecontrol/remotecontrol.ui new file mode 100644 index 0000000..5257547 --- /dev/null +++ b/examples/assistant/remotecontrol/remotecontrol.ui @@ -0,0 +1,228 @@ + + RemoteControlClass + + + + 0 + 0 + 344 + 364 + + + + RemoteControl + + + + + + + Start URL: + + + + + + + + + + Launch Qt HelpViewer + + + + + + + Qt::Horizontal + + + + 101 + 20 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 113 + 16 + + + + + + + + false + + + Actions + + + + + + Search in Index: + + + + + + + 0 + + + + + + + + + + + :/remotecontrol/enter.png + + + + + + + + + Identifier: + + + + + + + 0 + + + + + + + + + + + :/remotecontrol/enter.png + + + + + + + + + Show URL: + + + + + + + 0 + + + + + + + + + + + :/remotecontrol/enter.png + + + + + + + + + Sync Contents + + + + + + + Qt::Horizontal + + + + 81 + 20 + + + + + + + + Show Contents + + + + + + + Show Index + + + + + + + Show Bookmarks + + + + + + + + + + + + 0 + 0 + 344 + 21 + + + + + File + + + + + + + + + Quit + + + + + + + + + diff --git a/examples/assistant/simpletextviewer/CMakeLists.txt b/examples/assistant/simpletextviewer/CMakeLists.txt new file mode 100644 index 0000000..8d459a2 --- /dev/null +++ b/examples/assistant/simpletextviewer/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(simpletextviewer LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/assistant/simpletextviewer") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(simpletextviewer + assistant.cpp assistant.h + findfiledialog.cpp findfiledialog.h + main.cpp + mainwindow.cpp mainwindow.h + textedit.cpp textedit.h +) + +set_target_properties(simpletextviewer PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_compile_definitions(simpletextviewer PUBLIC + SRCDIR="${CMAKE_CURRENT_SOURCE_DIR}/" +) + +target_link_libraries(simpletextviewer PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) + +install(TARGETS simpletextviewer + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/assistant/simpletextviewer/assistant.cpp b/examples/assistant/simpletextviewer/assistant.cpp new file mode 100644 index 0000000..cd83723 --- /dev/null +++ b/examples/assistant/simpletextviewer/assistant.cpp @@ -0,0 +1,109 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "assistant.h" + +#include +#include +#include +#include +#include +#include + +using namespace Qt::StringLiterals; + +//! [0] +Assistant::~Assistant() +{ + if (!m_process.isNull() && m_process->state() == QProcess::Running) { + QObject::disconnect(m_process.data(), &QProcess::finished, nullptr, nullptr); + m_process->terminate(); + m_process->waitForFinished(3000); + } +} +//! [0] + +//! [1] +void Assistant::showDocumentation(const QString &page) +{ + if (!startAssistant()) + return; + + QByteArray ba("SetSource "); + ba.append("qthelp://org.qt-project.examples.simpletextviewer/doc/"); + + m_process->write(ba + page.toLocal8Bit() + '\n'); +} +//! [1] + +static QString documentationDirectory() +{ + QStringList paths; +#ifdef SRCDIR + paths.append(QLatin1StringView(SRCDIR)); +#endif + paths.append(QLibraryInfo::path(QLibraryInfo::ExamplesPath)); + paths.append(QCoreApplication::applicationDirPath()); + paths.append(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)); + for (const auto &dir : std::as_const(paths)) { + const QString path = dir + "/documentation"_L1; + if (QFileInfo::exists(path)) + return path; + } + return {}; +} + +//! [2] +bool Assistant::startAssistant() +{ + if (m_process.isNull()) { + m_process.reset(new QProcess); + QObject::connect(m_process.data(), &QProcess::finished, + m_process.data(), [this](int exitCode, QProcess::ExitStatus status) { + finished(exitCode, status); + }); + } + + if (m_process->state() != QProcess::Running) { + QString app = QLibraryInfo::path(QLibraryInfo::BinariesPath); +#ifndef Q_OS_DARWIN + app += "/assistant"_L1; +#else + app += "/Assistant.app/Contents/MacOS/Assistant"_L1; +#endif + + const QString collectionDirectory = documentationDirectory(); + if (collectionDirectory.isEmpty()) { + showError(tr("The documentation directory cannot be found")); + return false; + } + + const QStringList args{"-collectionFile"_L1, + collectionDirectory + "/simpletextviewer.qhc"_L1, + "-enableRemoteControl"_L1}; + + m_process->start(app, args); + + if (!m_process->waitForStarted(3000)) { + showError(tr("Unable to launch Qt Assistant (%1): %2") + .arg(QDir::toNativeSeparators(app), m_process->errorString())); + return false; + } + } + return true; +} +//! [2] + +void Assistant::showError(const QString &message) +{ + QMessageBox::critical(QApplication::activeWindow(), tr("Simple Text Viewer"), message); +} + +void Assistant::finished(int exitCode, QProcess::ExitStatus status) +{ + const QString stdErr = QString::fromLocal8Bit(m_process->readAllStandardError()); + if (status != QProcess::NormalExit) + showError(tr("Assistant crashed: %1").arg(stdErr)); + else if (exitCode != 0) + showError(tr("Assistant exited with %1: %2").arg(exitCode).arg(stdErr)); +} diff --git a/examples/assistant/simpletextviewer/assistant.h b/examples/assistant/simpletextviewer/assistant.h new file mode 100644 index 0000000..1cbc8b9 --- /dev/null +++ b/examples/assistant/simpletextviewer/assistant.h @@ -0,0 +1,28 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef ASSISTANT_H +#define ASSISTANT_H + +#include +#include +#include +#include + +class Assistant +{ + Q_DECLARE_TR_FUNCTIONS(Assistant) + +public: + ~Assistant(); + void showDocumentation(const QString &file); + +private: + bool startAssistant(); + void showError(const QString &message); + void finished(int exitCode, QProcess::ExitStatus status); + + QScopedPointer m_process; +}; + +#endif diff --git a/examples/assistant/simpletextviewer/documentation/about.txt b/examples/assistant/simpletextviewer/documentation/about.txt new file mode 100644 index 0000000..eeab35f --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/about.txt @@ -0,0 +1,9 @@ +The Simple Text Viewer enables the user to select and view existing +files. + +HTML files is displayed using rich text, while other files are +presented as plain text. The application provides a file dialog +allowing the user to search for files using wildcard matching. The +search is performed within in the specified directory, and the user is +given an option to browse the existing file system to find the +relevant directory. diff --git a/examples/assistant/simpletextviewer/documentation/browse.html b/examples/assistant/simpletextviewer/documentation/browse.html new file mode 100644 index 0000000..47e09f5 --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/browse.html @@ -0,0 +1,34 @@ + + + + Browse + + + +

Browse

+ +

+ The file dialog let you browse the current file system to + specify the directory in which the file you want to open + resides. + Note that only the specified directory will be searched, any + subdirectories will simply be ignored. +

+ +
+
+ + + + +
+ +
+
+

+ See also: File Dialog, Wildcard Matching, + Find File +

+ + + diff --git a/examples/assistant/simpletextviewer/documentation/filedialog.html b/examples/assistant/simpletextviewer/documentation/filedialog.html new file mode 100644 index 0000000..6ebf403 --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/filedialog.html @@ -0,0 +1,48 @@ + + + + File Dialog + + + +

File Dialog

+ +

+ In the file dialog you can name a particular file name, or + search for files using wildcard matching, i.e. specify a + file name containing wildcards. In addition you must specify + the directory in which the file you search for resides. +

+ +
+
+ + + + +
+ +
+
+

+ By default the dialog will search for all files (*) in the + current directory (the directory the application is run from). +

+ +

+ When editing the file name and directory parameters, an + overview of the matching files are displayed in the + dialog. The overview is updated whenever the parameters + change. +

+ +
+
+

+ See also: Browse, Wildcard Matching, + Find File +

+ + + + diff --git a/examples/assistant/simpletextviewer/documentation/findfile.html b/examples/assistant/simpletextviewer/documentation/findfile.html new file mode 100644 index 0000000..dad66ab --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/findfile.html @@ -0,0 +1,32 @@ + + + + Find File + + + +

Find File

+ +

+ To open and view a file in the Simple Text Viewer, select the + 'Open...' option in the 'File' menu. The application will then + provide you with a file dialog that you can use to search for + any existing file. +

+ +
+
+ + + + +
+ +
+
+

+ See also: Open File, File Dialog +

+ + + diff --git a/examples/assistant/simpletextviewer/documentation/images/browse.png b/examples/assistant/simpletextviewer/documentation/images/browse.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc5e97fb6ade68f808b5007d32957d3d90b30e6 GIT binary patch literal 38895 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 literal 0 HcmV?d00001 diff --git a/examples/assistant/simpletextviewer/documentation/images/fadedfilemenu.png b/examples/assistant/simpletextviewer/documentation/images/fadedfilemenu.png new file mode 100644 index 0000000000000000000000000000000000000000..e00dd4b12cb5e4cd381f72b481c4617b02578648 GIT binary patch literal 11616 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 literal 0 HcmV?d00001 diff --git a/examples/assistant/simpletextviewer/documentation/images/filedialog.png b/examples/assistant/simpletextviewer/documentation/images/filedialog.png new file mode 100644 index 0000000000000000000000000000000000000000..a7372b86dbf2d245d450c50f7be70b0b0904c46b GIT binary patch literal 17875 zcmZ^~1z40{^ewC+-HoJlr*ulg&^0i04V@B#q;w-Olypf9HIyRV3?L;TAV_yN65rtO z-uwUJ`JM-OhcoleiFcoU_F8-GXiarRYz%UYXV0EtD=W!qKYRAP67l;24F%Ei_Qkcz zvuE;I%5u`WKJy1Ts0PFXf1A!8>0)Ye+1ZgdtZg(zKaSA zdnp^;!83(bnZN)eUg4_ys1K22AJ=R!bq8%!$JBBmpfEdg3K}8ml2+ zzxMs>Q44*AaY!SeuTzS)Qr2?r_4h}_m(uf^wtYYjmnpah)4X~WeSGZ3xf;Z)HbOYe zBZd4qZ*FdG-Qoa82Kdo3TP*U+8!{}wF61)ud%I9`3>Z_J;K(m9EIs+IVGSu$m4wb^ z#FB?B?n*?L4btTGY2gcRlvi)dh(_1C;x((Jq~sye$sndXGcRIkWcklPi&XQ6HI(1&`&6eaLP+|9O8rMLPFQhE!1d zrAH(V)z_4FuU0<*Ok0{@d#4|A9Qj8yHt_-p_OyUl3fgyfXJZCUPMPXIawTG4y|F1) zlCA=7`HF?2U{977sQ_2yez4)b;O4GTy~&X969hvB21m_5E@H8n2cB|Er4olG$;Zl* zI?g~Ryno-HjY-XOHP1#D$VS?*vh&`uR*Iu>nYL!I(xmDw8DS$|V8pdH*eo^OMXhmj zpS}A`1CQw%lmV)J7M%8;F4-7Nq8ARl``YAn_WS*^eLCq?@?xtmq2JZvw7OBV3l$EP zXmOhmgL72iZ3?r7!rz@MAX{5$6Si418k*am)%snzw1EX|Mlw=?t58t=c(dzr zs%KkU+qe1>JPjij7EpA8m3Fa2x|0|OcmF+Dr?#&y=j>={@!0L zBf1$J({gE1Xxn%nXsU>qS6*CIxwZYq`yK%pwwjOH-W=74_cF;KFsBt)52Mlc=G0D1Vd7Z+MOx2w{Q!3tW}0#`S4{w#Fy_ivp^JmRSOdJoal4{!LsKmUcD0V$iq9yD)dgaJt2 zDu7B30v8(W30)Sy7Q7eAZ-)^KGcGf5uwiAYM$A+hIeuxhpF;V2c`yZhUHI4a{`O*V zLb+fpIHEP7eeD@0POUjlOfmHC{@x3|JCwrAT2&cBj_q^&rz_QKcebV`prUW3C(_GB z$5_^i0VT3}OG1jW<%fA|3GBnkYPdQm8n5Wc=0|pPlk-BMMNhNk+ zh6_fxuDO=gP^(z~Ko)j{Z+m7or1kSm7KCt!3fjluNO_^Beb;4fZ6|#!a4P?A7b5;c zbC+Ng3dQJ!A&aym-xNTfJlz5Sw1~;zVoI!Gga;{kd&gZG?Hlfk+4(p7Y3m_6m6!b2 zg*oD3K6%mqF8NRQhIak0%eX#pbs9w@Pm(bVPbP+;TgMO-JxhRxaQyQ4@EF7uERRMT zrU^v6%fgOm4TI7B@0Y2>h;u{+gg7J$p#R?c*N}`0!$3nbx4_d5L#*@j?3^6;-Jh&7 zORQWbEn%O9TsLpC@p5lp^YDBken~5@r0e41f^db5#e;5Z{qg>OF)hOF5)u*~UZ!Xo z0eF;@BY==kbRl+H(H#zrS)b@aKTQWc-lw}V&}`k(iI)WA5woMq#Gn*bBF1&p0!>cz z2vGLDp+htxM)Wi&uslm;Ap`rx6wO45lb7nFV(C5f-Ez!Ik6@SDO|?EhhR@+GhRPVS zG^7B_6thpjf5A^Y(6>6U%9H4Qv&E_qGk$TBB>R!2Js}0Z(cufQ7Rz_6n~ANAiLl35 zFEeNl7__pXljmHKN7dijH-ML407V1q6@yEnTNBjYwD(WgMnfv^y5;6(uQ6tckD8#m zrW6xh?d*a6&=Fmfn(+PCk^;^N0;UzS0A_D9sf@-O97%_r`c1|kfnrpZW5_Z(L~w4| zz;h9spJN@0V)gCU5~atd<+FloN2{MV^?26Z+;ZV*FgkfXV2x=TTnIxM=svf+j7Q^t zER6!lA2_U4W#0ZvTZwkZJNaBK{hM^svOQ@SG{(obeHxY=^-UOAV3)JgS^ha3aD#=) z)F|)eXhlXw)>G>Je&l{lga~SY4u$}MOm4>e75b!;e_yl5KdO7O&mw()!GDt{3BH&a zVw2wZNUX+gem<1(!U@#ZwFgK3dLvKAHBTxecp2iHsZqx**9V7a0zV7^ykCZnd^X=e z(jy^Gd?Cq9T4W|GmI2}as5hF(yJ7pjcY6`Yz{TYy6Km*Q%VHmli><2pqfUq<4?S3P z4Mh4@#QYo&5-|y9iHzIv+AKPrXUleCd^ZyVQ&Ek5;qkiJ8%0X&C>i0>W%?# zm~tk-?tWBYuyHo&(%enWF(ZxVt-FpGKlL3|c2CMM8~N-^U`(PzYIyni@7T|21HpMn z#s2FJYS=P0(7t@8OCBM$yUf|QYT$^HthHvZ1G5Eh(&4$k5nkN{I# zmtb{D?I{+hP4n?^S-c!R#pb;jtMwc%d(l)7t4s(z)~tQUm^!^T3v!J&rb8CRj>!Pt*r1)e-fK^*&AuT^Km}J+PgJd zON%3BXQc#%hlQE<$GsxswUlG(_2lh98?0c!QU&z%_cI}XPn=!($c75Ce^J|3$te1; z{dt}*ne8w8RAsltY2V1KA0MznxoFJ>Sl_j+1LIJz3WyXeDaK$F)dQA9Qb%(5zPgcD z^)3ssPuDTRj@hf%{JrUsowmi8XrV3d6p>)`Li?;t7PxczTF*2Ht9b^PA>#(8sMjh| z4Z5Q)9IB(>Q3NT2o6b_S?HWd?Dx6RZtkd*%C8u!6PK?8CO0thVGL+uD>^QG`8=t99 zXoO*y>SkhW_5~W|FVw)Ncx9rzGe9|T%yu!6uMoh|^5rL2k>?Hh1-nMSysr!nOnYUe zSxu|r40v5pznwhw67aW^GQn(lYA?&;r)z*_#eu?aa8mx3;Kr8|-(T?S)g!bVB-Y-m zN?QqnAh=mQ1LpGC7t{EL$?Ag zC9qH7yT#GSk{xgqM{omPq^&2G%9d%IxrY}HM;fLuG9SkWWvyINjKdp*0^@(kQct(f zEp}l^1mTPodX=jH0bl)1Gyq2$_E{+0&E6r(d)O^OURyvQ-6PHh&taVS!bGsh5F4$x z)a_@1fnCK({ys+4D(iSH=BbCbg9^aO_kp!ARwyd9OtBp}`*Rd|fcNV4Il0}p+av}p zfn2C#IEIxvJr^xAsBi7KQ@gC~k9td*zoSsS9T;0`lehZzZf^+S9>hwX zb>7X5-@=YxawX{5>=+?GOAjhfmPyxBu-KumKwNWMp3BP{)?q1RN6q>vd6dR5{B97= z&f5-#nauvl3d(L`WC&`-aaOHQfiMFF-t+{^!pOTIu~G#Av{Gdl%j#zWZ*t064sCd!4xCSt}8dU4Z8*o*YJobb$Xs1(c;r3x^l1p2D4c`xWw%q1zWYZ zuKc^+Mn@y2QzVrl5xeebgf)Z%3SrlmS+*S|Q3kPcsmk;<{0=rz)$*Dz={5UV!_Iy| z2z9iNq4w*Z(qFWxp{-opsr-<05=zEF5Ja+8>D_&$ z-=R1VL+?&!conGrenbhp@vTKfmEoJXuFW(yON+~BvDGA_hmd)%sc>e~Cph;-uF$WE zu3D$u9O(I+je0aj+;+-Ws$bFgwJr@rhZVE?C~@K;v_N9shqyzR&N4sLx;+{x z7EnR55KCoi!Us-mnD!DVcpjX6oKmChcOtd_(?FN6Dn4nU#sO7gu+Xe~4Q|q^xm5xt z4%I1g)~eo8SVUKDMJYM|6?IuN1%o?Nmuw)O0c6y!GaC%a_YcN)=CoM$ApYSK@mifE zv@??Hn1Fs}J;=&Q#4qlap8teAiwUjcqFLsfi=+E5W&%E64jB_*eu?s~U1wR4ZGn0@ zIobKWln*5ds;{wdtDtv;y(yT{FbEZ;yV?_TM0_>jQ-;#@x19b-^_kis01(|l+;o{7 zIPF+ym`;-d=Q4Fb*Owlh7tp+keG@fmDfLClhT#<|RgV%XCO=sRg;4PkB{xK)_RNx> zSB)x@)Z2lQ&4|CZx2<`Ff>G8#4EONcU+EIh{_HFgyS8hBJ21I(J6+%rZmbiyWI%8_ z@T=c1s*fiQcB%NsqJqyGNBu0Dy=hoh=%N<`7c+}#u%eMTTDI(i<*}+qJ;Ho1x~^9y zb;A1lX}hl^jdK-L&aB<;-C0_&mvL~B*>z<=ZD~*fqTD%wpZJ0}YwilU1{S_lxsglQ z?D@LUmOMO73h|ei?p%D_Faii1nd4{{Z$kwVR*=LCQ0|b~YcLA-0Abe-Y5~8E6b;ID zYHVv&i+M9G+d_;$_KM>seea9r^D9bdv9Lxh^#2kdRS$Ra@3C! zM0el+HY@aSb|fPf3IjJbZ7+4>Z1g|mghS=N?aSc4d2_y)b6bpcGo4umEEL>a23oh} zy`g%et+FvhW59P!ugmHjW=TU5S*|9)zw?cJcEkSiSAmGzyM}Q4lU1I&invljTotL; z{86oq=siZlos)xjjPYt(E&iMh*`9b0yHGG>N>E@W$Vz<}P|L#+Vacd!Vbm;UL*tt@ z2h;gx1tRo|6UpKKpi3B2M9@qDYjM8C zT=3yu{;SR>joQ};{N?+hr7LB0+j<%Ob6e`s<`t$}4>qn@*g$1ceq6{^IPtcnK82Y{ z{MLhp%Azxeiy?APV)*f|;4oMrG=>h~b1zOhJcdddAKA0XP9Bz%{gFfuYVM?og~G_G zS?BIL^6OD^Oq%6rB9SDI-r#r?y>(q@79A`P!tcI9U{J&l0`5%?u}Mu8LD77N-Mzg_ zp@U-FmFsKSFuHZlzNsm~tD|LG&?h?iKu;v5SoribaU!)uA>zZNVT^&kG1UYzONh-VgvVrT@Mo%3gCiQP>a5jxHe76ti{ekb1Fxt`*2g1vzHLDi{eJU}H zfapGTH&zB$H^f~UxKRmUosQ~YWr}(=Ey%*SKcOY`#^%x*I`!pZhGfy;fRATb}kj<^rq-dA)zTa zkBc^JJtW)uf~h$#5jOeyZj(fH+Yom{J=TCZF-bgmhq5NZl zg*jeQchqUmzzI*p0i10NHYrCr;AX_$V0O;Dh-8IFafqt1B~^RSN!81w2?QOz#%_I_ zC7HRuqBVA4)k5$N@QP>PlGg8NUaGf$nz#4EyipgJ8Wb-k`&mF0FUWBX5DBSGP2uUtoS%hn z`0J%Fa+iBd7_F!A7@Zx9q)R4w*i2v85NBB)N9>_ZZPPa^85{40ku>g`YfV0*AfhBA-y{Rd7&=j+J%1jf1<(@;ORO&f^Er^BjPdyZj9fHk;?5R zf86D5@~jF*A-wA1)WwkxL%ocz!v0)sbi$#ml*L0Mj-8~;r%Br#fGXM8+_#;=8@)H2 zV6Is{pW(|(#s?v&>ynPr1nj+}>N<`Qv}-=bb&lnd6n#cb&CRgF0RN&pp!fBL1M4Kl z?mF8f3YdnI;Umdq3$i!0bhIUGFbu@)atD;H-nj<^yicts55A`*uQHY$D3s&_zjxxG8#I1SKO;NJeIvts!=}Ig?#u{zdrG#D6blBn($_AU-SKn2}=qXy!4Wtrfd>@ zb0Uj`$FW&!08P^5cW~Xc)mH(BAMWa^Zxlh92yFh^E;(JxxM##ckSqX@w&&6bZDFIS zxdjCGmX6kTcQZKMISn%<@a!qy-_5Oo?f%p z<QS zD1qfwQ3uEa8d$s^mBi!)JWrwwLAH`&wmfd8^)C#o>5mws>hPtuP(fWbP1M0qo zq8nEG73M)b(}-!;1Bhne5cUg#X+O@&mp3A+wd|ouL2yJo2^noMudF%jLOY!*ve2;- zbYamkvP=J>HzW;Y^MTr7O>0oL2l;W(0UgJJvN67*Z_d$?}zdMsaC%X3zC z8KuDC)tn><{EVf2IRK@4tOt)hg;ul^JBVv13|8Xzhnbe81a+pFH*$0!*1cN-j~6B! zChE<0GivvqGv~KsCXoh0Xa|vTKcajaR$(*bo zBE77@Gz5}|ty>1mZ*skT&*5rJBAXyr`kBjGZzzxm=dXe^Q*4*Yl~nE56i%qMj!>|%wqF|D%+1}@MXK;E-rm@; zlgJdfd}dZcwLKzhh=hQ5!(P+0!hD5v9G^M*sKU!aAG$`!Ny;uaM0x>d#MR4%@;ex0 zA710_io4|D!Z?e~*mvH?5LBy>=P%Y4MXjUDp!qI<_0Sf+K$pUxL|g{O9h4C?v8#x7 z1dUu-$jaW=Chx`}^kSgM=iSr!2d^0PMk07(azVt>pz>)488p^OYi3Ma+pQPN?QyG$c-uJw8cDN+Fd?c{)rdE8gg0R zh>5{^!QE_8Pj|WkNL01XbW-g z6Wt!pR=J4sMDUYg!P%;y4}XuO5T8F1N!8<;5SIx4HRZkXktr7Z^QsnFj-6HG7JeJQ zu9XlwRZ(Dkb#ScwNwTuqN-q!ndRpissyOX>PAbF)W#v03JA}LXPzw1<_ zCu?LP?}{&7lae62%iLNMUta;gmU{+fhHWj3Pj}-Gj!j+MGzA=tOE@D^j;S@R0v=ys$I%}uHyJ(6g^(ia`^3sxytmD zsJ&E=s|dDy9K&n7RA@Vc@!edbUNdjki6f#ZUsNzeOSdIVuMGC#VporV{Mvt(WYa{! zDoR#BwVmv<%@R9{NqV6&gqCb-^u?&YREVLN$^3D&vReZuvTW!oPg8lM>2kJWK9hS+ zSncm^8v`uHxOfW=9@oU9T!)6URS8_n1RbO~2wzx1CU(Wu;!E7d2C8zeEptQsgbTLL z#qnnmB#;T4MXs-Xj2n!!cnhKkiuQ*&L)2J>*mdAril6n|*lVkO=H}MnSvj_=QO$g6 z57Wm|9Vk6U`u{`GN_OXSx@eZ%Bn)l*`Tk8pRrKzelv=-Uw7oBfYuu#lW3aJ*8cp{X z8DQcx>o9}dQr9K=fmT}sFCeWVmE=;@Wg7{7?c<3u)9=A%-LRmQ$u%x&xuV+>;vbX}2mu$9Zk*e9B-g#1)ffB%&> zvJI8s8c6$Lt$I{8V7P_sVoL3LpgcbYvp195v2Zb?qhQb#RGw$@Ky~O{A$H z+4)fCWj^Ia;a`0I6SqU&qcX8>7Z{}*f&K|qB3M5^f*BJLWDiy-C7`H|LSJmPvm{2S z7#GAmU|O*Q)$C1JP1>c`;8t#;0OaNpY>PLisR;l{-uQaNS?{`HQf1&d48X2wr0IhG zzGCH5o()N{*88^WX{^Phq4M^)+J(pQ8?^>MXsCj2^g30}-5qcDO5lj=2j9cT!df`N-zvclS(lnlm<{Y*Asaq`Bv}ZzB9(=e{$G z*Sc~N2fO?(|IIh_GR**z>R9`%^f@-`MtU@IUNdjGWIaNte?CBs`Tt9}f$f`$!uD2g zlc5hG;yTnned?4QK#hjceh<&l(3DoeWUD`;mwyF-=#_y3v{NpR`3#kt25XE>HBYov zro62V%H}3#?WBpJ!u<QCmHn=(Mic&5FRRw@ycv5iU~Uc8vMx zVlfupjXY;t8+Io4!U^Gy4G{P3>O&sqW_0f27^8zWbSjQL%WeyeF!mDupGm?Z{nXP_ zgB9O#KvdKQ7>%31CyjtRr91SM4O2vLVn}b z7xI(u+)<@u%Oi&Wl>U6{?Lg#I5yaT&!_T^BoMrfG+Qu~8*gxS{2EcJPNHdiMn$2f)I z#RAn|2`{*0fO&Zlswup= ztMwDSvWAWAj~C7(7H223*FE=)g=-~82LYI~G+~Zsl{jgE(Tf-Iaj8vw^Qc=}0+Bb0 zj>k$vDdBjho5FsV9MvY@ONI1nOgR_z!-}neT-66pf!wK6$EUkhYu$#wFKof99+cvN zxxtsy2)J)R&|jKXTQyH4+a#sx&{u~4!5;YouvsqJlF?l28&%pJMNtm5uPZ5&R|UEw zB55YnC_O!M<}TUt`iYSV+pA{XHXbHd=)<-EHFCBz*G_pOwKqZedB3(5kX9H8b#^br z$PJ;Ib@fjiX#R$y^%WmEyDX#0dNZU&@81GED_oH2LoHccoZ`u8G&-yQ@;;fCYbu41 z%WDRPm+tQFAMmND;?ud!+Mk2k^aoFl(ji*5OI4|dYERKiOfB@M!-ZJlOMbZVs3@?$(w(AE(2t~MhtK-7@)1~u$5OW#cJ4;bR-KLN_X4I?zpB=#F#@%RX#N?LqFdl3cUp(1f{99rKtSp1JD)2@H8{nw4|x*KSM zvcwUmZ>CZ*DlSnrqnUy_lBwN?_q0#vWq&ziUGJZv756f|d2~Nh2W`_&219`_ajxZL zszh^F_tnO&uV$-2k<&=!fR#wRG(ldHFzp&>n0TkvUA^Eq^WXU8vc8 z4&<-vwT-lV3jYeA+ESH^=>-MKu6N`mR=#R1E_Xc7^Z3ic-U&99X@28_XBk*tqrZZE zplZ8Ye5qR|i8)LB>r2%;qZkyF2%csH(jy0?!{idFo{#^pF{MN(bKfN^vyg}F(>&V) zAk|GVlJTj%;II3Uq2H|91Ote1?lq)rdG%<}lH(v)H3VZ4>GF1h%620dean(>XmxWj zE15%GCU;V8>*Gdz)p?rUj6L%xyHzNyVs{FPuC*M35JSYu5LHT-k@sP~@wC?`F+$yI z;HXoNfq)~jmxt~l@(_#bKKd7IbDCb&FOU(kRzM`MWME#Cb3J5M!Mv@>qO+Y<`*a{ zAdNpH`WrTz&~}7?5aa`#?Q=uQW)4UaZIm6`qb7+Z<+a-4uw0Zexrif} z_Yi^hWT)MqL(repCjXQq@R)yElm7@f3>}~}Qe}^=k8CU1L=>LPpkGAUV<-xc{T4>> z3Y%sDVdH~-F~@I{(x+qblcD|mOksGZxpd;1=U{=>=iO4S=eMd9A#-Z7c)X=r55P--l@34L4S3G_qH?FkW(<{cVtD;119Q3mrpe=xuTrxk~G0St~B<)`lIt^Y0|7Oav0bLvS$&3A8qTW z1yD_%j`|Qivhd7nowYYmi`&bPkKTxO_e-0muP-g_94filj;r-EqCIlvA7<~-&`lp@ z*IB6cl4Y3r(bJG=8JNRRmtKIBZKzCzl6?0H59T6DdsX zFnZ8F9{-_7x%ETfvpO>Y6=QEtnqH|7TtgMot6M{7`50}eH@i5Kt))>0aF&J{)u|}GA zbD*pxYTifef*C;#QWHzJyTIe2153~Bcc`q@pjXtuesqgS$`Ms-n4t)HYgYT zIRjx=K@FQ-;>&6^jq@a4CQ4N$=y#CE zDc)Dm+PPt;;nOluxh6v8kQJ6*`Ab%%(N$Vo$UB4!pe5b{<;U}PIhbXXFkHi-{wT8$ zKBJsg*=v78fgC6wW@QXQM#F(1S}b#T*Ev7wjoa$2yeZ?##ic#Mphj}AP1b|e(lJ<= z+&QouURI(Xb$J<>hP7@*p{O7}8Pg8E7TtQEI5?=30|VGwD4!iIwZ(RJzGZ;`(q275 zYIzkP5y{gFMBgkNh<(e>?)8V0Qt|_47y03Nvow&kXTGS=?Py8jB`T_HiFVx`!WAz$ zgND?xBK1NUzt*g(bHiZ-f5olJrX73`M#{v5LLMIhR(j$HlDgCN5VF|u_5i9%o__K*_-r6RgjZIIAl$rYc%xd$FB_*{PVi(NNWTNwa{_ADRp_J zv-PhHwXfJR(!)MVE2ma3p}$G=kxfznk|6RmNp-w&j3bn!e@gkDS19`NG=(uszSnsK2Ju%J0J3aYN~T}cYqSQleN#F4MU7Q$k3B{3#LYzZhjxT|_`G8z zkB(Kz881wu;sB8Ukwqd*_1KT^vwe}j`8-`2D5HxIZKrdYOa?x5JpPpu54a^_*2oQu zjErph%gxWP`#nLXARqd)G$^2<6wY;5_c{gk2)th zCyEA6qHNWq`rMgzit{i45xSIFOJPI?6us7g3>{y(kcKyZ7ZMI@MgfuNHH1 zidgf}o&0e6TKp_JScbleW9 z+MC}0=MaofewIploPoROYeeR>6VGjG9I6R9D7uTVu=1#;>$7Yh@x53d{)BAlH=|0oeYL2i)+8{f!ZnG}>yEm?&XhXwaGmSK`| z+2OJT1qJo?_ZM3B#TZ&}8q_}H;^IQcBtJI&O=M=LO`Tq#XKyaCGoQrluug`c(Edg3 zY?*w!?}?5;6VKG3Z#JhnQxt&o=V{Z8plL1l(>{J!OQgKlu;sb^x68-Agfu(eM~+b8~Z^>n-h@2?DFV zf6rqFB~CJ?0Em;}#$_dxPaw246Lv9}@`+hP$JgA~s*(saG^X)3Jga^M>aPZp-w#i* z$Aq3}e@WmM7bQy@Wn~Y*zfy^|*p@LIx>Ao`*;(COUY~)4hK2%xiiyX$jDj_j!j7sRb{B#ob?tgOAg)j8zSFavw^P%tL`=tq2gJ^kp2{hwyhzbd2VO(bI z*^|zL@94u|AN(S!*#l0lU?1l#HW;zu%h(F? z5X-?5*MnY`%xJD?XTLsb`VLs+J7@(5rq*LOo+MAQ`-HS5>1In#dL`bYVu{3h4Lh2lYI?uKk!AY*st!2v&3Tp z+(td)uDA-~@u2Twbj4%422wuaD`Hh>v<%IQ?Ezx5XpuzUgpk=?M-<}u?oYla=nt^F z`iJkPwbco+jEXnn3N{MK7J(1v)3gztWcyN;Ba@zE-lkPY4UvMCKIQdqf!Wt$JLp+h z5a37+v>})ruD1P+yw=T}3GgS$78s+$!24~E^lgKUo{3T@1QZeK5273bC*Uze! z3&dE4NMW=+r7&JJ{v`1K>d4|?B$9uSK|B}4j0T|gM8rxgu$G|5zzIJcw|~MONI0}f z3FZLN0>UZ5ko!s**e3|nGb6FgKSf@Nj3CCw{g9)TKkx}V&=LmrmmG!s2vlkWcU=E( zP|3oGEY=2Jo~4=Hrpw#D3s4!b1vhYL4tuVP2s<4ibIDKy$>;UjeLuZkRrN3RFUv)! z;Fz52CKN|Q6Y*r0K>w`3K$!uzwxEh}->4tc1IRJ8CND{4M^IW@wLn#$D6Xc4n4D5In9=whqI!Y@|7^Jx8iObL{W$^@5cv!PluMiPidG?hK{Td}|Jh1*B7=fBKb+4~%Diyni_S}D6dOF@;v3*>BuI2sD zjg0;3PKKPPuje)cfQX_iVYffe(l|gN+#Pq?vDap*gl7nkgJA|d$xS@*UtwExouX)_ z%(BCOjJ3Cdw!`>iq6M46a&UsPCEl zsrm#ltm;QpoFwq-*mT=ZKW|uG2}ukLD3HHfR>=~)90tubyXJZS3K7Q>pkCmyytesT z$;y5jIJ^r1Am@D>UNLT>&aT?qUbp%7!ACNv#QRsXmbkCTvPbeXd%S{74t)^SJ<{32 zMQ820BIRezA8kFGx z+F3^mSKM%xN(2S9fe!Kfs9Ji26m&O2@A5lBX9ZQflj_Yz>xM zaRvDgqrI^G>~8Ap^(hagG4=bgJ4a9M-CQkcd{hsSYs!DG2<%u<9?ZVf|B+n?#V`UE z$}`GM-(97%S~~dgpskHXTFrOhZ@=Rg)-#;slQAd^he+o5P;R@1+#(opxX)zxfr3y9 zBCBJd2Es4g68Y=&gRe~<5|?Oau>;U}ptPM$lqDTFWdYj-ua<(f>Mx#+h}{zHzSjn7 zn-l>z2G<|xYl|(U{B>%C+N+LIpTNL@{RBR$VRH}7f#J%?0H<5Iiq4|916DfiXr1%n z&k_56iLT|!fkG7tv5a(%=WBeIc5}|+GkN}4nbbGFMM_{`F?i$XsOI2yc&_mFKua7) zB4HqjnNNep@MMJ1e`XtULy^l(WCyG9rStslC+gnn(W3!ul zaKEZOv~oELMRP(s?roo{WNBCRTle?b;=Z@B>^~p@N6*s)4`(BDp zB&apF6E1NMMx>V|@fMmRc$>uJ-S(TNU*#AzflnE_u@J~k3BO(IWyDK|8kpY%e0YAb27 zdRRlJ%txxGY0ez4h8dv52|2Z2T_|=uRPJiDJd};&>+0o^?q*<;>YinZt|%AEDL{@x z4!2aeFHW_*^PM*?I2U#jJ8yR>lg$y{rV_N-SEz2$XrNE!VNAF9~_BJ+6GS?w-MhlYV z_N+DLAHWE?9bsJ9esyBFROI_IV+U|K{!Tk#-u!~)dHGy1S8oxSwJGTvu2#7?ZEW8% z_Diht7xg%w5fU0i$UO4)fKQEQr&$awDv6eBomV_pUS(nA{wDWHaag0BL|@c`+bJB0 zlVMmzSUS5M^-4gpf8X$+WCkb(r53%wS0(vS7zM1>E7Vji% zW@1^-oQ@o#6Qpisa$waj^h@=_d)8w$qr}F!8K_!giU+t zRY_9Dp#zbo_jPXvMwc|d6yt}o>r{4HtiEFLETxqlPpIZ`tRqj{c-Ph*BwEbJJ_#e} z9&&j3C;d_GkIg3nPS1F(yFSH8sOq1ua{?#NG(wp?(|^JM$&ghV)jFZzq;PB}C+)f> z{?O?tt4|0S&C5n^!Db=pVK0rz?3by;zKYh2=!##Ug7>MWKtZoJ!xextPuOkmS89^) z*39h0cR#AS2XVZ#>Y&(82PGI2a5Ozz6P46N91vsgp<+<|rop{Z_SEC5N+gWzEM44V z-dA|}OddEt6DO{y5Fm<%Fw5x&yF77U*kw)GY^i!q1tP!uRESADv;V7}H?T7})svsQ zSy>8$0rWd&)Y2#!YNHH$mu!s?5@c%-s5xbUt(WE&)hniE#KS}{IZAau!ABR#1!u|) zjFdJ6_wlSj#k7x(lx(`66ZY!Upq;|MFtY}Y#=%BJY3o~3 z9o-ynba#-10LWnpIHC-kVCJ2#7H=0O9CLY#&|m<>GwH^VI7n*L-kX|c0g0JL(^gIW zNnpYf}(TssJ-Iug97PD#h5Dr-Wh=zl<2lEWk=q?HUOGEPvgH{RBYgEjVi zO<5Tq%(eJ_Zq*7;^@tip6iZHyXI8iWcZP3mMNXuY?(JjTEis~eHnHMEEOxOQEs(C(nAPxy9&)FZ7YlS{q`0OjOh}jTu z{f1i-^V3P00wD{`oh{gNpKRIr(ebH1!*mR*>7b+gFo>p8N}K?{i@Vt45bZMeB4Y7> z1X@uZnuMYCgp}v$kjrf9NH_^Lulten4kd(EvK#ek@jf5nE=)6EyT2PznN%}ARRSS+ z(}nV}JcaL(I$c|SsR6L9}k)^WGIl;W+oO=5(MkH(?Af4!vz+!9p42e*T zpS+gR_CD43;fH+zbntVsp7+4Uo0E(Z<0Yayrw2feeRqF`+l*v9Db~icbVEuZ)10W*g6$aiCk!3!$lpi~x^(*Uk zm{Y5L7M6PGYRr@&9imrTfA@0h5tYDjeUfzgB2?_2xjL(QVm(Al7qN`)>e1J+2y68> zBiKPuviRx?c+X_n%Q#N<{J?Zx1HVP2I{UPgyd1PhzW9kE$F^e8QIhyu?yg_96ZVss zp|kCv?ce$S+TTD09~MM}DYCO7fT|NaQ=`Iat_a_H3e#pGGUy1DxE))_rxH%uSW7D= zf#5-W7L&Czr3%MUX)6JGr7N&j^T{tzC9PD5A=j*=jPlU{=LHRYRS`?W*{K>rg=p#V zf9=Wixz-P_>|MUCbZYQwFOIB-hgxr4yZXVgdE@VP`7gKH^q!=vdz9Wv@zmN_>8E_Z zQX04cM(5%oA^&B->>a-4Zk4%I;z{m7G#7WVa@otRJ`pL6entlit2;MyaH`L_Q4* zS+5H2wF8F&ft`4)y>{TN186!Nbg~Kh2%O?ih1E~$ff^?)>iBTwKf6`_f0?{s!7aeU PS{OWC{an^LB{Ts5sT2fT literal 0 HcmV?d00001 diff --git a/examples/assistant/simpletextviewer/documentation/images/handbook.png b/examples/assistant/simpletextviewer/documentation/images/handbook.png new file mode 100644 index 0000000000000000000000000000000000000000..3bd2b928b083296cb9b85d0b5fb07106e1fd6f48 GIT binary patch literal 1060 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 literal 0 HcmV?d00001 diff --git a/examples/assistant/simpletextviewer/documentation/images/mainwindow.png b/examples/assistant/simpletextviewer/documentation/images/mainwindow.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ef7ca1a6067de5fbf25440745bfe5e738ffd6d GIT binary patch literal 24819 zcmb@tWmH_jvId$2cLom{G+1!g1Si4W8QcbU5AF~M5S#$P2@JuV87#pqFi3EBcX>mO z-1F|ccilg4X04IlyL)$cbya^=^>vhrk}MWFDf+W#&#>g>q|}~0d%^eY+4FEzWO#{n z^1z>G&w$PHQtvdpX7~S~=n!bnvmN<%JR2_uyWu8UIsy;rOLR=9VfhTHun+k%plUmw z-i5d}`s(iJHqV~Q$l%EFTdjl3h3v2}q%RDntcQ=@0m@M5a8A0RK-Fee`nC9DM2~5; z4l7a2|1V!GBj{*orKfAZK2!ET#(UqKZ4=PIUy2ekxIcnV9_<2?O^`buMFJoEdTOjj z#%XA2joSn6h2AhQ$N-3mQU2@RPcK9Fz7FCJh-awaQ&Ngar8hqC)6=g9PE1Td{?T7e z7~YSNPt?h}FZvZYx5Tk{*^OEoD-;aS+u!_SG|~DJMJkptF^`%h-en3pe#HM6Wm^N@ zkDk-bkzBu{MAC55e+->lO6^f#--nISupGCS7Q>Yjix9!~{0eD2{r%pA0>oafT z5fOZ`1_P9~iL}^GRh~+BT`M0iA0Mdb@X(Tyl5nZ05@0YL!sW-XzR*K7@ve(xkZ;oG z&yV~(JZ?5oNMy?ft~ujF@KIEZ2#%ue3Y(gmqDx=RBw-R5`}l?q0`TyX)J(Mrh}gb$ zC5S%BMXWFBmwB0L1k_{J7U~+h`9qX`f|A;Iz9Vq)vJHi7dAtv*`Qm!a3NFW~O)b;v z#-9`(4p2T=&gCMd1sD(!8tTRp1<2%t(to->wyGBMD=aE9y*^ooRh!91b!fS`a5*ft z$JN%d?=Er@vm13o!!W{jt&R{bk>V!_8YZ-Ssc7jz%ENd+`X-8%Wjii`wDYy^8#@qU zdTx%+`=|oHsujLxhR-N__4M^_RmTsb*E~FDF*nmb=R3bOZ~xR-DF-aIf5n>$Y#lgz zA27kzqpJ?RXf>3$P_g_}ipj@1^;*ls!~|ArgTFDDUO*}+;B^F?8r`qApVP>8NMY7I zT7rb&*>zPKwnP(x*bJLPwcdw3DdmJ{qqwvR?T&?ad<{Z5r2FD9OXjWaq`(S&o zoquzCx-k@Dz$W{dK~dvY)MZUBfTZIqbH4;EpEb?Gcfr_~1f)};3=U-~D$^{t3`4Jz zi6gae@0YOlI@?l>sxN4SSg!WQIqpk3U=Xn~dI;N1mgLP&Rp{27+@+^;TUoYk{QCUb z)$ee9;EV1e8MkHZPW9H7_3sh+LbZTb9bd;XF8|n+w+%4?WUv-7uv>3)nEUf{0SeArnu|w)b|PijtQf7qf9rvT}@gM=vF+(juX5` zzw1M{>6Uj>ShUfLrutoV=K_;D?;$viDudNa#b?<9koO+ZVx-k+lTEse$yK~uKrA< z)v2>hv0YQTa+HslVQ&f@(U(U;GnqSV5mhf%8k&v(CEyxNRT{>?Ek*me=rZTB?{!#a zer*IKyyvEySwKLb{4=4D$WSN#%a^4$fz+7R$k;T8edye3yJYb)Xq8 zmS;8?d=g}{_4c<@r=js;9e6Tnlj7wEcVTLb1SDd9SNe0e%Mnx>ixZd>60g}?1HS9k zX&5VK@G$j7#xV>{t9)txBLO4`j%c2(vn%##bzbdNG0rU4t+6cY>y0DRF1gnVtddQj zEUOAz&ANr5(Kfxntx2W)K)4W6C3Z z{u}|>-2aWjFSOhH{f0$L#UB65Kc2eJ6)2P@Au_0p9d{=&(Hs(=TS5VE&g;VxiSrH? zJ95v{IBfGP=OIO3?{kDb9kjd%WIR&e=%=3hFY91fX_t09z=eu2?>NMV`drq_xW#Qd zf9Dl^tWZst56m#sxq!ueCZ(s5T8EQSj}q`r64Z(Q)p#B4Dx;yB>9!19t=hQyHEceK zV&*%C4N6s98MM$UyXngNDZa@-jZj|ZoxV_;)OiR4d~?{{z1W{CuNQRN8m;K7cle{X z2BbwKSguCKA}y(Zt%4$a4mC8PuDjuMUuyB#*PG%)uj!Q$-6?#~rGAB0XymKjjfygOBne@mbBTcyfK z^!N1n?zFa{vg}m>y+RLx+pLEXL`JlbHk8mLE@l+l?Oth(X#tQ6(o`Mf#W@d#G8_gI<~z>)ul@bPBqduUEDg4i_kM4&XrH zgmw8#;IAb5vKoibc;&6`FkVt9PXxG~!ISY91!xij7z2Ma0B3nK4Cy;`MkJ$v*!N;j zRdEK@UNwS&+7GRO5_(WIA*IT zB{vC`G=$of$_j^WqGJ?=-4hTQVB7+5aX)4Ydv44SnGnd45aQQUgN)~-uUt?v;Znyf z;ZeRNg2wxheh1%v&mq-lcqhOeuX)mgaYk4r+WM9XjuMnV%&nLZ?S5cifG({%;)Gzu z^e}V4N~Zn6I^=cBim$~l{_cwvFGAur&h>RF*xN$4yDon_+qEn+&#AZmy$A<&n%#H@ zkDBvJ`ls!7ppjd^G1~+i)ODfnC+b`muC3Pl^Le|4Vp3RHr8ZRx8cc(|r zLo%FW=KSsijl$ZSRv?zFJW!ZC_3CIjIF6h>l_%JF zRUDULN%}>PyOie>`lN21s$&9q1rpQZpcKWw_XWPi|r&U-x1gDl8ZweBo z_&JjGUVVx9346xiK{$4^+2+Ok0EWl_Jh50%c!MKS0GNYWDs88jo`QY-4*1mkETs2YbFUU|l$0)c#$cbQ?mLJ~cuU9B$C6#PZW|Eyc2y3zl#9^Q zya$KR$MO5Uw=dSGwL!VYFZY1g>NUAlkp^+8MT%fngG5*vXt+dt#k%o&trG@C**wnOK4=oFEK_(V0PnCgflySvgu7ntU3h%mR7<=)y0pl#v>i z2K68GmV2n_>3Rw2g?aK({zPx`(y;?4QhVuXk~GXj>iq4=0xxWjDGFFq|1CI<&KlLg zML}9K!sPrVYU+bFgu#rtf2bBoD|EeDOa>5Nh=fEYO~;3*(`mI*XIhB`-$rlpLxwVv zQ2Er~GsEPn6hoD_pH*OeE^B8GtOo4czfp0$;xoWeersz>4t2=x+LyaV!Qk5-Z_!a% zB+q9Ef)SHPeXAwt7HNr&xm@8I$%yXSxF}*2zaYmWOo#MN61^#+czaY%N7om=v=&_N zRP7K&sNCe078zMW0 z-!M${gH5y*44WK#VlCU)qf%t6ad=jSQ_EM`TOTTKS@9jb3od_X3RGrQH~RHy<<-eK!_y%EO7qe%G6#K;ER6Rk(ASeyDfra%{U(6{>j~=;zPmQB+uLE6K zW7wWs5uf#)(K@SG-?t+Y_cnHwOkmR?D@$q?r0U%3LBw&N49_7ujB$k>_^ad>>{h|( zjv^&uIiZ)qx=oBBA^!U(lilk>YwOwP`kxn;ujS{C;!4>q#pQZ~+WC&soe0PAeAFin zK%FpWG5{-E)cyeA$0l*Co(~#Y^y8Keksz!c8oj|_-E`wldDuZP9a#EtyzPSe`dDO= z%O+w7?QZog+~ldlu@0ovi7298m+Ca}Q1N&0#4F&xH(OZZ@XcMj(kZd;Mck~v?r?PURrHunVj^LUdk1j9cOJizf^A_5U7cbCCnrBl6GiVCwLSg=&Gm;K>;h) zWdAp!xC5q)n(6}LHZSXr2Jw0}t4T>Qf~dOH)p<@r+ucz*`De||$lw#}@G)PBA5(jBN?0M8w{lr2=CLxZcXpV1TI?wL-2UsF(;=nm zif+bU&pDwN<~SU4(*zH8i{_p;Ot+)@Yavn7-*oqr7tp0FA`Xun%P=7m<&d(?MLBhU zcFNa9NMBPM5LhwYj#DZ*_n7M>ml9Fq0HqR?10fTmg+&;HSaCqjdN!XC;V5mFbJ8>y z@OB#xldj1+)>IvrX>sj)oPqx4W+rl2Q(KXvb-9$)p;^uiB;-UdE84U_HZqpcZHo~>2{jk zjo0N=6|Fv|Jxwaz#YL|e*03otme(nQ<3#+pg=U3UcGG2#7=_T}6EB^-9P|Y&+{tc`ugQOn6NKjGL~VHFka#mU9XSTuiVv6#M#<61f0IKKkI%Iug_P{5fI0wq z^A5~L+1OGPuAZ?%uYX~qv**xoNmtG{(22~5;x1@ChaJ7oqlcLz0#2s}j_DW(xoq$x z5|ktiKWJl$pg$Gn<#{~+hXuF6?xNbayDl%p5C(#R>yWXPi-)Y zFQa#p-lq8Zq4w)|_T_l3-{RMy?X%uQ(s?H|h-4;{R=8PQ!sfb1ZW_G9tDsaG zluV7i16$}1jdP4m1m#&+z5?@wEog^>aUaAJc==|7<2KBoJW_H;o$8nG%CtAK={loJcyA?5C3n}(cZi@zRE9Kb7BNgNO@GkR<>sXK} zCb$R-QTx<}YVy?{Aq|D1oDWGnBc&xMu6MvaVY17@YX#{#j-cDfi5Uz%aXE0}%89ZO zc-!@r@2rdmF?X3hF)i#HZGfXP?Dipgq8wp~n@8@tk$i-RU#5u~#n~xR*mlG_t?8k( z1X9G&$E8+>+jtjRu2+=bY~ty+;d=((E@rRH*0*O$^l4YGWWqIAkF6hT^Xoo);PMC^dEv|Euk+UA1UZ(73vjvjlyb??va28tOdjA#Zhq0zu<9>2>e(l z5}iyDTfdi?Y}M;WD#Xgd)_J$~?W1;rw0{usVjRx(dQ35Ht_SU?ADSCI$~A&M3j4KE zj5^2M3|5*OWK6&Oj_f$pD8v;4M{psE^2nLp6xk;DU;yX#%b>L?N}8)G@6R^vZSg}>#bf$EO#CCo}$9=i86zGNt zTwaRPvoBu#)~lg4`#F%K(APe>jw^In+lK>?A{oc$>O{S}VqfZ2duvr&zJpk!CN#?X zBgm>iUVz9r2_gdkkb=TfCQuH0FW{+QCj9$pLQ${TT;r z6Tkwxizd&oOO+|KrOX_uh;^pY!gtmFE)KE<#LsX@N&@;{q3Qp5;h)&{3EBSR-hb8j zzb^dizNec1fr@g<0Z@ zcfb7azD;v}4@Yh1c977csY;Ljy1WkD?7*XTc}T*8JG{6cf@5N^`K0-tKLh(ie1F%G ztQmf-5fy?oXFYA;gD`1^N9WWx_Pvm#+bbrc<*kauG*{LiI(^)thCwjl+v?RpjGwL| zY_dOdj5%z#>FF4+KD?Kb%9R2qE=_WHUJbS4q}viK(ez$x^i2x?NIFat-vAcKb+`Rd zA1zPB*;6>nbEFAwF_w+h^~SFHHk(V^B8<;_t_b%oShe3?HOA-Gk91yhQaZi#d4c!9 z1@G}|3eY!xfKCs}D*Fd+;Z0-J30Hz)`SFfakZw&@+c~;Hf8TnF+1S+j%^r} z9UZ^0;dB5d!dVm&ixS`&MQFFz>nvQ6GDzWo;ldaE5ZpUk`I>lCjsMQv0@IK}QYSk3 zu}WiB3P&hB6<3g_C-|1@Tk-xo{!`L4Z+0_8*$XFHWS&w?elq;z@v)%xFtQyigBamo z85BVO(}XqOb#BEqzO4G0 zae=ldjHk%_B`B`?M3m72T{w4x}mKz3YBwPfIn zP1!$5k;QL2AU7j8?US$nGRKAm=p)Kpf$SNyJNWm5zo%BN2a+CdXG9ic{5`z?*YFaDJ z^+YhKkO@^>6n|)6g!4qhrG58(QVz0756q*a1k;UhHm>VH7Fwc-_pqZ&>T!>~28cCp z6fPjev>WOBT!B=lHh)#f#jMo$BFd$us=*JTcw^wmzzp(zr?Kg*C+A2?F4^7>zZEBF zXwT&PU6bsO*nM0p8zXvG}+`_3UZiyTto1xVqJtOtIciguUgd zvWm(02GTp^PdEJj2Lg1r?tw$o<18Tzo990uYazk^8?u%%{tL2Z?CQ}82#-@#&K7AA z$a|62zA&59{P^9U*X_X@OG)&@Fr{IlXeY>+mdD_PX2hLqCFCAxFZ?W`Cq03lstY*M zt3#gD)F~v0Dmh>N>ZpiE7W~V5KKb_MM%fMQ0vW}(ag2ymh>Adg_LO0Gbi zjhieePK`h$`w|4$$z)0}FkHD(PyGCjC=jcXJ*AX9|V2ut$CRXyEV$99POGcJS)#IlFRyCuBQ7Di$_69Tf zb03L;iWmb%PJCJKK3I_YUKH^^fa`D;Elf^nMb^rYM9*BIuyjTz1KNCPEGQ9-yJo~{ z{{lhOsy5yKg-NO2YzZb`jV)=OY5-(oF#rrW@1u5>>7!_C!|3;z4d##D17zu|mq86* z5~!V^@s+KP{UmYU5Lazay#RjBX+_9ZXgm50MrK|F7Ic;q4gn1u^DfkD@DG6#TOLPm zHlHPC)AlzCcMiVw-^0L%uXEgbW2@&m{jLv#-#g!P*mNL8g60?}2yxU7Vj@lzjo$X-5mFLb)P)IRC}3h zl8qa@AEHS>c~Kkk3{y zFe0RBWHc!Ps2VbEUB@R;SEVO>*^RKZxfFSr@NGkokiMOVa)F2ftc&`pDZ#k1l<`-3 zq7%y>w7Gm&Md@F;zyON%Q@-vEk>R2pAZ^dkuas9iz8ur2+QWSEY_abI%P+3DFEx^X zC~6k3^Ae`)a@r6$?5BqWDfUBW3n_1gVexi}cn^TT-bw&C@lQ^$?ZOVz3ya7!LyUti z<;^>7U%21pgU+T@g6KLzspG+iSY2u(hl@8^vEMyQ`*A z;~RZD^iR$7alVT7fa1HYQr@ek53dK?lV%|O#-@(qIBBUiVZZc8R*y5K=z7{ahwd>( zTYk{*>9cnkI`>-l)I||4O+FgGDG+uEWwm`)gB+tl%LwIksVfN^6Rx6VrG_WRzkr*+ z@U!|`Efwid(3C+-BmzMKkC@I(@?P$C!r__?q0^E0Wn-Tj;8WexHSVhe>%<$jw_N=h z!uX9bL38as(~BT8GUR^GWZIImQrcp#fX9uK)J6e;VQ>4n-83ca>=kxt6ua~76!*9G zI$)V>nJ6U``N43vlj(_mg>sb#Rsqd8Rd;A{%|Uicy9~`c5fB z6j$Ph!Dz6^v_b5Le?o6pp|3w7GF!kdCodoENjFYOCPPUbA#IsUUYwN2eC_a0_@BpM zzeyE|tg_hN1bUMMJtus{H3@m#BI$%e$m(X5`sCwED{5aq0^eWGGTK?7W>=j7X_zBO z?KhSrrc5U^L}u@~XEoh}b(tu(NM|~0#^}gn*oS$|7M50a#;lENFe@-SppW+*$l&j5 z3Dh0A5{*9M6G|pty0T~D$yt3b{`6Q|8xT%}SiH2t(4Bs3_xK*nJr!12jZo&E6J*A2 zAt)moJJ28%L8`<-ScEFQK6X>w=0!7X6q&htWh4COc1VzQN1y5-^=9-pe?-X0begAP z8D`|Wv|~k0fsB`L7e`Sor`Q-5ooM3eEgkevNc53!Agb1xi4@G23z5a}$cS$h8OzPjpD#;7Y3`0$!wOn6jUgUH{b!)p@*MmXODH)>BYYDhR_ z<35RYVI$G-);W)3;#$qg(3b&7A#;8rI=)2q5TJHK|o7r zx3Y!thckf%XxTe)vuTX$FLjIZ`eTqz+`?>wr4`DLg$e7CbH9lx+4`&OWAlsy0TS!z ztztritNdaQF5*;h1n&n{ZH12M(sFL<&=G{$nsZT7%*7TYm{VOW5j16|k5`(yX!4)I z2%k$QHsrnC%f5#>Zy>GZ4n(lp<20vFcMkggU}uM=UBhAT%J;*<{avI?WC^f*#M!Z0 zt&@Ee;mF!u$WwYwLHZXZu)c~(=+ejzm-&ux@vc#xLJ}JVG%XRj0ZLmn?8_7l5a1~r!PvAD z;}$1g4>4z@3o%GpL9#mqLEzRHIZFbC%;L67d)cn#87NZYC;{$O6bt+d?|#q7Nfe!r ze6GmcHPnk(=;rp-aXH)vVR}XBhb4F{B)%OG8;h8FwJ`!~sAutQe2ze>E%DOMY-@W9 z`m+*GNJ&3|7hfp9C?@@c(_hD%r?1hU{ndb9{M`()+vSfSc_zcHbb+rvm(q;{<)gF1 zZt&RjfdLL-)_35yVgy4;J1<_UxIfDDy7r&Ma}tE=MZx@>_Y@7(h-98yJEZ0Z<(%Xl z;)uukyIKxr?*@K=xyC~rm|T-fg>iR7)PKmt(cs(%eMz#<%fb%4n1T|7{i8Y(@{TR}NX zE=FUvMV8jJP)y8+v1=O&G&6mE*}nkhW-7dIT)!0i_Il;6RjFj(1_OVcbAsDhYBLTX zSadNyJo1*?QMPf?IxhV8{n8=~AKRgwr1>&sXY=Gw`x&;@xkKa$w&f^sd{hY&k!3iz zwlC1crH|C9dNd+F{y8hT|0r-&EA(fN0Ous3Qb_!!mm?iqX=i7)=P|Q<#d#y)oft{Ma8(1KSi~}^Tdq}plJ5s~-#|y;+nAm6 zg-rebNA0&m}E$^lwR0t*~*!m#~0d z!u89~58GIC=p9SV8uvn6^O@TieTUdIg^{#;(Axl$iq{4aId^_CJCTv?Yrxf_JM9Bq z`3s?xX}fz9Jzp`(ffyo?i^FTQmS&y&n<~#58S8QN%e;m-{(xrI-_3EMZ}v^5fX*@z z1-!{?S!#)*X;{WJ&p@%NB|`PGu6B^t-Ge=)x}S4i4W*3NHPR)HQ_uDBYeM2QM><>) z@wR`X9dTavWqcu}u%A_O#QF!IUqBF+5#8T3BJD?Y zqBBPj=s{!K?ABRS{F)OwN3nP?==F)Q6)~5aW_Zro8)I$a2z4(72iB z*O)kV%ReK2IUTB8J#2UYk=oDYusfgs3-szhn#^iC}c zp0i5_SS<71xp~NaGJ=eVU645GWQqPBtz&DYkOsRuNefzi*n%TT1l?r`d7l#xH=gsZ ztO%%?+B|v%qUvJE%mlvlf($hr5m10g|6sSTlfU}mD)rJ6CGBO2u&O;oCP>KwSd0*v{p96KyWtpE0&3wRp?aX` z$c-LEuf29Kwgb!eEF5eck$7_URsRHl##h1RnW*hG2jxbx52j2RLT8;hS7&ff8ogEb zlvjcDF^EI(krB=p@PIJ%nf-`JJB^2IeE;;SEy$P|mcQ6x`uh<@>xa~LG}&WT)iQKM zRkArqE929>F25t=qye%Z7cmA%*ozQF^mrVPF#CZffk!TBUS{e55vp^=6CA$f51E~f z?Uq(I9^jcjzcClYjp1>}D4hj@UUztWn}>>J=T>EHcZ&dgFA_X7JX~~??)D}Z_&NTn z!xsYaBsv&aL)D!}9d3JkC*gA=90wD5G$g5>`Ka?(M1vu5cSxx`T<>7(AWd99?*@Xk}neQ~brEl4W=+(Up9rjN-gx(V6oL#*&WsVM|;nloIgD|2ZIPYq` zo#LJnn@<${noSfQ({Aa|ijXf@bPdmy_>}b$tzkSH{6&~BG(_Rs>sv?=?w%p zNEN=iKKGyZBDvIht$hZ_{6KPO5#$q8e9Wboccbk(KNSyVlmRD->wavevG9`fdm2Kh zs3>rM79o?eo0|8S-eJ)v)s-6LgNJ=l6JeOy(@2!!Y?=6kT+`R6K;sjYChy(3llL1D zt&F;9nRd|F?q;aO!r{n$-6V4qq7{maf4QP$j@QjJ=H+s4)Jp@L@!!i5qG+MA6|~p) z!NDNYfi?|+$h~pHl56Rt7CNG2q3NkGZ_(-h*m+~rfT(5sWrJnpAjJR zI8&E3dV3h^iOsj2)Cc1l#fk52hKH9W*B8i+d8SdLSKM(e{nQELEs;n0d4kjHF#7qk zBF(N@DYcNk9?@Ra*(o-rr3B}|kt{M#NP@HI9TCk)hSvMWCTz(}@2*}I0badOmvaUh zMtSSafHS83V3T6HS>?+BSL~RZsfdU}+wv5N)dfr#2vcLi-f83Z0+5*lJwY%;9H-XUJ)8c!J}u zNCKQHgI;=C_~FA{fh~0$z6j3FYQ7CI( z5IXYeR@locp-UQV5gt;bxd{zXoR4P%U-iFuD9=5OWqc}|lm(eb&Wk^z%qeh=r+xAc z&bF(nuZi#X$V`u2E34s8p~*Q=gqQ!zH8{NQ?+VGXThTW1h7*1w#jb&$y{k%w=ZqvF z{6S9z+wj8!e{R=!u6%{_Fc1IM&GmTdE?l1f^$F*)X1@3ZXtqb4RTjsH+S%1W#9~Jg zeoR44{L;P4bYy9TBOrJQJ+SVva=N4Q0z0r4QLIY1EVudlyD|4V`e8CX3IO2o*gO7j zjf6Tc`TQ?v-*9h*?p-4lq#8*`a1u)o&{hF27g)SL+X>rv@&Wf6<4}nLZ~BxktT{=A z8@hk=sXa(A%TuvSV@#*&9LH*}Gtk_;fYa&5rumkNhwD^_dr=E92Z5WKRHA`1v1FXy zZiuB;oMl;TAm95iot_9(zC-2Nb|Y{l7gB>o?k)-k_T!ne^w72^mu!n138a~~P-q^R zQi3J{1u(Wr4q8K<`-79SVKHVo&X9$PE!{nUJ12V*pHl(b5GGwbMu zt$T3zA=lJ`#SZ52C((6Q#s_7>_6j&TqmxCpk2R8L^(kZ3Te}+EM%7CG2KcocLz0F6 z$~`?+6i59>@q5Y&$;F%XWsn7;A;o{j-)Uh#aYfolD|{ZZDcsf)1=_$=J6~9^3e>{E zZe`H`l+|Q--0O_aSRkG3t@iXYEy^JdA7#0ztuR~=Yx{UeULqhiS%F2d+rmZ&9vz1~ z#cj(DEgfet0*hdBmAW z>vt%}Q)+dsm{{)%5VeabTI=$6oSZ;N~e9FO} zq3|3NL3A!zwqEKNfX=7V+{JirPw_pm$|Lw95`L20Aa8I|m(b1dHyYILW3HOJ- zR+XdBsv^~9yI@wW&E!^TwvpwxhMe3A^^YXLJ%feMrD6!neH&l34Ly_jCWl~wBYIo) zd)VLl{YC!a8CZ3QzfZz2TPpQa9zngO#pxPnC>(nj*@|_SwXxzD?!z;pRelu~SPNI0 zh^JVo<{OheLF_Mdvw<>xK8W)Q2w+a;TjQo*vM7)Oh=hoiht$dIn9|uo;7e+7x@bivdl#*) zNpKtqf8cpYinelv0CDuyBtduRJjCob)EH^SSVY=9`lN16-s^|&tJd$yk7|L1KRvq{ ztiXWPsX@Jd+);{w5ph`qKST!*Mf8rA5s8P0SdAIMMU99IMZ(*BsqoqoeVxSuRs}Nl zUuHqB5BtnlY3Aezryn&@d5W#sYww8^_udE|6`js2Ys#WT7t9B>J zT4>a$KNr$4aLIVb(xc`WQ)yWU0hfQ_cE=RT#WhDWwKG`XT1i+Q`9^C#&5yG6z1 z&}S&VXE(xZzQQLQcVjRf7ML7+e2CR{Jd9LxzLbJ99@Fh@nEW(1G_8Il;l%aA$``{c zUaXi?DDC*ADjXrgD7H*jV{|XMb6?{?@{v8Z+J(jJx_5h$!Z~c(C^_$F;kXFS_Hiv3 zC<|6}$$?nnzld$E368s)!Q(k64=wmLBmNnWhu}byH#oRHUVaAc4`v1G>PN-MfpTs_ zp<$Wc67I(+F1mFpkU3;R^>-r$-)n1|8e4RW|A9>-1p66W%+#<*l)2)1uP}-$FoV+# zaHhOXZ>6A6gcE}DDtV$Pd?EWIqToF^qSb*-ak zfiWK0N^_G;>tA6PFJh>_uNft2h-KHtf=P9uQ1i9unc3Eo*VU5t}1A?P0F7 zbVi@XWRt2BzfIh^BS9JV_V6V0=){<0sahe=);rlgtS;rk4SY~?_DO9SheHjZ+S`34 z8nwdOGIsWqikKX9pdd+TadgGKyzd`9yYQ0FqSH%q zh6m?AhQR4MdY5fZPjj#a0I7vepLJh-khmHoskTYwH9d8xVrdaYN>LC3+LkG0s5OROcd;(y+d5Fz0>$&d(e(Nnrp;$iBzUgu3iXq}S_usdyl%VHnuV zSNei{GCOjrG*k|R3jR3KZCAF9{5{)i<@uSb6P!Y(rumLxv9zSSZOdjq`dwerg6+V1 zZfo+0@Tay&jlaAcEBCdP)sUq%UhFU5t`(*CB^}?Wt4hE%<;}}ymPaqgveaCa?>trHRIP49tVf71m3<| z6T~Jioas9sjQjy-LS1vSIsij$Cqj6MnmQ=pl$?08NPT*Rb{`b(`*}_}M&7<#Pm|bx zI8D58ly|tjpeCX>fgNutwk5R7Zh#dhV`@r}V>BDKz3A0hvDW~55Ge?eCz^7(vZnfz zXp|dzQrH0@GHDANy4@6-faXum{MNnzBSTphn<#8Y1d`0QgtEgab?+c~?4wTu=`e;0Ce zzm{3Kh+i%-Sr5vSUF9LdAr9y+`szXGrx0qULMTIcW7c(T75PCPTCP9GS$OqvwOIqG_G{;X(NH4&BjyLE_-E+6I!DYQ-oPZ9J&vm$(?hNtnShW{b zh{9%uTcpBY^)_D$T&NXsX4Ddk-w3{)w82=Zjnk6*|0Yo2I|gZVN8fEh>vuFvOiU9H zTd+Q1;9y%tJGvv61KWQ1d%phq?F{IQBjQDymbKs?eSz#&_MIku$Y>{~(r;lJCu2E} z*LrY*b}j$olYF@1gPgBZw^X{&ir{Hff)v@^+>4c{-rq@t0K2i-O2Vmf()4!(>EdyCAt=dvB{<|9;WF zZ0DjaNLGis=w92|f6 zA+b~AI_j%}D0=2Np`a2^Ltwh`X65;$E%Q9*FSEEI{gvvDT$VD$rt5%W19M24(I3it zoG1)u&elR}(}Mz$g8W@n2@B^&s_0UGT4Lw&#^h3& zUnyhvg%xHmZOjwjni3+3T@K|^ENyk{wB`8IB{YV_cy{4tGHZt?A%-H}M-ySg`I&5W zoty8e*xqq#2Ueh`*kxVwQt-CQE`ylGec@)q9jvNM9BO=x!D6Kd$!(y6KOXKze$Sr0 z!nY(3JdO9fRO)K7iCea@dm&0;xRojR)ifqg$6vHkW$pt;OYDI1;-*~W3r-Z7JS|;2 zlI@+h)15Eae$ScQUEGE>+JawHB@A_j4k^1T3Kmi`FhqHZzoxn2`g>Rc@I<(Sds%;- zN;nZomVxkH*8pTVa9!BIwj(k|{$Cs~-K;utE|Ga5B5`~FljH0YmbQ2sUO3Csxdzwj zeyzr1^X5Hfj?b%AnwY!wzx40?PqgME>?Cz(czEv{X*IizJCjsDS6`;LlFR{Cw{Vn7 zIJWhwp^{N5|H|>0R}(cdINl-`ofA_hU&=mtzhW>(8gOg+^F7}K2-){{bZEsgUY+1& zyjI4jcvTl<+IiM7e`OOTChb>#Huz~gM$t!?L>Xc$z|)+k@r-@JhZD6pn}R7HWJfRx zMv7Yf?-Q10N-WL@-P!`67B=#+@1Ho#X34p3qO#ymqB`+x?tz`da?96+ z-U-Axe~*{k_`#iZ4rBohsRXe);DnpausdfoKWt`*u`?jp(EWquWL^c{%b| zO+pL{a6dNR6KRV3FKLQ{qfWMP%B=d_!KL@os7jiJ+jubGlYboLuQ1LdCz8l6o0$n` z6L{G3$G(h|U8_f8E=yn-;gI5}%lU5XeTq@n%J8`gWCmTv2hNPF4c9&$gWYQ+nZ-?X z8H(pA()*_E>~`fV*t^*-V@VFd&&t5PzjH}{&vg&Xp=E;t{1=zv1C4Kj_MThbWM$DHnwf9;Y{`s0VlN}-G-%NpgG2s%P2gQR<&%eLcPlwp0Ph@&HfCG+VfK=#LNnr1n9^M%6;)y(sm z7$YBn z@u6!1p+>X8dA}9mNr5{it?QVjNgwQZ@%VSpOkWs(DL_i;4_LBM^y41&##K5?SIUh{ zw2MfFK8BAFA;9aypX1l2p8RB!k8kKUDJEiW45TZ81+$AMB<>8h@#Ek2^h0>@$-E-9jiBxzmake_4^LyX}EiBU6&hm|pw_D%I=?H+BDu_%!BELC> zIGiFuyFDI$u;_m{tT0c71V^jkw-J(o)XEA_Ta<_HW`G| zVOTm%R~OPb@XuL7tdiO>vT(Cd!NOg{r@{jX;#0k}Te;hgpX(z;dF{F=AjLrzn6=w* znLF~OyV>3;!|EIgN42yDK*SOMB zih#fXgOv0P-637lASfXrokK{2gosEeEh!<=4ALMmq>>^vgmeiEAzgajL0xO@v)4J- zK6`&VpSb3FV`l#I&O7(>Jiq%!;VGMl2pNQUW+C6lT~-y~t@NfJo(=Wjm@GgoLlbkG zcS`a(!!lL++}KroV4UKd0#5Py_+nWGpFfa|0^wAcG!9t zlBe3{iX(K<#w-hox|6I8--?knH^aB0I}!CZUHnR>6k`3_>Qw-eE=k1Law{aW>|r!J z(=;M)5H)Mqf0;o{F&^gm_*GEga=1D@P&ff|0eOF#2*tNY7lFTTxdJomN%I?6VN>zQ{+xzbykI9;KtMeSK4H;P838L`U8uO z4rL!3u{R}ymAbcxTD4hwDsnX;Mj61$^u-9p8T#F#qFe|IK{XwDM@A@o5C+R>kT63f z#}3rb-jIA~leLB|Fq&D7!|beSY1{Mb^Yf3b!^6^kE4o(+GF;X!#^j6yg0Xyznxhu{ z{i?jdx3?ox;I@sFMW=NLXRGDrGiD0z&bBKd(N}1#{yLx8hd$a_IL>$04mI5!wI~o4 zZvj`&(8{TN(;{RyB&gW5cAviTV=v5N-xe=X!A$ls_8QbDQ9?!26#nrznH@=qYieO6 ziLk2UMGp4?{4Vg-_DSKrUF&VSC&Cj8*lny2jl-j+!_*mE0=B#1PU#^N-S#Fr`ZkDIPkwyT9H+BvQDNz<;C2Lcs-`?n4i}rC2!LD7f)Q^hhn_PJaQb8ISWX z{z}(^R5v{@W1eK8_k(NqC08up6QAsL;-zxUc2j#refKY}_(6J&JO{x{0~K)m^Qmkc zz;Ox~y+X#4-R`YhOlL%BgE4_Bt0uG{->Ro#vy-;DhGoM)u;c5<*3z$FWFv4GA$=So44h>oM4RPG{IZU`XpF7FIe#tZGr)y!zj!|vd> zvz2>Q){5K#3ZR+fIRcI{=khge%6U>om~WTMF#KD~ryP*yruqrRdHU4s($W(%mJ$1` z#NYmimz`9a?{MuuHKWL|-fUB3qJPz-hyY)4somwcyq+E2W`b!p6LW7J3;wh7Iy;ck zjwl{qQvtprALc4G2+=!%y+p0vUfv1;kDeG1R(tS1jb7JGQ~KB_e0-H(a9IBMfvd@S z-YP=DE8WKEJ`dRgefU$`g0j~ZrkCG-i{gIhYcoogr~m(tzE~^Laq(B8G0n%=^3w$WRv|OHnqjvo)2R@ zL-w6C{h~P9kqk)`|sCQPkkrCtY$-}hG5}tQVzpM}{@1V=IoikWBP(ONM zzBnJpU$WZ2fr$V!3Um0J8u!@f2@gr^kV5gMm;f^ez_zh6@ic@DO+aK*WRvcpwwvN8dTzrm^3Lla;^a{&9LN zyqlQ;=2UEN*5NiClt<~~LCikt-!L0?PI|#g+&RHh0ppDpdT}9AuYz%84$gwLRH5|J zT9F-j1A&UNL&V6Gy$4Oo6Q#bpLAk~qi-k_BHPtk4)NkoNB z)2eCC?W2iN7rS!t^?GQ8kl0{f)#V$K^J>iKn;n35>EH{#^94xwP|4Hl{9ui?S1-Za zRDoZmUflr9$0wY@QS4iK z->ju0Atoki_!)oat zrQ^tOCA(N*d=a1RYi?(~BycVNA!QM8UkSEEdIaA4H=D_%q)vAFvDc(yKG>NUMH2^nJ(V}Q~_Inw<$kIb|KlloZ*Knor2dm8&Xl<7l z1|w3PZ!*w>p^)O+8c@~YG#|BnQMS{QmeErdqq`5VAWN4{?gwHI^^0B0tb@K28`A7k zruv}mrhY0{{2i1y(lyuK!@ne{a_xLuvi0*jMTg#${>~+99;E~(hP_p$;VSoz*#)*& z0~F})I{e#e^wLTuBSaEIsG3;bg9Az^1}>Y(aFJqe*dn(2qGsp=qY-yk$=abadzGac zl+=@&<$l^%*b-k`!B@O?elR+3FTdwg$?F(l2_-LFk)wMRT76k+VP?!o_YPXIJz@Go z3+XY^m%;1xM086R!#kU;r^$Fx!Z;0gJK`trJMkAR@5{N&FOty8nqt0hd@3>Sbh(2 z`_lc5vxm~O$%00qET3BeS+$U9BDf6-WLvOGRX!c;8l}P{X0{X8$gZEPEpW|jO*oBr z&6lDP@0<;0vPeBlkpA#iMIPxf5oRzl&}HV!Ld|as%EJ$napB*p7vsRQMElHHG$I>W zm!N=a2C!;y&@;umoszJ0beApsgO>S%xaV`sa&(lhsEQ8^-ak$b4(ig%pfet*{BD`_ z!1On15DKpNZAS7(n-5_9)y!LREJM9)wDd0ij?b)mTC+66%2VTtvJq(^$#W|qpxm+s`YqK(XN3@1Le84%#kxSG~Xfqrnq;+*fB)HWe+u)Qnyffg3Wt~k zRS8LD(=r6dSIpj09a5dG z+_}z0_I!{RL9BtLFUrtNP0nOU@S~THA9iQ;eo&PvH7AjJ4+WoSQ zB8;+y|||TT6*N`o9JwW&F!5>x4XF3_~K$=wkFn< zp!a%IU@|2?m&-{q^YnUQQSsrb$&;<1j0FgLGei!q71l(}=V!LG&vn-rU)Il@$!Uu+ z8gv&->BA54o#6QTV9(PvBq@m^Ed5Roo^zJ!r{NCj>F6l9(n9a8<2dYmj>))lJFX`8~{L=hY z_QkNMrT^YpDP8Pcf2iwT@Rg7cQ`Vq)KQ`;pP z?M%_q@Xl#T2)vj_jq1b0+J^U}>_{qE4Mhr^yOvsS56z9j^~mhl*%#cml+sL)6#G*p zrZ%QHZ`Wu^YN1SHAgc1UjeKqJf{RwRlOopWQEE%}d1hgi%6Qj_q}7!aLnUi+<0{6F6zk$)wbSDoag=)OcNHAOaH@=h_PV6Z2bvClV1J<;+W zkzHIwN{YVmeCks6qYWDAdAhshUQu}ON@6U^n{lrb@bycbWCPBz{p!?|UhrX}@kwW?hy1YXEd?0e~@xYPl zDbV|cW#M7~csy=0h_Y>>=t{s8WtspA zz=WRzXur)vEJo%b6;jx}LO(x!>*N!FF^p=7tF95_EF5St9hAK1GW|oo5Hts?PsR8uK ze*Shoa?Gzfuj-qJ!d&Hvq^7M9PfjAiL}_3KEK9mhzdVYF0ah!JNXU);k+$qj)9UlAj+6p>yTv+2(jl7Ws5nlL6RnvUlQI~WIc*6jiyg6 zo3k`$kDU>haZw5`=Yn$JeyCbPL2X=J^S2f1!RZ19 z1e+q>ze_{sKE#uCOXj|O{> zeHr%@(8HyFa@#vM4-%)XpL^$d=#0kdXrG0_x2YRQgI zI%>QRQOlV`)TCj`J&I;s*u2Dt8Cd-M?jisM$9cYvE1&=n=DrGAU>-VWGj#Pwgd1HE zI?963*rJH0OYT>iRl3I+5XG}*pL`xe4}y9pk`tnnL?TKhd*wx93KCv1-!F@PEQ8iV zj)Om581$SnbpTGiYx@$(>!tIpz8=ImQ|UayO1%%n(IwnRK4fjY!j^vl04Z*kNFed( z^VZz4nqy3JP&^4_G;|yIlZkGy@0Jnl;^A}yivDpJ!Dj z0Br51aeyu$AgzrNj*1u0V3Cx>Q#V;~VH=AAF|R1JeJQ0Q&W$$(a8$&ZD3FmvFX&sM zyvSb3-6w?%hekcEk;iqo5uji%w&*2ZCU|GabyQoZzGXq%~>z}$SsS*`k z)PP|M@X-Pk0)9IU&12qMlB*SKG#MRl7`S%GH!~WeDPGYQiTg3%PfqBH!IgQL42t<= z?|BV^m6V%vgy^{LaqH~4B2U4!x9#)~>#+_iu5%+srh(K`s^f5s1`M22OgS0$5fp2@Ub9?Fo$=uS@fB4;Cdk z){abTb(6#GwfF0D1y*Ms;RkCUsVbrC6_Gp>FzP!Ms!$YB98c^X_gT-f05$`Na(gk#FsFR$Z5euZ57QV%l)$*?VZVS!Gf1Au3qE>fn=C#v3 z0>D52G(nM+a76fxv5)T!yL)UQ{pucu${VXVCJ_TVhTBp;{<-)oYsb3d>ih)W^NbG_0k?c)sgs2a;ypaf(H#*0 zx{QfC62&M=k{&~~HfEc{9Kx^qDWs25(nqvyZd!h_+b4(`+PhF5KnFvcZR|%2vLopw zYt!@ng_SvXzkd0)Z=sa!%EweU(Wkp;v;0HnCA9`XY()b~wg|`UB+A)^qns}Rdc%K> zQP3P~VEc(jQhN9bHBha{WuEB99|74vM@#Q7GXuK}zNK;q#%ZfkSS}$rx&;()KWC4F ziq{;NR=Qe>PIRY!2E1uVAX&3(awVHf#*8`Ov2~`|0>V`rS$VOzm*z#hri3l zK=1YU8-AStz5Q>`{6B8^fAy4JFMtd1-&>&n;Lv|s!auT1et)k2`s)7&hyK16|LNuY z_YMF1wbJyH*9KAzmEa!=v>Dw+G^A~^MD@afO2(_fLN|IVLvf|4Is@s?~yAK<%l S0RPP3lCr$IT&av{@P7ei0k%Z| literal 0 HcmV?d00001 diff --git a/examples/assistant/simpletextviewer/documentation/images/open.png b/examples/assistant/simpletextviewer/documentation/images/open.png new file mode 100644 index 0000000000000000000000000000000000000000..331adb4378f6678c39308b437d325d7f99bc9c55 GIT binary patch literal 18042 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 literal 0 HcmV?d00001 diff --git a/examples/assistant/simpletextviewer/documentation/images/wildcard.png b/examples/assistant/simpletextviewer/documentation/images/wildcard.png new file mode 100644 index 0000000000000000000000000000000000000000..bae9d45988178cc34406bb97c4c38673ea4d0a35 GIT binary patch literal 17976 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 literal 0 HcmV?d00001 diff --git a/examples/assistant/simpletextviewer/documentation/index.html b/examples/assistant/simpletextviewer/documentation/index.html new file mode 100644 index 0000000..569ff53 --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/index.html @@ -0,0 +1,41 @@ + + + + Manual + + + +

Simple Text Viewer

+ +

+ The Simple Text Viewer enables the user to select and view + existing files. +

+ +

+ +

+ +

+ HTML files is displayed using rich text, while + other files are presented as plain text. The application + provides a file dialog allowing the user to search for files + using wildcard matching. The search is performed within in the + specified directory, and the user is given an option to browse + the existing file system to find the relevant directory. +

+ + + + + + + diff --git a/examples/assistant/simpletextviewer/documentation/intro.html b/examples/assistant/simpletextviewer/documentation/intro.html new file mode 100644 index 0000000..958619b --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/intro.html @@ -0,0 +1,28 @@ + + + + Manual + + + +

Simple Text Viewer

+ +

+ The Simple Text Viewer enables the user to select and view + existing files. +

+ +

+ +

+ +

+ The application provides its own custom documentation that is + available through the Help menu in the main window's menubar + and through the Help button in the application's find file + dialog. +

+ + + + diff --git a/examples/assistant/simpletextviewer/documentation/openfile.html b/examples/assistant/simpletextviewer/documentation/openfile.html new file mode 100644 index 0000000..a68b1c1 --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/openfile.html @@ -0,0 +1,36 @@ + + + + Open File + + + +

Open File

+ +

+ Once the file you want to view appears in the dialog's + display, you can open it in two different ways. +

+ +

+ By pressing the 'Open' button the currently selected file will + be opened. By default, the first file in the list of matching + files is selected. Another way of opening a file is to simply + double click the displayed file name. +

+ +
+
+ + + + +
+ +
+
+

+ See also: Find File +

+ + diff --git a/examples/assistant/simpletextviewer/documentation/simpletextviewer.qch b/examples/assistant/simpletextviewer/documentation/simpletextviewer.qch new file mode 100644 index 0000000000000000000000000000000000000000..86457bc657726319a57f26ff61fece9115670217 GIT binary patch literal 196608 zcmeF)WmFv7x-ja-oyOhWA-KD{1wydk?(XgmArRaxSdb8$Ai-TiaCe8`fe*6QUOVgT zea`sqk2}Ua`*sbQu37I}Q=Sr1^axW;Mc%^Mgv8X&(Z;};gbjcK00RRElaK%a0Ia7M z%hT&03-Z$nEfHV`*(HciHxGUgp`CTiL$DUf{3aXiJXKM1IcfVJ+C_18yK0$i2s#K z>x~HWJe~lqKU%f0HhE6_qut*NNPlnn_YeuqA07E;7yi+##4B|M63M6B-$Uva&el)k ze~LYg836{Ho*pcK_^;NVvYr1i?x!7pDR$)(IrXms!oRfmSBT`l1%FQY^N0q3!@*#4ae)Pp|JBf6{Zlh>{tNkE zHp2f)!~c@{}xh5Qo!V;q0q2Jvs&{d>m$RpaJhFxa%TU;%#|_P-9wuV(*V0{>fse~i};OPsrfCNASAOVm7NB|@N5&#K+1V92H0gwPl03`6gM}Qs-0-$T>XvXB= z%xLduXK7;O%w*zjU}JA>;>6@+@%--m^y_0+3lldJ$LHt&rw{!>3rGMY01^NRfCNAS zAOVm7NB|@N5&#K+1V94+!2+P?|Nr2F3mO_o03-ks011EuKms5EkN`*kBmfcs34jFt z6aYQ{gH8pI07w8N01^NRfCNASAOVm7NB|@N5&#MO=LF z1E`r;KmFf$7(^r(0OoU9$=<{k1{DzoK>S=1w{U!l+BtfFBP0B|gQ%mOo0ADRHarY~ z{y9a{!rIu#z|ok*$-~y!z#SGB4hF#dx4_dM)i*M?ur-6lgoOc6KZl;nYEpv z;qzv67#INgUz?3B46NF7zL@V*o4wwg6**3BVn|1TY6U18e}+PeBTR+Ec^^VE;0RZR;_l%gA z^S1LWh(61jC`8Z@5@^Lx(9WfY2G|&0ud?ypjMj+Hi<-zKZxrl)i>sNOJwkHv?|9qa zC3Yq)YqeZ*k3N<()7O16myYBKU4La7ah17XGS0l{Y5mjDivnqd)M1*}niHzXu3Ej;scIi8GBvOCeSMj3Qu*28>1H*c=*Jc2SEa;btUnUcP2neY_O4 zTwj^>ah%9=noV))OCBkJt}EaI`6b*C3!1ovi&)n;WHW#QPP?s&aDeQwW_1N0%!@df zNO1;O)8Jy}4?ID^G%YK2g*n$={BYiZM%|o!vI@$Y$Nm{H@kwnsQ?%1`x3?mFR*InWaNY&gOw+|QRc8~IdPfm&kKW>NU1T2)FRyDX zp`W7$SOwgCrz1$AxPB8{I=E6aHFn_fp?4-zShhl%QG{c417&VhV~tl&_@GN4U5vZl zkRxH3O~KFoYQd0OE@Mepwjr>GH46U274qHdyn8dd0=Pl!e5oD(9KHR$9Tx?JlV<&0 z-ClJgX?oFFT7P(0l`lSe-6v>A;rD83h*vlQk6^XK7uLauFpz+70ssJ#@#!K^AC8?T zL1{Zyi!Lc^wO*r8Ewq{WG^H`OPc(dU|rX3Y0nQ0mM^qGj@DAGWud z^)c@w`7rw8-GM&94t?~3A;OF8XMI4KR__jy==DPs2o`PSpwUTdVV`h3%Q5-8v4tI6t}FDFcC6z9Eq6l5o&Zg zvjus@G0`M$r2@Q$q7s{`42&Q?3p+$?*62JIdHS0<+6rc@BfMKdk?_<+hed{)RBg{- zQ;M@FIF7GF<~(e5C9ppXAI!@ICfT{QMOsIq@w9nfd-A&Ap)BRUA13UCMD+)@@s=7H?VE0FFn@@m1Cu(yXX-dw6*3e0Va(o;x8C z7`^khA&WG7PT^r`VLAodJQP_~J((rwfCZB$)5SjX&7@{rLgjX4j=S1stRPXx^x#bd zPsGNR@)?PgwMSa?B+r^lmZnO(qemLD)-rS(!x_a4(B$L2+&v5>ILEsnaA-(C=+iwB zeDCRWNEEPJU_lJMy2A`kQH+g;056CrFO*AB6fjP)$&wJ(uQ%6cY8T{h=FxBWw?v~o z^Uz0|hz;W6i_a&V^KE4^5dO51>J%eHm6pCI7a5(f-^F7DwTQ=oxlaE9VGEbntT%8$ zS?0(3de`=CTN4=TN2&ASul=4CGv+Tf((7#1{6ag;;uKskVw%SA1s9LmgqT;yq0hFd z!+a%+Mj|_@5@L?JF=8c~TW=Iey@G>qg0VZ%WAo~`Ui(ay+*_Uh@U@5abA^zc_hjfe zOj(4>=h@F$lUT%S39I(~P;j^|KK*1xe7+{XJpW*7y`qQBPRBuF$+03Np2?jx z_=1e^sn&233ruT*qNnoeN4|Bh;+$!^5}88VRUb89Qm)Dk(n*pG#-=+#Cv*@1H&A!A zioAY)qkqSrxBRlT2Qu=-92J&-1e<+`-A_(m#M`FQWqBzaS!n4A%xqAGKD=g?vAzE4&3ymP=$$P&om;4 zCRb~G#14qg0xM@@O#bL4 zY+#xzk0eSvDX}$33E#_0$M#}`B)>0KT?xH@1z@tcs zd2nNyWSr&3_O`c~%s`kay^HSNUSNWRRBNe5hyHl3`nf}(Kw@xgpV{?xd6??G{kt($ zR{{a}5;Jx9PT)Y&5W-X@B5LW>xXI|~a+?|7DT#C%qDIvmo%=L&ly_Td(Dv0%Pk$*Y zSq!w?xP2 zgxqmh6n98?%Oav(M>&kjVJAgiqdR>ZU3Q#C?t$UZRdu@uP z<+)N`OC1T!1y`Z;sockNsogbPxWo%Nn4LQ#)`drD(GwG&-!&#uX)`irkc0CmN`^KC zeGN&EtRr6ui!HF>yRkb;X1=qBkJro=O{9)xjIp($-I6@R$$6ni*E3f*JId{;rQ}Y% zvJu#+99gjfkKy9oxag}JE#bdPCOc9qrOmfKyD3+z@EG{vsOrNVmSKrpuGe?NA^cM1 z!n2|y&w2Qfht-~4htE#%2ybQY9u@hA2JwsDiM#m+Hc-A%i)0s4OP^E?r;F({fi(Hy zTueD|ACGOg)-k$ErIHfIPCQ|%{(&sJ!Sx5w4hn-W1P%fc5Q+74b7*wW$W&K|nrA_2 z-PS~83Rz6-{vIOQY;{-7h#+t=6(XQi{*f`B@?~ODQ+pdZ->H;AE_>rn%=YkhQVmsI z*(~6(CR91P7wi)TN*c;Rtgq|YTWbAiZrs-k))m&j)p=3B^TkKEMeqY7M)(GMg=q93;0D*)ZDIQfyQJ2lE4 za!FnCNaQxyortuJDK#Z4`_k@Q1ixx|{!E?Yu;TTbM8ONBW9{h(UUF8>gN|VY3zX=! z;0v+6h}>et;R-nkuC&f!%?}D2k<;zcqEpF3A2X!E|70z4WC2`KrTsu;56==p2pyx! zyz5JBB~EG_FA40a&jfyBQH!4qiH{w4qXSg!YE00Xp3ztLIx}nJ}~Txewv$GHxSzB zy-JECZc)nzu3>|oX zc{wb>Wa(`q4Ix4Lv5m?gn)RDuLOA89BvH!pSxSVR;)Y`4$eXeNU(CxwO6QNS>@Y7W z9&C?lklhWIFv9S6$~!ua%qnAM1tGJ}N4qSPzg=WI>JF{INGy7u1902jW6St?c%3fm z;znPq@*|PReViR3`qu1jbG>%w*}A&Sd>O!5+&V#DDnT$Fayf*uriQur4Jjt%QS3|Ygo&mx8CEfUp>nV zom-DAqP^=4gA7OI7icO8wl=zl0p7Jy$1TxG)W;0#AZ{{mp*YQ%Lq<$h4WSeL(iC|# z8SV!eZW#YHY8VvBn>xI8S2*vhx2co?#5&L;6j(oydU|+ako?4O--`T%bxfW4q&RF< z){~l)e<%gLfj5sNn4`zDEMEerJoV@-!|6@3i&Kc^(anJ5Z2R{0eC~&C(mRK@mX}vg z{|00al|6vu%I~+gZ}9S?P6lk#)7&J`^KNEtC%PRnwYoKW>NXwng^RrGysYVZwXtZ^ zn~T1183Vhcu?rQ#$DFzQC}mHcOPp$o`V&>QjTcP&9Sbu*f@S(qcu6}Il=pjb6Vm-& zj%4ioWW=VF0>sh1*C*$Ap?2S{bv~U{$pIcATkmFcmrGkwgGd1 z;Xr$!>eI}BV8~<0g~-0hYRK$QVbB5+011EuKms5EkN`*kBmfcs34jDZ0{=+@lt3sr zHZV#H8v`>FCuX-lXSZXrw>1O)ULZtq&Y1X{w6y$%b&>}&>ae>kP8Mb+v zj?V3AhB`a9=Om=R7by{cZ_wPp*4WU_&gwZ4@%I8P!tXVjKFy%_$4q(8i3oo$GQ&f` z34k&G*|@2Jv5E0>!t;!I&)M+*vV{~53XU0!^v@j(f6egsoC){ORR-AS=Ku2e$DDpl z=FT?Ouz$?~sPN}he*`d4aMWPL&l=Ay|H^`So?4JZ@~Mag4FyLEhHGK#>}dBZ1g&6T z>tbMy1@-54V-xpZ+oAs0P5}9-w_coGcIEXJKD`GvE)5$ zpW(M`+sIL6@y$7yw2c-ks<6S2HC;{gH8)Lytf;2f4tsmErZhfxrYIW2Tif>hU+N^7J`43tr^=m$LpiJB~-M zsd)VE$vQbhz3EHV>1iz{$LCftUJ?atimVV?#8Ou6V>tC3xh)$N^{7Qxn?;FthF+6S z+6nlWZ>m7NyvWJVx}_3(MdeXMD{y_CtKPwVi^+)lJ))Cka%a#)3YCiEEZAgX{5Yh| zm^@LFRRz!;)okp_G+v9cX-vfzhMTD72{rgmaiM&2q>tUGa>zOE;n)Pvwo3o%rQ=BD zCa!2Xe1g^22k_95VeD*sAJ+k6(m;#%U$afEayQn_A!`c0`-oMB?b7dm`^MwyVYBy9 zA=8SC-2MC4&NK1JfC#4shUt2-HngEW&2}w30;+JcYYrHe*Jd9x&9@#^M!9$81`2+n zlU&Ou!?pVBxk-t;e@4rj#ZDbVb^JIOK@jP_jc!l+NrlvaOHG}R>PJx+O&lW^UN3bS znG;L*zQ6#5Uh z_FwA!`!e;?(q`yb({Les{S_m9t=$v(O2T4}#Mf?j2`@eTc$~_nFseU(d*?O<*7ad? zFpE9o%=OO1+Q_#AFMgLAd>6ZLfara81)A8)FUq?&+ElF;+afM*omh(&!t?8p>YSB2 zEW_~J4`K5y`NhJnj{Ku~T)G)l3~bXz^BcAT??MU8quE4}Btoy1QdwNUR?=X$D|Gxw>`IF((lwy~O|)}64y@hiUB9;y1q=V~pJaEO#<N%_D^iRbJ(uJ&He!Lgc+5&BC*jRGu}* z+9>9mz;w@1_8BI>Y`hLeqzR^68XK?fT_pLZ5StwsNhBi?qO&nnjWSm(MdjjUT=Iy_5Uh^0m2;AW)X5?Ntf@P65iy4w%V?mm&zAVHi+SZUVocPcb;Zk2n* z*k;;$C;a0Q9z0%io)`|d;Y)XD=L@9RhHBpitMjG_aD(FnoFnxit<-(itksYx)|D3x z0DJ?mypcJV`{|?|Mk3ZW9=poP%knJqJROx~^qHQoopyUhDbw@q+?Z{Br&+G6+PmD~ zlY5KZUq$%FH8FQ&GsH6z6n&bE9C4%#vQh1eSZQ@87m6p-jVQ!~1uML)ZMJXI7rh~V zR&*cRnGFrxc5Es>ng)E0uH>dOX!$Uy`%=9t?!tt zX&(&l-{Z$>7pE0`Leko?MSTxR=p^;+*kvK2Yb_>l&SeJ$xBW}_Plk_8Y2dS*{vP0G zcWU@-Y^9s?ap_`#V(H(FvYb7QvAS5^A~>>#Fea-nvgJB)5%kRXP@_K*peG%e6Ew!8 z$E=PWP+uhT?8CFq^e?X_Z%8$IZ!0;2?Uc6m zw_vi3o(^CK)5;7Uo6>tGY;*hAj)+7O?S3(bRPuhw7QPa6ITwFx@K!J#gEfO9Si2H( z2%)m1s7ih<^~K9eKlK7_^88q@w!|Iux{Fi>YIyfXW40992S@)TJ_3BkRcCV~JS0Fd zzkVYPDSqQEC!`5yZ+NWN!|t9!uZ*|lUg9^REn~&jdCPEjnxAao*Qk_jJ)3On{df_$ zhWvC2M%9wG}36^{n=*b!2vz?Ng2kiUyf@%|J^u#KBW#Gw4?Y5I=hxSo|hPW%ZQb@C7totN%cI8##eVnf-X(mqr zq^{C&$juL|z&Gh_05&5aM?PQE|P8wog@VfP&h;SzcOG#{rh>(fqN#Hj1>RcS z<#;jTcAb2le(p2p8byWcrDlh3bEL|&-X!XZM=1ti+2FTHhEQ|C_OV8>@zozlgpYZD zy?EJ55wKm;LGwykOq*7W!J0#G){!;)rGi6on7&$MtN}IzJb|XDt-?EbS3n7(c~Q~L z5{wJ0jA5gZ;c1>fi_AQA*$NFjKS#qSp6?WCq}9X@W?OB(kZFC;-KG(39;||biikp{ zp)VAtzz`9@no#6dBFz=qF6Ozh(9ID&Ec3m?X*thi|;tyK_Oj3NV6BrA9!U zra*kdZ5~XD%M#T&A{5rWa@QLusSF3isf02JLG8MlCgCtWR3Z~@T{0kjl-gSox;4l3 zePGlkfPgT1*-`~_=H+EqnwfUnX57Jw+SS_VFSMKL?E0b;qDk<+kPg-T1}tPzN~j&h z=4VPO61cHO;KXne-&bpLxQ-)ahzB#T*2ziQyH|;jM#ny726oV1%Mik{@T1(Lj}^kw zo2oRzMB5u1@+bmH6R}w+KiopHM}&O`p@PwtmdQGumgS`lD7b=t=of6K^aR_YDCBSH z=3=aVrZo$M?aDlR6&wd`ja7>l1{-XkMPWz?%W!9;N0uTAaaNof73i2Jgeik^Vh2V? zP;j|@A|xp8qjG;t`KqtBxEgJEf!fa&Qj&Njc7*|xO5{41zv`i8efP6X z5b?-iAw;=ThN3q0E7YDusKr59Kw48iI0^NMQUa5rlPi98c2wU`k2$IhHd3a8EtHE_ zkq?c_!anrq`PCxx>4NGG6+8|Pyphr_;u<4u{yu;|U>?5_{I-*(y@i|#JTC5qt1ec4 zsO1$p1>>q9`GAQ?b+aQMTcK{>So86tMUaGB{P#=Ghey{%kA~aF&rGu_e8g{}AToAw zx_6o7#RZe!lKfoP=Xfi2^N1hhRs87C$|u!!adbLk+!W7NxZHnfON3Gwj&W!0KRlwza|3JMDO)S@biE|xawJHpyjx7+@d@%+%5+9b6KAX*D%vA+^E z$8_45;fP3RxZo}mM0I}S9Z-9SH2dp<5Cvfvs;j=P&LI-M-MP4ZFA5WT^E-g7ARurQ zNR*hEh?d6QT++;#o4Zb%P~N2;pS}+DPEpap`O7y*p*XYzr z{vbLd)HnrUl}>ucoI%I1l0ntO7QaVF~$rXyR!vP7RVsvAp~X|R*ik^+QCE@e0yc`s;g(nnR~WPe103etdE_Y zou#^3)XMU6qr7(3Q5(C3b2vA*eb?GB1W6W5_&2X(=4Qd5P2x7W z^PMqSBW!~fO+j6wR!w$BOANXE7g>w(RtRB8dN9Ji8x6x;fq~SDE12f%LBciPv43!S zc$)BLtMQb!6*+!2g>SZX(*PTJW z#rhWelaZeVVo(g~ZeYiQ#ZSyPWEa(gv#WrVR4(09DZ-&i?>W>q5o3WydxNB1S@UYF zva<1^{t=>^1z1z=q;PO>r#wp(pW7OFv^dA^*y9`1@xAZ#=qPyk#%(L6K7Y(G$U}=? z=z!cmJj}FmmQUYnFJm>m#f_9LGu1XYs+~4{_r{)vPDZ=phs5W*o;^C@sdh~(XfO6T ziFn6Y@5hftGKXS=rf2DWJ+#ym6ke-Iz7mr~ZWfuTXT{f*~%bW28R@1FX`QDN%Oq3TEf zzU*P>GC5-jj0D?$O{8#_653X07Q`Pyiw4timv7_p2kg`!Ec+myp8wHg{JNz13whE68-Ta=T`33uY8G`8ikp_ysMgSD8c@kaY%kT(1y-JEn{{ zAg-H3cNE$4p&WkgRb~IS`q9bJr>|jjpCwkh^KAN5e8OX5ez>E)=~UKTUMRPKbB-9z z&pS-sxY_>ss9Kg9KH}keVtExX?%+Cg`^uk)urOHL9!X_B%6*pg;j`CG__P*%D=f{% z`TVle{ot$o!+Ze`l`Wjp7pqqxC}9pdFIgT~FO|>gz7+)%Cq(&uM*O1ZY$~B1Cl;3~ zo;Scx1gzW2SDOBQ1n$(r|APBWju)0vkm}06;e6g3m5i7?`kf2C$;Hrzva)a-gOZQw zyh`8*r*GQ?Vbc@m=jRMJDbRu2?kdH>ZTFjUtZZ$PpK!>j8)k|d5N*y8*p{eFNs#AO zqPp~tyrS}GGS!L5cbF?d+H*l)NLkg*N?eUSvOf`flRjsKe^e0Zr4f)}Zqf9TSO#VU{d> zIx@Hb+0SYEl_f)~Rw<@?`HSJu2~<66CRwYh`3h$!TcakWen;z=$&w@50g^8nDa5^= zL=9_$hLt(vad$RN*EA&W$XB0EUTs30VGYd8W9|5*$X+U`NBXBxFfi*>SB>i*o0fgT6v4yzssRZyb1to84i~VgM+4yg8)~<9%0TyRp zEOo)KLpYGL{ThN*<;t@Vf5uqu;^GEDlb*TNZ%_COQ(vzh zi8_tX00~`SBTQ}DigNp8`N3MEroa)>XrS0ToDXL@T#bYn&9vCF^H9qA;3z^6lF!fV zb=+HBZm-em?~PX_aA+%rMJi@$F{gyEGc*jlgg7$ThWvo@SdLjhYGZYkv`-Fx4ns!D z76rNwV5~u3_z)D@CqJ|cyxRYEbaB3rNG%hPoUyMrWb1 zKfHHW+7HR3UC@pLTvEH3S6H)3UTPXS!$QhWe@pBgrGJ@=;uL}>BNhjUogKqM@b&fG z+yk>(5v#_AJ{gn}v_hei!?dY@Co%L3hz;<>(6{JG$R^*Iy4UmGJ^d`(D>FAxz*Y6L)wh+Admcg@~-nHooJ9{@`bW_&B|h{$0I% zZuyr4%Tfsod=J6BZRatFsmh7|Ky_NW{jg6wDdbj6;F?E7Eh1d=RPN3E$UZYKP zvD8j>32VLjVTn^-K#Mh}oQR=&rS!cp?g*e%qLyRJ>Ke4t>{dDRm_Fn;i+g{Si zwG?S-l&G=#Ma8j~Ing{(2AW*d7#rbrWRJ~~NbGC&%Cli%gR7c?Sfip1JtT2D7%&+* z26IYpO94@j;afU&2t5GDjQ25Z0c1S=s+_x%M`CnJmF1PcvwH`Sz+DOZ-L6poO32l1 z6fMEq5QwI&p*tfvs`ZJy9Y4#DkHzwncB7vD4{co9Ku$?JxmMFE?`?JLQNrYzf+4wtN>$Cb~IVlk}ro$x$z zCtu7IRQlZ~^l?nNaLmoQKWxWA9P>sLH}$sS$kP~~$Ko*Fv;=(EKYOR{2!K*6iwE;- zL>2?nHe6$WdsooAUU}xi9o_GICKrvB|N3(-M@!X%rKvzTe|a|qZR*FHbQE2TGrvLa z;7}0?C^Ztb@i8QZ)#IeYcXtC5Ta-(Lzhpq8`(U_dw~uqn^@D{Wl^Z{`{=R zK>7z}Y_=USCW<5di?NmlRk)R6lpq*nH|i=L-=A?%Scz5+k(FzYClOflja>Tvy!-8v z=NHJA`k7`s_fZTrCLieaks+g$M?dNvz_Y_uB>i+kEk~z266M3@-5ag9m3*MT9W2JX zvu`r7@RPoKMNP7KmwEr87>$0>=c6%4pNVU}BzAyufAX|sQVMyq%)V}UH71laB(yAj zTF73>PmYp%t%ja{_D`q*z|npM-r&|-etH(a9_QCKs8NF%VxtL8VT2#;k~zLf>~m{p z8cz9yVWXtgdOkol-P@}W`f^FS79mf;g1dSgZ?jx*G&A%NL|{v_8g&Go32=8Ydx3ZT zaBGG&zWHu9UM;+4tRl)|l6HR3l}v+P>DXQS5-&yPbc_<+VDLe1p%IzhLalO>20a|u zME3Kvz^@loafdCf+1eHf8&(GXDyn;A3;E}pnT^fO%PS5eW`v~}7x*fyMgDQWng@qj zQ@rtqc&qY3lRZhP!qeF1;}!cz3*IqYELMJ3d+m<4gS(ygvziCB!HaEnKRaPsMqgjC zifAS0$WAx3n`UBb!zxb*dA4y=hHPA-sdVWw5_;34iha$jZn{N_+CbnbNj#e@uT?L3 ztl@l-+OPHUp*U|B_;u4$yyE9^*i@cx+*qqF3>lA2g5ft!?6rwOLsAVfxPd7rUDlL` zVZOv0-DRTU%^ab}iE=VYNYi_>xXMvGIoBzw!LYX{4LMnH7LQrf%$cJWE6mPwii87; z@$jcwgNxE+*%=9Z28N&M5fCwJuT2MJM*{9xiP5b)$BHto7{_P;;)GQmrW*Ua#w+al za&nY)%jliQF&ooK494vuo%_|Nt|@t$Y$0E`5;B|udcD0SY*2rwEWPVW`0T_u;*2+_ zcNPygQsOqy{P~g0C3aSn$;p9z2hS?#y&V+8yK-lY?`J8N9hCC{6(SLYK0S_(l8z3e zn{(gVJVg)+x`GG&dNLBG+mz)`35dAY7sK|T7F05_uHmHLKT4lCW9SKdBYc_By%V2j z7B%8LfAV(Tqjt~dQlrqq^e4#yPt=3%8V*;CLC&~NC(U&EbPRC~kC(mr^~_J%gEc0` zy03a~PHjm?V$>QX6nCLiivT>?1o6$fAxi&}aM;231K14AdpLS_(c;)2%sQzf)pKDb zcp6jmg{o_CG}OMe-lEAe-Zq&T297xtsTNHh7oY@4bYit}XyrxP+mq%OMBk*O3Kmx< z;c_*1vsrDKe@SjLBxfEL=eN^Jqf^K$OLf|kuGZ?jLaTU2Wj2#@xQTac=VV-Q3fewIi4Mt;kn44WP}pTG6tV>Su2?%+iv=El?2GqiKVB#-4`(1xDg?n9SvQtk zph4{FRAPiO1!4!Sg5w=d3F!50!B;MjL=ITick$U;AuRR|YRMJ)e%AZoq%4z{UNAp9n+$hg7p<~tT;UTjzN`-Am>-Snmr`xyNl?W00Ix5P&GB3O=`w@^U@IOjl z_XD>n_`Z3JN>So93V3{fAJXNEDoL$-PB@~jVB&N%k3q85>hz5!-v`V|B2w)2(o6NC z+Lh1%vYytfj3pbxk4HYENyE$;li*w79yQ7MGZZn219GOn+$ zBlm+&`(Ere7xVK2UcY{wNgqP&G9$rChKT5xLR=UWbb!rTDJXkA^WtJA)h#ef$Zvl< za>5lor8`f?B?p<+V7GU_8TI=ntM5JBW|hA;Y3aD@S$8+&-O&+(VKRb9obK12tkl;+ zg@x0um-=h00)YjgQH14MblYC(#I5wjcej<|1#;TQWlc))631tRH#c9|s#ITf*mr9c zDyo1%?)kbj@u6-u(4%@=@wPwxPQ4eSi29q;RLJ3G0VY4!afH~XWz5p=d1~M|wshHH z6BbZ(yL6BD=HUj)-d^>`IEqP{!87rHVuivaGxYANKSVYZiPILzLd;iGzq)T@x#k3BMm9mMvd38~~P`!B?K^yVC zwuYMTRK-@)Dmp{BrDXZ16dBS5ev;dH7VOWifx#Js2ouO3gMl1Eckp49E(vU;_^<9O z@B<=z&w+9C*3f0Adv%?wocwzp4do-NjEXEHqQ&ji2$mHm`b~|zLA)c5HvQ1;vHgCW zQfymk0(FvNFP%U6?hb9%Y5`6)3YvA-&m%_!k3>HYAJy&XEoAHl(f#EA%AV5|7f|}N6NYR{xdvX%t&gwNa&1x zKBix2qnjp8YYNh=zV-Al%Zj*4#KVF>{s!JXq{HX2X5+L({{TvwuL{0vZlpzNUn#SP z3PyxqbiuNfzer)8qz&A^IX*hV8h%4Mzg>11=^h-6X6!`ajWBFsRFW*?UvSgMNXk#0 zPqQFTY7!-LXHQOX=CUy9)8Ac!x0{uOUsrxZ#e};uG_w8Cu}7lWBDbi9ipH_G0F@|dF5qj{WZyX*XXePWaas&b~DhI;4*^O##grNWR_KP-Pl%_NNw^~I**m1&ii9W zq1>O80!jvtFKv>TAb;e$t);l{GpXan;9pvlfSq8;)!~}(QaAR~pOz;>=T6G6QjQ^q zkcy+I+)4|tNd@=Ky`O+t`PmdNh9CswV%=2S{)ni1dBEx4r`Z=s9*y;3GY@X@RI-G^ z+dXxPEqtC*Cxs6=HWnUl34x{atXHzCt)E6}1ldpo&I=tqdECua+1ULiu$__{yn{%x zAnSd%mqOCD^sO1&$oY7UPDu|= zQEP2ARvArgU&-JWDQgE}1()20wUDk5P6OI$ExK)B@Zr$sj+rsLdIH|l-QmxS`6KO~ zSIx?D-=yo!5iumjMW=i-D{u_O2n9#Jy4lXV4ZS*yvz+niew&AG?yR>uoOn{u7Wl(5 z`Elu%$--gH&8L`G@I#olAw9Ft7`wL~-rE%Bj}2|FG?rCQPG^;`s08uj&WW3vXQQaA zZk4*k7aIDNNRL8O?BvAd8XrnIFYxxH`}nmN8P>5+FG$eKnoc1`7JllwyxKJgYu6w! z|8i7Mb^10CQD2(3`K21q5}QuWg+D7Tx3OB$9BkS#YQ`hbOmtbYE>IO3Fr=-0+9z$T zS4S%wY_FB%Wy4f#SxFggT4V{;N$U$QG^@%lJw^l5Z5n3h2CcukaySyUMz%o@-Rdf? zk8b}sNv2)iup8<^y|C8U>$q2g7KgWqO4;ZT{HKWrKg5whqv!DuuyW{cpfg6y>EV?fH7ijefloQ zKD?5<>5Dr0!ZSTu5!!mEtNm<`lV)M(GoPmw!ORM*1}da0zDBxax=cy{x9SVOc(U?# z-;U@GD*S~TmC-#Sy|pu#bo-ZhMOnh9tQ>`CJ~TBuzB4P0K%MJzbrW zelAhF%LuOT6i9`C`>1K@APb*OP3E(Vh~V)RvAHFz(+e_xcJ^Ddj<(oQ9zir!9W$1b zSu9Fv@xztqu}%7;9oAi!H*f2ePVrgxVR7Ero2`fMKtGoF3XAHrP-ylo(Y~D=yVPDk`e}U9&IQ`E^3T#9Hs&$gHMnZi8+8 z*u!*=nqxJ0&Lf&ogw9ylJ!fWE=du-$^R2YoS|S2~R)v*-1ur8rsc%gms{xsI;rQfw zMa*TOv$?sDl7a$9+$?WyzK4&O7Xav(EL6vsr<0O^$$d0E`)PZ~xaM~p+pc_{;_-O! z@h2nuJ>{y9`n?dZRf#$Mjoy~R&czCqT6`1=dV0FcWx}>(JiwwT%h!Ta1i3CuC)5a4UNy^$X+kb^ zwbysm=xHih;F?XS=0E){yfm9CtY(e)Ny%yPJGk~XyC)`8HHp@!tzP^vFED-aVT<(L z27D;7<5oAq3(TFPg3!0eJ_0DK=&vQRZ`bOmg%XQgrEb4*rXd}3~D>EKMQ zhzRd>@h*L(EXbuT6HeVncxUz229wC9{_Jd0OKB+j*v-uk(N0L+X+DvRWBz!bMO;P) z)oBb|r^q58mnT_ei4{f{)kkV?b)STt#vMS<8?tC$jm2O|GO*Y{xah~CF?ZKnVQ|Jq z;ii!7uwOh}4{b4`Ip55!zw9DV(*LN&s~q_y)Ya27ZcN&+%rrsEMgapAx!CcN_yKRr zr#3fKjIsb)Q0Nu;Hk6QRcyYA7KJmfNGbpu`xd_b5Dr)kHWe@u=@0j%UvdQ~z^bGjF z6mNb$Jvs5urC?iE5{&pJl*|$U0Tme$^g)OHv$|Zj^HG>lo$G5uXO~Z1pP{Ng#G9FB zxgD`wX6h8XuLqDQCryTEeB|_mki*L(>hN9P$_o(evB9^!4UAiI&41| zly<>citra-A}=0kROi%_;7oO@UKu<=_1zEkiv-3SBjxJgZ-7(PXMJ0>R?Dl0d|Irt z+4!I@MD8}f#HWE{E96=6QNr;)n>Vx6?|Mfo`7}$V_ftx!f{aCJTDQDIEr^zm zn!nR0`JimZrk(i zQ$D}tIpCTAgVcRd3H%FuICHfqKo?(2TRIZQK-kV)iPYt*wC0ABBNuwz7rWyzUdF1h zX^M-JDD@H5gR;bT11`EdFKxYfLt;FI?ogpn^p7Y5D}RW5I{~x4OAO)HawaFw9~P~3 zt-`9(9SA6K;{aS+IM6+PRsubGnw-EhJhc59D*U_?0dg_UZ{$q24tL#_Jq+d@TgameNl~KH7&oMGin!?Pq zwvZP-PLw#hO~6v{0*C7J!=tURqcHr`%Si(IFd&NIZBF57;xt{o()Sz^^)U8G)1GMG zCQ0m0pSZ6lb^#jVuDEheB57m|8CS`*`8Q#MMi84Wu2w0}%tW2f!Y78c`H0s<}m93P2 zJc&P;AOT-ps0E4U(bp_EPt!CtAOK8O;;Ux^o$xD|JQGSC6>fHAgI7*OIZ(8gD0NCq zhc(f?U)tDhxBQUDv-|4a1Xt8+HPAU94yzQx13oM-BMmaV-vp;K=a5S3T$eODSfQ?o!ikZ3=@xP}A_b zuiUFV5-?#H{|d{Jrk{@e^wON%o6|U04=bPI)p1{A2_daeH|SgS4-R zFod5wNOR-M{)`p0Gt6fYbMm3#kKAgn0$P>KjUevwl*ei3yqp9an#^#$r`Jzbrg5#? zoCnI31rLVfuC&Ns2NT0Tkbu>TM0iQ;c%Aj!+;{>40vO!Md0sxaFZrNWa$SgWBmvxO zV@78q)uJ?PiXl~i9T8V4uRVtiax?|0Mm zQ_lA|riRU^oo#4gH;G*bH$HUecRaGttVW&_g%%lo^W9%Ev&DHbYb!hmgVKkg4n1?x z=}pl^RdHHfI`R!>yR?(=wPva%RejK}y6&|xW~_c%_K5QZEadF60tV6|_~vBbgx_Z5 zdF$(6tNZ(O?CP?`USR+H0<#SUMhyu3V} zUl*`wA}9juAvcQ5X(fD~O4U6rM|5+=8N#TPtN!aw> z(W=f=z8L=`gSOb>PJaqT9q1Y}-R7WV)cWz(ig6Hu&SY@9EA973(Je@IsDI zZF9453u+kk6EUg1H9IqpjCUcZad%VkpW;e2IqCZLxS$S~_p~K=)Fied`Kv9k61Nx6Q#Lruq&}xs{R(>x_+=~}`F07X9OCm|i z>$m`LMxE2e!JC`vbl>a#-@GjEK^%inX6(%Tmo&_aIK0obPU5q-g<>>K#GYC#d(9Dx zb6W%O&e-D?s|;@aL?Exm)kvw?3c-Sl(ATyd!iV!pT~{+$C5iNu8ZTr;YCcvqP9>?1VX zs3R(k^F_=z0~$-~dD0Eek(c%pPYjqg>}Jb3#nO%QlRpZ!Wi%@alXQxE^g|xv&6N|{ zBbUK{lqb^L07weDxZ3Yl1#vv$x9AE;a{D?YESo^t-7T)=9BD#`?EQE)9Fb{CVC1YuuIWMLiKgfn`G%9VrSjM>%B4lvVWEQENe z#xdo>+?bL|=|}hNwVAc8{a*=?bk;%=7-$ z@!nE<($5&QHMv+>od~0+muc(aSi}0G@P}v*n11Rsh`##zK{?4%+n0ne6BX;> z@Y2{5m=HD@SKN?P5Lr6bQQLbc` z(=JazaB!tbiU8tbl5P>f>-n+P)FLtQ1RJ`X4w)_N}+Nm~RYjPOKkN?7 zd5M5nr%KkATB38DPZJ(FnkZeH%n~Eijmv^=L#Iz1Ev@<~=f+FfP6aE%Wk-j>zV%ow zK5(N61@R(ugNAQm2@A3<_y3^aVxA@JUS~ z@%YBel%Bz)+oTQ=MK#GR4KY{g+U-0}iGlswocCDz`HTa0I_p`j_#4afe;$FYGo6Haof*`!bfmIj(M1+Yw(q8Wb}Ch>k5|GNK+sM&!dGWVTi8bNt_!r(VD@EIV1 zKTI`mu~ZfE!FeLspPAU9k(w@L@e3bLvN>V2V(?95Xgt|!*(D%u^@v`pZ1H)^Ir~Xm z1V{zVZGA<`P74KFuZE)oiYWWGz(#R^*{^YQ`m?`A%AR@q#!1lG_ncn;@0d2y<8FdE zmVKi=AR=66^DT670MTo6LGws;xX=A--lMMxEi6>c1<1qwNS?jc#QSmKkLtX$A`Ybh zO9GB#xDi)BC2;2RK{~G$&!^6mhvIKvno{dNevC+jRbSC!;GGWEPk!6f6Dx6gi92vo z)}7z5MV;kK7FzoKOhmAeH!k>Nh;ihYQ$Y66@__`&7q+i%Uxv^Qse z)VYfT#Kp)!2$H>f;+`k^e?y{cG_O3zUWfDme!kn9Wtf;kJXC90n#_wDEoQo}p7BWilj`$4n20((WqY$d(WfOXS4k&AbnAW=?A|p*0fIZJ z+XyQ3omOiE2hu(CY0IT90WdsTWZ+}ryIQLNgo6(JW*J8H*^-$)p4K+<^k4}2aokTP z{nJFHXKAFL2lIf0>|x^L;F>Yf@`u|m6f(-~gmB4xv)X)-1y-0JhI~NXe29dVst_s( z-rXgWRJ$fWNUrcqFi6;ug*+xfH_B-3eb_=>GhyMzq^o~zdp2ktJ^D%F$eO)54)uQ5 z*6s^$#oT_%)9HKIfvj8mP;A4%WN2zifzkeBS1Sv35^u~0#D~*=uGm&zQC1x43k0_n zGU>q^ujDq43+0%;%wXG(-3jYl(QV^kCa!_B7#E+kA7iA&y z+s3kAN=6oOZ4QEM{&2_zx3I~4V!bh>PzW|iaAJpgbhVq6c-A@%kf#mGh@Z8z>W1_% zjGNG3sw@_U>u`QtEyq;2YzG|fQEaz_{}8S2qK7_;m%gQPI7%E{Z zSea`TnD~hsD9c9uvA{C)Ox;9pN20OIFOPgz_M6bx_l(>Q_{B&+>(qfaNQ!%Q1U}y^ zB4#fxTPH^0H_de*|3VqZWy^$_Qu80>rq|dSj)W4AdtdS&vSqu=?b+~=`Lg?Ie^RYs z)I>fO6opFLf{ZL1K~`UD$^9=ZAEnxVLi+kn7};T&?JbV#7kz)(=ZW7Lgn8QqZ0YbS zT>a9xiInfY!WTXp|5%AWIo$K};GhQz$wC_EO$sN;;07=J6ASpAE_WH)7|F=PZ1_u} zLqQO9_u3byr}`7`vsSG}u+_4y4QAUh;^?gPO$?)4iYC(o@@NGbYrsHnE~b{Kjyy?ZDG z5+fgIw?3R_>!SyUSI|1g6^6*)KI0rdp!}+X2fdIG*<(^(C>$iV)VbAIjG$>%TctgH z{Tojy1%VTzM#smAM6XY&%S{KOm{jwnM<)1eXW=va?P#8B0cdGxAY7bnZEZt&mO=a& zL>zwvq2afm9jq`yf4i(!iBn-BQjc2&h{M4vrxdf!d#0&Fn0Ygi9$~oyCN9zdRorw4O15;B|^Rvb7=&U8rlWhq&tfsMt zAN3Yr_LobUFFzR8)VXYFf;BZ|HlEr{6Wv@MVEOy|XI|hGk*XG`#b&;U2W&L>`rU;D zGUwQ}Yv1}mM2B5pU6l1oFiXRUJAxdZo}IOIc0Ppj|1f-*pzC(3^gF5{2m7lR z8OXty`SL;C)f@MYb%x0}AZdoV+a_8y+9inTjFD|&Y{r9w4Y*?6`YBLL)eu>9&*Z#|<^-}dqd!FVZc=?2sp(*|WvwCraL8n8ZgKppmZpcE4Qb3E( zFXid(h{+%WPZodAd%*@=a#Xt3VgnAe9J_&Do8i!l#frfuKMTEN9`tfTmgqP4$L4io zidh5a+qw+Ql#4hVOuj{s1u>=wC^WLwUd)lw2iSWHx8EFJ^U>^<-efp&;24iA$m zPg8oigeR;D{c))F`bjZzzz^vj1q#wD-@k;g>C{UPF-}s7c%8nrnW;C}7%i&xI^(h! zD<+@9Nn?exan6p;wVyf8Xl2b8zWcvIX&Tc_HLPTR)H`qRg9=$k_Q+M}Jw91mS!L!| zniZrfzQF)i*K9}CWzZXh9vH4Q(Sob3h~wDxqCRn(8jhD5;2^jo1e-dOAkluY>m_oQ zynSgFf~V2YEl}E<94S6!(!mQlAn>9=UWpu4<(RP#-H0I> z_TSN;$;=x+mj;(0*~;5=lu7BLL}CP-bXc$IozXwlJw&r_u~nv;htWJO{7Iq44{aATaEV3YY!IWbau2cwmMnGJlo_MUnmUynf>YmQ+2v3bz0m*DXH%9-hmX+cn`ZE z@o)l6U}Cy6NV**00>u2h?r0b|9&<5QW1B)V{h8v;_ql9r!|%NEiTnn;EV!Uui7AQK zB4#m84F4Vt!rNirD~fo|)&V0W_KPtbyb-CDmFkeJVc<(l{Q0nj;0ATVkvJ`E&1};P z`&s}102KpbY0?!v;UI$?wcMveK|LUqAMXwQChD0gw&wLUnUyc1r>bktb)u$^L+<=l z>$^QOQ$9+m6rIk)$<9TzvZ$WzFA|Y41xs9BD*3_CZbv0{EzGCRqj9qd>-)Y&OsTaU zFTd6hUD*?QjFq*vD9jp3YzV;(`a-IJed?ESrEuN{WdQ_8=RC+GuTn&GEHrkHj$)x= zm4Yblc*0emkHk&t;a1~ymg?amn?|{~<_dg&*iW*^$;m0g$t4&YA2};@f~4moV*oN1 z1rZGt^!$5Y-)=XhFOk=c;Z!;j3sXJp7!)y8X^wlr|MWfkn|^?4D>@2F3}*YqR?VDY z5cp7RuHISJ!wc3b%xNi^BcH_ZkysqkP_+dS3wYG|&L9%dYXYW*k`PmHzrlD^S;M7o zhW?-NpLeLLkPbU*8Ed>;9>5oydLRP}QSt!AqBn|bE=GqJ^1Ch(ZxC5jICXDEZ4x?Y z#Gh<)D?l-;nlGgNs*CBcQ0+do{28@N8H`7*x+qk)VJ1z*LlM$K2qb8ye`5;}wAnkb zWJ)lkXP?COIbA?DSA)PWLs>!3?``(`<^#~NE|87mudhJVEjZ_H9-r-sB+Z)Dala`t z69vSAUs(nrG904Gp83PCFWD*X!)pn>oK+11dZKShf;E-GW#o0G;mSPf`6@3ze`ZxL z);>KN(=*Dk#ijfskR#j#cAVaGwLZd(X7UL8pBWXYBIZO;F`Py1Ruv{=-_fY2N?VZ% zko$kep4bxw<>A&R6~MgUYr;hGvAQ*1J{*LeoD%E6@l<|+p3F$;=!*_Ec{ z@M;rsa(fITbS@fTnN74YRa2relDP}mKLS}z^jwCkp>839D!m0AM%_^oqW1&&*Nbs2 zjK4*Bb#)E-rEubbnuV4)VcN!xATnoH5(|RW&?|#!TR4Fm31E7`*hE*b9zS;sKWDhKz!NG|_;X{}9ZPgB-ZD z_0ONWpkeKX$OF}8*ji*D8c*Q7<^^gQPKtb_L{oVY$Bv}FHZf*%_+#b7(yg*>8L`9g zvs2b+eBN^gZ2Mu%&i!8@N>;#j6!-G$w^r`qsmIV>NG2dh6gb+)mk9Vsi4YMJuztrG z;VZ;8v_F19x^a9i#d|}(nplEICf{WQ9c1UYskvOOmfDDLyA~YGdQKZ*)cc@=gIF3u zox&FUGEXwW{i?Ta6h|aizUuN!Xn4RROf^}+qNfnx>x&OZko&0if{vDXChb2EuNM9@ zX6lxXJV_+i#jayM>I&<%U7#e0dVUg8>oD*_I5niLvzSQ6Nh5^b#{lKr4eMl1Lb^tf zubgXtq_q2-v={{gD`4l#l-p(-t{Vr&K@wWo5LQDyamMkX=MUkOsLj-RGj(kdw?|kr zNB{zCw4D`t&Jt@>ymD~BNqz9kJGFuRW!aWF^>-FSGXxv%{OIp8<4Me0|BoEPDCg1Z z#;M2MbFxq3`By8<$j5gs0o1+uQ{;(56jk9%l5s3``YP4HE{c<}IfZTxj1Lq+{OgbC z)UL5_;u&$dzI~fTk`*!2uo-t|SqBq}t_%dri5g38Q>hxjN-@T~7Kwq3cgl!8(}S*8 zrR9)M<{-Vf(inYC{M6ImjKS67x0*uwoWRgNX1$zNc5ot}x=?Pay)lTG<<(?s50q?6 zvq}P4S=NcGLQ?(Ucr`oSHN=L9v-O^qDi!N;*I#mr7$;V!Ox9}w?u?L-kf+hrBGBOw z7?c}E!w#$dOP=w*_`fCF_bJuk1HIZJ`(=Ii}ymJkt5Bc~V@9QP@ioF>zfBb$%vmS7EHkUt)g^+>i|{wROk+0`RBBf!V`kI2fvXC@u7 zDld_AUj$S4%{)%?{TSU)sHG5i&h&P5fPwPn-cG+jHK!wKdH_4n&W@P;WRJV?dOCFV zN5<~X%e#*xALE1HYqY)k5)5l3l8tAJ{ZB9qhs7`hqV|itynKTGAOw+=ADZUreafa2 zEzOP<+vnlK2%RAZEc*hkg%7P|X&YtbU*xGgId2peF-Lc-sYda^#T1`?{v(F#?t8-m zSLaS$BPc~FUIb&b!P!VwN>o%7k$_WfpmwlS6d5Bh)9r9|kZAl+{XU$Q4en4r$0kBE zF@AzK;6i057dx=wl!w*w9a%*X@&`kQcbZ!*z8|h-Pqh-dPlG)5&=&4TCA25gq)KBq zA#Z8>Q9QTv>)FtH-maj`jk0dbr&$W0T0e_m*m=Uxp;A*>KxY1TQiE?{Y_nU*-LNf3>FiFetwCR)C;@Qg zO|NVaT1kEM<@gvT&Cs+%Wfk!aUE@E1t4eu-`fDDh3;_}CaB z7NrQZ%&5Ec&36*H6cKV47Z)77Cm5No#J!JN)XS}ix04`%Gb z#3Em;ik{SALD2dIZD5Q_W_X?rkm3d_9fX9vb@fQ;r=-E!Z(4X&&om1TY~e;Y8#zA&8IOM!&4VHjf%$RLI76| zB3|`o+ZjxkC8tje-F4I;Kf?e6NrHqk4XzloO*ie+?S5AaCZh%ckHjj?J^I@>SwCjkS3jnAeGQ6uYcg~akW&!jb)%*9z%nU^J&V7KTLWh zAwMw6+U@&9=;|1{@qO3F545qwe@)JfoN!84&1|5&KN)r22~bRtLZMKO6Ep%zPvRLm zn~WCdIi6lukYS57L$v2Ky9My)eMbe~^lgYDEi~7R7+!FTunHh`&V1Y5eogA<>JMnH zl@wZZBp-1Y8DG~{W$n%1deEcEQttwSOnEX^WzeCZjnz!iUPck z3j&WCa$1t&HBT*^NGRCzoeY^~S6@Xp<5PvxIL*45FXK0VVKaVe0V`*^1J4j)bY zOJ`vV_VMa7ylY*^vB!N0J0hyM(NJpz^wS+&sxh9ZIw3<5su?xXAI)MwdHdT8;mUwy zQC|^+J0o-%{vmN$q3{ze1}CI@muHSiy(e2!A=7n^9RmZ=h7!$|KpW*I8H8(;i)B@( z#RVs590#~F2ONIM3Ke?^Fa$QH@V1pqt(0J0MR`9T-mXrGN2Zi_l~PAs4fd;inAmA! z*iWT5T5J1TfV&27Ewoj98A^ZozdmNEUEpYGgOyxQdADe`y7%Y>=`$|54BP_q`cYf zz*?QCpr@jug0Y}uV#0|5PYjRc=!2aPF*{$@#>$n0LAf&V}oJ%SpVEbKuwe8A}6J*LWf@wT*% zQ8f59BJ)e&$-?M#@ z0w{Ro(L!C4>NFGH`mLp-!1JNoW3$I|gDnnH@H_AeX!?xs^ja9u{KBH6K#SIk#3EoG zIVF|f^kk{hLZ=8DmQO4-GWM7}a9)^t{^!<#9G-}0GVoj_B>~pH+X5i=A_e{=g_7lt zW$_}}bQS?*YbIPM!){!F?bd{so{p1MJDQNrexIStmkC1dT=gznBwDlt@3opdoD+rI zagNqUIFJ7suA-6L*S+EM-XNRoMNlV%?^&*4F1j9)~E`*VlLJP$2ni zd}@kxny>KP0xB+D>YVL2Aavb)tQulIhGdkj<5@}WhigI8__ztg5>;t~aii0%vXNI! zO8ziw&QaXTCLX4DgAIa+fu^$_BK2#Bw2MAa%>FG334{s2Iy5dK; zd8ULJ*s)$md&VwQI0;e-d!_K$9x)r%@*HC2O={AyvtqlBjWL3cz7^4 z`8R6we|WmP5I|FYvNIQ7JlE*XtyyJ3t7`x1)vGd_8G(SnQ#dhWP)dIwBqY?#pDWJ> z6%i5P;Np5enp66&@(Hw;gdiZ+T`Nzf@K(!l3ScBx94kU5xX}$3l{E%DnLt9({LnTb!f*hM^wM{kCz@2s;8@i8AC_pf-cbZAoRyLhQJ?&Y{tOO zbn^D^-(Teb!bc$pr&Fu}8tF@P&}Cmf&GHy~)G-fJN4f`of?5&M!Mdtyi+X@bBuQzp zD(;r$m}HKg&%0%oZQUDn3>!sUW|H`Jh@pu?M^X`;w%S#95;+?UQ6ekbONPv2Q+N;k=AUN$RZm?f` zWMyTQ$y6F19xe~;PZ1UM_xES8qIrc<77|7{O1oETjXKTu7MY1cE3Q>`#9F8p7YH9b zlgTP{UcFiIk*`1O80T~%YnJaV9!wl<2Yw{8){FVqBn$(=C_C&em;Nt8Fgta}T6}%i z{;1iSs_E9vS<4xgb~;%8Ugoqm^dJb_(dd4V8HaAK=K#DuT>GE`eA55`02ce;CG%}* zq)b`{XedCJlU(z)LLV@{DV9mKeeGwCQHkf}Y1uF(BaOOa62IHm(NW&x{lsUEnboMA zJ7r)6mAJ^rNLt|a>0ZWDtssCYunG1gOFl_pO(<7hx|-ik;cHkfUW?lvXx${c75ldS zF&xW}>o4GQbAOEwz1DeALym((^(vgsSZr?C3MT&Kb8q^NI;5CCO)6XN6Adwe>O6d( z$>PsBEcE~#m-~iLg`q|`?S-WaC(d4sAlPDE~G9zkNINBq7w#7Jlwx zvpF*Uz`iR41-vfSq+@6dgc}!x&_T`v?Z1?0?dAD@mp^>Yj``n_Kfu{=7CmM}S=XT> zHfMZBaYvpSb^?q0y`aiztd$#=)|+B+2lm!m7K939WQXEfk(c$ABNOm!zJ1uDY3O5( z9_qXC*|6y)^T>c+X}u4tl@<8DAc<;c^P5<^Tz3r?TeW+H3S>V;%OzUc{pSucx|{8e zO!&yY2&09wU%X({`Ac|zf^yd}nJ36K`$#By zr2<&`{7irA>WNMOePciS+gCrsIQnDpHOiM>d=pqEYCNVG^2{Jbj1B`?+0KwVN-4DC ztaJ`d&-t75#u=RLFAYtQgghpt1Ij-WF@&P5Ijt{tMFxEMfSb)fejB~P`LG*ZTkgR? z63Z6W?jGJM5(PkB9FE~)7=|fmA1M8yq+83s<@&3D|B-rSuyoc5rV}SqaP@)wVvqtu zJCg7&Y+ct%;7mTN=ay*D*#Avpp}#xdHl!dWC51sQpaNp+K$K1byoQ@`GmPya&OJ~e zYwOTbVy?v~>XGb{SoNm6Qn{{rpU56(b~k)?Eep#0yZ_OCSXi#)6V}XepjUIh?fuDR zo3PHEMGKqZBh*~&R6a~P6FCq~{L4AsU^2T~nci zV9CZb{}*jJ)2Bdw4udOkFRiFJ!xALeFw*!Dc%X^3oVH1`}z!7 z?8?iN7gn+AdzEeW$7n}^LvN2l3>z)*La4Ls*)oGtF8;N5a{Us@T}ZpODbJ)3;r${Zj#Cfe z;VQ&7oN^icMgyH>#ckuaQAg@TPJTejNEG~y1H@A7BJ5vAKtR;IjW+8!+qnFWsK(a)%CY+^u}1QYiPdjczzZ2p4O*(HGneHr6RXMKKI?fs~jR+H=VWK_-jWJ zz?_+)n(hLh_F5l!L)Q`GrM= zB3D~%J9}d0wz(1T`eB29NKY~?uz%{IV8Ei5&dK;j?4vO3Kl3ysWGlpv9$N;w!Jwg~ zWj3}k0Cu@T0JC>#MS)VjuQkGj^d3}rS^3D?fzR(i>r*5Xg`rxK&}yUXjj@GHbgWrY z0mZV2>L1sFKc49^@+V@*2f|NMtv4!Og{WVtikHQ04LHJokRNmO1`eTHPX3Y%n7}ex z?iyxq-h8nagX(r~8OmbJ)H`8t>N~%8fW`wTi`C7!Xy(L4${c2dE`k!sNY{ zk9`3w`Obej{IIc^q0f{?Dyi4wFHtormc+i*>u~gT@{Hs(W+G~eXhZmHrynqfCw!ti zJbH|dnk?jAe(0I9EnMbu#Ji8uS633Hg;#5LHWWJYs$Z$|q;yMF?ppXfB;Fah$ zr3YgFo=uA`1P;U_=I8aBHduA)@q|4NTiXJVvwHpf{LIFRlf+Jd{mrVib_T}%iNq7- zCNJQ3Ojd)BjWn1 z%w_?!cH?*V%I41@qTc5;s&ALZSXH(P;foUuXTbC?Zv_PWW1;JI@O@x+b?%&Jf~aUX zUS=dkwwJ_XNgxx;M5`Lxdsh)pW-Q#t$X=&E@!2~;SDQo;Z%v%g-;7PX`A^1n=w{lV z$Y@Ouhf!a@@Ucfxic(BX%B`xW(}MJ}%KEl6`atU~j{TD~{#dq$F*Fho`f&lnPbOF_ z7UTEOeX5|BlLR<7qdc7+AJp4Edb3{Vi-`?nV8Wicky)YRIIZiDl2s4s%RY%@p*k$N*iq`qQ{TZ4;l zzDFeUk>OpYk0}T^8f7F!`k%RbVb9e=@q5d9+_BXZ$bx+(AImoae9NW*K+Lv)$c`V` zng+jm-V;AVQw0?)Xze|fs}RE3i5`$Vd6X5yXGvhp4yO42sJN}^$u<~%8OaXvsjZQ2 zI7zM5U^PWlq@NE@ol-PgW&nBv!Lt-VP2{In?Ida?x>ucBfi(1z5DCkGva+&6~Y2t+%}@7*umg>s(;2eq#ZQt*%VNA}>BP=|Ty z!|kZdVU(&zm%vN_gYGjvDG6otVa5^Ui>r@#u$`BXPA3c437lm306UO?m6Jud>E)k^ zZYz6;c%7zJ%sh_=tPG^+381iZg0zb?dB~nQ&?Lw za)p{`0RvyF0SibChDWdjtfcAMjz0oQTv2g_~4!zu#fQ8>~#%+Q%h7ig+j01Od!T|%I~NR05C@AtNf-oJ!6 zrpmOy@9#;hHoxN9vVH>9gRzdn1_76QgK2gxWoW_$BdrkSb&+4}IKRWt{8EqLFU^M< zq53G&S>|+WE!1I)uU)wcG=8>_3+_dAO~;{zV%G{b+(T1YkBn8f?5Eq!wXn@hJuT}6oERTA)HcPKdU|@U zH8Z}YVFXRj4s)p?RI!nFsCN{l%vx~67{_ocE>xeahS?8xUjgJ}J?ubMivu zX~H~G4a1Be$_=yfU~1(42PifR>h4w-)|rNE-dJbQl0V4WIiJ1g&Isk5xH4 z!YL*NOebDr&=EWoDgwm-OjTQl3lqts6qxya{mA%%rX&3`^8-HkH$S0$69@2ahq=IL zs47ec;^IP?oZ;6+<^|5x0BmPjV_*7p(0F<58DSC5r1u@N@6-TZcyj~Q+8tvV9j=-I zC6z5@%E4_1vp#aRIyg&@$oy-#wlTXdE>)f~O%eTMJNKW#!RW|TB_0)2qvapXyE-}Z zdYb?2-Wav0?a88r|Kz1INAVP#t(QA*Y(->3V(Lw(f(sd`)5?rZcxcWHc zKF2OVBUJUGoXbYTzI-U4I*8VY;jvUB{`8K~m9)@BxrEZ0RJnjKC@I_q7*#L)a0KnR ztIZD0{xdED-5|;|4INtiMmN&(aw(G7R8Tu$wV8PwBIpiKNaV-n9o>420}BepH_XCv zap6HQ5{T_KYUhD_Zb~m+fsoOotFJlX2Cwt6-{`JrTf*03hQ_6Eh+@MK*9EhqXRJH1 zTeHX@f6viUDr10KS-io}JUr!xN6>}oSsZHFkSTTHzDoD-loB4Sk`9gzKjQu!qZ3eS zTK^kF$0Qm2j}V=ox0{R>t*X6cfJf?i18l#I+ijKLZ7D?2O;j}0-C8y^rjw+Rkh=M= zka*&oNP~o@+GL~Lh)cp4RlqPD$JunT4AzTQd9nZi02l1?RnL@;LZ%nbZD0NVrOF7! z6r~4{h6>}}4sUvRS$zn2E!^nXB&KB+Wz%Oha{g^}t*;r^XS_n`+W@hB;COXT?rG^ z;{4A6@u#mG(xr;`qfcApK4m6ffXi!I-(w9;7zwJ9O4`OmftE^Pn6c*hb)kj zqvA6e2*=$_|8TMB)8#0`Z9W>Ey@;~}`O77e8O9%nTxgdb>pYPfK zyRaT+%zx|Rio!`ro^4k?c_Pl_m-{xJe|s21Vxzw+K;?7Y`#Z6|8dL1<$w~b3HG!l1 zM#s#I&SzV}Cr_T&BNE0wB;)#5D1S+9v}ZH^o{0Z!KVGs)UfF*4m3Y1|)g6Jqh3P}D z|9@(v|MQP&+=KqFa5*qI*um*9-(9ob|BmBry@EnQCq%i3ibG=`X-GpX)`$P5=Ki%S z?R$jg5W!|pUb_EJlaUgx-?5(K6I>=!3Lgg*u>Tz;X3VwKy`*rOAn$Mb<91=_0y7%{ zf-aS9nV*-5wDO+Osam7uSVS6qnzMbPRtN@`IV|-Msj+l3tw?}{?TN05dHDB0m!11i z6#J)MJ2o8=%|%KDDq?t6YZ2;$dc})A#vJ)315t=9C{qI>y2rHtjPt(l1BAx;F|70P zC5u@;y~|Lftt4s2SLnc*%^$L*^dL5sILfQp6ZRUD{8Kxx<=QJR5C5%JlOB_<|vbhhz64C2R#R&$n^p>qLO-jL{CNJ3Q- z7PkoicJ??VqvluzpX(4V#LOawWW$NK_rPv2Vy_O+!hsnUndOS)Qs@}=&$rpFUp`FP zZN4J}QFqK=*^!>BU!z76Hi4Xwg8bF{zbg$6BRR_)wfTtB-fpayl>pa`fW5_TqpvYQ zA|bcZ!=HnL*e^mbV`F-EzrLfU4aAG45GDy|aFS~TSfZCYJ)_V6>K9LY1Zbd@HX z1lNV1D@^~U)gummiHzFa`EE&Ry8oeG-wG}{+6igFj|$Dl5&TP7GtPRnm&b?Vb;qO& zd78k@OFNaqr9eTYP;3~=bkfWp51Ed-dJ2kBLc>70D`MlZc@g$w>93bG((e2jQ(rW@ zdP*Aw3g6vD-${Vg)}~2vpu!n0?1)7dT1BtH>~t}6^g@p;NCnM$J+8%4PvFf3kdj2s&QHrr zH{YqlB3!0al%kLgL#Qj|sJC@N9I^l+^Fr}Pzlmte@iz%tyWyzDxs^OU!ZGYsTh^~eq;}CS7mr}eL+hPhuiE_^jeW@`t;iuyQ5|AVgCh4gP4*eF07i)8ljte zXCS7x;W8%&nTh}ML_H%YMsu8Mz`!ieYqd;Nf5911EpkF7e^ci zD7jGdiPkJtuJ9fge+OcVz6=&kA_?~}(K48ge7-99fbG1~>oTqzjnunxECTxdo9er> zZkDf~c?u%>zJ6?Ytk#e>7F{2iHxl07FHr-Kno0Jzc<8yP&wsaG1cu>pZxGH)ls#xD zu7T0a>=U4F0vM=vu5YSN{n0^)^d}egR+tIhGbMM}V<_gpJ9W5Fn6g^1D# zlDKD1oN_We?R^_!kBUm%M6iw=KN-g6!~sH*I!gDCI9JL*mX`-@qfwG6rrC;-DY9CZ zfuqdRqP;b8kXR8?gV-E_?Aa(pYYAs+c@3>Q9}<1-Y@MYOIlote_uqU{^e}k!W!jA6!k|x#S|I3@zEb0yEg^SxwUqZs?4Lh?{PLyEaZg# z94yYzn34i*tfp1`YWR58^3^H82}(e_-XC0zCA&rRWeVt<^vR8ZReAoK-s4?mRZlD5 z)u0#%$AiWx2wT@Wg<%$Z)I_}4wFh_lZDpeLu%>BHlO>*~s^}Iu-CBGH|Q#^!Tw%pryZ- zbsT0ctiN)G!)$~^619o=yfbDpY+)x9+cD5E(jewK; z)#?T*0gjOSzVX_L$c(2UeAMYSPK*vOTU5cQd$c~?bL`Ks>_6uN=ugfYTz4ejQAy0N ztekXm0UtDZp75U9zlOZ@_@6sg?GgD*8w`7EYpg=fSfETC2%1~yqWpKo%55tl#ms(c zI464^1fJj7i8kyEGuWAJvM4>fu zJ~vmcA6d0{qX6NvM)o~g_LP5j*DVI&mK(`@(}h%IwB=vKd){Zeu*-u z7bns*?V*;PqSfg(dDMDeRI3=c*xQRCDmtQ+oyurP?-r<+(3k__H-_^9;I!HN?9WOQ z+6^v`Q$)PqV@`5&akbwj5V>wozhfGQi9nZXZ0AU(+r^{}|AjUgRp2b0_$>97OpK1WF=_=$P)en`{u1@BBvwC5c`kZMk*)j#Pkf0v~ z42sV!iXaM*P63T_W2F?bd2aXpUu}8HIhjj{l9n_Y*KTku6h`;zv-Alg#h0X{B%03j zDiGV&U#V=&&~N3<`tTp0Zq>}u+`|W1*w|3y1RQ2JXDXfdY*Y)>B83y!M1TZx zUwB^$?8;Y)Lhdh%7X;;U^1nxxX<=< zT~^D>KJeex2hF}*{a@AWeN%I0UDtJNr(@f;ZQHqG+qP}nHahMk9ox3kv7I;fPxubM zs%O>SC;MO@%$lq<#+Z9AfCpzHn8Sraa3bXKaxgqNf9c&;l5zeTs8>bNuVV*_b*x;) zu&?HRTiU@qXmEKUSrCPhdH`w6Qvq8JK|4c)-qhgBHzRyz(4|Z=`c(!vsyE}1k(I;L3McR2+(bk*B9jH zbL4=Ui_jbQ2u>%R0yOGQ%JSrOp2y_7ESoxminSBjXV*U^2qdp3IjFR8woP1qs{sTN ziu97BgdgtpO3|-@+71Bh>;(*z6j2dxxPVFK9Y)_Uz@g$kCMb6ekL5ar8Jt*Cy5tay z)mydgUgG#iufmPnyJ{}qcoFnem<)c?LT-gWVz>K*!iIc1JAA~P-x&}T@zZk)DWkce zS+F(clcxt-mipnhzwpmk(dB3m6%qrH`K-eGBc*Lcp*`n z+{ToJcNJ8G9NWEu+7Iy75yKSa5rSR`>~3dB+QzUM%H z4)oKfcg#s=cxI=uD$ik=@Hw-8KZFS>@RXpTNp{N_MqN?H*!ytS;_f&}@D`#7ij~-l z@_fqtM_hJxY($a)o1^I}F6yG@|LTy93ULa4)AqVJFIz1cagmu78$jS*?zc}bw+?Zv z36_fQLYX?WXBWL7EnIP_t9Ny9gpkafzNj+CSXfF%f>UAc?0PmzzBOam9WD9o?1l)s z*n59g)F? z0oN)qT(cL;UvmzwmNsrS%%AxmC?W{(C1E%J`8Pz;!o2$3Aw_+x4DGZH*stS+Oqlrx z263P#Jr(M^#DaBF8SbS8Q81v*@016cjsWvKI}`DTUJl~if05p#C~Yi5Yd8`7i&n>_ zO%LgBc1=OYyHLh_D0msF5L#Ll?mn7ZWpiXMA#3?T z@il9N43CS|TF8gBasZ&o2PdmIc*-RPy$p!`T!NDjlWjDPC(5?dDYTw4=dRh*WsNEn zMA+u4Fr*kP7uQD;v)V79OlkF4D@6K?fGv-npT)5gP_3{rs&T)s9pJ5?#M1RB0z2j- z|8@KFi0DGt1{|dW-zNVzf>BjGz9xuo_fAsz`!Mtp)P&)5DH<_2^9`llD*(#MqP3y5 zx3;!+YlpXG>KbUzLhl=e-)Fx_9bm98dB@9*>Bp3z+hv6&jD&+VcqwWAF-1DtpP70; zEUbGP@{nIm@Uh1YwuA4qU(_a(;@zA7JAeR%LrcaV{NcCjFB~e@m3;pDBQ}0WCmy8Z z!o9GJMS2X~vSK!~5aar1&r*Cif+tF!*ZwkdVvTIu!LYM4g2-3t#OXAH!*S68v$;K& zzfo{;CYs>>h3XQHJ&IF=?{B@Q&?-wRm1R#KRD7cExQq4*U$y9K^tiU>pV{B)MfQ4F z$6lU~U%NB@Yr48{jES)_^@^z={#6K05+@M;bq~?-zU*42$Z?r??N%S;O&*@sH>rOS zj^V=hHr|(B_mfpr0AR>4SQJj$$a`f4Ikrp@FiG=#ZtrKj`werX>iwqve_!9pBrP%? zv6p@@T~kxFkMGYK7aJQJUGMMhxgagOoWb%LUxAhg^%?I)Pb)Js8F+q;5?-XHgU5Y> zs=Pzr9Nu$aU|*){$+vG;I>qtPs#cgL!+1nuC_t zTP&8Ejt&k;wfE?|g1^AQk2;bU76MyDxh?`}IbP)@%qTT2F6t01CRNysKZ;CS?39E912~@27@p{JFJ&*#s%U97bR%;h8=&Lp_Z=cPot! z=@-$8R9SQw)BB{*fk%e6^J4viQQC?zk&@E!wX(m07bP+w+t#N(X@N^+gr5E^7L4V3 z8(`lMjBWZxar3?TwWOvOJ?rw}r5+MK!=7`pHrR}1|AYjl$u}RF3ilbZPjinAhic>+ z^{>_UMdYV9d-h|%n-4rG z_hh#Y<4Xs&1~$lcAY3{bA#NTzptzi4Qrf-mP1v+^d2{Gg5se|uSly37*teOG?NXT& zsokyGv6s>e*m0nt8)0OSce8_|fM6+sla&ohXr%PBKeRU^Y@z|3-xaC-K^6TvuKP7< z5t+)Sa-rM7(!#7sF;>_LoMdcMWmZm9Wjw^bDsxU^GQ2$`@#{i%AMkAY!_1rK-*Ez? z`16C=8+E+!%FxoCm{Wg>_<>#y}qA3!`=@_|Mwc4@D;nZtwXN z<|94E97(1;I9pPM>w<)1o;C>>Fn6>j zAdoCQ!+TFVrIe1ykgYO4S$#BA`WXgGNr1=_@lf$NVke|u?FZH5b(|C+c74jBt9y&r z><2@v9_cQ`&#}Sze{JtT>OreU%t_16_;9vYXR1uNln&nhXfn&Qo__{M<~q~Iur(L( zz5!8OTC?t2s38~WwK3lM1}a#h2&7Ndm`k#}h<;j=`MHXsOidHc`FH>8aS8ATh6aQ2VW04uq~D(Q^I!o5M1I=66jv(fUKobUakx#l)yVO zi2_?fHI**f3BZ<*Cxj$ZuMm)Kro166N|+?GPw*QAq)<~T@Zm8he)Ie;;IF6sv#MZK z(bYs=5|ja~Z3?&kaX#T{t&$V{sLdEGXw+0cPe_v`OjAK=isghP^HNWqRSi@YEO;t{ zRsw%G^4)Tlj5jYb%KY?*(23824ZN#~)OLWN zVz*E_fEQsP5bDACpO<})e-6+=41fa z>P#H0cVAphp~;w8gb!XotVeno#jE4lSImiau8~)m3*u8eAx(X#(>y?_^f6^zXp|;B z|JO;UkN*t#g@Pw6qwf!MFRlqAHp&R`Z#pLXkiq2cE7or>@rR}K%jKp%3K*Zc;LP{t zco8Wl8YLslWKwVk=W%SX;~ybbzu9_m25oBpQ#LI17A)``b#U|bp}j|y|b*C$f;jPUJ#ck>H^ z_HMMnFWEHWH~uv$&89ZjpA|lAz2Ei7Ev(po$*N-Z!@!l_jj6+WjDbr)1VHsDCz@&( zCMYx*p1$6wCP#>S6NZ@cNEV)3cIARxFbkeRnoMG?jf!AUZ#Mi%;OR)Fhmmwzv; zEJ2d_hTmQs(R0weDj}<;*C(X7A#*pECIh1i-GHYci6f^-r!^$!^0W?fiOGVtQ9?Uq zG7t*fn$EMjH!`9+H!~ODSpNg>q37Cc@^tP!gxl5)PIM> z)QaTzvc8+tuH^!}%0|!>O&8I|bdlCo@U+(EE_ldn1GNIe2)g}k_FEOGyCz=Czm7awNT-}2}=IY!Jm z9E_1J^0~p-F9~FSasS4ps;_NSs1sMYXp!B6YXK)-RXin0F6vjv71l*+lR*%g%3ICx zyK=mLx7Q&B)#VS9yP*a{I7e=KzI!k+KV_!socy3cnb`D0 zgswU<^2Sr6QT+~qE|>IG?xupUqGM-yP-1S&)G7rP>hSnn>F(sltpM=am7Kpq@U0e# zMv3>JwzKI?xdkQ)_Zt%CW$5PMhpk5RoaEFgu&49in;Fw>(ov<=>`@d+gt^}H?$S#@ zE~Y1GXKurLp=f3TE*aYt2SQ_#bV#|hYV&W>zXaN}m=s!Qbz)KWRu$sGkr-hvM?Hu^f

oTe~Z`6~g+A*GWmDB?!2|J2htFojC$`IJ*M(5tOf}}oEOFK8% z`}8>43#h>4BUz_HO^iZp8VmVC(`bqc9t;=;yVS>BP3#Q**DteP-)qr;`rN;%oR%>qx{gFzUZVs1LX&%6 ztHC;&&n}#&r3J*3t9v^<9B;1%=?O!>-s>3zJk|s+o#!5_@j<<+m&G^@2M8Of3}^Dg zM&Gb6rWz&C3rJIM{W{99KnthH=s}^Rp4#kZV==z!{7V=DzyVhIfYfZd+%zxDVUb{F zsKq$;0axQY8PsPjb;*S3j5wJ>ozxw|!TB0Zolo7Zi|xiH5bKXahv07KhXj11xjeCW z;K@O#-Hv-0JRlK!ws773h=p9QgwoZ^xG10xB}WyT8m`+qoG&X<|MrAx#SLYTt;w*Kbkbvz(zVh<&eE%wsoP#v6{P1z-m;2s z5hsE?&e|7wR#7=8FEM~Wg>~u{AbmI*t#r0l`U@lT^8-Q-L}urI1p$9ftUKY>fq0nE z?br$+sZ~TdnIzS5BmP=`Xmh%AV74J)Ls{T3t=lTeFCs~ug;#E0wc7`Ewn9wJ|Map% z{0a_o;TD)WeZQ`ryY$#hLtkZ*`*;jWH2@vV-~K!V%`Mh7-o=D%)P`z#@Z)Y;_a(Z4tA+3;Qvt2iJkPQ8ZS zYV>F|iB}DliMNU{a21;vvnQA5ycA9?yzRHvv}BPh&_`yDeq!|2eJPPLy)i>JGU&NDjiDkGu;;z#LR*C#QuS)ED;D;Ap|!~KLW;7Rdzi<+m>YSm(0lu{`MfkO>yXg)P+$TjS4pCO@a zL9=MKz*Camv)0zF@0%>MFmwldTvrqwYMuXe*P$D}GpP|Hk{|~@eMg%FUp-5vfL06F z8Wy2owpY$9+nuXbSoJX|mmzyc8hA_z%&2~C0f8XBrA3+eI$g9O+cc#*SBYE$P-Om| z!}nTZkzedDA_Afw417RPgPvqW5w&y`8C*dy2a?0=vr`fbl)lzzsO5oViieC6rG+vJ zDQBqW3J#&{-fbx@c=6_i_6ain%RVYCr>JoqkP|D5KX+d6D6Rv?ZMrb9g+tgbnM-&w zhsTwR8|YRVL;0~23KV;7Os-Lt3Ekl0BGnqib;mi zF53g+bz?`HHrmkc4~g~IbmtMynKYu%P1Gd4hQD9Z&rayJIWfAeKU*M5ic6t)RD^V@ zUcegbJ#|^L)s63eRzS69+`mmxft@33L-Zd?a7s%E;v+!B6N(64A;)AhSd!FU<1b*< z$-$`0E3ha_LGt4;vV#7QwKmX6vJKX$Y8lY4u;2KFA~KR3w`gB8bi6{$NbjPd*@h=A zxL8^$pdO9BENb``SUGisdZuHrb-1*>xLvVP=@L|QmT!KP9}`Nv(D|DWWj1}dX=?Yf z@8Kdl^5Mpa*Q$7t%W(CWMH&{!NCnt^%4(GQ6qu$fdvM&H{&dZ;B1gU1Ninm?SaWMc z9*rEAM=e8Cvb|URwX0M~u&QeZ!6slEmbL*iFRMJ6nmWFpT8MSvOiV(IIZbKLBg$Ww z``X@IO2@DWLr_*tWemPxMB~bJb;vbX(1=g0JE<%1P&ta@$N#f5@fyh$dHAe&M<`+A znH96ZweOmzq15N(nT4RW1JO%+N4kt?+V~;!0Z9hJmKF>G0R|NIlM_MCf7~I7&wi5; zF6{XoEhJq&E&&vzB(kPdCS9J-B;7VoOc2m&0if^W=k4MG^aWUZ7NDV9vlpwy!Cbh zGdD`yPYsWG)h=78sb{y?srZKtn8(Yx{)+9K#o^z&W)Wc6n}fJJp$PXAFPn}UAWw|F z`ui(RysPI`p2#~S7&8Q805z_tg+tqSvEsw}{?X3?%HIuCe8Y?O&mAwCltWMuJ`V(r zcSh-X)E^CMS;qhpsfjJ}`>xaQ13C%A0A@Eiv3>+i)rMaRzusLsCmbsTw-XxYsf zx{Rce?=sBJO=0#y&VChU!;}O0o|h3ev_|Ec7C4ozt|j%!GB6ltLA28fwI9>G)u>qu zdQlYoK4ge9Z2hRDWvIpDNV_-(q_`QOzvk!Q>_>oYYc^Uy2iBS= z9jbeNEVdbuUpLEEAeQTW9;M((E>wDjze$$*b>x9N01*s?6{NRUPtv+^ICdJqUGvm4 z1Qx}#P7VwZ#x}$+Oc?Nl)44Nq_5a>GIH%_Vh5PmEn5Qsa*z^oDcJfR(7f*IA7_n!- zR+(vJe8Jx5_r~fCXGp4rxKOAi6RCAD6;RH*^Sf7e9+CU!o0O9}w9*gZA zE#FefQQGxA2IWpX-6=}C&Lyq(9h|O zYP~u8?i-rJ&K=~H6-*;c=DU;1SSjE394B0Zy%osR@?-9hLyP+h`1KXE$k>lP*bEE| zC|nT;2(H*_%*;-&InOPjWEPxy{rV4^A0pbPHzWAu`E+Qk5}7y_LT1h(gZ8}7WA&1+ zN46ochy9>XgoW@-+bO~O7XXdzB(#va^V67BT+EN{$nVUVhX9W=9-Z_Z9(zYfc<`!# zPBHwZ%zNf^lx@(S-Pj{20>WDQID7mJ!`g;Dn=Lxg?diN%zOtrJ!h8EqB16e@7)=J} z^?JtNCJrou%>uN6(`lR1tE`y$`QK?BN#r_obU7p-T=L>!ox#JQ*-ZXURnws6uXB(1By|#yD-08eLA-M z^kT#U_K2mYn}sAMlFM^MKOn1Y!;x|EZRe`Y>Z+Ck{vrca&lGWqBj_kdQLRp=tH-X{;q`zcUiwT z(82OOEQaza)HKp1kf|jlclPy?@Z3rm7P5An#-2=_BsY<_RILJiH-#z2i~zU&LdrtA zi1Ty1OQ8V7Ae0PVmA0n{auxAHKhTsgO_HR*b1M*~Z(s2HzP)(5r_+K!VEsayQAYMj zt&q(vEw+@+pMGsFM&^5&r5R6-LcQ$%kVABTX6Hp{7*MbJDkO3(B9u8~b;j2*A=41A z8_jxmtE{^k;kNG%?f%?U zC)nLIZxr47&eFz1_jvo9dv6{?8cJrbyuViQ;@{3l!|a?2Vi5JkkJzYD0t$l?U3naw zMC_)|?^qe59V*c^t`RZ%Hjid~D^ZKueHlgzAuosAu<^{NrztUdD<4})P%)y$UUI`2 z<`tvF2(lS*{Pdl>^hkU8L;0j>gQ`G3w5L)smqtx{v?tOpyQ?My55w(W;W(!?fBsyV z*Tt^#gXP`N3|cC#Jmfp+P3}U9ZF$`TVfA^$Rq=9hJ3qC=&uA<2!V}6gu1@2xba~jm z?7w^U?Cmf-1+tg-%+pkg;m(CVO(N~9plz+d$A-TUqi56N?fjcMM*NFc&lBtg@{@Et zews&^{|Z%O+I4AB$JfSy!L1lb z<$5I&73kp~>->QHq=S{so!z`ip~EaTck7e+H1?Ybpdi<~Wl3NZ*Q&wx?5R;EStv2c~QB3QX{wExvah{V~rx-E`J; zIPl;*S(4O}u+*a(>Cres3+bl}3yqp-^$&(T01c}Q5QVJ>LON$YkOZ`33MT*NzotShlaQz;r&IhQiMLCoHCZ%B zUlVD#k8)3v+%M^S+&jGVIxl%5gwzI}ng%rww`@lWQ8%Arp-$D$z(aW$a?idc97qZY zGR%-ak)?aNfpyXt8F*o>vacd4`pCh3GD%E{{*ADuVic#sb*7iV&75oB++&B?ue|8C z-POwm%iF-t6c8nrKD-~9k?6I&71G`j?Cvob3ozt?0)qkp{Wt#ug9!rtJpViYv%vo> z@IMRuKUp9s9TOTHD5Q=V2q^v9BPXZZRabTA`McU)6+6t%p`@$J9|Fq`M}{l25``t$SZ^MD`!#$%Gpd79h1zV&&%{$w4psFn4X zV8$92hLW6#lNb?_qwSG zS)Xa=cIOq3GA8zivx(1)k8+#x3GHUrrKR-G3Wn)4>ly5sv_=Y;87P3e>uA{cJ|pka zYi6j$fqK*4pz71>C*7LcEAb2cToUiy_kH--JtHSB&;Stg=o5a0zz9$g!pmJ{JU+`e zmrP;OY|rfx@8OrT2hbV~a;DCF@8qdKc<6;5GH<`hOda@p-Rbr9pdvYJ#0eGC-cp5+ z=+N>l^8Htd@B#%0zenVho9!V=NtKMfYYEz`4NwPtP=btzzlu0*gY+p-`crb?HH~+TEwOeiWmOmtK*en^VrLoB^kwY;;CrFS_&mlFDBtqANQVEoDP(T9fmy?HL$^6s^yC z22)q*^{tr=Pa!P2FlZgw%w)Q{zdpHCf~gpTNK@=4OOR5kLw&FITqC1UG_!RZ($^N= z6(EFySo`cBJUO3M+IA(0HL1V1lxCf4Up4rUsjO-dG%KCUSG4*=bW{t@XPB6Ui_acn zdgA#De}6zSB0+~|RlhbdPJM68JU%3i_9ZG4zA9Ucx%QIE&=^Fv#{E@!Fq7uc`rE_W zAuSZRJdC0`4CPRxr)jGp8x|f=YUR-o*ka$Er&OsY=VvC1kPTQfQ}ZK6NHs7xDBJIQ z$~hzVWkyf`pl+&`IZnJsTVuOEA%LKu9A-n0WIbq{J_O@WM#LD~aD$c9dM}r^qU*DR z0Oc~Vu%J5f?`&4n(~I3H$AAwMMA_dze0YfX{(DLcobbnHoQ{yNU^(gElMkKC#-2sZ zG$DE475W!qnLeByn&Hi%n{7Bh<*ASxDne#|uBo^uolKNNovK0h)kJzeN_(;C&rYy9 zd%I;MC>srpm4JzZz6CNtWaEg6EH0V*Z3mdA(%%&o+>tVs={a7;GH1NADp;Mjx|=mo zr_o%+iiMh`zT+p|AUY(;l1ouEVhGxm0dm6H>mlv-1TJGAxh1r|@gxyxUBTDV8_IVg zCsc|(SK^QBWIAUl@L3$T9UHCWhO}$nSwXCRQ7-oOhyxHW!*adVpw~?5yL`-!#;- zCExZ~KcgOWMC|k1z#d@_bXBLw!IIYCrHhj-l_p_Y>)TPh(8Z1*@~eHSXeFvDwyAP` ze_4?$`1H!+&kYEPJeGb|ni>7y5Ik!C>bCDk}vPa_TWDU$z1wymWVFYAb9Pz+kn65i;_cA&SDOE(mx zp7wJQk<59mn+LPf)h(IXtf(KMS;cEEwoPlNJaqdzC1?;^RbZpcZ>5}wZq0d9g0O<0 z2t|rEgD5M*F%8U^>I93(@Zp(0fkm7j;iJC8SY%FPKelS)C}glr>qK^1G!b*wib0kp z%Tne!_SsumS$W;IUHl+wrgz{wZjCumyqE{{a``d%m%tzTT( z-0n%hix2Fve&XFpx3baW8j@l*ECJ2!JVAYodRuvmF&R9XmmEqSnDDQFCFV9>j4h2n z(k9u%A_iAPI{3B%0eZ%Ia|9Qb>S|U|qoQH9y30D;ZOd*5k!6dtB4QyqBXeS3|6W+7 zW`@3(K(L~(_0D~!$P6ztGq{*yK`k6){?_}L zSPdjK^7eEyrV1so{>1yAhFemu2mt(rFUzpiwC5*trc_ zuA)6ZR3aHxo4NAMV-WVj#aQ8$$JX!kk=k5>)^{!s(J3^&^B?Y@sp)&rHwD9-Av;9N z1A4Sk$KbOZofLHzbAvz0H^X9;umf?Lr{vrZOTVaKqwe% z)i(bGoO0i}B~SYpr#UG3@BRLnXgLZAk2}%8s3Ho(g+=XV-k3e{Un_{XmYltiQ?Ai3 zdXCb3eqa|uvlpfeBlt_dFblm_JX<7e4laL5Lz>Z1IkzRdJN4O#G>u40HmN7 zQPLe_vk=~#;_d3P#&$7Lh9^|n4Tb-`;q8nMJPWkhaqEmE z#AT&;rRJ4E)=gqr-5vSoKi$halFQ7LlI#5f_Hy=ju5qacrf zPy0n%-=N?}Z_q(M{pqt9XFh0rjoYZ3(1v|7ij??urM5HGS{c5;`)Ywh5p!!Z`P$#> z`5apLB_;TfX#6yhrNe!qrDLSLj^5<8zzXCnlD;GMSL#Nly_vj=TH4S!nPoM{>t0*y z{KkM+E%Z|$xhBebsVYIXzTbL@&tlw43c7NseA(6=csuYqC(_y!s-k{zH|yhV{n=~m z6`~w0Tj|kp0vR-N&^q)l!M|VFZ0UICP|jX?yIRw6T6`7dCet2bbibQyaOlEZ9XK*B z#_i`e$!$os4^0&yy#7ch;Ull^?DWavnGm_7;iHq&Km>HrY0Dz5jk5PEjIQ2o6aO^& zNIx%5WAKSwD6y7|)o^VO*YHEQ9~lAPbDlOrvwhrSzP!$EZB(EjO-e%gK{XwvqI7nd zlyB4xBDm5ECBVHxN%S`(Dq9?AlwY9@ofdZUHi{1qH-e@oyyRH}X`3kjXcqX2*a_+i ztLfDpVVRBp*+i$mearWoF_V_ZG5yxn>leX2cg1F~$_`90J!rC7h-fj?_~tc{-eBr`FKoBfCAyt>I?S(KKY%+hi&6q=!`{4 zkexl+aAH=mgLaES5Cqy$e>#wKSOV+iY$PJqBPN3AvaS4{s|eaG0!@gkd7ieElUaZz zHMrhB7Er(~jXh^j%k4T1MfqHaC(~j77jO#gjLRV6)Yv!O`9jzH0LgC1_9&Ct~ zC~o@Uhd{>pF`I=GzV($#a4C~2N?q(){cM#YBy}InR30p8^^5o=R}q!pZ%GX->xqZ9 z*a`F-IzOsnSdXJlj|fahhyS~d<9hZXh=l*IhKI>i*)i$cFUgt78)1QzfW`5A4%ERyjz;w8=mX!Zj|gWd$Y(gW?c~5Q%C9qmyY!#rjypOK~Dl((UC8yhovi z^)V%k+U;|WnmZ%21ycLf=?_TKef%&Z5f^%t-$3q*qtuw&NKZ4jkNxMZQ>}{XX2*NB zEI{-Yj z$?8da55?;o>Xr#ad@xxZU!Ghtr<6ap@r*&m%hF(Qf4(7eTBE*)L<0i6@3BMhe=nf=fO&0`#%jsfK1yJerA7lXMXU#rn!#yNL_L9ly$8c|XV5 z=zI(9v3DQ6CpulzVKhwmyB*!cU|KvFolsz4koG}*^2GR^%Rq?(`yid9c36)?HPIz& zE<|fx)r@>e`-bb?pdq*)cZ1Z;2f;TSY&q_fC}*^qZC7nMNZV@(V){E0?HD8vJ-U*V z!_1Qn38iiWfS?5)*~ZY+B%C7hycZ@q)|*hJZP?-9^TszGhfd)W7Z)Y9f4P%WNP2CWl>tcax% zgZRY{Bfd{6>IsPr2ird#jR$Y&Y)X~pM?Bq#qi;!XO2IyWJT0f*60AGEfcsmI5UaRz zDEgyU+cl**oSSnBoPo&t4!s$f(Jh&lHJQS9Qi}A9CoGYEcNa?O=`Gsn_CrTxihSa^eX<%TynN1gY>iiz5L*v3W! zdan;E9!NV8ARxnK#xl;JIOFAdg}_~?(f!3u?0j`MrZ!9dwePdB4$?35c%Se#%brt6 zGawwOlYu zPx8cgvR$DX$mNNj7`IkHj9VX0Oz#SHKJNEXu@r$$q5}C_*-^7g^#Z7atE*veID$B+ zo_YJk3POO5h4yW-3X*RNtxo}Ul_O*f5W$|`#xHO8r}?lIMpe%ZVC=5OMVxQem3C}tWLa2;#XpEt& zFlVDTsJ;^t9{IQJO3tH8SX&5R9aV_&Z=3B(T-FM=cr)hNx(7Db(?I!jg64+mdXDUr zY4Ja__SP#KBZ7p!Kp}e~6!l6j>(oy_dCsCVj2D5)GlOA)GH$TdQQ?K@co}#O>IlB< znCa>R$LgOZF0SxI`J~$B%-5$N|4BN6M)p|UK@4cTbx`IYBoX2>G9~8f&bFh{=A$ix z_87#l z8Fzo$ne&1A(+=o6u&O(!0UAAjiRk9Kf+l>C4ZbJgc#cG01oYD0f3yVc zMyXyyvMN+&N0Ndz1+JC)F|SJiV_nqf)`u-G7_~yot7^M7n9g+|2e~CF6cz@zKStX} z0V|g>2L~v9<9@>D#^1&9c6~aJ3G}6xy(yjPhKv?lgBp+Cd&yWghH$^cPI!8F?rXZPp zK|j(tLgjjZ4TQ^^BZ9Mn(i$~f-X;vqM7Yf)tCkAI@b=7!2*GP2!*gr@O@N+duI zIN|Ja1wCxE<2_#BGIyf*UB^!%*6%hZZzoVDZNd*mZD~+XX|aUcCP;Alq{>fRMo^h^ zT;G#Uv~zyJA*$~N?O32}H?m;C;kC=A5AY%uZhSS%WEDY0?Ao$kSodv3o%T35OQ1%g zco+s?9IEpG`Fj2q3}y|Us~9cu^t1LJEj!0c#Pmu{8em>=R8Zm6Ugg&wzuJ`Ypr##E z!NFVh5d7>B$$MMgyy%i)a_`T}*SNVIz)rU^%E#a!lVDPkSgwnf2Hs5k>aJIWW}xu; zA~je3R`mNn)UzYAlZ;b~AEq)!S2{j$V4zsh^foaIZySv;=HF{y+a15k7Q}diH!Lao z-+qd7g}~}c{jHX|B2~_k$WHZs#*q;bm?$WBz#xXe^RqxVw;c7&C4vgbx!AdY>v-&z zb0X3qD=|MqQ5%4G((1E9#P(U(=H~CC<6~t_ znnj8nJYHHmZZrYi;t(l01ch^!DUS`j7JHHbl&>EmY>#REEEA(wlqzhBQwd{-jmGH3 z<)uikKgPnGCkdLAdP`I8aVPO{e2AsHyJ2HThgJm!Wc=aK-2VQ)%C-MTY^W3Y!Os$s z#)h{%0*(CWMHQrZerjr~Cx$;LNXq`NWk|`f2ix~*%{cY?!Z1;HOyR!`Z_VC#`?=sY|@icnWOx;Nvw6-YkfqrQnz%#1}KtU34Ao^3ZnH8iF7J`4=PdU_eIoV6MV1& z7XA{86Xd_0%@+)I$(g5YIGrCDZy@NU+z!87H;gx*_9koKTJoh7wS9UE(+B+a(RcrsrrhXDutu41v z?;}CiA3M{E=All5`lP{@&IR2G3c2ajnmB zyyhGXf}E89IM>+2_ZFn{^3R6dMJtX9miU=eh)-)pPq!KV*4wA74xI>y#--4ill4pu zMeq&ZE_mcQX^MP1lAwf3RV&|j>vELrg606W6RIoR&Xs~f&V!ir5Cxy7o8HgolO*sX zxHJe#;#ah47zuvY9@5?sO0}|U>5}atXPLSQWVmt6JO9&Y-ct(=%ls)Zfgg^c9aB?$ zrsbDcB!~A~QJ5SK?nNX~lP?3JSbvG>F8_p@?Ls*a%0_K0w1)3k7|)U8;a4nSW7K10 z9;(Ilm*EOAi0^UgJs3=QMMn~bW<<|jYtSX0nGhzP3!+3edI9YdJXy=^HH7IS)X-ei zBB8x=qlfcLaMq`@Jqa~ah9?Emx)o*yWp#CQm@?bZX*ey7Dng_l z;q{}HMPq_+bd*VyMaL}IY*;G2F))9J^X-{EIbx?_YP%RxXX&Q-CX^bZafleTq;Jkb zI3kCuom2bb_O5@0VI`eR(E|E})5{W^3+TsD8v^Fews!WQp4Wgw25UuWi*x2yFP#J# z$6~He8Sg9+Em*Bp)W#E>KtqFA*(P0C0|EvXLh(Rn@UbO9#ani9=?wfA1@7E5ldbDFU zgBAfa>D6JmB3#$cnHAtJLCz3-#ulbfmytoDtWbx{qasN>qn#isp)s&Y(p6fe<*WEZ zcR`bCF_asR*!k1?PqSS5;`t9F@8DX=RLBiMXK*u-%y4{kjaB~n4}jNeJvP!l5o5?- zs#$9=KGG{fUQI)>jbf6H<)n^ill8N(&4d~+-A#oYUu{vwTUA$sHfhF7y@pKwmQR+8 zSw_=}MK>?QO4IBe;I}~^QxlBRlllaQO933?0Z1EUK|6rzvFIw)avbiHU+|>!vJK8H z?H3>=-pm>>u^sDG+rR|Yba)#iA0s^sfBwz|c~}n3FA|GAyeW7~#iv$`^e%E46kqj4 z&l1I=e4CwA>D56d!{X#@@gv|0s;DeN)W0IOx#~Ns`5hJVec?=oLXuh&-pJzVcl~h) zaPas_$iYz`RxP;OB+kt4M{@r&j_3v-h{4Gh#wu9QznsZ;Xi#P&LIU~UWnZlzF{X@) z{Rn^Rj>kkcDa%yojaP&Z?1Pk@NoZ|5-NI%?W0zCzoQG%nS}K(Xo#d#S%rxbpHlDg| zUl&Q`5x*kT(7#mAfV{4fI9ss(2|~9ooQ;_05{N7hKMnteuGUbNLE7NvuUT_x0#`Yf zDLy2K*6kkkib%Uf%|-^fpgXpebO>@aY>0qGGCxrxf3Cw+_Cx9KR=fI_m3&@FF#$@6 z=<><9Lz#9kLUHKKl~88MY_2;T4u)OzN)CXR<^tKhy`$KefxF@)8NrfWDuF2D&eC`v zUvS5qGYZC?M_j!F6hhY&@~9*9@5l5-v&4+TZpYl(zRaafcB%hk?;Lc5QJQW&wrzXf zv2EM7ZQHhO+qP}nw*Bv%yEw_$>ArzVI_b5#Ry~~q)#S)fKHLa5X;Mf2aFXg{CBw*R&mYVIJf|m$p8l82K-y0h>d*sPZ4HDXJ#vWq+rPDk2+=EI%D?_(zaKOWf zmtB55)PQsOp$1*OnbdK@PzT8gbH?3<-?;A_isXlwwV#J?jkI65!PqI$gYFX(6ZmQ( z0ITS77pj!kymHI3cw3t-2ClL@lIWZZBdoB*#Fn~C);{px{LiVuX2~XdHQ1)Ig~x$q zdKDL!o~UKSLuEmri}hCS!4vE*523^l!^m(4Yo62gc>5b{Lb5{}c)18D(i1y0f<9^> zut8-GK|LVlSvPdRhi8;!t7Af;Lqf7dtsWe=r}^q%=x=>y#c)WK*4poHf8a1>Hzbjy zpJd4?*3aA@@YNF%19M&6?cUc595}c}+*#5TNx-PXNSuLAsYBLmy*;dfvD=W1_%Ef- zmK=>%3I`nHc+%{GIciCr^%iUN1yX#Zi8v)OYqa3?iJ59U(&FQu(0Ivf>c{?Od$72B z3tdUm)=m)>u3YXd(9f>AL9TZ5;i>_Xvy+8m-n7o9-cqb%tum5QxG*MVNQ`Q9d@acl zASrB>9Xzs7!CHq|SKuD$NALO&oyRnn#0yBx?ZuH|AUZ&wNJB#1!lyXyth5zFqjsWg z&F|3W-92v~J=Lzf_E#Mb5nQTbZbp0-M9UJU=Rz&(cN6g7mq<0dMfz5vWg}IZaCyG$ zfd8R}kXGqPc1T$?<}-M&5O}<9h;DX6fehCX6npKRGy48a*l}gal?Xs2I)=!PWnn3h zMwNCY&7>VMO`V)Ll)u%1=liaxv)bGHS1Y~ukL-_|pE&C!PTITDiXa9Jv9D$h)N

CXvSf;%EF-7cjaV)rKIRfiwU~t9DO#v| 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

TU7~C__{Dsp^&zgO{ny&G2&2tzP*rmiCtXIwQ zPEH7;bC1-AyY^$2cDp{H7N2O!p54dZ|B=s@Asm8RBTybI#=f&+e(QN<|Mt+_s?X8w zgJJPEiNNv^DLQQJ#DVlSlh6q*RT~2( z2itwy0PCgSK}t8!M$zYld*2+_K`2SwD=5xF5y2G79VO#P?!8^YXhDXlVQCu;CL0Bt zaf$XJO(5GVo&S=GBRL0Zk#&s$a^@iw?uB1Gj`FaeqCKD{hW z9&RkWil>>{0TpBT;AOS0Ld9vt5zeiT+GI+e2)C

gw&x)yg_o?jy^2>)vdlO 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 Z4R1CLUM5}l{NVA4{3nu=T!G122LKCI-k<;g literal 0 HcmV?d00001 diff --git a/examples/assistant/simpletextviewer/documentation/simpletextviewer.qhc b/examples/assistant/simpletextviewer/documentation/simpletextviewer.qhc new file mode 100644 index 0000000000000000000000000000000000000000..55fead7af66e358548617f090d066cd461143b98 GIT binary patch literal 16384 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 w%+7=m=;z;>&VP|tM0w33Q6O~vJeIl!y?q}nOl + + + Simple Text Viewer + images/handbook.png + QtProject/SimpleTextViewer + qthelp://org.qt-project.examples.simpletextviewer/doc/index.html + + About Simple Text Viewer + + + about.txt + images/icon.png + + false + false + false + + + + + simpletextviewer.qhp + simpletextviewer.qch + + + + simpletextviewer.qch + + + diff --git a/examples/assistant/simpletextviewer/documentation/simpletextviewer.qhp b/examples/assistant/simpletextviewer/documentation/simpletextviewer.qhp new file mode 100644 index 0000000..d33702c --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/simpletextviewer.qhp @@ -0,0 +1,49 @@ + + + org.qt-project.examples.simpletextviewer + doc + + +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + browse.html + filedialog.html + findfile.html + index.html + intro.html + openfile.html + wildcardmatching.html + images/browse.png + images/fadedfilemenu.png + images/filedialog.png + images/handbook.png + images/mainwindow.png + images/open.png + images/wildcard.png + + + diff --git a/examples/assistant/simpletextviewer/documentation/wildcardmatching.html b/examples/assistant/simpletextviewer/documentation/wildcardmatching.html new file mode 100644 index 0000000..a6d8a74 --- /dev/null +++ b/examples/assistant/simpletextviewer/documentation/wildcardmatching.html @@ -0,0 +1,57 @@ + + + + Wildcard Matching + + + +

Wildcard Matching

+ +

+ Most command shells such as bash or cmd.exe support "file + globbing", the ability to identify a group of files by using + wildcards. + +
+
+ + + + +
+ +
+
+

+ Wildcard matching provides four features: +

+ +
    +
  • Any character represents itself apart from those + mentioned below. Thus 'c' matches the character 'c'. +
  • +
  • The '?' character matches any single character.
  • +
  • The '*' matches zero or more of any characters.
  • +
  • Sets of characters can be represented in square brackets. + Within the character class, like outside, backslash + has no special meaning. +
  • +
+ +

+ For example we could identify HTML files with + *.html. This will match zero or more characters + followed by a dot followed by 'h', 't', 'm' and 'l'. +

+ +
+
+

+ See also: Browse, File Dialog, + Find File +

+ + + + + diff --git a/examples/assistant/simpletextviewer/findfiledialog.cpp b/examples/assistant/simpletextviewer/findfiledialog.cpp new file mode 100644 index 0000000..7dccfb0 --- /dev/null +++ b/examples/assistant/simpletextviewer/findfiledialog.cpp @@ -0,0 +1,176 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "assistant.h" +#include "findfiledialog.h" +#include "textedit.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//! [0] +FindFileDialog::FindFileDialog(TextEdit *editor, Assistant *assistant) + : QDialog(editor) + , currentEditor(editor) + , currentAssistant(assistant) +{ +//! [0] + + createButtons(); + createComboBoxes(); + createFilesTree(); + createLabels(); + createLayout(); + + directoryComboBox->addItem(QDir::toNativeSeparators(QDir::currentPath())); + fileNameComboBox->addItem("*"); + findFiles(); + + setWindowTitle(tr("Find File")); +//! [1] +} +//! [1] + +void FindFileDialog::browse() +{ + const QString currentDirectory = directoryComboBox->currentText(); + const QString newDirectory = QFileDialog::getExistingDirectory(this, + tr("Select Directory"), currentDirectory); + if (!newDirectory.isEmpty()) { + directoryComboBox->addItem(QDir::toNativeSeparators(newDirectory)); + directoryComboBox->setCurrentIndex(directoryComboBox->count() - 1); + update(); + } +} + +//! [2] +void FindFileDialog::help() +{ + currentAssistant->showDocumentation("filedialog.html"); +} +//! [2] + +void FindFileDialog::openFile() +{ + QTreeWidgetItem *item = foundFilesTree->currentItem(); + if (!item) + return; + + const QString fileName = item->text(0); + const QString path = QDir(directoryComboBox->currentText()).filePath(fileName); + + currentEditor->setContents(path); + close(); +} + +void FindFileDialog::update() +{ + findFiles(); + buttonBox->button(QDialogButtonBox::Open)->setEnabled(foundFilesTree->topLevelItemCount() > 0); +} + +void FindFileDialog::findFiles() +{ + QString wildCard = fileNameComboBox->currentText(); + if (!wildCard.endsWith('*')) + wildCard += '*'; + const QRegularExpression filePattern(QRegularExpression::wildcardToRegularExpression(wildCard)); + + const QDir directory(directoryComboBox->currentText()); + + const QStringList allFiles = directory.entryList(QDir::Files | QDir::NoSymLinks); + QStringList matchingFiles; + + for (const QString &file : allFiles) { + if (filePattern.match(file).hasMatch()) + matchingFiles << file; + } + showFiles(matchingFiles); +} + +void FindFileDialog::showFiles(const QStringList &files) +{ + foundFilesTree->clear(); + + for (const QString &file : files) + new QTreeWidgetItem(foundFilesTree, {file}); + + if (files.count() > 0) + foundFilesTree->setCurrentItem(foundFilesTree->topLevelItem(0)); +} + +void FindFileDialog::createButtons() +{ + browseButton = new QToolButton; + browseButton->setText(tr("...")); + connect(browseButton, &QAbstractButton::clicked, this, &FindFileDialog::browse); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Open + | QDialogButtonBox::Cancel + | QDialogButtonBox::Help); + connect(buttonBox, &QDialogButtonBox::accepted, this, &FindFileDialog::openFile); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(buttonBox, &QDialogButtonBox::helpRequested, this, &FindFileDialog::help); +} + +void FindFileDialog::createComboBoxes() +{ + directoryComboBox = new QComboBox; + fileNameComboBox = new QComboBox; + + fileNameComboBox->setEditable(true); + fileNameComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + directoryComboBox->setMinimumContentsLength(30); + directoryComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + directoryComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + connect(fileNameComboBox, &QComboBox::editTextChanged, this, &FindFileDialog::update); + connect(directoryComboBox, &QComboBox::currentTextChanged, this, &FindFileDialog::update); +} + +void FindFileDialog::createFilesTree() +{ + foundFilesTree = new QTreeWidget; + foundFilesTree->setColumnCount(1); + foundFilesTree->setHeaderLabels(QStringList(tr("Matching Files"))); + foundFilesTree->setRootIsDecorated(false); + foundFilesTree->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(foundFilesTree, &QTreeWidget::itemActivated, this, &FindFileDialog::openFile); +} + +void FindFileDialog::createLabels() +{ + directoryLabel = new QLabel(tr("Search in:")); + fileNameLabel = new QLabel(tr("File name (including wildcards):")); +} + +void FindFileDialog::createLayout() +{ + QHBoxLayout *fileLayout = new QHBoxLayout; + fileLayout->addWidget(fileNameLabel); + fileLayout->addWidget(fileNameComboBox); + + QHBoxLayout *directoryLayout = new QHBoxLayout; + directoryLayout->addWidget(directoryLabel); + directoryLayout->addWidget(directoryComboBox); + directoryLayout->addWidget(browseButton); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(fileLayout); + mainLayout->addLayout(directoryLayout); + mainLayout->addWidget(foundFilesTree); + mainLayout->addStretch(); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); +} diff --git a/examples/assistant/simpletextviewer/findfiledialog.h b/examples/assistant/simpletextviewer/findfiledialog.h new file mode 100644 index 0000000..7e77938 --- /dev/null +++ b/examples/assistant/simpletextviewer/findfiledialog.h @@ -0,0 +1,60 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef FINDFILEDIALOG_H +#define FINDFILEDIALOG_H + +#include + +QT_BEGIN_NAMESPACE +class QComboBox; +class QDialogButtonBox; +class QLabel; +class QToolButton; +class QTreeWidget; +QT_END_NAMESPACE + +class Assistant; +class TextEdit; + +//! [0] +class FindFileDialog : public QDialog +{ + Q_OBJECT + +public: + FindFileDialog(TextEdit *editor, Assistant *assistant); + +private slots: + void browse(); + void help(); + void openFile(); + void update(); + +private: + void findFiles(); + void showFiles(const QStringList &files); + + void createButtons(); + void createComboBoxes(); + void createFilesTree(); + void createLabels(); + void createLayout(); + + TextEdit *currentEditor; + Assistant *currentAssistant; + QTreeWidget *foundFilesTree; + + QComboBox *directoryComboBox; + QComboBox *fileNameComboBox; + + QLabel *directoryLabel; + QLabel *fileNameLabel; + + QDialogButtonBox *buttonBox; + + QToolButton *browseButton; +}; +//! [0] + +#endif diff --git a/examples/assistant/simpletextviewer/main.cpp b/examples/assistant/simpletextviewer/main.cpp new file mode 100644 index 0000000..fb0633c --- /dev/null +++ b/examples/assistant/simpletextviewer/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + MainWindow window; + window.show(); + return app.exec(); +} diff --git a/examples/assistant/simpletextviewer/mainwindow.cpp b/examples/assistant/simpletextviewer/mainwindow.cpp new file mode 100644 index 0000000..c92ab53 --- /dev/null +++ b/examples/assistant/simpletextviewer/mainwindow.cpp @@ -0,0 +1,117 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "assistant.h" +#include "findfiledialog.h" +#include "mainwindow.h" +#include "textedit.h" + +#include +#include +#include +#include +#include +#include + +using namespace Qt::StringLiterals; + +// ![0] +MainWindow::MainWindow() + : textViewer(new TextEdit) + , assistant(new Assistant) +{ +// ![0] + textViewer->setContents(QLibraryInfo::path(QLibraryInfo::ExamplesPath) + + "/assistant/simpletextviewer/documentation/intro.html"_L1); + setCentralWidget(textViewer); + + createActions(); + createMenus(); + + setWindowTitle(tr("Simple Text Viewer")); + resize(750, 400); + + connect(textViewer, &TextEdit::fileNameChanged, this, &MainWindow::updateWindowTitle); +// ![1] +} +//! [1] + +//! [2] +void MainWindow::closeEvent(QCloseEvent *) +{ + delete assistant; +} +//! [2] + +void MainWindow::updateWindowTitle(const QString &fileName) +{ + setWindowTitle(tr("Simple Text Viewer - %1").arg(fileName)); +} + +void MainWindow::about() +{ + QMessageBox::about(this, tr("About Simple Text Viewer"), + tr("This example demonstrates how to use\n" + "Qt Assistant as help system for your\n" + "own application.")); +} + +//! [3] +void MainWindow::showDocumentation() +{ + assistant->showDocumentation("index.html"); +} +//! [3] + +void MainWindow::open() +{ + FindFileDialog dialog(textViewer, assistant); + dialog.exec(); +} + +//! [4] +void MainWindow::createActions() +{ + assistantAct = new QAction(tr("Help Contents"), this); + assistantAct->setShortcut(QKeySequence::HelpContents); + connect(assistantAct, &QAction::triggered, this, &MainWindow::showDocumentation); +//! [4] + + openAct = new QAction(tr("&Open..."), this); + openAct->setShortcut(QKeySequence::Open); + connect(openAct, &QAction::triggered, this, &MainWindow::open); + + clearAct = new QAction(tr("&Clear"), this); + clearAct->setShortcut(tr("Ctrl+C")); + connect(clearAct, &QAction::triggered, textViewer, &QTextEdit::clear); + + exitAct = new QAction(tr("E&xit"), this); + exitAct->setShortcuts(QKeySequence::Quit); + connect(exitAct, &QAction::triggered, this, &QWidget::close); + + aboutAct = new QAction(tr("&About"), this); + connect(aboutAct, &QAction::triggered, this, &MainWindow::about); + + aboutQtAct = new QAction(tr("About &Qt"), this); + connect(aboutQtAct, &QAction::triggered, QApplication::aboutQt); +//! [5] +} +//! [5] + +void MainWindow::createMenus() +{ + fileMenu = new QMenu(tr("&File"), this); + fileMenu->addAction(openAct); + fileMenu->addAction(clearAct); + fileMenu->addSeparator(); + fileMenu->addAction(exitAct); + + helpMenu = new QMenu(tr("&Help"), this); + helpMenu->addAction(assistantAct); + helpMenu->addSeparator(); + helpMenu->addAction(aboutAct); + helpMenu->addAction(aboutQtAct); + + menuBar()->addMenu(fileMenu); + menuBar()->addMenu(helpMenu); +} diff --git a/examples/assistant/simpletextviewer/mainwindow.h b/examples/assistant/simpletextviewer/mainwindow.h new file mode 100644 index 0000000..b9b8a17 --- /dev/null +++ b/examples/assistant/simpletextviewer/mainwindow.h @@ -0,0 +1,51 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QMenu; +QT_END_NAMESPACE + +class Assistant; +class TextEdit; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + +private slots: + void updateWindowTitle(const QString &fileName); + void about(); + void showDocumentation(); + void open(); + +protected: + void closeEvent(QCloseEvent *event) override; + +private: + void createActions(); + void createMenus(); + + TextEdit *textViewer; + Assistant *assistant; + + QMenu *fileMenu; + QMenu *helpMenu; + + QAction *assistantAct; + QAction *clearAct; + QAction *openAct; + QAction *exitAct; + QAction *aboutAct; + QAction *aboutQtAct; +}; + +#endif diff --git a/examples/assistant/simpletextviewer/simpletextviewer.pro b/examples/assistant/simpletextviewer/simpletextviewer.pro new file mode 100644 index 0000000..b0de7f4 --- /dev/null +++ b/examples/assistant/simpletextviewer/simpletextviewer.pro @@ -0,0 +1,19 @@ +HEADERS = mainwindow.h \ + findfiledialog.h \ + assistant.h \ + textedit.h +SOURCES = main.cpp \ + mainwindow.cpp \ + findfiledialog.cpp \ + assistant.cpp \ + textedit.cpp + +DEFINES += SRCDIR=\\\"$$PWD/\\\" + +QT += widgets + +target.path = $$[QT_INSTALL_EXAMPLES]/assistant/simpletextviewer +docs.files += $$PWD/documentation +docs.path = $$target.path + +INSTALLS += target docs diff --git a/examples/assistant/simpletextviewer/textedit.cpp b/examples/assistant/simpletextviewer/textedit.cpp new file mode 100644 index 0000000..13e67d5 --- /dev/null +++ b/examples/assistant/simpletextviewer/textedit.cpp @@ -0,0 +1,38 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "textedit.h" + +#include +#include + +TextEdit::TextEdit(QWidget *parent) + : QTextEdit(parent) +{ + setReadOnly(true); +} + +void TextEdit::setContents(const QString &fileName) +{ + const QFileInfo fi(fileName); + srcUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + const QString data(QString::fromUtf8(file.readAll())); + if (fileName.endsWith(".html")) + setHtml(data); + else + setPlainText(data); + } + emit fileNameChanged(fileName); +} + +QVariant TextEdit::loadResource(int type, const QUrl &name) +{ + if (type == QTextDocument::ImageResource) { + QFile file(srcUrl.resolved(name).toLocalFile()); + if (file.open(QIODevice::ReadOnly)) + return file.readAll(); + } + return QTextEdit::loadResource(type, name); +} diff --git a/examples/assistant/simpletextviewer/textedit.h b/examples/assistant/simpletextviewer/textedit.h new file mode 100644 index 0000000..3115778 --- /dev/null +++ b/examples/assistant/simpletextviewer/textedit.h @@ -0,0 +1,26 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TEXTEDIT_H +#define TEXTEDIT_H + +#include +#include + +class TextEdit : public QTextEdit +{ + Q_OBJECT + +public: + TextEdit(QWidget *parent = nullptr); + void setContents(const QString &fileName); + +signals: + void fileNameChanged(const QString &fileName); + +private: + QVariant loadResource(int type, const QUrl &name) override; + QUrl srcUrl; +}; + +#endif diff --git a/examples/designer/CMakeLists.txt b/examples/designer/CMakeLists.txt new file mode 100644 index 0000000..d7c90bd --- /dev/null +++ b/examples/designer/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +qt_exclude_tool_directories_from_default_target( + containerextension + taskmenuextension +) + +qt_internal_add_example(calculatorform) +qt_internal_add_example(calculatorform_mi) +if(QT_BUILD_SHARED_LIBS AND NOT solaris-cc_x_) + qt_internal_add_example(calculatorbuilder) +endif() +if(QT_BUILD_SHARED_LIBS AND NOT CMAKE_CROSSCOMPILING) + qt_internal_add_example(containerextension) + qt_internal_add_example(customwidgetplugin) + qt_internal_add_example(taskmenuextension) +endif() diff --git a/examples/designer/README b/examples/designer/README new file mode 100644 index 0000000..2393834 --- /dev/null +++ b/examples/designer/README @@ -0,0 +1,10 @@ +Qt Widgets Designer is a capable graphical user interface designer that lets you +create and configure forms without writing code. GUIs created with +Qt Widgets Designer can be compiled into an application or created at run-time. + +Some of the examples in this directory can only be used from within Qt +Designer. + + +Documentation for these examples can be found via the Examples +link in the main Qt documentation. diff --git a/examples/designer/calculatorbuilder/CMakeLists.txt b/examples/designer/calculatorbuilder/CMakeLists.txt new file mode 100644 index 0000000..bc67a13 --- /dev/null +++ b/examples/designer/calculatorbuilder/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(calculatorbuilder LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/designer/calculatorbuilder") + +find_package(Qt6 REQUIRED COMPONENTS Core UiTools Widgets) + +qt_standard_project_setup() + +qt_add_executable(calculatorbuilder main.cpp) + +set_target_properties(calculatorbuilder PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +#! [0] +target_link_libraries(calculatorbuilder PRIVATE + Qt6::UiTools + Qt6::Widgets +) +#! [0] + +# Resources: +#! [1] +qt_add_resources(calculatorbuilder "calculatorbuilder" + PREFIX + "/forms" + FILES + "calculatorform.ui" +) +#! [1] + + +install(TARGETS calculatorbuilder + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/designer/calculatorbuilder/calculatorbuilder.pro b/examples/designer/calculatorbuilder/calculatorbuilder.pro new file mode 100644 index 0000000..740c31a --- /dev/null +++ b/examples/designer/calculatorbuilder/calculatorbuilder.pro @@ -0,0 +1,8 @@ +#! [0] +RESOURCES = calculatorbuilder.qrc +SOURCES = main.cpp +QT += widgets uitools +#! [0] + +target.path = $$[QT_INSTALL_EXAMPLES]/designer/calculatorbuilder +INSTALLS += target diff --git a/examples/designer/calculatorbuilder/calculatorbuilder.qrc b/examples/designer/calculatorbuilder/calculatorbuilder.qrc new file mode 100644 index 0000000..19b3905 --- /dev/null +++ b/examples/designer/calculatorbuilder/calculatorbuilder.qrc @@ -0,0 +1,5 @@ + + + calculatorform.ui + + diff --git a/examples/designer/calculatorbuilder/calculatorform.ui b/examples/designer/calculatorbuilder/calculatorform.ui new file mode 100644 index 0000000..dda0e62 --- /dev/null +++ b/examples/designer/calculatorbuilder/calculatorform.ui @@ -0,0 +1,303 @@ + + + + + CalculatorForm + + + CalculatorForm + + + + 0 + 0 + 276 + 98 + + + + + 5 + 5 + 0 + 0 + + + + Calculator Builder + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + + + + 1 + + + 6 + + + + + label + + + + 1 + 1 + 45 + 19 + + + + Input 1 + + + + + + + inputSpinBox1 + + + + 1 + 26 + 45 + 25 + + + + true + + + + + + + + + label_3 + + + + 54 + 1 + 7 + 52 + + + + + + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2 + + + + 1 + 1 + 45 + 19 + + + + Input 2 + + + + + + + inputSpinBox2 + + + + 1 + 26 + 45 + 25 + + + + true + + + + + + + + + label_3_2 + + + + 120 + 1 + 7 + 52 + + + + = + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2_2_2 + + + + 1 + 1 + 37 + 17 + + + + Output + + + + + + + outputWidget + + + + 1 + 24 + 37 + 27 + + + + QFrame::Box + + + QFrame::Sunken + + + 0 + + + Qt::AlignAbsolute|Qt::AlignBottom|Qt::AlignCenter|Qt::AlignHCenter|Qt::AlignHorizontal_Mask|Qt::AlignJustify|Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing|Qt::AlignVCenter|Qt::AlignVertical_Mask + + + + + + + + + + + verticalSpacer + + + + 85 + 69 + 20 + 20 + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + horizontalSpacer + + + + 188 + 26 + 79 + 20 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + diff --git a/examples/designer/calculatorbuilder/main.cpp b/examples/designer/calculatorbuilder/main.cpp new file mode 100644 index 0000000..a72d795 --- /dev/null +++ b/examples/designer/calculatorbuilder/main.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +#include +//! [0] + +#include +#include +#include +#include + +#include + +using namespace Qt::StringLiterals; + +//! [1] +static QWidget *loadCalculatorForm(QWidget *parent = nullptr) +{ + QFile file(u":/forms/calculatorform.ui"_s); + if (!file.open(QFile::ReadOnly)) + return nullptr; + + QWidget *formWidget = QUiLoader().load(&file, parent); + if (!formWidget) + return nullptr; +//! [1] + +//! [2] + auto *inputSpinBox1 = formWidget->findChild(u"inputSpinBox1"_s); + auto *inputSpinBox2 = formWidget->findChild(u"inputSpinBox2"_s); + auto *outputWidget = formWidget->findChild(u"outputWidget"_s); +//! [2] + +//! [3] + auto updateResult = [inputSpinBox1, inputSpinBox2, outputWidget]() + { + const int sum = inputSpinBox1->value() + inputSpinBox2->value(); + outputWidget->setText(QString::number(sum)); + }; + QObject::connect(inputSpinBox1, &QSpinBox::valueChanged, formWidget, updateResult); + QObject::connect(inputSpinBox2, &QSpinBox::valueChanged, formWidget, updateResult); +//! [3] + + return formWidget; +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QWidget w; + auto *formWidget = loadCalculatorForm(&w); + if (formWidget == nullptr) + return -1; + //! [4] + auto *layout = new QVBoxLayout(&w); + layout->addWidget(formWidget); + w.setWindowTitle(QCoreApplication::translate("CalculatorForm", + "Calculator Builder")); + //! [4] + w.show(); + return app.exec(); +} diff --git a/examples/designer/calculatorform/CMakeLists.txt b/examples/designer/calculatorform/CMakeLists.txt new file mode 100644 index 0000000..c07ed3d --- /dev/null +++ b/examples/designer/calculatorform/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(calculatorform LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +#! [0] +set(CMAKE_AUTOUIC ON) +#! [0] + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/designer/calculatorform") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +#! [1] +qt_add_executable(calculatorform + calculatorform.cpp calculatorform.h calculatorform.ui + main.cpp +) +#! [1] + +set_target_properties(calculatorform PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(calculatorform PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) + +install(TARGETS calculatorform + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/designer/calculatorform/calculatorform.cpp b/examples/designer/calculatorform/calculatorform.cpp new file mode 100644 index 0000000..24ce24a --- /dev/null +++ b/examples/designer/calculatorform/calculatorform.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "calculatorform.h" + +//! [0] +CalculatorForm::CalculatorForm(QWidget *parent) + : QWidget(parent) +{ + ui.setupUi(this); + connect(ui.inputSpinBox1, &QSpinBox::valueChanged, this, &CalculatorForm::updateResult); + connect(ui.inputSpinBox2, &QSpinBox::valueChanged, this, &CalculatorForm::updateResult); +} +//! [0] + +//! [1] +void CalculatorForm::updateResult() +{ + const int sum = ui.inputSpinBox1->value() + ui.inputSpinBox2->value(); + ui.outputWidget->setText(QString::number(sum)); +} +//! [1] diff --git a/examples/designer/calculatorform/calculatorform.h b/examples/designer/calculatorform/calculatorform.h new file mode 100644 index 0000000..9224ba0 --- /dev/null +++ b/examples/designer/calculatorform/calculatorform.h @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef CALCULATORFORM_H +#define CALCULATORFORM_H + +//! [0] +#include "ui_calculatorform.h" +//! [0] + +//! [1] +class CalculatorForm : public QWidget +{ + Q_OBJECT + +public: + explicit CalculatorForm(QWidget *parent = nullptr); + +private slots: + void updateResult(); + +private: + Ui::CalculatorForm ui; +}; +//! [1] + +#endif diff --git a/examples/designer/calculatorform/calculatorform.pro b/examples/designer/calculatorform/calculatorform.pro new file mode 100644 index 0000000..0ee1b1f --- /dev/null +++ b/examples/designer/calculatorform/calculatorform.pro @@ -0,0 +1,11 @@ +#! [0] +HEADERS = calculatorform.h +#! [0] #! [1] +FORMS = calculatorform.ui +#! [1] +SOURCES = calculatorform.cpp \ + main.cpp +QT += widgets + +target.path = $$[QT_INSTALL_EXAMPLES]/designer/calculatorform +INSTALLS += target diff --git a/examples/designer/calculatorform/calculatorform.ui b/examples/designer/calculatorform/calculatorform.ui new file mode 100644 index 0000000..3a95639 --- /dev/null +++ b/examples/designer/calculatorform/calculatorform.ui @@ -0,0 +1,284 @@ + + + + + CalculatorForm + + + CalculatorForm + + + + 0 + 0 + 400 + 300 + + + + + 5 + 5 + 0 + 0 + + + + Calculator Form + + + + + + + 9 + + + 6 + + + + + horizontalSpacer + + + + 239 + 9 + 152 + 52 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + label_3_2 + + + + 169 + 9 + 20 + 52 + + + + = + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2_2_2 + + + + 1 + 1 + 36 + 17 + + + + Output + + + + + + + outputWidget + + + + 1 + 24 + 36 + 27 + + + + QFrame::Box + + + QFrame::Sunken + + + 0 + + + Qt::AlignAbsolute|Qt::AlignBottom|Qt::AlignCenter|Qt::AlignHCenter|Qt::AlignHorizontal_Mask|Qt::AlignJustify|Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing|Qt::AlignVCenter|Qt::AlignVertical_Mask + + + + + + + + + verticalSpacer + + + + 89 + 67 + 20 + 224 + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 1 + + + 6 + + + + + label_2 + + + + 1 + 1 + 46 + 19 + + + + Input 2 + + + + + + + inputSpinBox2 + + + + 1 + 26 + 46 + 25 + + + + + + + + + + label_3 + + + + 63 + 9 + 20 + 52 + + + + + + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label + + + + 1 + 1 + 46 + 19 + + + + Input 1 + + + + + + + inputSpinBox1 + + + + 1 + 26 + 46 + 25 + + + + + + + + + + + + diff --git a/examples/designer/calculatorform/main.cpp b/examples/designer/calculatorform/main.cpp new file mode 100644 index 0000000..893c90b --- /dev/null +++ b/examples/designer/calculatorform/main.cpp @@ -0,0 +1,15 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "calculatorform.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + CalculatorForm calculator; + calculator.show(); + return app.exec(); +} + diff --git a/examples/designer/calculatorform_mi/CMakeLists.txt b/examples/designer/calculatorform_mi/CMakeLists.txt new file mode 100644 index 0000000..1123a05 --- /dev/null +++ b/examples/designer/calculatorform_mi/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(calculatorform_mi LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/designer/calculatorform_mi") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(calculatorform_mi + calculatorform.cpp calculatorform.h calculatorform.ui + main.cpp +) + +set_target_properties(calculatorform_mi PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(calculatorform_mi PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) + +install(TARGETS calculatorform_mi + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/designer/calculatorform_mi/calculatorform.cpp b/examples/designer/calculatorform_mi/calculatorform.cpp new file mode 100644 index 0000000..6a877de --- /dev/null +++ b/examples/designer/calculatorform_mi/calculatorform.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "calculatorform.h" +#include + + +//! [0] +CalculatorForm::CalculatorForm(QWidget *parent) + : QWidget(parent) +{ + setupUi(this); +} +//! [0] + +//! [1] +void CalculatorForm::on_inputSpinBox1_valueChanged(int value) +{ + outputWidget->setText(QString::number(value + inputSpinBox2->value())); +} +//! [1] + +//! [2] +void CalculatorForm::on_inputSpinBox2_valueChanged(int value) +{ + outputWidget->setText(QString::number(value + inputSpinBox1->value())); +} +//! [2] diff --git a/examples/designer/calculatorform_mi/calculatorform.h b/examples/designer/calculatorform_mi/calculatorform.h new file mode 100644 index 0000000..aba28c2 --- /dev/null +++ b/examples/designer/calculatorform_mi/calculatorform.h @@ -0,0 +1,25 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef CALCULATORFORM_H +#define CALCULATORFORM_H + +//! [0] +#include "ui_calculatorform.h" +//! [0] + +//! [1] +class CalculatorForm : public QWidget, private Ui::CalculatorForm +{ + Q_OBJECT + +public: + explicit CalculatorForm(QWidget *parent = nullptr); + +private slots: + void on_inputSpinBox1_valueChanged(int value); + void on_inputSpinBox2_valueChanged(int value); +}; +//! [1] + +#endif diff --git a/examples/designer/calculatorform_mi/calculatorform.ui b/examples/designer/calculatorform_mi/calculatorform.ui new file mode 100644 index 0000000..9bcca91 --- /dev/null +++ b/examples/designer/calculatorform_mi/calculatorform.ui @@ -0,0 +1,303 @@ + + + + + CalculatorForm + + + CalculatorForm + + + + 0 + 0 + 276 + 98 + + + + + 5 + 5 + 0 + 0 + + + + Calculator Form + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + + + + 1 + + + 6 + + + + + label + + + + 1 + 1 + 45 + 19 + + + + Input 1 + + + + + + + inputSpinBox1 + + + + 1 + 26 + 45 + 25 + + + + true + + + + + + + + + label_3 + + + + 54 + 1 + 7 + 52 + + + + + + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2 + + + + 1 + 1 + 45 + 19 + + + + Input 2 + + + + + + + inputSpinBox2 + + + + 1 + 26 + 45 + 25 + + + + true + + + + + + + + + label_3_2 + + + + 120 + 1 + 7 + 52 + + + + = + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2_2_2 + + + + 1 + 1 + 37 + 17 + + + + Output + + + + + + + outputWidget + + + + 1 + 24 + 37 + 27 + + + + QFrame::Box + + + QFrame::Sunken + + + 0 + + + Qt::AlignAbsolute|Qt::AlignBottom|Qt::AlignCenter|Qt::AlignHCenter|Qt::AlignHorizontal_Mask|Qt::AlignJustify|Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing|Qt::AlignVCenter|Qt::AlignVertical_Mask + + + + + + + + + + + verticalSpacer + + + + 85 + 69 + 20 + 20 + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + horizontalSpacer + + + + 188 + 26 + 79 + 20 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + diff --git a/examples/designer/calculatorform_mi/calculatorform_mi.pro b/examples/designer/calculatorform_mi/calculatorform_mi.pro new file mode 100644 index 0000000..c0f5031 --- /dev/null +++ b/examples/designer/calculatorform_mi/calculatorform_mi.pro @@ -0,0 +1,11 @@ +#! [0] +QT += widgets + +HEADERS = calculatorform.h +SOURCES = calculatorform.cpp main.cpp +FORMS = calculatorform.ui +#! [0] + +target.path = $$[QT_INSTALL_EXAMPLES]/designer/calculatorform_mi +INSTALLS += target + diff --git a/examples/designer/calculatorform_mi/main.cpp b/examples/designer/calculatorform_mi/main.cpp new file mode 100644 index 0000000..d6b0aee --- /dev/null +++ b/examples/designer/calculatorform_mi/main.cpp @@ -0,0 +1,15 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "calculatorform.h" +#include + +//! [0] +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + CalculatorForm calculator; + calculator.show(); + return app.exec(); +} +//! [0] diff --git a/examples/designer/containerextension/CMakeLists.txt b/examples/designer/containerextension/CMakeLists.txt new file mode 100644 index 0000000..2b2c150 --- /dev/null +++ b/examples/designer/containerextension/CMakeLists.txt @@ -0,0 +1,55 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(containerextension LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +#! [0] +find_package(Qt6 REQUIRED COMPONENTS Core Designer Gui Widgets) + +qt_add_plugin(containerextension) +#! [0] + +#! [1] +target_sources(containerextension PRIVATE + multipagewidget.cpp multipagewidget.h + multipagewidgetcontainerextension.cpp multipagewidgetcontainerextension.h + multipagewidgetextensionfactory.cpp multipagewidgetextensionfactory.h + multipagewidgetplugin.cpp multipagewidgetplugin.h +) +#! [1] + +set_target_properties(containerextension PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +#! [2] +target_link_libraries(containerextension PUBLIC + Qt::Core + Qt::Designer + Qt::Gui + Qt::Widgets +) +#! [2] + +if(QT6_INSTALL_PREFIX) +#! [3] + set(INSTALL_EXAMPLEDIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/designer") +#! [3] +else() + if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") + endif() + set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/plugins/designer") +endif() + +#! [4] +install(TARGETS containerextension + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) +#! [4] diff --git a/examples/designer/containerextension/containerextension.pro b/examples/designer/containerextension/containerextension.pro new file mode 100644 index 0000000..bf7b18e --- /dev/null +++ b/examples/designer/containerextension/containerextension.pro @@ -0,0 +1,29 @@ +#! [0] +TEMPLATE = lib +CONFIG += plugin +#! [0] + +TARGET = $$qtLibraryTarget($$TARGET) + +#! [3] +target.path = $$[QT_INSTALL_PLUGINS]/designer +INSTALLS += target +#! [3] + +#! [1] +QT += widgets designer +#! [1] + +#! [2] +HEADERS += multipagewidget.h \ + multipagewidgetplugin.h \ + multipagewidgetcontainerextension.h \ + multipagewidgetextensionfactory.h + +SOURCES += multipagewidget.cpp \ + multipagewidgetplugin.cpp \ + multipagewidgetcontainerextension.cpp \ + multipagewidgetextensionfactory.cpp + +OTHER_FILES += multipagewidget.json +#! [2] diff --git a/examples/designer/containerextension/multipagewidget.cpp b/examples/designer/containerextension/multipagewidget.cpp new file mode 100644 index 0000000..c4131ba --- /dev/null +++ b/examples/designer/containerextension/multipagewidget.cpp @@ -0,0 +1,102 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include + +#include "multipagewidget.h" + +MultiPageWidget::MultiPageWidget(QWidget *parent) + : QWidget(parent) + , stackWidget(new QStackedWidget) + , comboBox(new QComboBox) +{ + comboBox->setObjectName("__qt__passive_comboBox"); + + connect(comboBox, &QComboBox::activated, + this, &MultiPageWidget::setCurrentIndex); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(comboBox); + layout->addWidget(stackWidget); +} + +QSize MultiPageWidget::sizeHint() const +{ + return {200, 150}; +} + +void MultiPageWidget::addPage(QWidget *page) +{ + insertPage(count(), page); +} + +void MultiPageWidget::removePage(int index) +{ + auto *widget = stackWidget->widget(index); + stackWidget->removeWidget(widget); + + comboBox->removeItem(index); +} + +int MultiPageWidget::count() const +{ + return stackWidget->count(); +} + +int MultiPageWidget::currentIndex() const +{ + return stackWidget->currentIndex(); +} + +void MultiPageWidget::insertPage(int index, QWidget *page) +{ + page->setParent(stackWidget); + + stackWidget->insertWidget(index, page); + + QString title = page->windowTitle(); + if (title.isEmpty()) { + title = tr("Page %1").arg(comboBox->count() + 1); + page->setWindowTitle(title); + } + connect(page, &QWidget::windowTitleChanged, + this, &MultiPageWidget::pageWindowTitleChanged); + comboBox->insertItem(index, title); +} + +void MultiPageWidget::setCurrentIndex(int index) +{ + if (index != currentIndex()) { + stackWidget->setCurrentIndex(index); + comboBox->setCurrentIndex(index); + emit currentIndexChanged(index); + } +} + +void MultiPageWidget::pageWindowTitleChanged() +{ + QWidget *page = qobject_cast(sender()); + const int index = stackWidget->indexOf(page); + comboBox->setItemText(index, page->windowTitle()); +} + +QWidget* MultiPageWidget::widget(int index) +{ + return stackWidget->widget(index); +} + +QString MultiPageWidget::pageTitle() const +{ + if (const QWidget *currentWidget = stackWidget->currentWidget()) + return currentWidget->windowTitle(); + return {}; +} + +void MultiPageWidget::setPageTitle(const QString &newTitle) +{ + if (QWidget *currentWidget = stackWidget->currentWidget()) + currentWidget->setWindowTitle(newTitle); + emit pageTitleChanged(newTitle); +} diff --git a/examples/designer/containerextension/multipagewidget.h b/examples/designer/containerextension/multipagewidget.h new file mode 100644 index 0000000..788aaa6 --- /dev/null +++ b/examples/designer/containerextension/multipagewidget.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MULTIPAGEWIDGET_H +#define MULTIPAGEWIDGET_H + +#include + +QT_BEGIN_NAMESPACE +class QComboBox; +class QStackedWidget; +QT_END_NAMESPACE + +//! [0] +class MultiPageWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex) + Q_PROPERTY(QString pageTitle READ pageTitle WRITE setPageTitle STORED false) + +public: + explicit MultiPageWidget(QWidget *parent = nullptr); + + QSize sizeHint() const override; + + int count() const; + int currentIndex() const; + QWidget *widget(int index); + QString pageTitle() const; + +public slots: + void addPage(QWidget *page); + void insertPage(int index, QWidget *page); + void removePage(int index); + void setPageTitle(const QString &newTitle); + void setCurrentIndex(int index); + +private slots: + void pageWindowTitleChanged(); + +signals: + void currentIndexChanged(int index); + void pageTitleChanged(const QString &title); + +private: + QStackedWidget *stackWidget; + QComboBox *comboBox; +}; +//! [0] + +#endif diff --git a/examples/designer/containerextension/multipagewidgetcontainerextension.cpp b/examples/designer/containerextension/multipagewidgetcontainerextension.cpp new file mode 100644 index 0000000..d94e91a --- /dev/null +++ b/examples/designer/containerextension/multipagewidgetcontainerextension.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "multipagewidgetcontainerextension.h" +#include "multipagewidget.h" + +//! [0] +MultiPageWidgetContainerExtension::MultiPageWidgetContainerExtension(MultiPageWidget *widget, + QObject *parent) + : QObject(parent) + , myWidget(widget) +{ +} +//! [0] + +//! [1] +bool MultiPageWidgetContainerExtension::canAddWidget() const +{ + return true; +} + +void MultiPageWidgetContainerExtension::addWidget(QWidget *widget) +{ + myWidget->addPage(widget); +} +//! [1] + +//! [2] +int MultiPageWidgetContainerExtension::count() const +{ + return myWidget->count(); +} +//! [2] + +//! [3] +int MultiPageWidgetContainerExtension::currentIndex() const +{ + return myWidget->currentIndex(); +} +//! [3] + +//! [4] +void MultiPageWidgetContainerExtension::insertWidget(int index, QWidget *widget) +{ + myWidget->insertPage(index, widget); +} +//! [4] + +//! [5] +bool MultiPageWidgetContainerExtension::canRemove(int index) const +{ + Q_UNUSED(index); + return true; +} + +void MultiPageWidgetContainerExtension::remove(int index) +{ + myWidget->removePage(index); +} +//! [5] + +//! [6] +void MultiPageWidgetContainerExtension::setCurrentIndex(int index) +{ + myWidget->setCurrentIndex(index); +} +//! [6] + +//! [7] +QWidget* MultiPageWidgetContainerExtension::widget(int index) const +{ + return myWidget->widget(index); +} +//! [7] diff --git a/examples/designer/containerextension/multipagewidgetcontainerextension.h b/examples/designer/containerextension/multipagewidgetcontainerextension.h new file mode 100644 index 0000000..cecef36 --- /dev/null +++ b/examples/designer/containerextension/multipagewidgetcontainerextension.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MULTIPAGEWIDGETCONTAINEREXTENSION_H +#define MULTIPAGEWIDGETCONTAINEREXTENSION_H + +#include + +QT_BEGIN_NAMESPACE +class QExtensionManager; +QT_END_NAMESPACE +class MultiPageWidget; + +//! [0] +class MultiPageWidgetContainerExtension: public QObject, + public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) + +public: + explicit MultiPageWidgetContainerExtension(MultiPageWidget *widget, QObject *parent); + + bool canAddWidget() const override; + void addWidget(QWidget *widget) override; + int count() const override; + int currentIndex() const override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int index) const override; + void remove(int index) override; + void setCurrentIndex(int index) override; + QWidget *widget(int index) const override; + +private: + MultiPageWidget *myWidget; +}; +//! [0] + +#endif diff --git a/examples/designer/containerextension/multipagewidgetextensionfactory.cpp b/examples/designer/containerextension/multipagewidgetextensionfactory.cpp new file mode 100644 index 0000000..746fe31 --- /dev/null +++ b/examples/designer/containerextension/multipagewidgetextensionfactory.cpp @@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "multipagewidgetextensionfactory.h" +#include "multipagewidgetcontainerextension.h" +#include "multipagewidget.h" + +//! [0] +MultiPageWidgetExtensionFactory::MultiPageWidgetExtensionFactory(QExtensionManager *parent) + : QExtensionFactory(parent) +{} +//! [0] + +//! [1] +QObject *MultiPageWidgetExtensionFactory::createExtension(QObject *object, + const QString &iid, + QObject *parent) const +{ + auto *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerContainerExtension))) + return new MultiPageWidgetContainerExtension(widget, parent); + return nullptr; +} +//! [1] diff --git a/examples/designer/containerextension/multipagewidgetextensionfactory.h b/examples/designer/containerextension/multipagewidgetextensionfactory.h new file mode 100644 index 0000000..9610715 --- /dev/null +++ b/examples/designer/containerextension/multipagewidgetextensionfactory.h @@ -0,0 +1,26 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MULTIPAGEWIDGETEXTENSIONFACTORY_H +#define MULTIPAGEWIDGETEXTENSIONFACTORY_H + +#include + +QT_BEGIN_NAMESPACE +class QExtensionManager; +QT_END_NAMESPACE + +//! [0] +class MultiPageWidgetExtensionFactory: public QExtensionFactory +{ + Q_OBJECT + +public: + explicit MultiPageWidgetExtensionFactory(QExtensionManager *parent = nullptr); + +protected: + QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; +}; +//! [0] + +#endif diff --git a/examples/designer/containerextension/multipagewidgetplugin.cpp b/examples/designer/containerextension/multipagewidgetplugin.cpp new file mode 100644 index 0000000..fe0e8da --- /dev/null +++ b/examples/designer/containerextension/multipagewidgetplugin.cpp @@ -0,0 +1,154 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "multipagewidget.h" +#include "multipagewidgetplugin.h" +#include "multipagewidgetextensionfactory.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace Qt::StringLiterals; + +//! [0] +MultiPageWidgetPlugin::MultiPageWidgetPlugin(QObject *parent) + : QObject(parent) +{ +} + +QString MultiPageWidgetPlugin::name() const +{ + return u"MultiPageWidget"_s; +} + +QString MultiPageWidgetPlugin::group() const +{ + return u"Display Widgets [Examples]"_s; +} + +QString MultiPageWidgetPlugin::toolTip() const +{ + return {}; +} + +QString MultiPageWidgetPlugin::whatsThis() const +{ + return {}; +} + +QString MultiPageWidgetPlugin::includeFile() const +{ + return u"multipagewidget.h"_s; +} + +QIcon MultiPageWidgetPlugin::icon() const +{ + return {}; +} + +//! [0] //! [1] +bool MultiPageWidgetPlugin::isContainer() const +{ + return true; +} + +//! [1] //! [2] +QWidget *MultiPageWidgetPlugin::createWidget(QWidget *parent) +{ + auto *widget = new MultiPageWidget(parent); + connect(widget, &MultiPageWidget::currentIndexChanged, + this, &MultiPageWidgetPlugin::currentIndexChanged); + connect(widget, &MultiPageWidget::pageTitleChanged, + this, &MultiPageWidgetPlugin::pageTitleChanged); + return widget; +} + +//! [2] //! [3] +bool MultiPageWidgetPlugin::isInitialized() const +{ + return initialized; +} +//! [3] + +//! [4] +void MultiPageWidgetPlugin::initialize(QDesignerFormEditorInterface *formEditor) +{ + if (initialized) + return; +//! [4] + +//! [5] + auto *manager = formEditor->extensionManager(); +//! [5] //! [6] + auto *factory = new MultiPageWidgetExtensionFactory(manager); + + Q_ASSERT(manager != nullptr); + manager->registerExtensions(factory, Q_TYPEID(QDesignerContainerExtension)); + + initialized = true; +} +//! [6] + +//! [7] +QString MultiPageWidgetPlugin::domXml() const +{ + return uR"( + + + + + + + MultiPageWidget + QWidget + addPage + + +)"_s; +} +//! [7] + +//! [8] +void MultiPageWidgetPlugin::currentIndexChanged(int index) +{ + Q_UNUSED(index); + auto *widget = qobject_cast(sender()); +//! [8] //! [9] + if (widget) { + auto *form = QDesignerFormWindowInterface::findFormWindow(widget); + if (form) + form->emitSelectionChanged(); + } +} +//! [9] + +//! [10] +void MultiPageWidgetPlugin::pageTitleChanged(const QString &title) +{ + Q_UNUSED(title); + auto *widget = qobject_cast(sender()); +//! [10] //! [11] + if (widget) { + auto *page = widget->widget(widget->currentIndex()); + auto *form = QDesignerFormWindowInterface::findFormWindow(widget); +//! [11] + if (form) { +//! [12] + auto *editor = form->core(); + auto *manager = editor->extensionManager(); +//! [12] //! [13] + auto *sheet = qt_extension(manager, page); + const int propertyIndex = sheet->indexOf(QLatin1String("windowTitle")); + sheet->setChanged(propertyIndex, true); + } + } +} + +//! [13] diff --git a/examples/designer/containerextension/multipagewidgetplugin.h b/examples/designer/containerextension/multipagewidgetplugin.h new file mode 100644 index 0000000..f26f743 --- /dev/null +++ b/examples/designer/containerextension/multipagewidgetplugin.h @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +#ifndef MULTIPAGEWIDGETPLUGIN_H +#define MULTIPAGEWIDGETPLUGIN_H + +#include + +QT_BEGIN_NAMESPACE +class QIcon; +class QWidget; +QT_END_NAMESPACE + +class MultiPageWidgetPlugin: public QObject, public QDesignerCustomWidgetInterface +{ + Q_OBJECT +//! [1] + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidget") +//! [1] + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + explicit MultiPageWidgetPlugin(QObject *parent = nullptr); + + QString name() const override; + QString group() const override; + QString toolTip() const override; + QString whatsThis() const override; + QString includeFile() const override; + QIcon icon() const override; + bool isContainer() const override; + QWidget *createWidget(QWidget *parent) override; + bool isInitialized() const override; + void initialize(QDesignerFormEditorInterface *formEditor) override; + QString domXml() const override; + +private slots: + void currentIndexChanged(int index); + void pageTitleChanged(const QString &title); + +private: + bool initialized = false; +}; + +#endif +//! [0] diff --git a/examples/designer/customwidgetplugin/CMakeLists.txt b/examples/designer/customwidgetplugin/CMakeLists.txt new file mode 100644 index 0000000..259661c --- /dev/null +++ b/examples/designer/customwidgetplugin/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(customwidgetplugin LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +#! [0] +find_package(Qt6 REQUIRED COMPONENTS Core Gui UiPlugin Widgets) + +qt_add_plugin(customwidgetplugin) +#! [0] + +#! [1] +target_sources(customwidgetplugin PRIVATE + analogclock.cpp analogclock.h + customwidgetplugin.cpp customwidgetplugin.h +) +#! [1] + +set_target_properties(customwidgetplugin PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +#! [2] +target_link_libraries(customwidgetplugin PUBLIC + Qt::Core + Qt::Gui + Qt::UiPlugin + Qt::Widgets +) +#! [2] + +if(QT6_INSTALL_PREFIX) +#! [3] + set(INSTALL_EXAMPLEDIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/designer") +#! [3] +else() + if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") + endif() + set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/plugins/designer") +endif() + +#! [4] +install(TARGETS customwidgetplugin + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) +#! [4] diff --git a/examples/designer/customwidgetplugin/analogclock.cpp b/examples/designer/customwidgetplugin/analogclock.cpp new file mode 100644 index 0000000..590fe6e --- /dev/null +++ b/examples/designer/customwidgetplugin/analogclock.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "analogclock.h" + +#include +#include + +#include +#include + +AnalogClock::AnalogClock(QWidget *parent) + : QWidget(parent) +{ + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, qOverload<>(&QWidget::update)); + timer->start(1000); + + setWindowTitle(tr("Analog Clock")); + resize(200, 200); +} + +void AnalogClock::paintEvent(QPaintEvent *) +{ + static const QPoint hourHand[4] = { + QPoint(5, 14), + QPoint(-5, 14), + QPoint(-4, -71), + QPoint(4, -71) + }; + static const QPoint minuteHand[4] = { + QPoint(4, 14), + QPoint(-4, 14), + QPoint(-3, -89), + QPoint(3, -89) + }; + static const QPoint secondsHand[4] = { + QPoint(1, 14), + QPoint(-1, 14), + QPoint(-1, -89), + QPoint(1, -89) + }; + + const QColor hourColor(palette().color(QPalette::Text)); + const QColor minuteColor(palette().color(QPalette::Text)); + const QColor secondsColor(palette().color(QPalette::Accent)); + + int side = qMin(width(), height()); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.translate(width() / 2, height() / 2); + painter.scale(side / 200.0, side / 200.0); + + QTime time = QTime::currentTime(); + + painter.setPen(Qt::NoPen); + painter.setBrush(hourColor); + + painter.save(); + painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0))); + painter.drawConvexPolygon(hourHand, 4); + painter.restore(); + + for (int i = 0; i < 12; ++i) { + painter.drawRect(73, -3, 16, 6); + painter.rotate(30.0); + } + + painter.setBrush(minuteColor); + + painter.save(); + painter.rotate(6.0 * time.minute()); + painter.drawConvexPolygon(minuteHand, 4); + painter.restore(); + + painter.setBrush(secondsColor); + + painter.save(); + painter.rotate(6.0 * time.second()); + painter.drawConvexPolygon(secondsHand, 4); + painter.drawEllipse(-3, -3, 6, 6); + painter.drawEllipse(-5, -68, 10, 10); + painter.restore(); + + painter.setPen(minuteColor); + + for (int j = 0; j < 60; ++j) { + painter.drawLine(92, 0, 96, 0); + painter.rotate(6.0); + } +} diff --git a/examples/designer/customwidgetplugin/analogclock.h b/examples/designer/customwidgetplugin/analogclock.h new file mode 100644 index 0000000..c3eabf5 --- /dev/null +++ b/examples/designer/customwidgetplugin/analogclock.h @@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef ANALOGCLOCK_H +#define ANALOGCLOCK_H + +#include +#include + +//! [0] +class QDESIGNER_WIDGET_EXPORT AnalogClock : public QWidget +{ + Q_OBJECT +//! [0] + +public: + explicit AnalogClock(QWidget *parent = nullptr); + +protected: + void paintEvent(QPaintEvent *event) override; +//! [1] +}; +//! [1] + +#endif diff --git a/examples/designer/customwidgetplugin/customwidgetplugin.cpp b/examples/designer/customwidgetplugin/customwidgetplugin.cpp new file mode 100644 index 0000000..b85bf4c --- /dev/null +++ b/examples/designer/customwidgetplugin/customwidgetplugin.cpp @@ -0,0 +1,121 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "analogclock.h" +#include "customwidgetplugin.h" + +#include + +using namespace Qt::StringLiterals; + +//! [0] +AnalogClockPlugin::AnalogClockPlugin(QObject *parent) + : QObject(parent) +{ +} +//! [0] + +//! [1] +void AnalogClockPlugin::initialize(QDesignerFormEditorInterface * /* core */) +{ + if (initialized) + return; + + initialized = true; +} +//! [1] + +//! [2] +bool AnalogClockPlugin::isInitialized() const +{ + return initialized; +} +//! [2] + +//! [3] +QWidget *AnalogClockPlugin::createWidget(QWidget *parent) +{ + return new AnalogClock(parent); +} +//! [3] + +//! [4] +QString AnalogClockPlugin::name() const +{ + return u"AnalogClock"_s; +} +//! [4] + +//! [5] +QString AnalogClockPlugin::group() const +{ + return u"Display Widgets [Examples]"_s; +} +//! [5] + +//! [6] +QIcon AnalogClockPlugin::icon() const +{ + return {}; +} +//! [6] + +//! [7] +QString AnalogClockPlugin::toolTip() const +{ + return {}; +} +//! [7] + +//! [8] +QString AnalogClockPlugin::whatsThis() const +{ + return {}; +} +//! [8] + +//! [9] +bool AnalogClockPlugin::isContainer() const +{ + return false; +} +//! [9] + +//! [10] +QString AnalogClockPlugin::domXml() const +{ + return uR"( + + +)" +//! [11] +R"( + + + 0 + 0 + 100 + 100 + + +") +//! [11] +R"( + + The current time + + + The analog clock widget displays the current time. + + + +)"_s; +} +//! [10] + +//! [12] +QString AnalogClockPlugin::includeFile() const +{ + return u"analogclock.h"_s; +} +//! [12] diff --git a/examples/designer/customwidgetplugin/customwidgetplugin.h b/examples/designer/customwidgetplugin/customwidgetplugin.h new file mode 100644 index 0000000..90afe42 --- /dev/null +++ b/examples/designer/customwidgetplugin/customwidgetplugin.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef CUSTOMWIDGETPLUGIN_H +#define CUSTOMWIDGETPLUGIN_H + +#include + +//! [0] +class AnalogClockPlugin : public QObject, public QDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface") + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + explicit AnalogClockPlugin(QObject *parent = nullptr); + + bool isContainer() const override; + bool isInitialized() const override; + QIcon icon() const override; + QString domXml() const override; + QString group() const override; + QString includeFile() const override; + QString name() const override; + QString toolTip() const override; + QString whatsThis() const override; + QWidget *createWidget(QWidget *parent) override; + void initialize(QDesignerFormEditorInterface *core) override; + +private: + bool initialized = false; +}; +//! [0] + +#endif diff --git a/examples/designer/customwidgetplugin/customwidgetplugin.pro b/examples/designer/customwidgetplugin/customwidgetplugin.pro new file mode 100644 index 0000000..0742830 --- /dev/null +++ b/examples/designer/customwidgetplugin/customwidgetplugin.pro @@ -0,0 +1,23 @@ +#! [1] +QT += widgets uiplugin +#! [1] + +#! [0] +CONFIG += plugin +TEMPLATE = lib +#! [0] + +#! [3] +TARGET = $$qtLibraryTarget($$TARGET) + +target.path = $$[QT_INSTALL_PLUGINS]/designer +INSTALLS += target +#! [3] + +#! [2] +HEADERS = analogclock.h \ + customwidgetplugin.h +SOURCES = analogclock.cpp \ + customwidgetplugin.cpp +OTHER_FILES += analogclock.json +#! [2] diff --git a/examples/designer/designer.pro b/examples/designer/designer.pro new file mode 100644 index 0000000..3f2f636 --- /dev/null +++ b/examples/designer/designer.pro @@ -0,0 +1,14 @@ +TEMPLATE = subdirs +SUBDIRS = calculatorform \ + calculatorform_mi + +!contains(CONFIG, static) { + SUBDIRS += calculatorbuilder \ + containerextension \ + customwidgetplugin \ + taskmenuextension +} + +# the sun cc compiler has a problem with the include lines for the form.prf +solaris-cc*:SUBDIRS -= calculatorbuilder \ + worldtimeclockbuilder diff --git a/examples/designer/doc/images/calculatorbuilder-example.webp b/examples/designer/doc/images/calculatorbuilder-example.webp new file mode 100644 index 0000000000000000000000000000000000000000..2337ce79e3f1961e74a093faf469ad1685196de4 GIT binary patch literal 7108 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 OD$mM3#Gjad#eV^68y8Ff literal 0 HcmV?d00001 diff --git a/examples/designer/doc/images/calculatorform-example.webp b/examples/designer/doc/images/calculatorform-example.webp new file mode 100644 index 0000000000000000000000000000000000000000..5fa67778a8fa2f83e0fc5a1d1a27c3b5b13ac39d GIT binary patch literal 6926 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 literal 0 HcmV?d00001 diff --git a/examples/designer/doc/images/containerextension-example.webp b/examples/designer/doc/images/containerextension-example.webp new file mode 100644 index 0000000000000000000000000000000000000000..b4fe4fde2aeba2969f0668ae61d86fecad8c89db GIT binary patch literal 22794 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 z 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 literal 0 HcmV?d00001 diff --git a/examples/designer/doc/images/taskmenuextension-menu.webp b/examples/designer/doc/images/taskmenuextension-menu.webp new file mode 100644 index 0000000000000000000000000000000000000000..f855f3594b03dc5d50abebf45fe9f402788e4810 GIT binary patch literal 18886 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 Uw(oA3KRrwFKSPET!~dHM0MGVF1ONa4 literal 0 HcmV?d00001 diff --git a/examples/designer/doc/snippets/doc_src_examples_containerextension.pro b/examples/designer/doc/snippets/doc_src_examples_containerextension.pro new file mode 100644 index 0000000..903cef9 --- /dev/null +++ b/examples/designer/doc/snippets/doc_src_examples_containerextension.pro @@ -0,0 +1,7 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#! [0] +target.path = $$[QT_INSTALL_PLUGINS]/designer +INSTALLS += target +#! [0] diff --git a/examples/designer/doc/snippets/doc_src_examples_customwidgetplugin.pro b/examples/designer/doc/snippets/doc_src_examples_customwidgetplugin.pro new file mode 100644 index 0000000..903cef9 --- /dev/null +++ b/examples/designer/doc/snippets/doc_src_examples_customwidgetplugin.pro @@ -0,0 +1,7 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#! [0] +target.path = $$[QT_INSTALL_PLUGINS]/designer +INSTALLS += target +#! [0] diff --git a/examples/designer/doc/snippets/doc_src_examples_taskmenuextension.pro b/examples/designer/doc/snippets/doc_src_examples_taskmenuextension.pro new file mode 100644 index 0000000..903cef9 --- /dev/null +++ b/examples/designer/doc/snippets/doc_src_examples_taskmenuextension.pro @@ -0,0 +1,7 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#! [0] +target.path = $$[QT_INSTALL_PLUGINS]/designer +INSTALLS += target +#! [0] diff --git a/examples/designer/doc/src/calculatorbuilder.qdoc b/examples/designer/doc/src/calculatorbuilder.qdoc new file mode 100644 index 0000000..1c7cec8 --- /dev/null +++ b/examples/designer/doc/src/calculatorbuilder.qdoc @@ -0,0 +1,87 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example calculatorbuilder + \examplecategory {Desktop} + \meta tags {widgets,designer} + \ingroup examples-designer + \title Calculator Builder + + \brief Creating a user interface from a \QD form at run-time. + + \image calculatorbuilder-example.webp + + We use the form created in the \l{calculatorform}{Calculator Form} + example to show that the same user interface can be generated when the + application is executed or defined when the application is built. + + \section1 Preparation + + The \l{calculatorform}{Calculator Form} example defines a user + interface that we can use without modification. In this example, we use a + \l{The Qt Resource System}{resource file} to contain the \c{calculatorform.ui} + file created in the previous example, but it could be stored on disk instead. + + To generate a form at run time, we need to link the example against the + \c QtUiTools module library. The project file we use contains all the + necessary information to do this: + + \snippet calculatorbuilder/CMakeLists.txt 0 + + The UI file is loaded from a resource: + + \snippet calculatorbuilder/CMakeLists.txt 1 + + For \c qmake: + + \snippet calculatorbuilder/calculatorbuilder.pro 0 + + All the other necessary files are declared as usual. + + \section1 Loading the Calculator Form + + We will need to use the QUiLoader class that is provided by the + \c libQtUiTools library, so we first ensure that we include the header + file for the class: + + \snippet calculatorbuilder/main.cpp 0 + + We create a static helper function that creates a top level + widget and loads the user interface that we retrieve, + via a QFile object, from the example's resources: + + \snippet calculatorbuilder/main.cpp 1 + + By including the user interface in the example's resources, we ensure + that it will be present when the example is run. The \l{QUiLoader::load()} + function takes the user interface description contained in the file + and constructs the form widget as a child widget of the \c{CalculatorForm}. + + We are interested in three widgets in the generated user interface: + two spin boxes and a label. For convenience, we retrieve pointers to + these widgets from the widget that was constructed by the \c FormBuilder, + and we record them for later use. The \c findChild() template function + allows us to query widgets in order to find named child widgets. + + \snippet calculatorbuilder/main.cpp 2 + + The slot that modifies the output widget provided by the form is defined + in a similar way to that in the + \l{calculatorform}{Calculator Form} example, except that we use a + lambda, capturing the widgets found: + + \snippet calculatorbuilder/main.cpp 3 + + The form widget is added to a layout, and the window title is set: + + \snippet calculatorbuilder/main.cpp 4 + + The advantage of this approach is that we can replace the form when the + application is run, but we can still manipulate the widgets it contains + as long as they are given appropriate names. + + However, loading a form at runtime incurs a runtime cost compared + to converting it to C++ code using the \l {User Interface Compiler (uic)} + tool. +*/ diff --git a/examples/designer/doc/src/calculatorform.qdoc b/examples/designer/doc/src/calculatorform.qdoc new file mode 100644 index 0000000..ccfe16a --- /dev/null +++ b/examples/designer/doc/src/calculatorform.qdoc @@ -0,0 +1,82 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example calculatorform + \examplecategory {Desktop} + \meta tags {widgets,designer} + \ingroup examples-designer + \title Calculator Form + + \brief Using a form created with \QD in an application. + + The Calculator Form Example shows how to use a form created with + \QD in an application by using the user interface information from + a QWidget subclass. + + \image calculatorform-example.webp Screenshot of the Calculator Form example + + The example presents two spin boxes that are used to input integer values + and a label that shows their sum. Whenever either of the spin boxes are + updated, the signal-slot connections between the widgets and the form + ensure that the label is also updated. + + \section1 Preparation + + The user interface for this example is designed completely using \QD. The + result is a UI file describing the form, the widgets used, any signal-slot + connections between them, and other standard user interface properties. + + To ensure that the example can use this file, we enable the \c CMAKE_AUTOUIC + feature and list the UI file in the source files: + + \snippet calculatorform/CMakeLists.txt 0 + \codeline + \snippet calculatorform/CMakeLists.txt 1 + + For \c qmake, we need to include a \c FORMS declaration in the example's project file: + + \snippet calculatorform/calculatorform.pro 1 + + When the project is built, \c uic will create a header file that lets us + construct the form. + + \section1 CalculatorForm Class Definition + + The \c CalculatorForm class uses the user interface described in the + \c calculatorform.ui file. To access the form and its contents, we need + to include the \c ui_calculatorform.h header file created by \c uic + during the build process: + + \snippet calculatorform/calculatorform.h 0 + + We define the \c CalculatorForm class by subclassing QWidget because the + form itself is based on QWidget: + + \snippet calculatorform/calculatorform.h 1 + + Apart from the constructor, the class contains a private slot + \c updateResult() that performs the calculation and updates the output + widget accordingly. + The private \c ui member variable refers to the form, and is used to + access the contents of the user interface. + + \section1 CalculatorForm Class Implementation + + The constructor simply calls the base class's constructor, + sets up the form's user interface and connects + the signals \l{QSpinBox::valueChanged()} to the slot \c updateResult(). + + \snippet calculatorform/calculatorform.cpp 0 + + The user interface is set up with the \c setupUI() function. We pass + \c this as the argument to this function to use the \c CalculatorForm + widget itself as the container for the user interface. + + Slot \c updateResult() adds the values and sets the result on + the output widget: + + \snippet calculatorform/calculatorform.cpp 1 + + It is called whenever the value of one of the spin boxes changes. +*/ diff --git a/examples/designer/doc/src/calculatorform_mi.qdoc b/examples/designer/doc/src/calculatorform_mi.qdoc new file mode 100644 index 0000000..7cf0bca --- /dev/null +++ b/examples/designer/doc/src/calculatorform_mi.qdoc @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example calculatorform_mi + \examplecategory {Desktop} + \meta tags {widgets,designer} + \ingroup examples-designer + \title Calculator Form/Multiple Inheritance + + \brief Using a form created with \QD in an application. + + The Multiple Inheritance Example shows how to use a form created with + \l{Qt Widgets Designer} in an application by subclassing both QWidget and the user + interface class, which is \c{Ui::CalculatorForm}. + + \image calculatorform-example.webp + + To subclass the \c calculatorform.ui file and ensure that \c qmake + processes it with the \c uic, we have to include \c calculatorform.ui + in the \c .pro file, as shown below: + + \snippet calculatorform_mi/calculatorform_mi.pro 0 + + When the project is compiled, the \c uic will generate a corresponding + \c ui_calculatorform.h. + + \section1 CalculatorForm Definition + + In the \c CalculatorForm definition, we include the \c ui_calculatorform.h + that was generated earlier. + + \snippet calculatorform_mi/calculatorform.h 0 + + As mentioned earlier, the class is a subclass of both QWidget and + \c{Ui::CalculatorForm}. + + \snippet calculatorform_mi/calculatorform.h 1 + + Two slots are defined according to the \l{Automatic Connections} + {automatic connection} naming convention required by \c uic. This is + to ensure that \l{QMetaObject}'s auto-connection facilities connect + all the signals and slots involved automatically. + + \section1 CalculatorForm Implementation + + In the constructor, we call \c setupUi() to load the user interface file. + Note that setupUi is a method of \c Ui::CalculatorForm. + + \snippet calculatorform_mi/calculatorform.cpp 0 + + We include two slots, \c{on_inputSpinBox1_valueChanged()} and + \c{on_inputSpinBox2_valueChanged()}. These slots respond to the + \l{QSpinBox::valueChanged()}{valueChanged()} signal that both spin boxes + emit. Whenever there is a change in one spin box's value, we take that + value and add it to whatever value the other spin box has. + + \snippet calculatorform_mi/calculatorform.cpp 1 + \codeline + \snippet calculatorform_mi/calculatorform.cpp 2 + + \section1 \c main() Function + + The \c main() function instantiates QApplication and \c CalculatorForm. + The \c calculator object is displayed by invoking the \l{QWidget::show()} + {show()} function. + + \snippet calculatorform_mi/main.cpp 0 + + There are various approaches to include forms into applications. The + Multiple Inheritance approach is just one of them. See + \l{Using a Designer UI File in Your Application} for more information on + the other approaches available. +*/ diff --git a/examples/designer/doc/src/containerextension.qdoc b/examples/designer/doc/src/containerextension.qdoc new file mode 100644 index 0000000..5fe5a2c --- /dev/null +++ b/examples/designer/doc/src/containerextension.qdoc @@ -0,0 +1,476 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example containerextension + \examplecategory {Desktop} + \meta tags {widgets,designer} + \ingroup examples-designer + \title Container Extension Example + + \brief Creating a custom multi-page plugin for \QD. + + The Container Extension example shows how to create a custom multi-page + plugin for \QD using the + QDesignerContainerExtension class. + + \image containerextension-example.webp + + To provide a custom widget that can be used with \QD, we need to + supply a self-contained implementation. In this example we use a + custom multi-page widget designed to show the container extension + feature. + + An extension is an object which modifies the behavior of \QD. The + QDesignerContainerExtension enables \QD to manage and manipulate a + custom multi-page widget, i.e. adding and deleting pages to the + widget. + + There are four available types of extensions in \QD: + + \list + \li QDesignerMemberSheetExtension provides an extension that allows + you to manipulate a widget's member functions which is displayed + when configuring connections using \QD's mode for editing + signals and slots. + \li QDesignerPropertySheetExtension provides an extension that + allows you to manipulate a widget's properties which is displayed + in \QD's property editor. + \li QDesignerTaskMenuExtension provides an extension that allows + you to add custom menu entries to \QD's task menu. + \li QDesignerContainerExtension provides an extension that allows + you to add (and delete) pages to a multi-page container plugin + in \QD. + \endlist + + You can use all the extensions following the same pattern as in + this example, only replacing the respective extension base + class. For more information, see \l{Qt Widgets Designer C++ Classes}. + + The Container Extension example consists of four classes: + + \list + \li \c MultiPageWidget is a custom container widget that lets the user + manipulate and populate its pages, and navigate among these + using a combobox. + \li \c MultiPageWidgetPlugin exposes the \c MultiPageWidget class + to \QD. + \li \c MultiPageWidgetExtensionFactory creates a + \c MultiPageWidgetContainerExtension object. + \li \c MultiPageWidgetContainerExtension provides the container + extension. + \endlist + + Project files for custom widget plugins need some additional + information to ensure that they will work within \QD. For example, + custom widget plugins rely on components supplied with \QD, and + this must be specified in the project files that we use. We will + first take a look at the plugin's project files. + + Then we will continue by reviewing the \c MultiPageWidgetPlugin + class, and take a look at the \c MultiPageWidgetExtensionFactory + and \c MultiPageWidgetContainerExtension classes. Finally, we will + take a quick look at the \c MultiPageWidget class definition. + + \section1 Project files + + \section2 CMake + + The project files need to state that a plugin linking + to the \QD libraries is to be built: + + \snippet containerextension/CMakeLists.txt 0 + \codeline + \snippet containerextension/CMakeLists.txt 2 + + The following example shows how to add the header and source files of the + widget: + + \snippet containerextension/CMakeLists.txt 1 + + We provide an implementation of the plugin interface so that \QD + can use the custom widget. In this particular example we also + provide implementations of the container extension interface and + the extension factory. + + It is important to ensure that the plugin is installed in a + location that is searched by \QD. We do this by specifying a + target path for the project and adding it to the list of items to + install: + + \snippet containerextension/CMakeLists.txt 3 + \snippet containerextension/CMakeLists.txt 4 + + The container extension is created as a library. It will be + installed alongside the other \QD plugins when the project is + installed (using \c{ninja install} or an equivalent installation + procedure). + + For more information about plugins, see the + \l {How to Create Qt Plugins} documentation. + + \section2 qmake + + The following example shows how to link a plugin to the \QD libraries: + + \snippet containerextension/containerextension.pro 0 + \codeline + \snippet containerextension/containerextension.pro 1 + + The following example shows how to add the header and source files of the + widget: + + \snippet containerextension/containerextension.pro 2 + + The following example shows how to install a plugin to the \QD's plugin path: + + \snippet containerextension/containerextension.pro 3 + + \section1 MultiPageWidgetPlugin Class Definition + + The \c MultiPageWidgetPlugin class exposes the \c MultiPageWidget + class to \QD. Its definition is similar to the \l + {customwidgetplugin}{Custom Widget Plugin} example's + plugin class which is explained in detail. The parts of the class + definition that is specific to this particular custom widget is + the class name and a couple of private slots: + + \snippet containerextension/multipagewidgetplugin.h 0 + + The plugin class provides \QD with basic information about our + plugin, such as its class name and its include file. Furthermore + it knows how to create instances of the \c MultiPageWidget widget. + \c MultiPageWidgetPlugin also defines the \l + {QDesignerCustomWidgetInterface::initialize()}{initialize()} + function which is called after the plugin is loaded into \QD. The + function's QDesignerFormEditorInterface parameter provides the + plugin with a gateway to all of \QD's API's. + + In the case of a multipage widget such as ours, we must also implement + two private slots, currentIndexChanged() and pageTitleChanged(), + to be able to update \QD's property editor whenever the user views + another page or changes one of the page titles. To be able to give + each page their own title, we have chosen to use the + QWidget::windowTitle property to store the page title (for more + information see the MultiPageWidget class implementation in + \e containerextension/multipagewidget.cpp. Note that currently there + is no way of adding a custom property (for example, a page title) to + the pages without using a predefined property as placeholder. + + The \c MultiPageWidgetPlugin class inherits from both QObject and + QDesignerCustomWidgetInterface. It is important to remember, when + using multiple inheritance, to ensure that all the interfaces + (i.e. the classes that doesn't inherit Q_OBJECT) are made known to + the meta object system using the Q_INTERFACES() macro. This + enables \QD to use qobject_cast() to query for supported + interfaces using nothing but a QObject pointer. + + To ensure that Qt recognizes the widget as a plugin, export relevant + information about the widget by adding the \c Q_PLUGIN_METADATA() macro: + + \snippet containerextension/multipagewidgetplugin.h 1 + + With this macro, \QD can access and construct the custom widget. + Without this macro, there is no way for \QD to use the widget. + + \section1 MultiPageWidgetPlugin Class Implementation + + The MultiPageWidgetPlugin class implementation is in most parts + equivalent to the \l {customwidgetplugin}{Custom Widget + Plugin} example's plugin class: + + \snippet containerextension/multipagewidgetplugin.cpp 0 + \codeline + \snippet containerextension/multipagewidgetplugin.cpp 3 + + One of the functions that differ is the isContainer() function + which returns true in this example since our custom widget is + intended to be used as a container. + + \snippet containerextension/multipagewidgetplugin.cpp 1 + + Another function that differ is the function creating our custom widget: + + \snippet containerextension/multipagewidgetplugin.cpp 2 + + In addition to create and return the widget, we connect our custom + container widget's currentIndexChanged() signal to the plugin's + currentIndexChanged() slot to ensure that \QD's property editor is + updated whenever the user views another page. We also connect the + widget's pageTitleChanged() signal to the plugin's + pageTitleChanged() slot. + + The currentIndexChanged() slot is called whenever our custom + widget's currentIndexChanged() \e signal is emitted, i.e. whenever + the user views another page: + + \snippet containerextension/multipagewidgetplugin.cpp 8 + + First, we retrieve the object emitting the signal using the + QObject::sender() and qobject_cast() functions. If it's called in + a slot activated by a signal, QObject::sender() returns a pointer + to the object that sent the signal; otherwise it returns 0. + + \snippet containerextension/multipagewidgetplugin.cpp 9 + + Once we have the widget we can update the property editor. \QD + uses the QDesignerPropertySheetExtension class to feed its + property editor, and whenever a widget is selected in its + workspace, \QD will query for the widget's property sheet + extension and update the property editor. + + So what we want to achieve is to notify \QD that our widget's \e + internal selection has changed: First we use the static + QDesignerFormWindowInterface::findFormWindow() function to + retrieve the QDesignerFormWindowInterface object containing the + widget. The QDesignerFormWindowInterface class allows you to query + and manipulate form windows appearing in \QD's + workspace. Then, all we have to do is to emit its \l + {QDesignerFormWindowInterface::emitSelectionChanged()}{emitSelectionChanged()} + signal, forcing an update of the property editor. + + When changing a page title a generic refresh of the property + editor is not enough because it is actually the page's property + extension that needs to be updated. For that reason we need to + access the QDesignerPropertySheetExtension object for the page + which title we want to change. The QDesignerPropertySheetExtension + class also allows you to manipulate a widget's properties, but to + get hold of the extension we must first retrieve access to \QD's + extension manager: + + \snippet containerextension/multipagewidgetplugin.cpp 10 + \snippet containerextension/multipagewidgetplugin.cpp 11 + + Again we first retrieve the widget emitting the signal, using the + QObject::sender() and qobject_cast() functions. Then we retrieve + the current page from the widget that emitted the signal, and we + use the static QDesignerFormWindowInterface::findFormWindow() + function to retrieve the form containing our widget. + + \snippet containerextension/multipagewidgetplugin.cpp 12 + + Now that we have the form window, the QDesignerFormWindowInterface + class provides the \l + {QDesignerFormWindowInterface::core()}{core()} function which + returns the current QDesignerFormEditorInterface object. The + QDesignerFormEditorInterface class allows you to access Qt + Designer's various components. In particular, the + QDesignerFormEditorInterface::extensionManager() function returns + a reference to the current extension manager. + + \snippet containerextension/multipagewidgetplugin.cpp 13 + + Once we have the extension manager we can update the extension + sheet: First we retrieve the property extension for the page which + title we want to change, using the qt_extension() function. Then + we retrieve the index for the page title using the + QDesignerPropertySheetExtension::indexOf() function. As previously + mentioned, we have chosen to use the QWidget::windowTitle property + to store the page title (for more information see the + MultiPageWidget class implementation in + \e containerextension/multipagewidget.cpp. + Finally, we implicitly force an update of the page's property + sheet by calling the + QDesignerPropertySheetExtension::setChanged() function. + + \snippet containerextension/multipagewidgetplugin.cpp 4 + + Note also the initialize() function: The \c initialize() function + takes a QDesignerFormEditorInterface object as argument. + + \snippet containerextension/multipagewidgetplugin.cpp 5 + + When creating extensions associated with custom widget plugins, we + need to access \QD's current extension manager which we retrieve + from the QDesignerFormEditorInterface parameter. + + In addition to allowing you to manipulate a widget's properties, + the QExtensionManager class provides extension management + facilities for \QD. Using \QD's current extension manager you can + retrieve the extension for a given object. You can also register + and unregister an extension for a given object. Remember that an + extension is an object which modifies the behavior of \QD. + + When registrering an extension, it is actually the associated + extension factory that is registered. In \QD, extension factories + are used to look up and create named extensions as they are + required. So, in this example, the container extension itself is + not created until \QD must know whether the associated widget is a + container, or not. + + \snippet containerextension/multipagewidgetplugin.cpp 6 + + We create a \c MultiPageWidgetExtensionFactory object that we + register using \QD's current \l {QExtensionManager}{extension + manager} retrieved from the QDesignerFormEditorInterface + parameter. The first argument is the newly created factory and the + second argument is an extension identifier which is a string. The + \c Q_TYPEID() macro simply convert the string into a + QLatin1String. + + The \c MultiPageWidgetExtensionFactory class is a subclass of + QExtensionFactory. When \QD must know whether a widget is a + container, or not, \QD's extension manager will run through all + its registered factories invoking the first one which is able to + create a container extension for that widget. This factory will in + turn create a \c MultiPageWidgetExtension object. + + \snippet containerextension/multipagewidgetplugin.cpp 7 + + Finally, take a look at the \c domXml() function. This function + includes default settings for the widget in the standard XML + format used by \QD. In this case, we specify the container's first + page; any inital pages of a multi-page widget must be specified + within this function. + + \section1 MultiPageWidgetExtensionFactory Class Definition + + The \c MultiPageWidgetExtensionFactory class inherits QExtensionFactory + which provides a standard extension factory for \QD. + + \snippet containerextension/multipagewidgetextensionfactory.h 0 + + The subclass's purpose is to reimplement the + QExtensionFactory::createExtension() function, making it able to + create a \c MultiPageWidget container extension. + + + \section1 MultiPageWidgetExtensionFactory Class Implementation + + The class constructor simply calls the QExtensionFactory base + class constructor: + + \snippet containerextension/multipagewidgetextensionfactory.cpp 0 + + As described above, the factory is invoked when \QD must know + whether the associated widget is a container, or not. + + \snippet containerextension/multipagewidgetextensionfactory.cpp 1 + + \QD's behavior is the same whether the requested extension is + associated with a container, a member sheet, a property sheet or a + task menu: Its extension manager runs through all its registered + extension factories calling \c createExtension() for each until + one responds by creating the requested extension. + + So the first thing we do in \c + MultiPageWidgetExtensionFactory::createExtension() is to check if + the QObject, for which the extension is requested, is in fact a \c + MultiPageWidget object. Then we check if the requested extension + is a container extension. + + If the object is a MultiPageWidget requesting a container + extension, we create and return a \c MultiPageWidgetExtension + object. Otherwise, we simply return a null pointer, allowing \QD's + extension manager to continue its search through the registered + factories. + + + \section1 MultiPageWidgetContainerExtension Class Definition + + The \c MultiPageWidgetContainerExtension class inherits + QDesignerContainerExtension which allows you to add (and delete) + pages to a multi-page container plugin in \QD. + + \snippet containerextension/multipagewidgetcontainerextension.h 0 + + It is important to recognize that the QDesignerContainerExtension + class only is intended to provide \QD access to your custom + multi-page widget's functionality; your custom multi-page widget + must implement functionality corresponding to the extension's + functions. + + Note also that we implement a constructor that takes \e two + arguments: the parent widget, and the \c MultiPageWidget object + for which the task menu is requested. + + QDesignerContainerExtension provides a couple of menu entries in + \QD's task menu by default, enabling the user to add or delete + pages to the associated custom multi-page widget in \QD's + workspace. + + \section1 MultiPageWidgetContainerExtension Class Implementation + + In the constructor we save the reference to the \c MultiPageWidget + object sent as parameter, i.e the widget associated with the + extension. We will need this later to access the custom multi-page + widget performing the requested actions. + + \snippet containerextension/multipagewidgetcontainerextension.cpp 0 + + To fully enable \QD to manage and manipulate your custom + multi-page widget, you must reimplement all the functions of + QDesignerContainerExtension: + + \snippet containerextension/multipagewidgetcontainerextension.cpp 1 + \codeline + \snippet containerextension/multipagewidgetcontainerextension.cpp 2 + \codeline + \snippet containerextension/multipagewidgetcontainerextension.cpp 3 + + You must reimplement \l + {QDesignerContainerExtension::canAddWidget()}{canAddWidget()} and \l + {QDesignerContainerExtension::addWidget()}{addWidget()} adding a + given page to the container, \l + {QDesignerContainerExtension::count()}{count()} returning the + number of pages in the container, and \l + {QDesignerContainerExtension::currentIndex()}{currentIndex()} + returning the index of the currently selected page. + + \snippet containerextension/multipagewidgetcontainerextension.cpp 4 + \codeline + \snippet containerextension/multipagewidgetcontainerextension.cpp 5 + \codeline + \snippet containerextension/multipagewidgetcontainerextension.cpp 6 + \codeline + \snippet containerextension/multipagewidgetcontainerextension.cpp 7 + + You must reimplement \l + {QDesignerContainerExtension::insertWidget()}{insertWidget()} + adding a given page to the container at a given index, \l + {QDesignerContainerExtension::canRemove()}{canRemove()} and \l + {QDesignerContainerExtension::remove()}{remove()} deleting the + page at a given index, \l + {QDesignerContainerExtension::setCurrentIndex()}{setCurrentIndex()} + setting the index of the currently selected page, and finally \l + {QDesignerContainerExtension::widget()}{widget()} returning the + page at a given index. + + \section1 MultiPageWidget Class Definition + + The MultiPageWidget class is a custom container widget that lets + the user manipulate and populate its pages, and navigate among + these using a combobox. + + \snippet containerextension/multipagewidget.h 0 + + The main detail to observe is that your custom multi-page widget + must implement functionality corresponding to the + QDesignerContainerExtension's member functions since the + QDesignerContainerExtension class only is intended to provide Qt + Designer access to your custom multi-page widget's functionality. + + In addition, we declare the \c currentIndex and \c pageTitle + properties, and their associated set and get functions. By + declaring these attributes as properties, we allow \QD to manage + them in the same way it manages the properties the MultiPageWidget + widget inherits from QWidget and QObject, for example featuring + the property editor. + + Note the \c STORED attribute in the declaration of the \c + pageTitle property: The \c STORED attribute indicates persistence, + i.e. it declares whether the property's value must be remembered + when storing an object's state. As mentioned above, we have chosen + to store the page title using the QWidget::windowTitle property to + be able to give each page their own title. For that reason the \c + pageTitle property is a "fake" property, provided for editing + purposes, and doesn't need to be stored. + + We must also implement and emit the currentIndexChanged() and + pageTitleChanged() signals to ensure that \QD's property editor is + updated whenever the user views another page or changes one of the + page titles. + + See the MultiPageWidget class implementation in + \e containerextension/multipagewidget.cpp for more details. +*/ diff --git a/examples/designer/doc/src/customwidgetplugin.qdoc b/examples/designer/doc/src/customwidgetplugin.qdoc new file mode 100644 index 0000000..dd8faf6 --- /dev/null +++ b/examples/designer/doc/src/customwidgetplugin.qdoc @@ -0,0 +1,207 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example customwidgetplugin + \examplecategory {Desktop} + \meta tags {widgets,designer} + \ingroup examples-designer + \title Custom Widget Plugin + + \brief Creating a custom widget plugin for \QD. + + \image customwidgetplugin-example.webp + + In this example, the custom widget used is based on the + \l{widgets/analogclock}{Analog Clock example}, and does not provide any custom + signals or slots. + + \section1 Preparation + + To provide a custom widget that can be used with \QD, we need to supply a + self-contained implementation and provide a plugin interface. In this + example, we reuse the \l{widgets/analogclock}{Analog Clock example} for + convenience. + + \section1 Project files + + \section2 CMake + + The project files need to state that a plugin linking + to the \QD libraries is to be built: + + \snippet customwidgetplugin/CMakeLists.txt 0 + \codeline + \snippet customwidgetplugin/CMakeLists.txt 2 + + The link libraries list specifies \c Qt::UiPlugin. This indicates that + the plugin uses the abstract interfaces QDesignerCustomWidgetInterface + and QDesignerCustomWidgetCollectionInterface only and has no linkage + to the \QD libraries. When accessing other interfaces of \QD that have + linkage, \c Designer should be used instead; this ensures that the plugin + dynamically links to the \QD libraries and has a run-time dependency on + them. + + The following example shows how to add the header and source files of the + widget: + + \snippet customwidgetplugin/CMakeLists.txt 1 + + We provide an implementation of the plugin interface so that \QD + can use the custom widget. + + It is also important to ensure that the plugin is installed in a + location that is searched by \QD. We do this by specifying a + target path for the project and adding it to the list of items to + install: + + \snippet customwidgetplugin/CMakeLists.txt 3 + \snippet customwidgetplugin/CMakeLists.txt 4 + + The custom widget is created as a library. It will be + installed alongside the other \QD plugins when the project is + installed (using \c{ninja install} or an equivalent installation + procedure). + + For more information about plugins, see the + \l {How to Create Qt Plugins} documentation. + + \section2 qmake + + The following example shows how to link a plugin to the \QD libraries: + + \snippet customwidgetplugin/customwidgetplugin.pro 0 + \codeline + \snippet customwidgetplugin/customwidgetplugin.pro 1 + + The \c QT variable contains the keyword \c uiplugin, which is + the equivalent of the \c Qt::UiPlugin library. + + The following example shows how to add the header and source files of the + widget: + + \snippet customwidgetplugin/customwidgetplugin.pro 2 + + The following example shows how to install a plugin to the \QD's plugin path: + + \snippet customwidgetplugin/customwidgetplugin.pro 3 + + \section1 AnalogClock Class Definition and Implementation + + The \c AnalogClock class is defined and implemented in exactly the same + way as described in the \l{widgets/analogclock}{Analog Clock example}. + Since the class is self-contained, and does not require any external + configuration, it can be used without modification as a custom widget in + \QD. + + \section1 AnalogClockPlugin Class Definition + + The \c AnalogClock class is exposed to \QD through the \c + AnalogClockPlugin class. This class inherits from both QObject and + the QDesignerCustomWidgetInterface class, and implements an + interface defined by QDesignerCustomWidgetInterface. + + To ensure that Qt recognizes the widget as a plugin, export relevant + information about the widget by adding the \c Q_PLUGIN_METADATA() macro: + + \snippet customwidgetplugin/customwidgetplugin.h 0 + + The functions provide information about the widget that \QD can use in + the \l{Getting to Know Qt Widgets Designer#WidgetBox}{widget box}. + The \c initialized private member variable is used to record whether + the plugin has been initialized by \QD. + + Note that the only part of the class definition that is specific to + this particular custom widget is the class name. + + \section1 AnalogClockPlugin Implementation + + The class constructor simply calls the QObject base class constructor + and sets the \c initialized variable to \c false. + + \snippet customwidgetplugin/customwidgetplugin.cpp 0 + + \QD will initialize the plugin when it is required by calling the + \c initialize() function: + + \snippet customwidgetplugin/customwidgetplugin.cpp 1 + + In this example, the \c initialized private variable is tested, and only + set to \c true if the plugin is not already initialized. Although, this + plugin does not require any special code to be executed when it is + initialized, we could include such code after the test for initialization. + + The \c isInitialized() function lets \QD know whether the plugin is + ready for use: + + \snippet customwidgetplugin/customwidgetplugin.cpp 2 + + Instances of the custom widget are supplied by the \c createWidget() + function. The implementation for the analog clock is straightforward: + + \snippet customwidgetplugin/customwidgetplugin.cpp 3 + + In this case, the custom widget only requires a \c parent to be specified. + If other arguments need to be supplied to the widget, they can be + introduced here. + + The following functions provide information for \QD to use to represent + the widget in the widget box. + The \c name() function returns the name of class that provides the + custom widget: + + \snippet customwidgetplugin/customwidgetplugin.cpp 4 + + The \c group() function is used to describe the type of widget that the + custom widget belongs to: + + \snippet customwidgetplugin/customwidgetplugin.cpp 5 + + The widget plugin will be placed in a section identified by its + group name in \QD's widget box. The icon used to represent the + widget in the widget box is returned by the \c icon() function: + + \snippet customwidgetplugin/customwidgetplugin.cpp 6 + + In this case, we return a null icon to indicate that we have no icon + that can be used to represent the widget. + + A tool tip and "What's This?" help can be supplied for the custom widget's + entry in the widget box. The \c toolTip() function should return a short + message describing the widget: + + \snippet customwidgetplugin/customwidgetplugin.cpp 7 + + The \c whatsThis() function can return a longer description: + + \snippet customwidgetplugin/customwidgetplugin.cpp 8 + + The \c isContainer() function tells \QD whether the widget is supposed to + be used as a container for other widgets. If not, \QD will not allow the + user to place widgets inside it. + + \snippet customwidgetplugin/customwidgetplugin.cpp 9 + + Most widgets in Qt can contain child widgets, but it only makes sense + to use dedicated container widgets for this purpose in \QD. By returning + \c false, we indicate that the custom widget cannot hold other widgets; + if we returned true, \QD would allow other widgets to be placed inside + the analog clock and a layout to be defined. + + The \c domXml() function provides a way to include default settings for + the widget in the standard XML format used by \QD. In this case, we only + specify the widget's geometry: + + \snippet customwidgetplugin/customwidgetplugin.cpp 10 + + If the widget provides a reasonable size hint, it is not necessary to + define it here. In addition, returning an empty string instead of a + \c{} element will tell \QD not to install the widget in the + widget box. + + To make the analog clock widget usable by applications, we implement + the \c includeFile() function to return the name of the header file + containing the custom widget class definition: + + \snippet customwidgetplugin/customwidgetplugin.cpp 12 +*/ diff --git a/examples/designer/doc/src/taskmenuextension.qdoc b/examples/designer/doc/src/taskmenuextension.qdoc new file mode 100644 index 0000000..6a9521a --- /dev/null +++ b/examples/designer/doc/src/taskmenuextension.qdoc @@ -0,0 +1,418 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example taskmenuextension + \examplecategory {Desktop} + \meta tags {widgets,designer} + \ingroup examples-designer + \title Task Menu Extension + + \brief Creating a custom widget plugin for \QD and providing custom task + menu entries that are associated with the plugin. + + The Task Menu Extension example shows how to create a custom + widget plugin for \l {Qt Widgets Designer Manual}{\QD}, and how to use + the QDesignerTaskMenuExtension class to provide custom task menu + entries associated with the plugin. + + \image taskmenuextension-example.webp + + To provide a custom widget that can be used with \QD, we need to + supply a self-contained implementation. In this example we use a + custom widget designed to show the task menu extension feature: + The TicTacToe widget. + + An extension is an object which modifies the behavior of \QD. The + QDesignerTaskMenuExtension can provide custom task menu entries + when a widget with this extension is selected. + + There are four available types of extensions in \QD: + + \list + \li QDesignerContainerExtension provides an extension that allows + you to add (and delete) pages to a multi-page container plugin + in \QD. + \li QDesignerMemberSheetExtension provides an extension that allows + you to manipulate a widget's member functions which is displayed + when configuring connections using \QD's mode for editing + signals and slots. + \li QDesignerPropertySheetExtension provides an extension that + allows you to manipulate a widget's properties which is displayed + in \QD's property editor. + \li QDesignerTaskMenuExtension provides an extension that allows + you to add custom menu entries to \QD's task menu. + \endlist + + You can use all the extensions following the same pattern as in + this example, only replacing the respective extension base + class. For more information, see the \l{Qt Widgets Designer C++ Classes}. + + The Task Menu Extension example consists of five classes: + + \list + \li \c TicTacToe is a custom widget that lets the user play + the Tic-Tac-Toe game. + \li \c TicTacToePlugin exposes the \c TicTacToe class to \QD. + \li \c TicTacToeTaskMenuFactory creates a \c TicTacToeTaskMenu object. + \li \c TicTacToeTaskMenu provides the task menu extension, i.e the + plugin's associated task menu entries. + \li \c TicTacToeDialog lets the user modify the state of a + Tic-Tac-Toe plugin loaded into \QD. + \endlist + + The project file for custom widget plugins needs some additional + information to ensure that they will work within \QD. For example, + custom widget plugins rely on components supplied with \QD, and + this must be specified in the project file that we use. We will + first take a look at the plugin's project file. + + Then we will continue by reviewing the \c TicTacToePlugin class, + and take a look at the \c TicTacToeTaskMenuFactory and \c + TicTacToeTaskMenu classes. Finally, we will review the \c + TicTacToeDialog class before we take a quick look at the \c + TicTacToe widget's class definition. + + \section1 Project files + + \section2 CMake + + The project files need to state that a plugin linking + to the \QD libraries is to be built: + + \snippet taskmenuextension/CMakeLists.txt 0 + \codeline + \snippet taskmenuextension/CMakeLists.txt 2 + + The following example shows how to add the header and source files of the + widget: + + \snippet taskmenuextension/CMakeLists.txt 1 + + We provide an implementation of the plugin interface so that \QD + can use the custom widget. In this particular example we also + provide implementations of the task menu extension and the + extension factory as well as a dialog implementation. + + It is important to ensure that the plugin is installed in a + location that is searched by \QD. We do this by specifying a + target path for the project and adding it to the list of items to + install: + + \snippet taskmenuextension/CMakeLists.txt 3 + \snippet taskmenuextension/CMakeLists.txt 4 + + The task menu extension is created as a library. It will be + installed alongside the other \QD plugins when the project is + installed (using \c{ninja install} or an equivalent installation + procedure). + + For more information about plugins, see the + \l {How to Create Qt Plugins} documentation. + + \section2 qmake + + The following example shows how to link a plugin to the \QD libraries: + + \snippet taskmenuextension/taskmenuextension.pro 0 + \codeline + \snippet taskmenuextension/taskmenuextension.pro 1 + + The following example shows how to add the header and source files of the + widget: + + \snippet taskmenuextension/taskmenuextension.pro 2 + + The following example shows how to install a plugin to the \QD's plugin path: + + \snippet taskmenuextension/taskmenuextension.pro 3 + + \section1 TicTacToePlugin Class Definition + + The \c TicTacToePlugin class exposes \c the TicTacToe class to + \QD. Its definition is equivalent to the \l + {customwidgetplugin}{Custom Widget Plugin} example's + plugin class which is explained in detail. The only part of the + class definition that is specific to this particular custom widget + is the class name. + + To ensure that Qt recognizes the widget as a plugin, export relevant + information about the widget by adding the \c Q_PLUGIN_METADATA() macro: + + \snippet taskmenuextension/tictactoeplugin.h 0 + + The plugin class provides \QD with basic information about our + plugin, such as its class name and its include file. Furthermore + it knows how to create instances of the \c TicTacToe widget. + TicTacToePlugin also defines the \l + {QDesignerCustomWidgetInterface::initialize()}{initialize()} + function which is called after the plugin is loaded into \QD. The + function's QDesignerFormEditorInterface parameter provides the + plugin with a gateway to all of \QD's API's. + + The \c TicTacToePlugin class inherits from both QObject and + QDesignerCustomWidgetInterface. It is important to remember, when + using multiple inheritance, to ensure that all the interfaces + (i.e. the classes that doesn't inherit Q_OBJECT) are made known to + the meta object system using the Q_INTERFACES() macro. This + enables \QD to use qobject_cast() to query for supported + interfaces using nothing but a QObject pointer. + + \section1 TicTacToePlugin Class Implementation + + The TicTacToePlugin class implementation is in most parts + equivalent to the \l {customwidgetplugin}{Custom Widget + Plugin} example's plugin class: + + \snippet taskmenuextension/tictactoeplugin.cpp 0 + + The only function that differs significantly is the initialize() + function: + + \snippet taskmenuextension/tictactoeplugin.cpp 1 + + The \c initialize() function takes a QDesignerFormEditorInterface + object as argument. The QDesignerFormEditorInterface class + provides access to \QD's components. + + In \QD you can create two kinds of plugins: custom widget plugins + and tool plugins. QDesignerFormEditorInterface provides access to + all the \QD components that you normally need to create a tool + plugin: the extension manager, the object inspector, the property + editor and the widget box. Custom widget plugins have access to + the same components. + + \snippet taskmenuextension/tictactoeplugin.cpp 2 + + When creating extensions associated with custom widget plugins, we + need to access \QD's current extension manager which we retrieve + from the QDesignerFormEditorInterface parameter. + + \QD's QDesignerFormEditorInterface holds information about all Qt + Designer's components: The action editor, the object inspector, + the property editor, the widget box, and the extension and form + window managers. + + The QExtensionManager class provides extension management + facilities for \QD. Using \QD's current extension manager you can + retrieve the extension for a given object. You can also register + and unregister an extension for a given object. Remember that an + extension is an object which modifies the behavior of \QD. + + When registrering an extension, it is actually the associated + extension factory that is registered. In \QD, extension factories + are used to look up and create named extensions as they are + required. So, in this example, the task menu extension itself is + not created until a task menu is requested by the user. + + \snippet taskmenuextension/tictactoeplugin.cpp 3 + + We create a \c TicTacToeTaskMenuFactory object that we register + using \QD's current \l {QExtensionManager}{extension manager} + retrieved from the QDesignerFormEditorInterface parameter. The + first argument is the newly created factory and the second + argument is an extension identifier which is a string. The \c + Q_TYPEID() macro simply converts the string into a QLatin1String. + + The \c TicTacToeTaskMenuFactory class is a subclass of + QExtensionFactory. When the user request a task menu by clicking + the right mouse button over a widget with the specified task menu + extension, \QD's extension manager will run through all its + registered factories invoking the first one that is able to create + a task menu extension for the selected widget. This factory will + in turn create a \c TicTacToeTaskMenu object (the extension). + + We omit to reimplement the + QDesignerCustomWidgetInterface::domXml() function (which include + default settings for the widget in the standard XML format used by + \QD), since no default values are necessary. + + \snippet taskmenuextension/tictactoeplugin.h 1 + + Finally, we use the Q_PLUGIN_METADATA() macro to export the + TicTacToePlugin class for use with Qt's plugin handling classes: + This macro ensures that \QD can access and construct the custom + widget. Without this macro, there is no way for \QD to use the + widget. + + \section1 TicTacToeTaskMenuFactory Class Definition + + The \c TicTacToeTaskMenuFactory class inherits QExtensionFactory + which provides a standard extension factory for \QD. + + \snippet taskmenuextension/tictactoetaskmenu.h 1 + + The subclass's purpose is to reimplement the + QExtensionFactory::createExtension() function, making it able to + create a \c TicTacToe task menu extension. + + \section1 TicTacToeTaskMenuFactory Class Implementation + + The class constructor simply calls the QExtensionFactory base + class constructor: + + \snippet taskmenuextension/tictactoetaskmenu.cpp 4 + + As described above, the factory is invoked when the user request a + task menu by clicking the right mouse button over a widget with + the specified task menu extension in \QD. + + \QD's behavior is the same whether the requested extension is + associated with a container, a member sheet, a property sheet or a + task menu: Its extension manager runs through all its registered + extension factories calling \c createExtension() for each until + one responds by creating the requested extension. + + \snippet taskmenuextension/tictactoetaskmenu.cpp 5 + + So the first thing we do in \c + TicTacToeTaskMenuFactory::createExtension() is to check if the + requested extension is a task menu extension. If it is, and the + widget requesting it is a \c TicTacToe widget, we create and + return a \c TicTacToeTaskMenu object. Otherwise, we simply return + a null pointer, allowing \QD's extension manager to continue its + search through the registered factories. + + + \section1 TicTacToeTaskMenu Class Definition + + \image taskmenuextension-menu.webp + + The \c TicTacToeTaskMenu class inherits QDesignerTaskMenuExtension + which allows you to add custom entries (in the form of QActions) + to the task menu in \QD. + + \snippet taskmenuextension/tictactoetaskmenu.h 0 + + We reimplement the \c preferredEditAction() and \c taskActions() + functions. Note that we implement a constructor that takes \e two + arguments: the parent widget, and the \c TicTacToe widget for + which the task menu is requested. + + In addition we declare the private \c editState() slot, our custom + \c editStateAction and a private pointer to the \c TicTacToe + widget which state we want to modify. + + \section1 TicTacToeTaskMenu Class Implementation + + \snippet taskmenuextension/tictactoetaskmenu.cpp 0 + + In the constructor we first save the reference to the \c TicTacToe + widget sent as parameter, i.e the widget which state we want to + modify. We will need this later when our custom action is + invoked. We also create our custom \c editStateAction and connect + it to the \c editState() slot. + + \snippet taskmenuextension/tictactoetaskmenu.cpp 1 + + The \c editState() slot is called whenever the user chooses the + \gui {Edit State...} option in a \c TicTacToe widget's task menu. The + slot creates a \c TicTacToeDialog presenting the current state of + the widget, and allowing the user to edit its state by playing the + game. + + \snippet taskmenuextension/tictactoetaskmenu.cpp 2 + + We reimplement the \c preferredEditAction() function to return our + custom \c editStateAction as the action that should be invoked + when selecting a \c TicTacToe widget and pressing \key F2 . + + \snippet taskmenuextension/tictactoetaskmenu.cpp 3 + + We reimplement the \c taskActions() function to return a list of + our custom actions making these appear on top of the default menu + entries in a \c TicTacToe widget's task menu. + + \section1 TicTacToeDialog Class Definition + + \image taskmenuextension-dialog.webp + + The \c TicTacToeDialog class inherits QDialog. The dialog lets the + user modify the state of the currently selected Tic-Tac-Toe + plugin. + + \snippet taskmenuextension/tictactoedialog.h 0 + + We reimplement the \c sizeHint() function. We also declare two + private slots: \c resetState() and \c saveState(). In addition to + the dialog's buttons and layouts we declare two \c TicTacToe + pointers, one to the widget the user can interact with and the + other to the original custom widget plugin which state the user + wants to edit. + + \section1 TicTacToeDialog Class Implementation + + \snippet taskmenuextension/tictactoedialog.cpp 0 + + In the constructor we first save the reference to the TicTacToe + widget sent as parameter, i.e the widget which state the user want + to modify. Then we create a new \c TicTacToe widget, and set its + state to be equivalent to the parameter widget's state. + + Finally, we create the dialog's buttons and layout. + + \snippet taskmenuextension/tictactoedialog.cpp 1 + + We reimplement the \c sizeHint() function to ensure that the + dialog is given a reasonable size. + + \snippet taskmenuextension/tictactoedialog.cpp 2 + + The \c resetState() slot is called whenever the user press the + \gui Reset button. The only thing we do is to call the \c + clearBoard() function for the editor widget, i.e. the \c TicTacToe + widget we created in the dialog's constructor. + + \snippet taskmenuextension/tictactoedialog.cpp 3 + + The \c saveState() slot is called whenever the user press the \gui + OK button, and transfers the state of the editor widget to the + widget which state we want to modify. In order to make the change + of state visible to \QD we need to set the latter widget's state + property using the QDesignerFormWindowInterface class. + + QDesignerFormWindowInterface provides you with information about + the associated form window as well as allowing you to alter its + properties. The interface is not intended to be instantiated + directly, but to provide access to \QD's current form + windows controlled by \QD's form window manager. + + If you are looking for the form window containing a specific + widget, you can use the static + QDesignerFormWindowInterface::findFormWindow() function: + + \snippet taskmenuextension/tictactoedialog.cpp 4 + + After retrieving the form window of the widget (which state we + want to modify), we use the QDesignerFormWindowInterface::cursor() + function to retrieve the form window's cursor. + + The QDesignerFormWindowCursorInterface class provides an interface + to the form window's text cursor. Once we have cursor, we can + finally set the state property using the + QDesignerFormWindowCursorInterface::setProperty() function. + + \snippet taskmenuextension/tictactoedialog.cpp 5 + + In the end we call the QEvent::accept() function which sets the + accept flag of the event object. Setting the \c accept parameter + indicates that the event receiver wants the event. Unwanted events + might be propagated to the parent widget. + + \section1 TicTacToe Class Definition + + The \c TicTacToe class is a custom widget that lets the user play + the Tic-Tac-Toe game. + + \snippet taskmenuextension/tictactoe.h 0 + + The main details to observe in the \c TicTacToe class defintion is + the declaration of the \c state property and its \c state() and \c + setState() functions. + + We need to declare the \c TicTacToe widget's state as a property + to make it visible to \QD; allowing \QD to manage it in the same + way it manages the properties the \c TicTacToe widget inherits + from QWidget and QObject, for example featuring the property + editor. +*/ diff --git a/examples/designer/taskmenuextension/CMakeLists.txt b/examples/designer/taskmenuextension/CMakeLists.txt new file mode 100644 index 0000000..b1ed491 --- /dev/null +++ b/examples/designer/taskmenuextension/CMakeLists.txt @@ -0,0 +1,55 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(taskmenuextension LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +#! [0] +find_package(Qt6 REQUIRED COMPONENTS Core Designer Gui Widgets) + +qt_add_plugin(taskmenuextension) +#! [0] + +#! [1] +target_sources(taskmenuextension PRIVATE + tictactoe.cpp tictactoe.h + tictactoedialog.cpp tictactoedialog.h + tictactoeplugin.cpp tictactoeplugin.h + tictactoetaskmenu.cpp tictactoetaskmenu.h +) +#! [1] + +set_target_properties(taskmenuextension PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +#! [2] +target_link_libraries(taskmenuextension PUBLIC + Qt::Core + Qt::Designer + Qt::Gui + Qt::Widgets +) +#! [2] + +if(QT6_INSTALL_PREFIX) +#! [3] + set(INSTALL_EXAMPLEDIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/designer") +#! [3] +else() + if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") + endif() + set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/plugins/designer") +endif() + +#! [4] +install(TARGETS taskmenuextension + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) +#! [4] diff --git a/examples/designer/taskmenuextension/taskmenuextension.pro b/examples/designer/taskmenuextension/taskmenuextension.pro new file mode 100644 index 0000000..b4f8cbd --- /dev/null +++ b/examples/designer/taskmenuextension/taskmenuextension.pro @@ -0,0 +1,27 @@ +#! [1] +QT += widgets designer +#! [1] + +#! [0] +TEMPLATE = lib +CONFIG += plugin +#! [0] + +TARGET = $$qtLibraryTarget($$TARGET) + +#! [3] +target.path = $$[QT_INSTALL_PLUGINS]/designer +INSTALLS += target +#! [3] + +#! [2] +HEADERS += tictactoe.h \ + tictactoedialog.h \ + tictactoeplugin.h \ + tictactoetaskmenu.h +SOURCES += tictactoe.cpp \ + tictactoedialog.cpp \ + tictactoeplugin.cpp \ + tictactoetaskmenu.cpp +OTHER_FILES += tictactoe.json +#! [2] diff --git a/examples/designer/taskmenuextension/tictactoe.cpp b/examples/designer/taskmenuextension/tictactoe.cpp new file mode 100644 index 0000000..58bc359 --- /dev/null +++ b/examples/designer/taskmenuextension/tictactoe.cpp @@ -0,0 +1,142 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "tictactoe.h" + +#include +#include + +using namespace Qt::StringLiterals; + +static inline QString defaultState() { return u"---------"_s; } + +TicTacToe::TicTacToe(QWidget *parent) + : QWidget(parent), myState(defaultState()) +{ +} + +QSize TicTacToe::minimumSizeHint() const +{ + return {200, 200}; +} + +QSize TicTacToe::sizeHint() const +{ + return {200, 200}; +} + +void TicTacToe::setState(const QString &newState) +{ + turnNumber = 0; + myState = defaultState(); + const int count = qMin(9, int(newState.length())); + for (int position = 0; position < count; ++position) { + const QChar mark = newState.at(position); + if (mark == Cross || mark == Nought) { + ++turnNumber; + myState[position] = mark; + } + } + update(); +} + +QString TicTacToe::state() const +{ + return myState; +} + +void TicTacToe::clearBoard() +{ + myState = defaultState(); + turnNumber = 0; + update(); +} + +void TicTacToe::mousePressEvent(QMouseEvent *event) +{ + if (turnNumber == 9) { + clearBoard(); + return; + } + for (int position = 0; position < 9; ++position) { + const QRect &cell = cellRect(position); + if (cell.contains(event->position().toPoint())) { + if (myState.at(position) == Empty) { + myState[position] = turnNumber % 2 == 0 + ? Cross : Nought; + ++turnNumber; + update(); + } + } + } +} + +void TicTacToe::paintEvent(QPaintEvent * /* event */) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + painter.setPen(QPen(Qt::darkGreen, 1)); + painter.drawLine(cellWidth(), 0, cellWidth(), height()); + painter.drawLine(2 * cellWidth(), 0, 2 * cellWidth(), height()); + painter.drawLine(0, cellHeight(), width(), cellHeight()); + painter.drawLine(0, 2 * cellHeight(), width(), 2 * cellHeight()); + + painter.setPen(QPen(Qt::darkBlue, 2)); + + for (int position = 0; position < 9; ++position) { + const QRect &cell = cellRect(position); + + if (myState.at(position) == Cross) { + painter.drawLine(cell.topLeft(), cell.bottomRight()); + painter.drawLine(cell.topRight(), cell.bottomLeft()); + } else if (myState.at(position) == Nought) { + painter.drawEllipse(cell); + } + } + + painter.setPen(QPen(Qt::yellow, 3)); + + for (int position = 0; position < 9; position = position + 3) { + if (myState.at(position) != Empty + && myState.at(position + 1) == myState.at(position) + && myState.at(position + 2) == myState.at(position)) { + const int y = cellRect(position).center().y(); + painter.drawLine(0, y, width(), y); + turnNumber = 9; + } + } + + for (int position = 0; position < 3; ++position) { + if (myState.at(position) != Empty + && myState.at(position + 3) == myState.at(position) + && myState.at(position + 6) == myState.at(position)) { + const int x = cellRect(position).center().x(); + painter.drawLine(x, 0, x, height()); + turnNumber = 9; + } + } + if (myState.at(0) != Empty && myState.at(4) == myState.at(0) + && myState.at(8) == myState.at(0)) { + painter.drawLine(0, 0, width(), height()); + turnNumber = 9; + } + if (myState.at(2) != Empty && myState.at(4) == myState.at(2) + && myState.at(6) == myState.at(2)) { + painter.drawLine(0, height(), width(), 0); + turnNumber = 9; + } +} + +QRect TicTacToe::cellRect(int position) const +{ + const int HMargin = width() / 30; + const int VMargin = height() / 30; + const int row = position / 3; + const int column = position % 3; + const QPoint pos{column * cellWidth() + HMargin, + row * cellHeight() + VMargin}; + const QSize size{cellWidth() - 2 * HMargin, + cellHeight() - 2 * VMargin}; + return {pos, size}; +} diff --git a/examples/designer/taskmenuextension/tictactoe.h b/examples/designer/taskmenuextension/tictactoe.h new file mode 100644 index 0000000..60b82a5 --- /dev/null +++ b/examples/designer/taskmenuextension/tictactoe.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TICTACTOE_H +#define TICTACTOE_H + +#include + +QT_BEGIN_NAMESPACE +class QRect; +class QSize; +QT_END_NAMESPACE + +//! [0] +class TicTacToe : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString state READ state WRITE setState) + +public: + explicit TicTacToe(QWidget *parent = nullptr); + + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + void setState(const QString &newState); + QString state() const; + void clearBoard(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + static constexpr char16_t Empty = '-'; + static constexpr char16_t Cross = 'X'; + static constexpr char16_t Nought = 'O'; + + QRect cellRect(int position) const; + int cellWidth() const { return width() / 3; } + int cellHeight() const { return height() / 3; } + + QString myState; + int turnNumber = 0; +}; +//! [0] + +#endif diff --git a/examples/designer/taskmenuextension/tictactoedialog.cpp b/examples/designer/taskmenuextension/tictactoedialog.cpp new file mode 100644 index 0000000..b0c9acc --- /dev/null +++ b/examples/designer/taskmenuextension/tictactoedialog.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "tictactoe.h" +#include "tictactoedialog.h" + +#include +#include +#include +#include +#include +#include + +//! [0] +TicTacToeDialog::TicTacToeDialog(TicTacToe *tic, QWidget *parent) + : QDialog(parent) + , editor(new TicTacToe) + , ticTacToe(tic) + , buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok + | QDialogButtonBox::Cancel + | QDialogButtonBox::Reset)) +{ + editor->setState(ticTacToe->state()); + + connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, + this, &TicTacToeDialog::resetState); + connect(buttonBox, &QDialogButtonBox::accepted, this, &TicTacToeDialog::saveState); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto *mainLayout = new QVBoxLayout(this); + mainLayout->addWidget(editor); + mainLayout->addWidget(buttonBox); + + setWindowTitle(tr("Edit State")); +} +//! [0] + +//! [1] +QSize TicTacToeDialog::sizeHint() const +{ + return {250, 250}; +} +//! [1] + +//! [2] +void TicTacToeDialog::resetState() +{ + editor->clearBoard(); +} +//! [2] + +//! [3] +void TicTacToeDialog::saveState() +{ +//! [3] //! [4] + if (auto *formWindow = QDesignerFormWindowInterface::findFormWindow(ticTacToe)) + formWindow->cursor()->setProperty("state", editor->state()); +//! [4] //! [5] + accept(); +} +//! [5] diff --git a/examples/designer/taskmenuextension/tictactoedialog.h b/examples/designer/taskmenuextension/tictactoedialog.h new file mode 100644 index 0000000..4dfae74 --- /dev/null +++ b/examples/designer/taskmenuextension/tictactoedialog.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TICTACTOEDIALOG_H +#define TICTACTOEDIALOG_H + +#include + +QT_BEGIN_NAMESPACE +class QDialogButtonBox; +QT_END_NAMESPACE +class TicTacToe; + +//! [0] +class TicTacToeDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TicTacToeDialog(TicTacToe *plugin = nullptr, QWidget *parent = nullptr); + + QSize sizeHint() const override; + +private slots: + void resetState(); + void saveState(); + +private: + TicTacToe *editor; + TicTacToe *ticTacToe; + QDialogButtonBox *buttonBox; +}; +//! [0] + +#endif diff --git a/examples/designer/taskmenuextension/tictactoeplugin.cpp b/examples/designer/taskmenuextension/tictactoeplugin.cpp new file mode 100644 index 0000000..fc729c8 --- /dev/null +++ b/examples/designer/taskmenuextension/tictactoeplugin.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "tictactoe.h" +#include "tictactoeplugin.h" +#include "tictactoetaskmenu.h" + +#include +#include +#include + +using namespace Qt::StringLiterals; + +//! [0] +TicTacToePlugin::TicTacToePlugin(QObject *parent) + : QObject(parent) +{ +} + +QString TicTacToePlugin::name() const +{ + return u"TicTacToe"_s; +} + +QString TicTacToePlugin::group() const +{ + return u"Display Widgets [Examples]"_s; +} + +QString TicTacToePlugin::toolTip() const +{ + return u"Tic Tac Toe Example, demonstrating class QDesignerTaskMenuExtension (C++)"_s; +} + +QString TicTacToePlugin::whatsThis() const +{ + return {}; +} + +QString TicTacToePlugin::includeFile() const +{ + return u"tictactoe.h"_s; +} + +QIcon TicTacToePlugin::icon() const +{ + return {}; +} + +bool TicTacToePlugin::isContainer() const +{ + return false; +} + +QWidget *TicTacToePlugin::createWidget(QWidget *parent) +{ + auto *ticTacToe = new TicTacToe(parent); + ticTacToe->setState(u"-X-XO----"_s); + return ticTacToe; +} + +bool TicTacToePlugin::isInitialized() const +{ + return initialized; +} + +//! [0] //! [1] +void TicTacToePlugin::initialize(QDesignerFormEditorInterface *formEditor) +{ +//! [1] //! [2] + if (initialized) + return; + + auto *manager = formEditor->extensionManager(); + Q_ASSERT(manager != nullptr); +//! [2] + +//! [3] + manager->registerExtensions(new TicTacToeTaskMenuFactory(manager), + Q_TYPEID(QDesignerTaskMenuExtension)); + + initialized = true; +} + +QString TicTacToePlugin::domXml() const +{ + return uR"( + + + + + TicTacToe + + Tic Tac Toe state + + + + + +)"_s; +} + +//! [3] diff --git a/examples/designer/taskmenuextension/tictactoeplugin.h b/examples/designer/taskmenuextension/tictactoeplugin.h new file mode 100644 index 0000000..8826266 --- /dev/null +++ b/examples/designer/taskmenuextension/tictactoeplugin.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +#ifndef TICTACTOEPLUGIN_H +#define TICTACTOEPLUGIN_H + +#include + +QT_BEGIN_NAMESPACE +class QIcon; +class QWidget; +QT_END_NAMESPACE + +class TicTacToePlugin : public QObject, public QDesignerCustomWidgetInterface +{ + Q_OBJECT +//! [1] + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface") +//! [1] + Q_INTERFACES(QDesignerCustomWidgetInterface) + +public: + explicit TicTacToePlugin(QObject *parent = nullptr); + + QString name() const override; + QString group() const override; + QString toolTip() const override; + QString whatsThis() const override; + QString includeFile() const override; + QIcon icon() const override; + bool isContainer() const override; + QWidget *createWidget(QWidget *parent) override; + bool isInitialized() const override; + void initialize(QDesignerFormEditorInterface *formEditor) override; + QString domXml() const override; + +private: + bool initialized = false; +}; + +#endif +//! [0] diff --git a/examples/designer/taskmenuextension/tictactoetaskmenu.cpp b/examples/designer/taskmenuextension/tictactoetaskmenu.cpp new file mode 100644 index 0000000..b919949 --- /dev/null +++ b/examples/designer/taskmenuextension/tictactoetaskmenu.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "tictactoe.h" +#include "tictactoedialog.h" +#include "tictactoetaskmenu.h" + +#include +#include + +//! [0] +TicTacToeTaskMenu::TicTacToeTaskMenu(TicTacToe *tic, QObject *parent) + : QObject(parent) + , editStateAction(new QAction(tr("Edit State..."), this)) + , ticTacToe(tic) +{ + connect(editStateAction, &QAction::triggered, this, &TicTacToeTaskMenu::editState); +} +//! [0] + +//! [1] +void TicTacToeTaskMenu::editState() +{ + TicTacToeDialog dialog(ticTacToe); + dialog.exec(); +} +//! [1] + +//! [2] +QAction *TicTacToeTaskMenu::preferredEditAction() const +{ + return editStateAction; +} +//! [2] + +//! [3] +QList TicTacToeTaskMenu::taskActions() const +{ + return QList{editStateAction}; +} +//! [3] + +//! [4] +TicTacToeTaskMenuFactory::TicTacToeTaskMenuFactory(QExtensionManager *parent) + : QExtensionFactory(parent) +{ +} +//! [4] + +//! [5] +QObject *TicTacToeTaskMenuFactory::createExtension(QObject *object, + const QString &iid, + QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerTaskMenuExtension)) + return nullptr; + + if (auto *tic = qobject_cast(object)) + return new TicTacToeTaskMenu(tic, parent); + + return nullptr; +} +//! [5] diff --git a/examples/designer/taskmenuextension/tictactoetaskmenu.h b/examples/designer/taskmenuextension/tictactoetaskmenu.h new file mode 100644 index 0000000..ef85733 --- /dev/null +++ b/examples/designer/taskmenuextension/tictactoetaskmenu.h @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TICTACTOETASKMENU_H +#define TICTACTOETASKMENU_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QExtensionManager; +QT_END_NAMESPACE +class TicTacToe; + +//! [0] +class TicTacToeTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) + +public: + explicit TicTacToeTaskMenu(TicTacToe *tic, QObject *parent); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editState(); + +private: + QAction *editStateAction; + TicTacToe *ticTacToe; +}; +//! [0] + +//! [1] +class TicTacToeTaskMenuFactory : public QExtensionFactory +{ + Q_OBJECT + +public: + explicit TicTacToeTaskMenuFactory(QExtensionManager *parent = nullptr); + +protected: + QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; +}; +//! [1] + +#endif diff --git a/examples/examples.pro b/examples/examples.pro new file mode 100644 index 0000000..0300e3c --- /dev/null +++ b/examples/examples.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +qtHaveModule(widgets): SUBDIRS += help designer linguist uitools assistant + +!qtConfig(process): SUBDIRS -= assistant designer diff --git a/examples/help/CMakeLists.txt b/examples/help/CMakeLists.txt new file mode 100644 index 0000000..d51ae1d --- /dev/null +++ b/examples/help/CMakeLists.txt @@ -0,0 +1 @@ +qt_internal_add_example(contextsensitivehelp) diff --git a/examples/help/README b/examples/help/README new file mode 100644 index 0000000..e09e00f --- /dev/null +++ b/examples/help/README @@ -0,0 +1,7 @@ +Support for interactive help is provided by the Qt Assistant application. +Developers can take advantages of the facilities it offers to display +specially-prepared documentation to users of their applications. + + +Documentation for these examples can be found via the Tutorial and Examples +link in the main Qt documentation. diff --git a/examples/help/contextsensitivehelp/CMakeLists.txt b/examples/help/contextsensitivehelp/CMakeLists.txt new file mode 100644 index 0000000..fb251de --- /dev/null +++ b/examples/help/contextsensitivehelp/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(contextsensitivehelp LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/help/contextsensitivehelp") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Help Widgets) + +qt_standard_project_setup() + +qt_add_executable(contextsensitivehelp + helpbrowser.cpp helpbrowser.h + main.cpp + wateringconfigdialog.cpp wateringconfigdialog.h wateringconfigdialog.ui +) + +set_target_properties(contextsensitivehelp PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_compile_definitions(contextsensitivehelp PUBLIC + SRCDIR="${CMAKE_CURRENT_SOURCE_DIR}/" +) + +target_link_libraries(contextsensitivehelp PUBLIC + Qt6::Core + Qt6::Gui + Qt6::Help + Qt6::Widgets +) + +install(TARGETS contextsensitivehelp + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/help/contextsensitivehelp/contextsensitivehelp.pro b/examples/help/contextsensitivehelp/contextsensitivehelp.pro new file mode 100644 index 0000000..cdbe938 --- /dev/null +++ b/examples/help/contextsensitivehelp/contextsensitivehelp.pro @@ -0,0 +1,19 @@ +TEMPLATE = app + +QT += help widgets +SOURCES += main.cpp \ + wateringconfigdialog.cpp \ + helpbrowser.cpp + +HEADERS += wateringconfigdialog.h \ + helpbrowser.h + +FORMS += wateringconfigdialog.ui + +DEFINES += SRCDIR=\\\"$$PWD/\\\" + +target.path = $$[QT_INSTALL_EXAMPLES]/help/contextsensitivehelp +docs.files += $$PWD/docs +docs.path = $$target.path + +INSTALLS += target docs diff --git a/examples/help/contextsensitivehelp/docs/amount.html b/examples/help/contextsensitivehelp/docs/amount.html new file mode 100644 index 0000000..78ab220 --- /dev/null +++ b/examples/help/contextsensitivehelp/docs/amount.html @@ -0,0 +1,11 @@ + + + + Water amount + + + Depending on the plant, temperature and rain fall the amount needs to be larger + or smaller. On a really hot day without rain, the suggested amount + can be increased by about 10%. + + diff --git a/examples/help/contextsensitivehelp/docs/filter.html b/examples/help/contextsensitivehelp/docs/filter.html new file mode 100644 index 0000000..6486112 --- /dev/null +++ b/examples/help/contextsensitivehelp/docs/filter.html @@ -0,0 +1,12 @@ + + + +Filter + + +Depending on the source of water, it needs to be filtered or not. Filtering +is strongly recommened for the river and lake. + + + + diff --git a/examples/help/contextsensitivehelp/docs/plants.html b/examples/help/contextsensitivehelp/docs/plants.html new file mode 100644 index 0000000..d0fd4d7 --- /dev/null +++ b/examples/help/contextsensitivehelp/docs/plants.html @@ -0,0 +1,44 @@ + + + + Plants + + + Different kind of plants need different amounts of water. Here's a short + overview over the most common grown plants and their avarage need of water: +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KindAmount
Squash2000
Bean1500
Carrot1200
Strawberry1300
Raspberry1000
Blueberry1100
+

+ Warning: Watering them too much or too little will + cause irreversible damage! + + diff --git a/examples/help/contextsensitivehelp/docs/rain.html b/examples/help/contextsensitivehelp/docs/rain.html new file mode 100644 index 0000000..09b5fa4 --- /dev/null +++ b/examples/help/contextsensitivehelp/docs/rain.html @@ -0,0 +1,11 @@ + + + + Rain + + + Depending on the rain fall, the automated watering system should not be + switched on at all. Also, the temperature should be + considered. + + diff --git a/examples/help/contextsensitivehelp/docs/source.html b/examples/help/contextsensitivehelp/docs/source.html new file mode 100644 index 0000000..68b2f8c --- /dev/null +++ b/examples/help/contextsensitivehelp/docs/source.html @@ -0,0 +1,33 @@ + + + + Water Source + + + The current pipe system connects to four different sources. Be aware + that only a limited amount of water can be taken from some sources. +
+ + + + + + + + + + + + + + + + + + + + + +
SourceAmount
Fountain4000
River6000
Lake10000
Public Water Systemunlimited
+ + diff --git a/examples/help/contextsensitivehelp/docs/temperature.html b/examples/help/contextsensitivehelp/docs/temperature.html new file mode 100644 index 0000000..92e8723 --- /dev/null +++ b/examples/help/contextsensitivehelp/docs/temperature.html @@ -0,0 +1,13 @@ + + + + Temperature + + + Depending on the temperature, the plants need more or less water. The higher + the temperature the higher the need for water. If the temperature does not + reach a certain level, maybe no automatic watering should be done at all.
+ Before setting this parameter for good, you should also take the amount of + rain into account. + + diff --git a/examples/help/contextsensitivehelp/docs/time.html b/examples/help/contextsensitivehelp/docs/time.html new file mode 100644 index 0000000..0cc81f4 --- /dev/null +++ b/examples/help/contextsensitivehelp/docs/time.html @@ -0,0 +1,11 @@ + + + +Starting time + + +Starting the watering too early may be ineffective since most water +will evaporate. + + + diff --git a/examples/help/contextsensitivehelp/docs/wateringmachine.qch b/examples/help/contextsensitivehelp/docs/wateringmachine.qch new file mode 100644 index 0000000000000000000000000000000000000000..1433acfc98dc73eabe2816c2f91e56989e7f1359 GIT binary patch literal 61440 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 TBbJAT&l5z)2&2RkZSDRCygYHy literal 0 HcmV?d00001 diff --git a/examples/help/contextsensitivehelp/docs/wateringmachine.qhc b/examples/help/contextsensitivehelp/docs/wateringmachine.qhc new file mode 100644 index 0000000000000000000000000000000000000000..98cfa14817f46f6d9c2da2aab0a52b58d077b9c3 GIT binary patch literal 94208 zcmeI*&2QW09l&wYltjykLdS1{)1+bM8L?)|mE#1>3$V;d?bc}AWVVnsyBM@gM@04F zNEDo;!vO8j`!?*d>kjL=hXLE6$6f|(m;DJl3}_Ama@u;?!{^ZpsnA|}Yt*k`*?eBa z-;W=@*v}&+-1+G}&v)&EdZX(2_Nq3n>ALn4+txH~PCT>X*?mllg+#X?e(Is+;}++% z<@Z0&WWLa5lAmkVXO?y5H)npB`AcRzb9DN%Q~x|w9(y_VJpD5L!RVKxKQO;E4^l5v zzet@Hop~UD00Ia|fxQ_sd-<~7HhgEV;y!d6O|M?t@G8FBh!nDSx7XK->vnPN&b@Uz z(qu1migt1RDSDQAoi;H@DQpUJjuO8NGZp~Ny28uJ0I0KFL zLa*W@r|KRTuQg?6^Lf2}AsTPXIXv`gk9(`=l&9k(c3MLr`&+bR2jQECPRZ?8=AKNN z*<9|Gu*Cqj7gUy};zP=|*nswf?ai&V?ML>9>yK1$x$U`!6J~aHR&SefOBJa4J{bw2 z>g)yev!C;E>)ktRX6NU->+S4bnO=B#A`&CqVlTu1Ep-nYm3w=3TzOSx@7-8{S^a4hc6C*6zavw))Rxv&i%-_<|%cVQhea1h7EVP1Mw;w}{`y|MAPijPmhAT&-?8u9yMHIwzL++% zmoDkQO82Q{FexLK#v)<+^(V*doT8ii;`s3nJh#!Qh`p#pL6+>bTB%%DugJB{QN^<* zGYq;0>yW8M3h79mktVS>Ub&V8E9vAE4IlL@dVWRq%eg6)h@ukblrMf2HDw}^f*DC9 z(qu1u=suHCqfs6@m6j{_h@HD(rm|a?b@~i|(PZa7HPj`2HWaOwR%ipGb%~g`(H^fAyfvaw)$bE^K&hDW*piDzG*rLgPo%|V0gEyG1iBaz_Iwv*$B&V$~sS38#xN0M%RrHL;d2q1s}0tg_0 z00IagfB*srAn=_QxUMI(`>RKen28b7RVDGxjZ=HJEPgJp+kdik{kjEAbSuy)SA<*e^`IAes6th9auZok3|I!1Q0*~0R#|0009IL zKmY**zFz{fqsEQVjaH>nbf5Y=uG1)$H=Igk&nbN}X&Up1yA9Xz#od^Kd(-Ux;Xfar zO&PiL2j0HBw!hzSo6S2;V=`&v)9bb14nzIQ3khSbQ~6%K)T)Y`XvpyLCJf9syp^tau|UNgAmgUIYk;YuODBK|wT zYsHnTHw)KqUM(zNUtJj!`*PH}tyw>}erf&I`kQsz`o#Ledhz|*#cVAC2q1s}0tg_0 z00IagfB*s~3Y;@;=o>gSvFR-@#~`kw{2=d*WY#rdRcb*lAN z%@4JzinkKU^hNEmFW$D7Z9Bfx@WqY&?4V}C2pXpwj#rb7+hV~UNv8AKobOf--G<}0 z8tQe$etAZhX&zRbn%|VKdmtCp_B!K!JjQ@4(znb{sfdB#s zAb+hQQ z;(-7H2q1s}0tg_000IagfB*t-hQK>|DxF?6R*xLtZHT)KR-IDWtGUZhN@eZe+LgkU zLVjf>U$|CWxq7p3{pQtz8vlQzS>L=F8%UN2Ab + + + + + wateringmachine.qhp + wateringmachine.qch + + + + wateringmachine.qch + + + \ No newline at end of file diff --git a/examples/help/contextsensitivehelp/docs/wateringmachine.qhp b/examples/help/contextsensitivehelp/docs/wateringmachine.qhp new file mode 100644 index 0000000..6dd08e7 --- /dev/null +++ b/examples/help/contextsensitivehelp/docs/wateringmachine.qhp @@ -0,0 +1,25 @@ + + + wateringmachine + wateringcompany.com.1-0-0.premium + + + + + + + + + + + + plants.html + temperature.html + rain.html + time.html + amount.html + source.html + filter.html + + + diff --git a/examples/help/contextsensitivehelp/helpbrowser.cpp b/examples/help/contextsensitivehelp/helpbrowser.cpp new file mode 100644 index 0000000..7dfb110 --- /dev/null +++ b/examples/help/contextsensitivehelp/helpbrowser.cpp @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include + +#include "helpbrowser.h" +#include "qhelplink.h" + +using namespace Qt::StringLiterals; + +static QString documentationDirectory() +{ + QStringList paths; +#ifdef SRCDIR + paths.append(QLatin1StringView(SRCDIR)); +#endif + paths.append(QLibraryInfo::path(QLibraryInfo::ExamplesPath)); + paths.append(QCoreApplication::applicationDirPath()); + paths.append(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)); + for (const auto &dir : std::as_const(paths)) { + const QString path = dir + "/docs"_L1; + if (QFileInfo::exists(path)) + return path; + } + return {}; +} + +HelpBrowser::HelpBrowser(QWidget *parent) + : QTextBrowser(parent) +{ + const QString collectionFile = documentationDirectory() + "/wateringmachine.qhc"_L1; + + m_helpEngine = new QHelpEngineCore(collectionFile, this); + if (!m_helpEngine->setupData()) { + delete m_helpEngine; + m_helpEngine = nullptr; + } +} + +void HelpBrowser::showHelpForKeyword(const QString &id) +{ + if (m_helpEngine) { + QList documents = m_helpEngine->documentsForIdentifier(id); + if (documents.count()) + setSource(documents.first().url); + } +} + +QVariant HelpBrowser::loadResource(int type, const QUrl &name) +{ + QByteArray ba; + if (type < 4 && m_helpEngine) { + QUrl url(name); + if (name.isRelative()) + url = source().resolved(url); + ba = m_helpEngine->fileData(url); + } + return ba; +} + diff --git a/examples/help/contextsensitivehelp/helpbrowser.h b/examples/help/contextsensitivehelp/helpbrowser.h new file mode 100644 index 0000000..b0a59f8 --- /dev/null +++ b/examples/help/contextsensitivehelp/helpbrowser.h @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef HELPBROWSER_H +#define HELPBROWSER_H + +#include + +QT_BEGIN_NAMESPACE +class QHelpEngineCore; +QT_END_NAMESPACE; + +class HelpBrowser : public QTextBrowser +{ + Q_OBJECT + +public: + HelpBrowser(QWidget *parent); + void showHelpForKeyword(const QString &id); + +private: + QVariant loadResource(int type, const QUrl &name) override; + + QHelpEngineCore *m_helpEngine; +}; + +#endif diff --git a/examples/help/contextsensitivehelp/main.cpp b/examples/help/contextsensitivehelp/main.cpp new file mode 100644 index 0000000..5b75595 --- /dev/null +++ b/examples/help/contextsensitivehelp/main.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "wateringconfigdialog.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + WateringConfigDialog dia; + return dia.exec(); +} diff --git a/examples/help/contextsensitivehelp/wateringconfigdialog.cpp b/examples/help/contextsensitivehelp/wateringconfigdialog.cpp new file mode 100644 index 0000000..602fed4 --- /dev/null +++ b/examples/help/contextsensitivehelp/wateringconfigdialog.cpp @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "wateringconfigdialog.h" + +WateringConfigDialog::WateringConfigDialog() +{ + m_ui.setupUi(this); + m_widgetInfo.insert(m_ui.plantComboBox, tr("plants")); + m_widgetInfo.insert(m_ui.temperatureCheckBox, tr("temperature")); + m_widgetInfo.insert(m_ui.temperatureSpinBox, tr("temperature")); + m_widgetInfo.insert(m_ui.rainCheckBox, tr("rain")); + m_widgetInfo.insert(m_ui.rainSpinBox, tr("rain")); + m_widgetInfo.insert(m_ui.startTimeEdit, tr("starting time")); + m_widgetInfo.insert(m_ui.amountSpinBox, tr("water amount")); + m_widgetInfo.insert(m_ui.sourceComboBox, tr("water source")); + m_widgetInfo.insert(m_ui.filterCheckBox, tr("water filtering")); + + connect(qApp, &QApplication::focusChanged, + this, &WateringConfigDialog::focusChanged); +} + +void WateringConfigDialog::focusChanged(QWidget *, QWidget *now) +{ + if (m_widgetInfo.contains(now)) { + m_ui.helpLabel->setText(tr("Information about %1:").arg(m_widgetInfo.value(now))); + QStringList lst = m_widgetInfo.value(now).split(QLatin1Char(' ')); + m_ui.helpBrowser->showHelpForKeyword(lst.last()); + } +} + diff --git a/examples/help/contextsensitivehelp/wateringconfigdialog.h b/examples/help/contextsensitivehelp/wateringconfigdialog.h new file mode 100644 index 0000000..d2f3587 --- /dev/null +++ b/examples/help/contextsensitivehelp/wateringconfigdialog.h @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef WATERINGCONFIGDIALOG_H +#define WATERINGCONFIGDIALOG_H + +#include +#include "ui_wateringconfigdialog.h" + +class WateringConfigDialog : public QDialog +{ + Q_OBJECT +public: + WateringConfigDialog(); + +private slots: + void focusChanged(QWidget *old, QWidget *now); + +private: + Ui::WateringConfigDialog m_ui; + QMap m_widgetInfo; +}; + +#endif diff --git a/examples/help/contextsensitivehelp/wateringconfigdialog.ui b/examples/help/contextsensitivehelp/wateringconfigdialog.ui new file mode 100644 index 0000000..d7a473a --- /dev/null +++ b/examples/help/contextsensitivehelp/wateringconfigdialog.ui @@ -0,0 +1,446 @@ + + WateringConfigDialog + + + + 0 + 0 + 334 + 550 + + + + Watering Configuration + + + + + + + + Plant: + + + + + + + + 0 + 0 + + + + + Squash + + + + + Bean + + + + + Carrot + + + + + Strawberry + + + + + Raspberry + + + + + Blueberry + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 67 + 16 + + + + + + + + Water when: + + + + + + + Temperature is higher than: + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 16 + 20 + + + + + + + + false + + + + + + C + + + 10 + + + 60 + + + 20 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Rain less than: + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 16 + 20 + + + + + + + + false + + + + + + mm + + + 1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Starting Time: + + + + + + + + + + Amount: + + + + + + + l + + + 100 + + + 10000 + + + 100 + + + 1000 + + + + + + + Source: + + + + + + + + Fountain + + + + + River + + + + + Lake + + + + + Public Water System + + + + + + + + Filter: + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + <a href="test">Show Details</a> + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + HelpBrowser + QTextBrowser +
helpbrowser.h
+
+
+ + + + buttonBox + accepted() + WateringConfigDialog + accept() + + + 227 + 372 + + + 157 + 274 + + + + + buttonBox + rejected() + WateringConfigDialog + reject() + + + 286 + 378 + + + 286 + 274 + + + + + temperatureCheckBox + toggled(bool) + temperatureSpinBox + setEnabled(bool) + + + 132 + 101 + + + 132 + 125 + + + + + rainCheckBox + toggled(bool) + rainSpinBox + setEnabled(bool) + + + 110 + 154 + + + 113 + 169 + + + + +
diff --git a/examples/help/doc/images/context-sensitive-help-example.png b/examples/help/doc/images/context-sensitive-help-example.png new file mode 100644 index 0000000000000000000000000000000000000000..26b501cdab132ff20dcda4cd90f6682efd8e31d3 GIT binary patch literal 41582 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 + +#include "arrowpad.h" + +ArrowPad::ArrowPad(QWidget *parent) + : QWidget(parent) +{ +//! [0] + upButton = new QPushButton(tr("&Up")); +//! [0] //! [1] + downButton = new QPushButton(tr("&Down")); +//! [1] //! [2] + leftButton = new QPushButton(tr("&Left")); +//! [2] //! [3] + rightButton = new QPushButton(tr("&Right")); +//! [3] + + QGridLayout *mainLayout = new QGridLayout; + mainLayout->addWidget(upButton, 0, 1); + mainLayout->addWidget(leftButton, 1, 0); + mainLayout->addWidget(rightButton, 1, 2); + mainLayout->addWidget(downButton, 2, 1); + setLayout(mainLayout); +} diff --git a/examples/linguist/arrowpad/arrowpad.h b/examples/linguist/arrowpad/arrowpad.h new file mode 100644 index 0000000..f908baa --- /dev/null +++ b/examples/linguist/arrowpad/arrowpad.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef ARROWPAD_H +#define ARROWPAD_H + +#include + +QT_BEGIN_NAMESPACE +class QPushButton; +QT_END_NAMESPACE + +//! [0] +class ArrowPad : public QWidget +//! [0] //! [1] +{ +//! [1] //! [2] + Q_OBJECT +//! [2] + +public: + ArrowPad(QWidget *parent = 0); + +private: + QPushButton *upButton; + QPushButton *downButton; + QPushButton *leftButton; + QPushButton *rightButton; +}; + +#endif diff --git a/examples/linguist/arrowpad/arrowpad.pro b/examples/linguist/arrowpad/arrowpad.pro new file mode 100644 index 0000000..9723703 --- /dev/null +++ b/examples/linguist/arrowpad/arrowpad.pro @@ -0,0 +1,17 @@ +#! [0] +HEADERS = arrowpad.h \ + mainwindow.h +SOURCES = arrowpad.cpp \ + main.cpp \ + mainwindow.cpp +#! [0] #! [1] +TRANSLATIONS = arrowpad_fr.ts \ + arrowpad_nl.ts +#! [1] + +target.path = $$[QT_INSTALL_EXAMPLES]/linguist/arrowpad +INSTALLS += target + +QT += widgets + +simulator: warning(This example might not fully work on Simulator platform) diff --git a/examples/linguist/arrowpad/arrowpad_en.ts b/examples/linguist/arrowpad/arrowpad_en.ts new file mode 100644 index 0000000..405e97c --- /dev/null +++ b/examples/linguist/arrowpad/arrowpad_en.ts @@ -0,0 +1,10 @@ + + + + diff --git a/examples/linguist/arrowpad/arrowpad_fr.ts b/examples/linguist/arrowpad/arrowpad_fr.ts new file mode 100644 index 0000000..1f068c5 --- /dev/null +++ b/examples/linguist/arrowpad/arrowpad_fr.ts @@ -0,0 +1,3 @@ + + + diff --git a/examples/linguist/arrowpad/arrowpad_nl.ts b/examples/linguist/arrowpad/arrowpad_nl.ts new file mode 100644 index 0000000..1f068c5 --- /dev/null +++ b/examples/linguist/arrowpad/arrowpad_nl.ts @@ -0,0 +1,3 @@ + + + diff --git a/examples/linguist/arrowpad/main.cpp b/examples/linguist/arrowpad/main.cpp new file mode 100644 index 0000000..4458105 --- /dev/null +++ b/examples/linguist/arrowpad/main.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "mainwindow.h" + +using namespace Qt::StringLiterals; + +//! [0] +int main(int argc, char *argv[]) +//! [0] //! [1] +{ + QApplication app(argc, argv); + + auto locale = QLocale::system(); + +//! [2] + QTranslator translator; +//! [2] //! [3] + if (translator.load(locale, u"arrowpad"_s, u"_"_s)) + app.installTranslator(&translator); +//! [1] //! [3] + + MainWindow mainWindow; + mainWindow.show(); + return app.exec(); +} diff --git a/examples/linguist/arrowpad/mainwindow.cpp b/examples/linguist/arrowpad/mainwindow.cpp new file mode 100644 index 0000000..6da16a4 --- /dev/null +++ b/examples/linguist/arrowpad/mainwindow.cpp @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "arrowpad.h" +#include "mainwindow.h" + +MainWindow::MainWindow() +{ +//! [0] + arrowPad = new ArrowPad; +//! [0] + setCentralWidget(arrowPad); + +//! [1] + exitAct = new QAction(tr("E&xit"), this); + exitAct->setShortcut(QKeySequence(tr("Ctrl+Q", "Quit"))); + connect(exitAct, &QAction::triggered, this, &MainWindow::close); +//! [1] + + fileMenu = menuBar()->addMenu(tr("&File")); + fileMenu->addAction(exitAct); +} diff --git a/examples/linguist/arrowpad/mainwindow.h b/examples/linguist/arrowpad/mainwindow.h new file mode 100644 index 0000000..0b16d15 --- /dev/null +++ b/examples/linguist/arrowpad/mainwindow.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QMenu; +QT_END_NAMESPACE +class ArrowPad; + +//! [0] +class MainWindow : public QMainWindow +//! [0] //! [1] +{ + Q_OBJECT +//! [1] + +public: + MainWindow(); + +private: + ArrowPad *arrowPad; + QMenu *fileMenu; + QAction *exitAct; +}; + +#endif diff --git a/examples/linguist/doc/images/linguist-arrowpad_en.png b/examples/linguist/doc/images/linguist-arrowpad_en.png new file mode 100644 index 0000000000000000000000000000000000000000..9a95eb24ff694b4c056b892b6dd78cc8edbad4d9 GIT binary patch literal 1429 zcmV;G1#0?vONsOGs z@0`c)#>d7=X`D)FrE7wc<}#zIw;bCSI&L4+YRb16xL zbDGXVVU!^>dnrwfF-3$zX`Fka)_bPbLTROpw%(Ms=Y+1-rM~BsuGUg%oI+)#DM5RL zhJ=Kg&V-)EN@1KKICF%e)`X_c5G-pLFncjkjEtt%F;SF^uGUg*rHr!HF;$dOajiK` zjFht0QhBXfb)^6R|2b8RT6whyC~KU$-kiGUoW9;#g0-B!?_+tbV|}e-eYFrYbEU)Q zrNi%~$LFQT@1@K4Ia!=3JbPn`y^S5rcK`qbmq|oHRCr$OmSwBtKoCV!(Q$W|r|$0V zvhMEg?(X`Fw>!l!GwH`>nN45=dm$kW{o!!9REjHDB~GpY*2pkLwT4vdq1H1Qs2|uG zZRnJwslBp1_(Rzz_#wSkQ zN!&Y;oS8|zPKlXMiw2>xR&PjC2h;K(ubih=o6+hnhs)zut*)Te09svbUk`;!q|vA_ zauuzfNz|Q8&b~^CB3gy&IJIZ?Ml~u_h9j(KH5NyyWf)!v9848@P3)kkPm6c;j!UkHe%gW5B5LyP*foF&f0u5OFUl~MyL)l94c(5?cB zQCgx8W1xyJQDe+-ZmnuTL3M4J`rFIp-%&xMi zG-z25wQLo;3Se?qSyc3@hpO!=4`AF}HP0%Ys{lAx|J|;lRZ437T;l*0Du3t;;Lfq4lrC5r}oU=_}#i<>JH%43423#v--cU)|6lE z+EI$bFboFa>2q$OTjc&1TMTARDU__Y0;!ol)Xtkfgi|{v5q;mSq)L@4mHJcwf)>=- zx1ln6pveRnPI^=opbK>X`cS#sW94cE05{wNNIlEvm-;9l=jwi>N|h>As#K|uYdI=V zuZ{H8RH?kb3Q_4e0f<+5!KyB5PF1ZcK$EJDo+ect-TkU+RRdskb*s8U`PGVGoB@#VTORjO2}Ql(0LBp>YV5AsTlq%GafAm2xoDwS9XGN=Hok%CW9 zi6DXc?3+~XKm|qYV58^q1RH;&nMXFS(eibZLs?-4J zLLI+8)Swp?_&tak7e%R4yQrhBO3dCoqu2#$xrMg{_>Q}+OT<1rs jRH;&>NUI00000NkvXXu0mjf9yP%} literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-arrowpad_fr.png b/examples/linguist/doc/images/linguist-arrowpad_fr.png new file mode 100644 index 0000000000000000000000000000000000000000..fc33f9fd736a2cf4c9f03a566a7f4ae770aebf4c GIT binary patch literal 1671 zcmV;226*|2P)WBoDe8$Qf;MDb*)-? ztz&()V}iA7j=gi1#tGp3XvDl!T_vl)C33GYl)BzhX`Gz8-YGnL zLRpm7)6S*C=cUKz5HNGb$Hq!&r71y#IbED7O^iZir9x??Ym&WdlE!P6#xX^N5G-p- zWu-AmjB}RGF-?SXn$B~c&Il-TN^zwjJcN6m#xYTpF;$FvqSkw+&U>cT0094lhJ=Kg z&N)qt2rP3_d968Bj9PK6gs#?vuHKBM){LsoT6Lu%MT8hUdyKNyIbD>Dw%(Mk)+s!M zDM5Rbw&!DctwL3ll)mR7G;^G~=bXOY7%X#~zVBm#y`01EDM^H-zUM+=lw*p$Yl5|< z%l9ETb8CjZYl^jHA%x8U00du2L_t(|UhJ1;kK8a6Me~wjW@cJsW@cuFGBYzXGt*D4 z?eS{BtETO$o7JdSiX}__a&&a#XKo?4qU9DKm(q`ca!F9G0yQ$JY^-cEI*XidmV*Vs zf|ui?zR{CD<=om@C{(h)q)eq!T|VtsUsJ0!^Lc}LdAgQeEeD%dXB1NkQ)%2>Zmy`Z zR6VP<+N|%sM4O`D6N%P9YoMdU5k4I5k@J`MfwQiBSN<)*ebe35<&nu`PkV=FhhKTU zZ$G|YEbi|9`d3KZjTtAYp$IW_&o@C0eMlOrIpeQeH>A_I=$j1&g#jCirwm!Cp#(Ox zC$@2yh%(e6 z0Nf!!+2RiUE)a$zD+_cn6=DbgbqIj)jEn(o& z`VO%`IK-GJ5H(Z;kn9kE6de)=qK?FXvO5$3*y?-e-x#7D3KE_i=+GoJ1R&Kt#F4N- z>|ajY5GH~mL<9vuB1MNNkoY|$YDn@~Zu64@myzu-%k^hOo4sdG{Bk&zewNdUggJqL zw)=bN|9j|X`*xJ#FbIWF_{?)Q)-AaIjSLmz1SAjDqVdl83&uX#kDC00EG=g4i1ru0rPsB!J8+S2n)oICUh>E8AA60TpEsP@zNvu{kKg1p+*zLhcmE znEW?BuJ1TL4pJc67%Tb2A zY*e9p5YZ|`hzbE^l3?cw<&=2=g{e>h#dwB@R-sBENQDwmIXtF9?i485e5dfyYYgPi z2SKlnJQ;L!#qNWk??;dzL4pJc5+q2FAVF)p*yGE$p!q%0(dDOr1PKy!d)SLvp!Op$ z03r{IL9Kx?P+AX4d>d5T*VH-rEzAN@m#)(;P}9CHBXvFtR9l*L+5x(2Uso0^0s;M? zwtan_uL1QxLtH`7;Ftg+ed(4R4aE}TI5+n#97U+%I03-IwsjbISpLS8%8BFc3MomlGt&v*4Zu7bIwM1PKx(NRS{wf&>YAh04JZEWax%)UVBh R=&=9*002ovPDHLkV1nY40C)fZ literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-arrowpad_nl.png b/examples/linguist/doc/images/linguist-arrowpad_nl.png new file mode 100644 index 0000000000000000000000000000000000000000..f2645a81b3b526a8b270f15494e022955663e808 GIT binary patch literal 1706 zcmV;b237fqP)WBoDe8$Qf;MDaivmq zty+1lV|}$_g0*Xoy>phv5G-?qqRxz}){L^=oWAEFJbO7&jB}2?7&v>Bw%(Mw=WB|+ z7%+1wMTA;{wHP#ejIP$4$L~sEoKk6}5HNEoNsLl)tuaZ2T5+XXb*&I2Yg&1=T79)K zQH*1Ktua-ULS39=hP4PJYiowJbCSInG;?c?#(S2=7$|FVlE!nI#tGp3XvDl!T_voWtiKGcT0094lhJ=Kg z&N)qt2rP3_d968Bj9PK6gs#?vuHKBM){LsoT6Lu%MT8hUdyKNyIbD>Dw%(Mk)+s!M zDM5Rbw&!DctwL3ll)mR7G;^G~=bXOY7%X#~zVBm#y`01EDM^H-zUM+=lw*p$Yl5|< z%l9ETb8CjZYl^k7rl17?00e?bL_t(|UhJ1;kK8a6Me~wjW@escW@cu_GBYzXGt*D4 z?eS_LtETO$lhvqKiX}__a&&a#XJ)0aqGnd0kTQUQ3Q16*0yXhc)l}83cXT-3ltki@ z_{+eUckHBBKEJ*mjh5~&EmtTMmrwhY*OW@tLc>r)gQjR#(ZR*FSy{2nP-Zk$m@2Ey z)z4}yR?E9D)6LW0!{Ii6o4>Qu-hQ~fSIS;u2hTctoISTV*G*SnpIai4JPn5CLa#iY zw;$gxB_}4n{#7!7_!-w<(K`;5L=aJH;2R?H$|vPd?nV(&`OYH>MXj$M5vf!U7IqA2 zK4~@D#oapDtjuu2h=}f7HJi;fb+NjbwXt!!`STB(&F0_RVsEkAyQIgY?5Td|Ip_Id z?l$L2B;5Dil83>Wnb6#e*CU>hrQ~vQdFw!YL&x=wZfa0L%yFFSMy4STGBkn=VW6CDzdG65f0%16^@_{acNQVH# z?+_mdhZqwCqJ}yEQXK*iqeH?#)R7QSZiiX{@_i5e8$+~15yDdd9h#zs0K~e77!p1Z z|1T$O2opgEB7!)Oh|wVmBzzAE8WMe$+xn!yWMwsevSg2DvN- z#0M2re2{|ghd~e^K)Uh@08-Xn?voqkp(>=ZJNgF5nGE_zAde29)=!Z8aheH| z+StBDSw=ystS3_QenWlxTm`B@P-R&;t3qy^8)wcar~xN}Ofkr!VUYi3g5+^*mvg~o z*3yGQvq;PoTp+CgsfM{+I1r$fUF`AA_n_lW7I)Frc6JPQs8(24*81PBlyK!5-N0(7Rzp(9y-0Xl>anT~>sm;e9(07*qoM6N<$g7c3L AIsgCw literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-hellotr_en.png b/examples/linguist/doc/images/linguist-hellotr_en.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3d8072cde780db0219f5f0b03d727864821cf3 GIT binary patch literal 3367 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 literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-hellotr_la.png b/examples/linguist/doc/images/linguist-hellotr_la.png new file mode 100644 index 0000000000000000000000000000000000000000..f1ecdb07adb385aca8910fd7aeae73b96e6fb6c4 GIT binary patch literal 753 zcmVNUAw`5KL4*JR{}3p1DM^G7Fmo|U zgb*ZaF;SE`QH(iNlsQ?H7&LQ2Rg^+qltNvcLS>vvVVp{3oJwh>N^PZ5X{AzerBZRN zQhBXfb*)-`ty+DxT7tD>eYInTwPS|8YlgLJioF;#dvlJxbCSk$md1OQ#(SE^dz#LB zqRtpNdxVCBgr3fXrp|<>)`Y6ojH=d*uGWmQ-i)^1l(OEGy55w!=bXCUoWAFr!{?mC z@1?`%Avk*>JbNKLgr&>(YmU7n47^7G00G5GL_t(|UgemFj_NQFMQ7|#%l6)T@4ff_ z-v58kI+oeNag;%3F)QUJpbU(3bQs5x02wv{B!)~B149b-3RrM5++W@M_-YhOBa_p! z^NY)?>zmuVm7|l3tDC!rrXULl~H^5DeTXMLzNIzBZsx3ILbwz2bn|M2+q^7{7v z@%7ys$QzE4;X$h*+7Zdx?XZDXH#7|gS`4>-44-2|Ow1-(I~_@2AEgHD3{!#7j0dn# zMpTA~+7Qv1VVgh&H}4McoT3d8l_6q0Lqs$)WDHF~K+V!G7g(bhL&Q3&7{k;V@<6RE zSs)qe85+ZihoSc4fb0yrX{hhR5Hlphj2VJX29Q=MZ&*ddMiCM9Dn)mO1my2S){xyx zJKt%*N;`XLWJM%vw}W|FaC@m>;&c!&2hYQRF+dCxLBKF}xdTFX4+jW6ogt{0D|%-F zSsyTlkcom!AaQ^({5_z*A%Fyx=Cujv3aF5#M_(^2MyNPa484C+tA~I-_aR7o(HLghqOoyt%^UK8KfZZ0hTT1K_dJY(gn%MY jq|C=(Us2Eq5P|azH2&hyKnkwG00000NkvXXu0mjffEihO literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-i18n.png b/examples/linguist/doc/images/linguist-i18n.png new file mode 100644 index 0000000000000000000000000000000000000000..20c46c9e63a10a487c27c38e4830929a4ca42702 GIT binary patch literal 22531 zcmd3OcQ{<_)_0mnf@ndahv+S%20`>hiHP2l!DvDBP6W}3AVwz`z4so;AYpVyCweE* zyYHUlDd#!od!BP$@Av)jUf0fy?78>d*IK{(x7ON$PaezST_eAC?%X-NM-Qc*o;!EZ z@!Yxd1y?SBJ1QaV&bEev6aLyuo;UPy2=nLib56r6jU-$>_604h;Mv z>nnH1FFd*DVcySQETQ@85jODH*n^AUiKw@4pBV8HSzIBsxT2P+$wm(G+D@aX_aR{Q zJ3ja%`|FWJX#Sl5my^RCp5~~W?=G}X(Ni8Iri#&3qx5KkVwt?#(EOkl?P9_{T`e1I z)j*k9CCc|-JxE?DM(6!mVi{AS^pXZlk4AuUGlRRX@Iq1cI|nx;IUPEHPbyDsTyqNt z(rBS##<^a5<>1M!361kZQD@`S#cu-mpm>B91N7*iP`LTF578r%>Z!L}m`?mh6T`Ig z*(#Z&?3#BSmQ_!3?eTD%?G`$z?mK^&DGAaw`x@uWqp*hu!=Cv`S6Z)7>< zqG)d@uI?=M7|yhW%B>h=tVL)QjOz~s@!82^lUFXSj#gR=ELjW|%q=V&1;Pie3Ivwt zP*`Ma^zBnUgrxZ0b~6MdxMHrIlZ%gGC3FL+N2wR_n6-W|ze$zl$EavB(}! z$L8EDI$K!2y%Xf!m#MVENXnkX8YM=;XRF2NR1x?&O*WjA45G)GVtyOZ>M_>{W+)tS z!&bn0Vmm8?5;H?-it+qQ6cflm;+?|YQ(d*MHTA4i77YX{B2aa8by`|jM9=fqwzm2g z&`?db#)2GOLc0}oehilZIs=iO4i__UTPqK`)7-XpDUh#`V9Sl)rSb6z*QyOwn~i(o zB#vrxON-TLg<|~odi@>(%Zt^y-#VMH{-wyh zHhtx*bBkhFcrQnI>brj3XEI_v%0Bd0RGzMDN}UW+c3E4%Uy9P}otDTNJx8E;LWa z_jJwU{4$@K23ZO;D5G-1wSA8K0qW_S56P3eZFQr$+Qsus&}Dw~wfU`uE;dI6mz{{V zt}kDzJdP^n&yN$T)CV7uH0Mo!x}+T84I@wqWy{x>6)7&1p(O0fDB835LDGmMFINAd z7d?}oo9&e`V5wJQ|LAil>$4hs<2Z3*=?~gPP%Xu!w-ZLiagVC}<4?rSCype|3`H5j zN7Dv0yfnthB=Xc0@<=Ek<#*=%`OP*TvZ-!EbbiX3*s)tS^y6me<;T3^DHMb5YQ09j z>&k2o`?~^rx1;6FJSIy_T+7mK-lc+*ur3?gd+A70ah?>P8g$1ae4(Eu6#AMc>fHu| zl=`|{bYE%3rJd>8`@47*?rf2>YEbV*pKUf2-j9ppw~Rx;?wbR#X||N>J<8i~33`wL zc6}SmW{wIeYix}dt!g?5m)(m#&TH)|GTB}_12q|o6eHTmEH>41uF-Mrn8G4Tn zA^*H}aQf!YISJH@K0yyC=9Y;IZ0z$zAv`F`4tZL)VG`;S_q?Y{B34=Q?2P!XPsAs=7zAb4O2o#{ zB*_X!07xyGWfT-Io_vufXXqayzf1qq#}OEVnA=;d4n_7KWgi(_q#!SXc%-UnPCcXR*?z=$sH(35 zL$tEqf*zV92_ax?jid%YJO*OG;=bcot1 ze+jFcQpJFc?_Bozn|3ie7mp5Y5ej&YaXPxeX1W9_LbkI32cC#B5004Oh|Fe!i64lX z!*I!kf%rVhLr05n&Txw?q5!S9Bi3{Ate>q4bC7YvBCcN<6{o@U%!>|+6b|UsnB`OV zr(~*|$3>qaiiB>@Srl}$bEz<~@;l)3;y@}%b z>8KiS*WM=o)?Ld~VRxHn$A133y6}it4r1(c$1zjN=tLeJ$rWwY<^K?FjP)K(XJDa; zd6={BnIbp2(EkH|O9(5efnrHMTF?j`2G_HGb3qYdrLOY8XIGmbjeD7pgvWepK*OLs zM)^*b@m6$?eh;S)oH74Oe|41Msp>3zz^vvjR*u@lL|CUOVo6PzFn7a`x_Nbw08S(R zJ5B*gzPv;KsXg4?fqJ5{_+ZlD>Ucf*`|iX%fer;6_=UKO-1rw%$)F_aN{0MFIkhC8 zS#SX`Sf--g%`GA4wTMz{&mcU?h$|B_DdX(YI3}_{YBxKnHCXA@8^Y)-u4i)*$5xMZ z!3;x3Ai1A-?<`SU$(ZYEuOo$Py zmBS#0X@Y}Wp~kibSlXCcF$j;)B0w{@rdXW_pjHxjhV@+n2_(6M8ZY`-0!0H*>g}^y zkqI{606yOw**t)7d)6?3)t8kLayDmhI(Np=zp^p;@g`Y2qvQ5>Rj(QmHZnUlYYx zCG*H`SK9RU(br z62M9MzMVlIiSVfPd~%XLQ|l2e=Ei4fIM#M7tW|X7GUT_Bpgs`x-CJ(*48PC+7>SDgI!Iz?AZku zBKKqz;=Xr(nfk3#;ayAB&J5wqsEL>w5s`%yqAmTTTa-jExV-2D9;qzbTwUQa6Lnqv z-Xw|r8V+W#eN8lWp-)@^7pnfzF}Z)R(3O7vSkXe*e#JcJg=o@;Vg=1hKJW}#)JVC7 zFlnDCb*}sNW;Io9nSg91lU27SW}FH#5J&-I8oV!tUDd$!rmDMErGEV?O+8N7GlEjy zfNJ|4`8A=k;+LgoJ}2aSR#~?dR8Xv97VA7gqC6vX4S711>&-f`m<0ecntKt-IZ>VA zG06EHEMPkUWWy_EBg%w{*=Biq%5`VCp~~5DeaAi*9<5m{S|ge1_~P=XU-Ke`3fbG> zH%Hs=E^F{Q$8Ieh9dRZPd{B4^2EgEH+M}Y9^kTT?Ahx}AJCj;3UGnRa+oqLCr`|o4 zB@~YTb|%rS>aVV@$}^0398<^@oldL)D4*o+|Ni3%B{j zpL}p#iCr79E>N6Ph%zK7oej`xfAsP)7$+r!$`G&fBP}eJ9>y{Gn*VOVSL-N46!?b= z-K3cNB~v&5A~Cp7u!6tD?Qib?E`u#q7YKQ78{@ZBX>w{+W@2X=h6K;OYmTG#4TEsL zztxO{D=UQDYBst-Y{=LKr1WXNBxE0^UwoJriCco-l0Aqm_xPOO7FxE5Bmm7v#QBw+fcA_qaQVil>w?fxMpY~^EUNT?^r(Y^>yN5ySX>g#F=3#pE_SdYSH$qW1P%cry&bW@$S)PL(CF_9Xh-_ z4_fl-`fq=x?yYk%)%=q789P=|XD@H1Kf`B00;n!-waeX2w1~{gwa{!D)wAi2QaLd` zaf3=YnYJd$I~!S z4t217;v>N6+PzSa5F%O0#RhTPVQWgg{SD_bL@&W*s)*o;%21$x#kk#uNG#cPt<{@+ zDTQGLp<3F7`s2rgrSJN1glY{l_t-pmEgG?*GP@f*&Wj%t*vy1-t~#}n-fTwl5kiQc z?$(t#CvlA~w9(Uj8f-35uW<>6LFI&@FunWAnUB|QbtwsZ(YNMI_p-M{Ta53Lgu|gt z3xk|{#z(p?&Yv&0PdhP>m#N7^G7{;-$LjTKA18jrSx7uS)r$$-wC2XC#I8=bF|%1= z)f^@9kzTC5vH!ptnheIx2NmnGt|VB ztl7xa5a)0~n{d+AfF)bYn=wN*tru`013jGyJmXz9iA*hFu0})8*66H98$!PpD@C=g zjtv#7_!oUI+R;JZz5M3lSk2lvODT#_g=&qbhwD&SL)7e3iZXp!aGgWu^$E&qe!*0r zY*Psd*@&E6DT5w~*`K}jFLzgwgtK=ey`PQmu19Ij2V{q#`l0( zQLipQ?wcuDj}+$sHT--kbp>irOw)_DWw0&G6y~fg>kWZUgJHWlwdI*2%feMim#X%M zTz6JNlXnJU%<`_Gj5xj4Bl;1x@c@JJ|mXnk+fygub{($#SpNw%<0p3{G+ zhPlskaY#pC1zjj$G9HAYsryPzOL@<+@c^jJwV5bRQHLKdUq1YPK*5yfPjFSBbSwN& zLSZJBOM%dM1mAd;kuAa&$`ugEHV&G3K#z!J8`P-3C`)vofM?6y)qT^71F2hYHq;KNHX1~KD|*5}wAs1%c5{Xc#+E9kc2cb- zA1!(~kGFprFcA7>(20*tC;4LKx+OM|Z`#qE_Lrn=W|Mr1Sxap_>>rvIwT{T)d0ls} zYD7czYwYBJ4$--rD>!idrmwhyN%$>+k%s4E5~#AiY`q#sw0}0KW9DDK^ zL0`{E7=2sW!hML9Z%Q8_LAcS{N^iO0bDBZt4g>?WWHdQ}n)DiFuc2B}!riuFED=JL zJ8faMKT(-FH%9TlXTyB{Zft(n<$nOX|F90fmnif86V3gH-v8SQf44P%-{gNU0Wjz9 zh5o(U0G0mM^WRInr+_l1uPQ&Ujh@MU32jT#E;sjk^XALW@_KR|I;CK;ecgd!2nuhrHzCPb*pO<^<6Xy7_1nZsq$$mRx}I1qv8Q( z@Xe1AzJ-mahh2V8M~q0XxJ%f&zOuh- zx%6@-$@7E5=zEe7xJe!iw$qqh zm&lx)+eE+;cR`@dGdcuT%tSHlK_3-lj^kE#P`Pch=t>B6W=qwz+rRx(`jC9o;aMp( zAJ?pl=9JBj?h-^C)h4CDBW5-IeOR#1;zUu7?MCDArhMxyJN&xQBFn^+^JFj8EL?+djV$it%`YyE-nou7IN{Lb_tNZZ*|c} zP8OXbMvwKjpT<9?ioWFqsgvBo*-c;1xJ7$zO^?zIt&i;97B!(OREJ|CXf#^734>q?~RuYq5{V2D=cf#w^N(I zR3MTSOXNg_O>9SZ^LG}I*)!z)L07KjOx8)OtSr9Hu#3tb-i;!5Q}k&V>A8iEShGTZ zFzbviOKIG(K&KwjEDCeEzBhZWbL`~0vu4%!I9#P&%OzX#jfm>qaT?Oi#qZ*XBG|F8 z!%_;ACn*^>!y8WG4@VOq61h0qRGEdY8;ub1h%796jMs!5>=MT=gbm{7G}7DTzTru} zDQy0w+duzZ+#`+osgOGv;mLxwJ601vR#jKBV5c&c1~Ps|(|YQhF}?RMN!XT|d2z&$jhFF?Yx3H9wWS z`Z!@E<6_nfGvb8&@Ve`hX6^|$Ne+rTEv)dk;^6|f3CTGs57yESyi>kZ0?`X@Q zODL{e^=$#m^B6m;z>urn5DRq+46Wb_K`zVI0i0FLbD#`f+b!w ze4N^9hzIB!VdUKcTc4)NA@b!dst2zC*I)y-E1|jIQn18FU@3qqR4Z$ zKkBljIsz!0UphNko$|bARi3Gdoq#8Wp!AF8mLD8es}xDv;TKJw$s4=0F~L8UUG_|Q zsr>8JDLe{ho7wf95r<_3{d*g;dmAI8g24D?4BlZdU3g2hC6G^&6n7y$JkyjNXaomR zhFv|;75>ki1+)Y_=YFf*Yh{o;nAWZ^Gx)oZqnY)!7fNF(kcYWOj;G2QN=Zt}jr<0=hHYimaO_tLi!Yq?Sma#dw%;&OOuuFndhp9n7x+P@F3BQDiP17Rdh8gt@KipWWT}_DTE!D z2}r3cJln~`9j7B5Sv=-KgE`mAHioR4-W>q%NUX=qtx=*|Kin=0j5@6mkrNiv*<{K_=f}z_7B9(@vZ8StgSb}E zxiKecPI?4e@pdPcoD=sLPu{jjffK-o+X`r_*YNYNf2(&+%PzTiN49A>+}D+wL10aD zAXTNd=zF(AaiMcr>Cv;0-k9|STeD1$)(S4pq%|B!^l+^~cuY(^L8+ah>eQ>Oleewe z<-@xEQY~RzK@PFBusK=;skrc)=>@+{Vu*&NCa3#$<}ASCLEIu?1(P)uHjy&xYl}S( zgOn7@k0ekr?@^6s-d|c7Z zVl14WUbRzob5eaU6E=dYY9y?rcx$rM2_MgqTF9p8*@HRvnv)y1s?Fl=1(*^|*+k_><5N@yaJROu+}#cURdtg5eq0Lz$cs;!mWL3Ba!6L!$MYl3 z1t;G|RP{f`CD$>9sp_#k7fXD@fqj2Bt4+YP*Ll0_Ygt&Tq~yS z(^ck5_9rW313II{6ucqbu$SS1oAg>oHTG+!@2v}l%aGg(0C2MPo{v`N7S#;GVFQ=agY zd)qs@sB8Nj)FHVmc7Uzv7_@`5ADhK|Cnyl?YPJvIGw5obRgMCj+N8l=@pSjepvMD1 ztG(YG^vKh+J9D7H@JJcQ4m51&F|ToKBslIrIF=D?p@lX7<;&$ZU`}Cx>-+ul-|_12 zh!$`^za#B`L$v<@oh_dMA^N{^FnP{JI|WU0s{BjMv?fyb9GTFWz9V9ZK#eF<2mYGkNQ)TQ^YwKY-WV5Qo~| zeyl9j=;Lq6M>R&OO_PE}FJ;9bS9QwM>?Z~uJJIJm+Fn;rCt)Fx=feOXc5RIVcO+19 zlx)qkFc;e2L2gNR{d>y_mIX&vr$sd1J$C!kTpH1?0Yd8gqAGMQ; z-@eTg!^K~YrV;I~QNbQq{>53h^(YVYmQ`hoI`qg}3OSS|3$3jUUJmut#p<8`2BD8G z{qc)WBJQc`c|ElA2gcR|sBl`cZ(i3QtcFm}1C zGj^=P!(vaxWG>_St=+ddi|paoX^gH}kCzsx#9U|PpE>yYiY(w(O286CY@N{m-OK|7 zmYqM(yQq7f^IryPU0O?3I+TCRtsa!}Hi_6H(SzsvIOowUU@ms<>azL>!7fCEtj?5DR`3l6+5HR`2miN9v%h+xB{c;}+{p93(C? zavM&w7sl7>tq{CL#5KnpVNcJs(6@&_QDqty#FfeAp+!9Q8-7)HQBIj}AhQ}y(R7>r z%A`f=iw%<8$C@vl!#*m8+ctlK1E;!~%8hMjv<`z{sJ#11OKgX!W%{mK9GNaa( z`}%l89&&Y{@RI+VS6Z~NZ?uTk<0>Z%c-BscNzm_(rWCFhJKM=i(>lTo*KP}8{`#ng z5I@|_wM8M6(7Yn)TXyxcL=8C|YM?5PaQc|6n1xmKHJfB`i_M*U#UKdTezcof<6A#Y&Z2r?bRnty>o3q<}%82Ip}Y zFGxrtV;ok{9gTKP7a{ezZV4ll=99(auNk(^vs1nb&@#%a##FYeSL-VyWs0ecb6;o= zFr?PP4hl;=G-u=8X@;(c@BD)J6koPax7X|}8=o!e;Ul-RHah~ZF->!{rqk4Teff+R z&)-A9xN=V%^+591$nj(y7yCKc@2hTogSA#(Ph?A=nY6oG@yuwplf})YbsJ#Ap~LcL zI~HWt#;&voCx=#*_-vPr6(c>y04*$nK8EYTQVKHwr;;qQL`3LxbHp}BL4^5zPfwiF z3%7Id+OdKgI;V4MTC+P+tM)Y$Rf!kxf&=%ffF){+^#HT`4nO2SC@}CU>D{eUzGqp_ z$Rhq_5r=JUsad!(K*b}mxma#_#V-whAgF#|Xg%Ivc9h`2hD>}x^2s|8gCE1-Ej$Kr zx_%Wpk%ebXi=I=^HpNkm>|h&l5w?1$N7=KUL9MYzJwgsLRwdUn5{7V~(>XPypx5Jr zP*{@E;FAn>$%2V0-L!DIMbp9tkw&dx!Q+T2djA6-xOU8N)uXh79xl`GPeapTjJb!Y z#8}GtD)-cXh?YAnX0yz=?XIWxsN4l2URDn0ES_&5RY}N9mE-W!EwRXzp^5J9p;nHV z%xk9P*^DSQJQi2TZ-3FBmreS_9Moufs53we$iuA1Q@O6Q-^B58-Bc|gd{1&djlllI zg=&Y3@dC6Q;53GY)*$1Kv=x*S->{Z{w3$0J4Hb=x4y!-3KZ9}1e-{rArJTlxn(M)U z#|jt@;IcB8iX6|eQrB6&5vE3|M78s*(HnnnP+I{nF-Hi40gI{T8m7OOc8S_eDknJ# zmCFxy9?A`tY4tfkCRd9DZFb@OW0(9dO`bh4QrCYY7LZ+5v4MezyWcSoQN`uFMMK2k zSrfB`Y_%-OhKi1V*-WNlEk97i*D)veinK8X^q5l=O`T`c>Nl(24+kanqa8g_ItSx) zN9H-&qc*s+B!O@_i-jl2-WSVw^x~V!v+G{*iMc2dn5{AI6?}N!em-0zhZQf5ms-TK z#CE%|b9B9-vu9LbZ_&%^$ZAhq8-S)YW~V^DJ*qj`l3jVmdvpQHGk=^!wtcKh6 za*n5Byfrr$Hnv`%icsbu=5|3QwXYVJz5^-w_w~6H3O9Fm|&j9Rc(7V2FizJru zhSS0Z+&B@2l!vaI7~~b09K3YXq9G|5P>EX83s9k!xWa{>WaviWkuxg%a|{4_nFf15 z;57bRpvdFK^%;oRbpt3^IIlEH;M;?G{_9sA#;sin{qythP}skC!2b=W{flGncOUt0 zC47Oq@OL)zcXaq~(C{A^QH%%o-zo&|xdK=~?AsFApJ42u%9lgWZH$bZoQRTW?1S+Z zQ){>~>=#u!H{MnrMDzv`$CS(x4)>NKKPb!KSx2J$3U zk2`SPVAE6g)at%#zbR~Kbw9GkH@VRhwiSj4d~-f;UzSTLe2%K^glS^P&}}tiCo9B5o1R@b4^3kXa7Pgv zxTXR?WMEwnjmB9q zXw8u++EeY3@Kz3v4^$VAuNt2m@Cny9drqY<8&j z%Oxs{%e+(;JD%UYGv_-i7No9I^X^tJVVvT6Z8xj7!}u=7O{nc=SZRDqXs|~nH}y=@ z!Q9)>)9OuCZNQ~L;}jyf)Xvptp8yd^gDZtlW%$z5BGMa2>ZD5nHeCBs<)EZ6x@##R zA$cgZE9%0jD5!jq&On!Uuz&m)E!xy1&RV#1`N~xd9whJ94A5@(43kdlh4%pEn`D={ z8p~xp+FxgB5zJ^1q&J!JXhxlCbfdkim>!YHhd*B3yD{Yrm{RkOqb!m7Uto~Wiu@NE z{7zh8iy>Tpfx!@H7`3qO)o{sQ%wu{O3jlpRQ$Dy*t6#`1hZ`U_>akn3ZkD5t`H~Vo zM^d{AV)5w-DUA-64~PoHe+P7h-Y7+zNkVq=ZUw;~%aga~EvmtR*r3)Wno5YLi>i=D z)k}6-UGa5p+uKz9mBkMvIDXNHb+}DhS`5dt8^jtKf8w;J3_7}*dV$s0;wAr$>b;2s zrAYzPeGpfXDe8Pwe064>X0wP3xw`Z0jLAf?{b06!h&X#apqh;S$4~lPPAUD??2ZF&|r2um! zdojtcWkJ7Q7KIdC4_G4dH@MNJL2puMwDhEQ*K(tNZ$bmjUh(Sb#Ypm`?VE!)6djj) zdyLk{Xvm)4dmI}bqg?B_mKV&lxuI(c{KC5#k+h5jEf_!{NIQSPZjB6iBxWRwO0%AL zG9Q=jTeGtwNC#V{7>v5-eDa#>oJ0IelS+bu*O&(m%0uGY^WzVj zqz>x$)`i@9jX#W~OrcR}SZwKHo?R4l<>k78zyr914-GQlBOfU;BgT7Pf*=NGM|N)X z7)8&y^omGX-YWt+%_LW<2m7c7q1t9_5D;uq(aEWqIQ}!xq@Wrzv(j|1rmWcp%WFvh zp7FlANk)^~E#x^LKy?f7WD~_I*#0=0+-IlQbTl+P(BTmVFq69&6cZL|TH6I20U{MZ z&POleX{?S8WNE3$28?+LRzABq-$_C>{&H3=vifA(CU?DLpNi1~Lxo+#P+`tdmXcL+ z^a!^+lHrHCpDvG|0Yb0si=sN+98zD5(60G#@p|G&w5LA-h&SOt$WsaxH0#!|x_WNo z$SP$p=Qn*RCT=ihY~py}Zr-*J!Xz}ivnfSFvjB~kldWyD(Aam z?s%OFLv&TmxeNLF=FVv=#At4nXX^&j`$$NTZUhX-$>mokY;GR6aKr^-am4lW);xGJ zX7;e30-{#@D0sefkX`r?jb}Lri5;Ox_vPQqlON8^mtf>Mg;o$>IuAC3iYT??<5Bo%(%FE{l#CSU{u`K-kUL&%lWmMe-aL zz7KqHanA?g%wYGdhuIqZWTynznI~)9wqE%k3Yej#g5y4a`h19Ab@r{;!x7mZmdw94 za$nfQRie#mZgx_%Fby2(=+X|&{h#=}!7IxiQ;2FGcXM>xkMPekbLxPXrcr7PQq2K< zwu!r2rD7&R6VU7V{ksg_&f?DOAm?z5{DjWYM0Y^IbK;h34?Gn8EWSUj?bNBT763sY zYSGJ#X(Jc?v)5mBLR5$LCm{w`M-g9=kJHCD~^`+XF@;Z@4m~fk+W4_C? zXGW2TP@JRkCu{S=UPk_c_8@S{+vmuF;_WW(eZl8Z!cbO~nb_iHhPEt>Z=@sS;bdEjT_j7z)9uR})fe!RrkkIb<-oY8 zEWgFU#&>HDE?$A)8Jpk3o*_4OYGB7l@PeEX!LHCCkZT9+mC+Y#-#G@bBBxe`jEnB%P zC@c022y|T3m+3B9cqLSAkaq^7|5P2W?#7W$C?c_;V7Z&g@zJdcTOO3@~*6VD~YhiBgWaI3V zD_P(IM30~k8F$aN=YzCft>c`eI4w-@Hq7RT&lPeBdYKveUQPJQz$IkVg!@*9+j_PC zDK0qhzJ_l@uORbik;hZ`UPFZjjYUO8#fGhqlkN|*-lCuwO_RkxskWVK1DQMiZ{9dA zc8h!Eq{qUa9D*3a@mi0Im|t@iOwXOhThgU8Vu zJc{+<(uK{r_O+36m8i9m9mQGC&96*BPo#&_A4Q6u9ovcOUV@Sa!ZVhd6~;jx(eB!) zh8h&wAQ&S{f5jUvO%DUnzthvx7@Dxca+Fi61j(8`ED~CK{8{YSr&sJ~xa$;5!bYap zNp^jr!O615?c2A(Ts|$Xwp#${M-js3yy4WvyIaX;bzWzuAf=+xKN=e_SJ-7Uz2#vXp9L)Xc!uUIYunm$E562I#cHH%37pZkt}I1G-Ei<^ z+yc{cNcx59)dPia%{~Ev9XoQ5GE84i!Sok#ubJh~+(?bPq6YKy^o(Rw+GI5=y zm1z&rDZzo}(lC&X#&@Gl5wzK=S!x1^09k~HCEa!~SHqKe;f*>&x~MKPZ_E)m(I8JB zrJ?G|i)rB!)SR{t)n_I>pCKPpMvl_>%Ar%iT(&C9_dVNrMnx8AuG{lmUI+3FC?be- zmX^pH@DMwaU7c&5ja7tP9BgY>j`_53(VS3Ga`ISi<2Dd(u%P@DH>a35U-T;q)gN=8 zLG)x>g-WeKJ*3=xfK^mf-{2_f49p)~q!c`^gH(0>^7b~21JQ7;6Atmg2fb{TKuzyL z!y*>Puh58GfCN840dCfI|NE2=HvV;de6Qmlt!F2bXX9zJVrN@oveMGA493I5!)?l! z!l*??LJ+1{WjPl9iKI5>!qmibKSl4jnadMm+D%KG+IX^NeSn$r)5RSglg=mvnO%s~ zh77?4$iewXm!O*$-(68t0ukAN2(!2XCixH$&BT~&L%_9z-+o=ci~GNo=p%&e(4xFA zVjrI6xQjUB-X`cHgHF`B*nNC;-W3^0&EF4%kFM*fD`939j1F;PBYo&XEiHak-_KJ< zeqcv3!aW7_SrD32=gGB_PLc~02GNHXa2?+@)DYRQFxvzV`o;Pp!wmx3#h}11J zjo|j$$u;ov^&P8mkT-M4AZB_zJ0nYrP+I}Xh0C$Hrq}ir5QSMiT#~FO24_14*F^dv zuM%`@yi-aP;u8|8(X?6Cq)J^5g#$re>CcYjJIvbGuCz)d+M4em=DWrljb`-47d!5T z;MRNf4P?M&Z!R-2M<0|XpC~LPpKfQ45oNuhgUQLYsVIpU_r|e_*)P5&;km}dme0^- zu734|HP>nNArB@9lRQZ_c;c%#LGDj(BE7f?vKnko5pK~?Hc=GV; zD|{PFM)kRy0-_h>;yd+M6*uGgfR79!8*(%CVU_{2gBwU?JYAXO5-IK-(a-etc+*Mk zHqVw;ffuXj9wfrIzcXlHhcOtE%f0;)eF#O+GZA!%LMNGld+Ih&N@?2$r<-JFGyTOv zm6vi_0;*q5N%SRZ_4a$GY|Vs=q3 z_lKYDTAv}__Pr*_r69I4Picn0JU1hgnV-+E+E-5mxNOcolDTVg9@HP*%ZbT=_1Y44 zTqPjS+2}K>9G+di`3A&O>8}<(rtm2vQjPk`;cZR_T_$9mJl(C#CoMBiSIbpjT3QN& zr0u@|yFo(YH6!hZ4RnH-<_4E@klSLK$<<2_K9ow)E&fvl|pnly`Ybxe#4sQ{WKcr;~Rr<(!Ey(*pdC$ zjFl1f5eHifI6@vC9DQmPraclkSvwqHf<)9wpomQ{uyD81p6$FeVXmf;Y? zvPQL0EZ&^d?motgFr|Q-2Vr?lJU2ZXK@_?p2xFkTcl)8H9!)AF9+T~zo%29| z2{AYdXk_1Z^b{pXm=R>`ovcT?qf2@gS z*h^T7WG7e4ec}qzUJCWJg*A!QM{UmBnGlu<3gn#twP)dPb zLK^t=}}|hQX2vE()JgUT!h7lDE0L=(*&^7yCS5 zpO?lDT(E;h&gCC$1D$2OoFxO@E{$XrGwV(75<5%iy5S8!rJV_c&lScy+C&u*q%B2#cuC#Y7Eet2kG0qTHkXGPUN<5FbK+ZeXJU2 zJKi~rz2FKeok%uCWj89ih#8eIeeq;G* z)n!m(r$55wGSkzug22_a@3gjy9tPb~Sm@Jsc&gZ7}?}>O^ebJxQGhAwN zun6D<7c}X<%hQM%oA=4bvrMed(1^tkB6NL!j;j9EZY40TOWmoG^W7MX#kS36WfKiZ+Z+JG zh)@R@T4!+NgEUTP;p$NF=NqT%PK~*~yA1m|nC+|NK8(YhD!IE*b~a7cc$3%kiL>)Q z(Cnw9lc(=7yO%RuKSsOa#la^WF4N|8c@rl0ew9$wA-Nm3;?=cBiFo@zu#&rc1sWj@ zK+#)guV!NmJY5dA%|yz*@hQCF$FJ^4VE)mvg2|I}iCz{)?1{}pxq2|WKNCH_`%f+79`(8Yva|3%OK`-}i({Qpqm z0!Uo?>nQ&O)_GU8{~qoC z!&v^i;RGfAVN?DOH`ISQIsNyu6)5o!Tkd~2#Xq#a_Mq(G`knaKwORi|K^ z&=M=R@nhgd2Z_xnXU~!v9YAtAjIPY%)BY{zx%ogrGM2ovsY+%zkqaEdAuS{zZ*(RI zP@#X9;rYA!_&?KSes?hcp6dyw`mgbXkY9=4E&rbD`FHpK$Tqbxrv1~YoR8!@eqb(P zYWgK(bU|)7Y)4zJ@0pgK!n1|}@0WOdq>!1C-tBpicQfpi|-CJu59I|+0W_vxQ8x(Xy3GJ*mNyiNrtYNC|U5Oy$M&yg%pq!ie3PTmIruqVF z$6WLQ#k76{k(d@i>FU2E+2}_<@IJS|1cu2jlC1Vhub}h=A@PS#(Xam{VFp)tmD`%< zqgGA{Cq6PyJwU5G;1aNd7{5BmtE7EO4Szas`5MF+luIZ?4%N8XwRTdye*GHO8C#GB z{H|^NS>?Std&n}&Nya<8=}JZ$kqfaF{$+A?)Z{4#xR=x^V(`^JVF7SKf*Xic+c(p*WBTir-bL-RVh5QScyc)%F zvq8okqo)yr=k8s;0S!y$DHWe-+41p-FSjzb{S_kU|E;K7|Kf-1FUR{kZ;Y6Q?;TUaK-%$NG9gw&xraSvrkL z9vt2WeZ_>~-0NhpFwcc;yq3fDtMlDMeW7Jhiq)?gDnY#M|%RYkGlk&&n|ilL@mweKCXA3 zJO1=IQ>`tq9#K%({?g2-saLSAjQq!fv^QLh1abu2TJ{rPH;@I{8if-eTmjvKvTI$z z9+-@%Qj44ietbTo#n0;FAo+{6uh_Mzw}t*Hl$&8*wvm96C=UtpY@+W2m*q9euE#sy zsDH*X_J8Xp4EH=*Dn=`NY46G?Y}g$fNVwPEW@Xonzqi8@JytGOaLMGW8%L$=#=3<- zp{+ZcF6(uw(*QI_T?xzv@a;4dg8k`hG?CYGe?%C{+`n7oML2j4dT1&A#DyL<4c;lm zH2$UiS86Wn!XMc+oxmv$@@ww%+E%IQjlKVII_{^#zsT>532M}aXVf5GT}wXvw${oR zQVRmpsF_Hd6+s%$2<36UvLQn0M^%VE{;roTnwKh6Bw`sKDpY}X>h`I56|y(cy>rua z^e;I&;O#An*8t>YbO@8w)+jMaGbbZTFu;FVJk>0c>8xbV! z@^(kvOJ_?|FL;5#d}ER^l}JQ<`q`Zskj3-b?YAtR= zR~iE;<_34G3Dxqg#Q^_S5s?vA^^(hxeK@CkqK>YOIg}kE)?70SI5tuW8^MHKB0DI} zfstbCw4-c?4_g9{?~B4r&#;eJ`sTQgt&z!OSkU*q%|YcQQVKSK(b*ha=pJb{(rc#* z(TPVh64zS!I(kYqO7hBN9X5zrc>a>dLp}I5$*$l0&d&(S@6htqQ24~c=9b^St7Lx) zQO#qwuh7J~W@$U%^Sw@z3;h{jx#DLsXMCg1T9`jTcsw3BB^KIE78JM|*F-UIYB-Ik z9kdwC=5d*-i=1q+K?pNgHu7$kEOk5Z>i1)%eAN)emi`8y&uGDI4hNgZd_vETwvc+V1aG{t8`48q9 z&*$@5U(2*#x<}eDY|cf+EW{8)uc_xLtkD;Tfj9!TYrJX|;Dr*Vv1;rf>>zqKYaL{} z#j8(i?w-$G{}q@iH~j7E7RxdhqAMxNZLO-ceS6Cfyr`+Qd66B97Vhy;zZVRLi;~sZ zVN~O)QuNDe+%t-wf|fDc4rD)v63P30DpAmFLLoHH^riu6-v6oO?4y!M_c(s**72r! zHg!$2^)0jOE372yHD6A?R))#?4km>!1!$Hg1xs>ubA6%J?J6Rgl_uD(qM1^bid$mp zqWOv@Wu|0>iUui~h}=ipd-m*~_n(<_erM*)oM&c!b7r2;_cP2vJFA~>@3b+&X)ob| zI7qSIYJ7a0rsg^W@Kb)o3Ec-sHVpo#_%Us=F6^2KR;A zZqO-o#m5JO#?%ngV$imK_pG`44_{^~F%9IEtR5LF*y zOPT0{T0BnK=M{IQLYkrO^}HP5|FFz8K2LNh*5Dg1!nWCh9Yav~*#V$)@G`!gjmXd% zEqYO^YS!`~`5asalkmsw^F3SlZVqu?5<^_Nb0%~jrpOfQT0z%sc;^wLJOPVPAB}FI zOCmZfIFs($kKp)M{fi!;wH^lgeYE ze21a5vGp4$$+$*LPqnQawua-Y4Kf3I7+bQ6tDWo&c08H`wCov->n}QR9eP+OxxE)K zq?(+$X}FAYH4=Vzped*>-Fdml7()XuB&vBH6ZP(F+f&^emJpAyx_|<)!rQXR4a0bx zHY);5z~`Z_6YHeGyvu?LrwRGxM0}grkNbznpEB?SshW)CSDah%sEUfdN2s5e=(?;cG0i*g4UZt zCRItf&?g@*l(~Pu92CrYwPsM-qGMpHIoEVk}JJ zUO{_g(qu}(7T&S90y&x{IgE@)o&vz`O~?H;19E@ud|dh)pgq6{c1v-IQ%J9$-i|-N z(R`lGoZ*TZ!zIEhr8B`H>nn1?+n=%U4=UZ^CO_}gGTTvqLP#I)Zey1L_BUi{E|yq3 z@XUze>@Ut1C07diTORzQ?$pRUw}2N|mSTwrtuUFZ0>DH!0>YR)W3k?56@}&10It_$ zZl9IT{o(;1DI(|EOm%A?)-I7GOR1pD1y2liQNiO8PI*s%ts>(t5k-=a2E7&#$DGA# z5?l9I&N?I4S7aSNkUS0HLkcHO6QuO0;Iqq(@xYqY-c?-yEs zx1Nt|wDwo>e82s2od$&;vp<6LU669!F;g`uNIprkhSDa=^?#0UZZt~*DXI~Z+dx5c zR@tBkBD6KA^`=o*3`fkcFRs>$7R5-|AL%Q?2>Y9c*r9XU(N1RvBfNj;gU}Qj4#w!A zZ1KN4O@wjP)(ye_>R*rlnX3{FNq;I{4G*nZ=8I2s`nmnygek6j_*{wj@w3f!sBJ<; zROP#24pr-pWS ztzYTM^;Yj)Oxpo)UFOO6h*EfT11`s3YaZ$|QcLWG6I|2UALv7p@&;!gLc%RLsE5Vw z)RI+~=@nOQ9^NgPujkFhbl&KghEd=Zp}*c29;uK18nOoy_R5FELc3K;h2)Qi>2JR3 z6%alQ?BA9$PQEyuQz|Tl0(FqBj8tT(;ejbK$4=>@3zm$|Iqo=(88tb1xhrl-EHJuS zELy5L;|@-}@|amy{ZQ7SiP57N=cRAAYwtpxxV_Dx+7=U5K4GQdaDo0VAnlIZF3Q^w zY!$@#xM`J+&uluwEIxHgkc$S8FmX}7i$=!+O?p&;RF*N7{RObHx0MtFn#bl h;{P_{c0pBCt>CJqcB(;06-)_ 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 literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-localizedclock-idbased-dialog_en.webp b/examples/linguist/doc/images/linguist-localizedclock-idbased-dialog_en.webp new file mode 100644 index 0000000000000000000000000000000000000000..212bd8773cfd0d2148a077d969c828565271b8d7 GIT binary patch literal 3626 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 QRcA?jamatqOcMWp0KLth)c^nh literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-localizedclock-idbased_en.webp b/examples/linguist/doc/images/linguist-localizedclock-idbased_en.webp new file mode 100644 index 0000000000000000000000000000000000000000..432b77411fce3c956a62773771d5aa4f4c94e268 GIT binary patch literal 7444 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

78%b$~Mp`-~Mus`O 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 Az5oCK literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-localizedclock_en_GB.webp b/examples/linguist/doc/images/linguist-localizedclock_en_GB.webp new file mode 100644 index 0000000000000000000000000000000000000000..0979f398e35adc9d796b3acb4824fc198d45abad GIT binary patch literal 6452 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 NgmOavoEeDle*le@4l4iv literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-localizedclock_en_US.webp b/examples/linguist/doc/images/linguist-localizedclock_en_US.webp new file mode 100644 index 0000000000000000000000000000000000000000..57c27ad778454f286a415775e15319f33e319f18 GIT binary patch literal 6874 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 AbpQYW literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-localizedclock_switchlang_de.webp b/examples/linguist/doc/images/linguist-localizedclock_switchlang_de.webp new file mode 100644 index 0000000000000000000000000000000000000000..7777a7a7033d6e5be9d45f4d7338da2366d36d36 GIT binary patch literal 6224 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 literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-localizedclock_switchlang_en.webp b/examples/linguist/doc/images/linguist-localizedclock_switchlang_en.webp new file mode 100644 index 0000000000000000000000000000000000000000..9947a21175597bd0f6c4272a3d7feeced60df5e4 GIT binary patch literal 6208 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 HaS-r7qwerP literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-trollprint_10_en.png b/examples/linguist/doc/images/linguist-trollprint_10_en.png new file mode 100644 index 0000000000000000000000000000000000000000..e460481f96739c648d1434d1436ce8e4ac867d8c GIT binary patch literal 1951 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 Ze4#nz^-3kj@+E+ThkPI0O!;@g{{jBI=bQil literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/images/linguist-trollprint_11_en.png b/examples/linguist/doc/images/linguist-trollprint_11_en.png new file mode 100644 index 0000000000000000000000000000000000000000..f718c995c0dc9650536e4c6c20a26ca4ffa815b9 GIT binary patch literal 2019 zcmYk7dsI@{9>z4qF_{aL^L{bwJE ziCRym#^VT^n8hyU##bs7@!h8RA!MuRAHcRMntDjMywQ8 z8*!d|X@gPPU?O@$$VC&PGb?l^oHt+5X;ySksk*0(_fYm`MAd0lb(?t-En~AZE>i_p zq0|T|(UT7s!t_`vBsUnk4ctr>I{`s-CMrvmn5z-VQCN)vV`LYK@}(? z34!D&RE2_mB5*YnC)9|l4Ae+zVzx$f$|zE!BDFCwR|QL8SdP-eV3IeV9wDVi!Xz35 zHyGgtlT>ZV3grnkU@**1S4o?U>`ax^(tLxFSE$F)AV#E=9*ZCs%|sfXSFA_c&HO^G z!t%6s6E|I@XrJOs^oq_YezBI3q)@e+!7vyA=BUB2s^@opnVw%};8z&9*(zbzRtEsE zxfBt~ir=67@>u%0pEm;QddhWIW^)CWk@U#~q9+G+|LEN@=($+e@-oh6mN~EB0K+wE zS?tE|8m~K*KaAR{W)4R5@3VPco^^IlX8*B+)Vb$XwO`c*D$#q-hKw0Kw|bT@U%b*M zV0^ExJ38+Tl&4QUU3Yjb04;k{q)R(EOZFNY#ZRXa1oFGY6f( zK*8gY@9%#dxTNxzfdTcH`R{2LwhWf;i@ws`{Cv&R(Wt$%eQ*1IaeVB6ot@6siOTt5y*n^RY|{KhJ#YE1+`^2svH#Ck44j0G* zZ9ww$Bu|3ab7uX$ZpRSzWTfwjK9+JL!}kQ9=6voDxsFTDp>tN;@Be*f4tJ;S*y|iu z|NB>hh&XJcifp0rx`;MqEug+)2()=$_E?_wLL$x?ZuD#Mm$A3lG7EiUIUSDT0BoR< zNb$qs2v%$kb0^`053ztZ=c{~u^Ob~a6~Pf#e2pF7D{cYB{#c7B&610@X^Szk4)(zu zr51TbusQ=$@G9~S`GTFb>H@n*^KKWAF>e0YUq1r_2r9d$+DNrk+NVOu(pW_k2#3qNoxqFf^ z36<92!J{%E$HBV@3b#e9r*a0`sotSIhXj|??>xQBpKwc~OymqF`Bd#}3c|%S9Gw4Q zcIo?p2DdS_V^<96Lkg94<7)HVe}a|TdSS$xP;1YqhQU?e!u3ldaa$gD{(HCvYwsyz zVvb^JtuY;|oC%At@A7EGQl~9|RjHHmq1XB=3xT%@+c!)Sfo3;MEgrv7?7GdV%l+^Q zrjA-q>}<<GygPT_*^xZv;9{y;J8FZ7gIU3j_y-?XN{F9i3g!T-aa*)O9N zV}f8W?P3}8?Mgap_Q~6XlxpQft25J&UG-=t`}CrCN8QJGYj?ob7$*jjT5DYwwWZ5A z_7#lma``t!+sd}(c(H~~u&w%IzHqzb)fh_qRnEtOJ(mnYz5pJbO#b-O0saH4ezv@x4 zA@4Wa{?Xswv^lz4_Xgc{-J4fZ^s+#xyoO_nZccCS7%CZ?q)pr;dWWMfjl%V4aLD+* zi(@Byxk|3r=Y;y>>vhV!TuFWDU-NQQ26#KT5_Z^O{hwsLjkS5kOA(Jf2{{@n7c7CI zm_8c%g($R#x$g4*1o4*kXWT*u_j#;(TRedOl6%bL@n)yPjKQ-!^c%|9?wS(D)`H{B zm4RcsZPL(v&;NQ@|B6K^z%B^?V%HUd_+rMqPn5_X>Xx(?JyFlXr)l0MFC)qtu8Gu!Ia9>iGCPsC8O=m{Bxi?qSgY8mJuA`V zt?8sju}K)G%+yJfFmG8(wceyhk~la!Pk)^I`rO~|b>G+b`h2eY^ZDmK6CM@}HrZqX z000>OV<17-mAc(#q_3;S-`1xCfZjy-USg2$IHT39uC9tYv`|kb9L<9Jv0Q>U$WWe7 z1kWc*h}9ulh>a74=#U}`VTcJjWJ)MHWJ?lqbdV+F$vD+2PMsR!#)Nt>L#QHNotj=G zcfs=@ZgejFQNY1>VWR@vemsMD*2ul@nYE_(CHPn;Et5@?HHN0kxP=^k!s7ot;_*FfiqGZdU zZcI_DhE=8{WJ~B=nUMn>?#B_fYB-lw33+m;8(r9_VO6UMDI#T$(g;CE;&~w{Vjq$a z>c?UVl&mrpAwxpQ6tim8oIH+CoQRSsM&daT4<<(^TCc&z35^g;VXKyPL5WQfi+X0n zI`y6zQHO@km4{GuR6Jgt3K_~t$d)iK$c4>X2!a`sA%=LcM9mtXC{aRzT->7-wQ8AV zDganTl7ym;8HguS)HOrN5Q{ssgcPxh&irU0r(Q!Z(mh}46D{I3s^OkYra%tIuw3vQ zBR3|8%Yg@Rg!O7+y++umrWeSNK^zxdb{H0cA#uTQc(rN}>-s?eFu>shF~s9BA4*;w znF_H2YK&2+lG0Mk;!@qRL7`S8G~YDqp4@Gr=l5q`UBaJ{KFPJ@_q2KUcB~tSz4&Un zVuQVMeB0E-J#v3(j%?R!bKgFP(AtRom%g2S#oDvdP$4<+-t6kGwt-~2w6pb9I$Ct7 z?WuO+C4K=SuTljGyR3M@p_@P zZP7FTOExgrYEt~L;$`#6YwvMWb93jfN7g?|J-lLA!xArH8TRKc?sE$6WYAMk4V2jv zuUBtTnocC{8&7>SpR{y0-&DJS#%dc!y}47mNQdFL}O za=1jkQ+~?qtFc9J5h%5xkgve4@UQlX`jDCx1fZPNb>g2D)-x#ex*I8Vpw}(!~*?;5f}SK^4>eQgz@x!=is(+sAW-4 zUu`E8w#vXxwzM$bEebOh>td%ydqM9~5YhCk-;fBK{NyPNBc2w&5QE=Ug}#|{JANv1RLQLAM~t*Mja52KJdq^@yD0Zi z=Qqub16$hfde0$2^d@6oT2yfz*f=0nf3kCo{ldHQzQGNd-p`CwXh5J~MF!pFvN`rN zgIxz6x*vTaQmyK|t&0x``I z7#K-bCDqZ!M%_N=ek;S=Z_<4PGRB5T>r!u&!`?F#x2wVX&+wOBVOyb3#!l}h4lC0T z!(7ZJckX;B2@i8RqhFu`1DUYjZ60uAV6%7h!Z+qmbpeYE1oV6bjKVXSA%^pPNnT+Y z`G;NG_hW`W5}ky<@)4*y_Yb=e;7b;&nu?q2J$>L( z)n`|nue`MC$ktC)Awkbi%r4UM6F`+FHN?_e<*h47|9#OVJ}{l!|24l0N3=GR)@8hz zJ(zc(lHMx$X$$mcZ8Bxf@{ubx-ZMIgJGO^A=FtTz=lkoA_`o8*^x=dTk2>R~oBBJ< zH$StXU995PD}wB+y2+n5roG+X1w!@e#=jR}jh#eV|LAndI|a{oIF5Z!X!0v=xGV(J zjaP&N#6~MsVRv)=yrp}Xz>71IXTU&QayfMD)L>+VU>X|Gz_!D^BT@mzxnlH{ z%`t@$H+#v4{`RY;GXzPN!`APxk$n3vS^;@6|C=Qwl+p<^Q)p{!mb(q}R)VQn=4ZX+ zqdcU4jlcQkF*w)B%j1;NqY*}MFf@F2$mIAA&TG5;2cVbuzNQ9CV4p$!8*NR0>CQ9# z%BHVYL$sH)?Vg8^cn??#b{AKfku+eo^S>%lEY*6$1yyAeESsR=Z`*T>hEgmn7@dSZ z^NwjS-5hE!Kdr9Vd9P*F(GMc%Z&kk5juNv4xo+0~TOPGaG_qGDzrz3dQOh6r(`>j8 zmb-}hZWeFaVu&gdTjfMW{gV%%r9q}Q@%DV+-eW9V{qz>NUd`2y7&6gQ_X0a2hVpbB z@t>sTe30Vt3nI^#J$v1;sAYihonX-H=HdGyidF2i={4Kf@``AZ+~xPOQ=p{CVMDT( z7D?Kof8()nP7yUSaxGbw6n4z3I2sa5a!sEJ@H!hkG$PYG;6#h`Iu^txm6yY7Mo9sKG6QzQuk#5@IhgL_1M_F FzW}S}`X~SZ literal 0 HcmV?d00001 diff --git a/examples/linguist/doc/snippets/doc_src_examples_arrowpad.cpp b/examples/linguist/doc/snippets/doc_src_examples_arrowpad.cpp new file mode 100644 index 0000000..4d11681 --- /dev/null +++ b/examples/linguist/doc/snippets/doc_src_examples_arrowpad.cpp @@ -0,0 +1,6 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +qApp->translate("ArrowPad", x) +//! [0] diff --git a/examples/linguist/doc/snippets/doc_src_examples_arrowpad.qdoc b/examples/linguist/doc/snippets/doc_src_examples_arrowpad.qdoc new file mode 100644 index 0000000..1d618ef --- /dev/null +++ b/examples/linguist/doc/snippets/doc_src_examples_arrowpad.qdoc @@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +//! [0] +lupdate arrowpad.pro +//! [0] + +//! [1] +lrelease arrowpad.pro +//! [1] + + +//! [2] +export LANG=fr +setenv LANG fr +//! [2] + + +//! [4] +cmake --build . --target update_translations +//! [4] + +//! [5] +cmake --build . --target release_translations +//! [5] diff --git a/examples/linguist/doc/snippets/doc_src_examples_hellotr.qdoc b/examples/linguist/doc/snippets/doc_src_examples_hellotr.qdoc new file mode 100644 index 0000000..b5f9313 --- /dev/null +++ b/examples/linguist/doc/snippets/doc_src_examples_hellotr.qdoc @@ -0,0 +1,38 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +//! [0] +lupdate -verbose hellotr.pro +//! [0] + + +//! [1] + + + QPushButton + + Hello world! + + + + +//! [1] + + +//! [2] +linguist hellotr_la.ts +//! [2] + + +//! [3] + +//! [3] + + +//! [4] +Orbis, te saluto! +//! [4] + +//! [5] +cmake --build . --target update_translations +//! [5] diff --git a/examples/linguist/doc/snippets/doc_src_examples_trollprint.cpp b/examples/linguist/doc/snippets/doc_src_examples_trollprint.cpp new file mode 100644 index 0000000..667f674 --- /dev/null +++ b/examples/linguist/doc/snippets/doc_src_examples_trollprint.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +twoSidedEnabledRadio = new QRadioButton(tr("Enabled", "two-sided")); +twoSidedDisabledRadio = new QRadioButton(tr("Disabled", "two-sided")); +//! [0] + + +//! [1] +colorsEnabledRadio = new QRadioButton(tr("Enabled", "colors"), colors); +colorsDisabledRadio = new QRadioButton(tr("Disabled", "colors"), colors); +//! [1] + + +//! [2] +/* + TRANSLATOR MainWindow + + In this application the whole application is a MainWindow. + Choose Help|About from the menu bar to see some text + belonging to MainWindow. + + ... +*/ +//! [2] + + +//! [3] +/* + TRANSLATOR ZClientErrorDialog + + Choose Client|Edit to reach the Client Edit dialog, then choose + Client Specification from the drop down list at the top and pick + client Bartel Leendert van der Waerden. Now check the Profile + checkbox and then click the Start Processing button. You should + now see a pop up window with the text "Error: Name too long!". + This window is a ZClientErrorDialog. +*/ +//! [3] diff --git a/examples/linguist/doc/src/arrowpad.qdoc b/examples/linguist/doc/src/arrowpad.qdoc new file mode 100644 index 0000000..d4408f7 --- /dev/null +++ b/examples/linguist/doc/src/arrowpad.qdoc @@ -0,0 +1,220 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example arrowpad + \ingroup examples-linguist + \examplecategory {User Interface Components} + + \title Arrow Pad Example + + \brief Understanding the Qt Linguist \e contexts concept and using two + or more languages. + + \image linguist-arrowpad_en.png + + We will use two translations, French and Dutch, although there is no + effective limit on the number of possible translations that can be used + with an application. + + When using qmake, the relevant lines in \c arrowpad.pro are: + + \snippet arrowpad/arrowpad.pro 0 + \codeline + \snippet arrowpad/arrowpad.pro 1 + + When using CMake, the relevant lines in \c CMakeLists.txt are: + \snippet arrowpad/CMakeLists.txt 0 + \codeline + \snippet arrowpad/CMakeLists.txt 1 + + Run \c lupdate. It should produce two identical message files + \c arrowpad_fr.ts and \c arrowpad_nl.ts. These files will contain all the source + texts marked for translation with \c tr() calls and their contexts. + + When using qmake, \c lupdate must be run manually: + + \snippet doc/snippets/doc_src_examples_arrowpad.qdoc 0 + + When using CMake, build the \c update_translations target to run \c lupdate: + + \snippet doc/snippets/doc_src_examples_arrowpad.qdoc 4 + + See the \l{Qt Linguist Manual} for more information about + translating Qt applications. + + \section1 Line by Line Walkthrough + + In \c arrowpad.h we define the \c ArrowPad subclass which is a + subclass of QWidget. In the screenshot above, the central + widget with the four buttons is an \c ArrowPad. + + \snippet arrowpad/arrowpad.h 0 + \snippet arrowpad/arrowpad.h 1 + \snippet arrowpad/arrowpad.h 2 + + When \c lupdate is run it not only extracts the source texts but it + also groups them into contexts. A context is the name of the class in + which the source text appears. Thus, in this example, "ArrowPad" is a + context: it is the context of the texts in the \c ArrowPad class. + The \c Q_OBJECT macro defines \c tr(x) in \c ArrowPad like this: + + \snippet doc/snippets/doc_src_examples_arrowpad.cpp 0 + + Knowing which class each source text appears in enables \e {Qt + Linguist} to group texts that are logically related together, e.g. + all the text in a dialog will have the context of the dialog's class + name and will be shown together. This provides useful information for + the translator since the context in which text appears may influence how + it should be translated. For some translations keyboard + accelerators may need to be changed and having all the source texts in a + particular context (class) grouped together makes it easier for the + translator to perform any accelerator changes without introducing + conflicts. + + In \c arrowpad.cpp we implement the \c ArrowPad class. + + \snippet arrowpad/arrowpad.cpp 0 + \snippet arrowpad/arrowpad.cpp 1 + \snippet arrowpad/arrowpad.cpp 2 + \snippet arrowpad/arrowpad.cpp 3 + + We call \c ArrowPad::tr() for each button's label since the labels are + user-visible text. + + \image linguist-arrowpad_en.png + + \snippet arrowpad/mainwindow.h 0 + \snippet arrowpad/mainwindow.h 1 + + In the screenshot above, the whole window is a \c MainWindow. + This is defined in the \c mainwindow.h header file. Here too, we + use \c Q_OBJECT, so that \c MainWindow will become a context in + \e {Qt Linguist}. + + \snippet arrowpad/mainwindow.cpp 0 + + In the implementation of \c MainWindow, \c mainwindow.cpp, we create + an instance of our \c ArrowPad class. + + \snippet arrowpad/mainwindow.cpp 1 + + We also call \c MainWindow::tr() twice, once for the action and + once for the shortcut. + + Note the use of \c tr() to support different keys in other + languages. "Ctrl+Q" is a good choice for Quit in English, but a + Dutch translator might want to use "Ctrl+A" (for Afsluiten) and a + German translator "Strg+E" (for Beenden). When using \c tr() for + \uicontrol Ctrl key accelerators, the two argument form should be used + with the second argument describing the function that the + accelerator performs. + + Our \c main() function is defined in \c main.cpp as usual. + + \snippet arrowpad/main.cpp 2 + \snippet arrowpad/main.cpp 3 + + We choose which translation to use according to the current locale. + QLocale::system() can be influenced by setting the \c LANG + environment variable, for example. Notice that the use of a naming + convention that incorporates the locale for \c .qm message files, + (and TS files), makes it easy to implement choosing the + translation file according to locale. + + If there is no QM message file for the locale chosen the original + source text will be used and no error raised. + + \section1 Translating to French and Dutch + + We'll begin by translating the example application into French. Start + \e {Qt Linguist} with \c arrowpad_fr.ts. You should get the seven source + texts ("\&Up", "\&Left", etc.) grouped in two contexts ("ArrowPad" + and "MainWindow"). + + Now, enter the following translations: + + \list + \li \c ArrowPad + \list + \li \&Up - \&Haut + \li \&Left - \&Gauche + \li \&Right - \&Droite + \li \&Down - \&Bas + \endlist + \li \c MainWindow + \list + \li E\&xit - \&Quitter + \li Ctrl+Q - Ctrl+Q + \li \&File - \&Fichier + \endlist + \endlist + + The \uicontrol {Done \& Next} button marks the + translation as done and moves on to the next source text. + It's quicker to use its short cut (see the Translation menu bar) + after typing each translation. + + Save the file and do the same for Dutch working with \c arrowpad_nl.ts: + + \list + \li \c ArrowPad + \list + \li \&Up - \&Omhoog + \li \&Left - \&Links + \li \&Right - \&Rechts + \li \&Down - Omlaa\&g + \endlist + \li \c MainWindow + \list + \li E\&xit - \&Afsluiten + \li Ctrl+Q - Ctrl+A + \li File - \&Bestand + \endlist + \endlist + + We have to convert the \c tt1_fr.ts and \c tt1_nl.ts translation source + files into QM files. We could use \e {Qt Linguist} as we've done + before; however using the command line tool \c lrelease ensures that + \e all the QM files for the application are created without us + having to remember to load and \uicontrol File|Release each one + individually from \e {Qt Linguist}. + + Type + + \snippet doc/snippets/doc_src_examples_arrowpad.qdoc 1 + + when using CMake, type + + \snippet doc/snippets/doc_src_examples_arrowpad.qdoc 5 + + This should create both \c arrowpad_fr.qm and \c arrowpad_nl.qm. + + To use \c arrowpad_fr.qm, change your system language to French. + In Unix, one of the two following commands should work: + + \snippet doc/snippets/doc_src_examples_arrowpad.qdoc 2 + + In Windows or Mac, set your display language to French. + + When you run the program, you should now see the French version: + + \image linguist-arrowpad_fr.png + + Try the same with Dutch (use \c LANG=nl in Unix). Now the Dutch + version should appear: + + \image linguist-arrowpad_nl.png + + \section1 Exercises + + Mark one of the translations in \e {Qt Linguist} as not done, i.e. + by unchecking the "done" checkbox; run \c lupdate, then \c lrelease, + then the example. What effect did this change have? + + Set \c LANG=fr_CA (French Canada) and run the example program again. + Explain why the result is the same as with \c LANG=fr. + + Change one of the accelerators in the Dutch translation to eliminate the + conflict between \e \&Bestand and \e \&Boven. +*/ diff --git a/examples/linguist/doc/src/hellotr.qdoc b/examples/linguist/doc/src/hellotr.qdoc new file mode 100644 index 0000000..67de100 --- /dev/null +++ b/examples/linguist/doc/src/hellotr.qdoc @@ -0,0 +1,171 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example hellotr + \ingroup examples-linguist + \examplecategory {User Interface Components} + + \title Hello tr() Example + + \brief Translating a small Hello World program to Latin. + + The screenshot shows the English version. + + \image linguist-hellotr_en.png + + See the \l{Qt Linguist Manual} for more information about + translating Qt application. + + \section1 Line by Line Walkthrough + + + \snippet hellotr/main.cpp 0 + + This line includes the definition of the QTranslator class. + Objects of this class provide translations for user-visible text. + + \snippet hellotr/main.cpp 5 + + Creates a QTranslator object without a parent. + + \snippet hellotr/main.cpp 6 + + Tries to load a file called \c hellotr_la.qm (the \c .qm file extension is + implicit) that contains Latin translations for the source texts used in + the program. No error will occur if the file is not found. + This example works best on desktop platforms. + On platforms like Android and iOS, + the .qm file needs to be part of the app bundle. Usually, + this involves bundling the .qm file in a Qt resource. + + \snippet hellotr/main.cpp 7 + + Adds the translations from \c hellotr_la.qm to the pool of translations used + by the program. + + \snippet hellotr/main.cpp 8 + + Creates a push button that displays "Hello world!". If \c hellotr_la.qm + was found and contains a translation for "Hello world!", the + translation appears; if not, the source text appears. + + All classes that inherit QObject have a \c tr() function. Inside + a member function of a QObject class, we simply write \c tr("Hello + world!") instead of \c QPushButton::tr("Hello world!") or \c + QObject::tr("Hello world!"). + + \section1 Running the Application in English + + Since we haven't made the translation file \c hellotr_la.qm, the source text + is shown when we run the application: + + \image linguist-hellotr_en.png + + \section1 Creating a Latin Message File + + The first step is to create a project file that lists + all the source files for the project. + + When using qmake, the relevant lines in \c hellotr.pro are: + + \snippet hellotr/hellotr.pro 0 + \snippet hellotr/hellotr.pro 1 + + \c TRANSLATIONS specifies the message files we want to + maintain. In this example, we just maintain one set of translations, + namely Latin. + + When using CMake, the relevant lines in \c CMakeLists.txt are: + \snippet hellotr/CMakeLists.txt 0 + \codeline + \snippet hellotr/CMakeLists.txt 1 + + Note that the file extension is \c .ts, not \c .qm. The \c .ts + translation source format is designed for use during the + application's development. Programmers or release managers run + the \c lupdate program to generate and update TS files with + the source text that is extracted from the source code. + Translators read and update the TS files using \e {Qt + Linguist} adding and editing their translations. + + The TS format is human-readable XML that can be emailed directly + and is easy to put under version control. If you edit this file + manually, be aware that the default encoding for XML is UTF-8, not + Latin1 (ISO 8859-1). One way to type in a Latin1 character such as + '\oslash' (Norwegian o with slash) is to use an XML entity: + "\ø". This will work for any Unicode 4.0 character. + + Once the translations are complete the \c lrelease program is used to + convert the TS files into the QM Qt message file format. The + QM format is a compact binary format designed to deliver very + fast lookup performance. Both \c lupdate and \c lrelease read all the + project's source and header files (as specified in the HEADERS and + SOURCES lines of the project file) and extract the strings that + appear in \c tr() function calls. + + \c lupdate is used to create and update the message files (\c hellotr_la.ts + in this case) to keep them in sync with the source code. It is safe to + run \c lupdate at any time, as \c lupdate does not remove any + information. + + Try running \c lupdate right now. + + When using qmake, \c lupdate must be run manually: + + \snippet doc/snippets/doc_src_examples_hellotr.qdoc 0 + + (The \c -verbose option instructs \c lupdate to display messages that + explain what it is doing.) + + When using CMake, build the \c update_translations target to run \c lupdate: + + \snippet doc/snippets/doc_src_examples_hellotr.qdoc 5 + + You should now have a file \c hellotr_la.ts in + the source directory, containing this: + + \snippet doc/snippets/doc_src_examples_hellotr.qdoc 1 + + You don't need to understand the file format since it is read and + updated using tools (\c lupdate, \e {Qt Linguist}, \c lrelease). + + \section1 Translating to Latin with Qt Linguist + + We will use \e {Qt Linguist} to provide the translation, although + you can use any XML or plain text editor to enter a translation into a + TS file. + + To start \e {Qt Linguist}, type + + \snippet doc/snippets/doc_src_examples_hellotr.qdoc 2 + + You should now see the text "QPushButton" in the top left pane. + Double-click it, then click on "Hello world!" and enter "Orbis, te + saluto!" in the \uicontrol Translation pane (the middle right of the + window). Don't forget the exclamation mark! + + Click the \uicontrol Done checkbox and choose \uicontrol File|Save from the + menu bar. The TS file will no longer contain + + \snippet doc/snippets/doc_src_examples_hellotr.qdoc 3 + + but instead will have + + \snippet doc/snippets/doc_src_examples_hellotr.qdoc 4 + + \section1 Running the Application in Latin + + To see the application running in Latin, we have to generate a QM + file from the TS file. Generating a QM file can be achieved + either from within \e {Qt Linguist} (for a single TS file), or + by using the command line program \c lrelease which will produce one + QM file for each of the TS files listed in the project file. + Generate \c hellotr_la.qm from \c hellotr_la.ts by choosing + \uicontrol File|Release from \e {Qt Linguist}'s menu bar and pressing + \uicontrol Save in the file save dialog that pops up. Now run the \c hellotr + program again. This time the button will be labelled "Orbis, te + saluto!". + + \image linguist-hellotr_la.png +*/ diff --git a/examples/linguist/doc/src/i18n.qdoc b/examples/linguist/doc/src/i18n.qdoc new file mode 100644 index 0000000..a356b09 --- /dev/null +++ b/examples/linguist/doc/src/i18n.qdoc @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example i18n + \ingroup examples-linguist + \examplecategory {User Interface Components} + + \title I18N Example + + \brief Demonstrates Qt's support for translated text. + + The Internationalization (I18N) example demonstrates Qt's support for translated + text. Developers can write the initial application text in one language, and + translations can be provided later without any modifications to the code. It also + demonstrates how to detect the system language settings and show the UI in the appropriate + languages. + + \image linguist-i18n.png +*/ diff --git a/examples/linguist/doc/src/localizedclock-idbased.qdoc b/examples/linguist/doc/src/localizedclock-idbased.qdoc new file mode 100644 index 0000000..dbf1aee --- /dev/null +++ b/examples/linguist/doc/src/localizedclock-idbased.qdoc @@ -0,0 +1,301 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example localizedclock-idbased + \ingroup examples-linguist + \examplecategory {User Interface Components} + \meta tags {qtquick,i18n,l10n,linguist,plurals,id-based} + + \title Localized Clock with ID-based Translation + + \brief The example shows best practices for using Qt's ID-based + translation features in CMake and Qt Quick including the handling + of plurals in different languages, and localized time formats. + + \section1 User Interface + The example shows the current time and date in your system's locale and language. + Translation for these languages is supported: + English, German, French, Spanish, Italian, Japanese, Korean, Portuguese, + Arabic, and Chinese. + If your desktop is in another language, it falls back to English. + + The example also accepts a locale as a command-line argument to + test different languages and locales without altering your system.: + \c {localizedClockIdBased --locale en_GB} or + \c {localizedClockIdBased --locale de} + + By default, the application shows the time and date in the \e{current locale}, + but it also provides a button that opens a dialog with a list of time zones. + The user can use the dialog to change the time zone of the clock. + + \section2 ID-based Translation + + In this example, we use ID-based translation, where translatable + texts are identified by a \e{unique ID} rather than the + traditional \e{context + text} combination + (see \l{Text ID-based translations}). This approach allows + translations to be reused across different contexts. + We demonstrate this by sharing a translation between + \l Main.qml and the \l{Time Zone Dialog}, a QWidget-based + form written in C++. + Another aspect of ID-based translation is that it + separates the text shown in the UI from the developers, making + the source code independent of the actual words presented + to the user. + + In the following two screenshots from the application in English, + the two instances of the text "Select time zone: " in the main + window (QML) and the dialog (C++) share the same translation by + using the same ID. + + Screenshot from the application window in English version: + \image linguist-localizedclock-idbased_en.webp + The dialog with a list of time zones (English version): + \image linguist-localizedclock-idbased-dialog_en.webp + + The main window of the application in German: + \image linguist-localizedclock-idbased_de.webp + The dialog with a list of time zones (German version): + \image linguist-localizedclock-idbased-dialog_de.webp + + \section1 Implementation + + The application has five parts: + \list + \li \l CMakeLists.txt + \li \l main.cpp + \li \l {Time Zone Dialog} + \li \l {Time Zone Manager} + \li \l Main.qml + \endlist + + \section2 CMakeLists.txt + The CMake file of the application enables Qt's ID-based translation + and localization support. Here are the relevant pieces: + + \b{\c find_package(Qt6 REQUIRED COMPONENTS Core Linguist Qml Quick):} + Finds and links the required Qt 6 modules, including \c Linguist, + essential for internationalization. + \b{\c qt_standard_project_setup():} + Sets up the internationalization system with support for the listed + locales. Although the source language is English, an English + translation is still required when using ID-based + translation. The source code only contains the IDs and does not see + the source texts. Hence, we need to set up the project with the + English translation, so that qt_add_translations creates a TS + file for English; otherwise, they will be missing at runtime. + \quotefromfile localizedclock-idbased/CMakeLists.txt + \skipto qt_standard_project_setup + \printuntil I18N_TRANSLATED_LANGUAGES + + \b{\c qt_add_translations(...):} + Bundles the functionality of \c lupdate and \c lrelease by generating the + translation source files (TS files) in the "i18n" directory using + \c clock as the base name, and compiling them into binary \c .qm files if + they contain translations. + \list + \li Generates one TS file per language + listed in \c I18N_TRANSLATED_LANGUAGES of \c qt_standard_project_setup. + \li \c MERGE_QT_TRANSLATIONS and \c {QT_TRANSLATION_CATALOGS qtbase} include + the Qt translations in the project. This is necessary to translate + the buttons of the \c QDialog widget in the \l{Time Zone Dialog}. As the text + on those buttons is controlled by QDialog, without including Qt + translations those buttons do not get translated (see the translated + texts of the dialog in the German screenshots in \l {ID-based Translation}). + \endlist + \quotefromfile localizedclock-idbased/CMakeLists.txt + \skipto qt_add_translations + \printuntil ) + + \b{\c qt_add_qml_module(...):} + Adds a QML module under the URI \c qtexamples.localizedclock, + includes the \l Main.qml file, and imports the source and header + files of \l{Time Zone Manager} into the QML module. + \quotefromfile localizedclock-idbased/CMakeLists.txt + \skipto qt_add_qml_module + \printuntil ) + + \section2 main.cpp + The starting point of the application. This part is responsible + for setting the locale, installing required translations, and + loading UI. Below is an explanation of the relevant pieces of code: + + Define the locale argument, e.g., \c {--locale en_US} or \c{--locale de_DE}: + + \quotefromfile localizedclock-idbased/main.cpp + \skipto QCommandLineParser parser + \printuntil parser.process + + Parse the arguments, fetch the provided locale, and set the input + locale as the default locale of the application: + + \quotefromfile localizedclock-idbased/main.cpp + \skipto QLocale locale + \printuntil QLocale::setDefault + + Install the English translation regardless of the locale, to allow incomplete + translations for other languages. QTranslator queries translations + for texts in the reversed order in which the translations are installed: + + \quotefromfile localizedclock-idbased/main.cpp + \skipto QTranslator enPlurals + \printuntil app.installTranslator + + Install a translation according to the given locale: + + \quotefromfile localizedclock-idbased/main.cpp + \skipto QTranslator translation + \printuntil } + \printuntil } + \printuntil } + + As we installed English translation in the previous step, we might end + up with two installed translations. Qt uses the most recently installed + translation for any overlapping keys. Therefore, the locale-specific + translation will take precedence over English, and in case of any missing + translations, QTranslator falls back to English. + + \section2 Time Zone Dialog + This class is a C++ \l {QWidget}-based dialog (\l QDialog) that shows a + \l QComboBox containing a list of time zones. + Below is an explanation of the code: + + Enable ID-based translation in the UI form (dialog.ui): + \code + + \endcode + + Set the title with ID-based translation (dialog.ui). Here, + "title" is the unique ID of the translation: + \code + + Time Zone + + \endcode + + Add a label with ID-based translation (dialog.ui), with the + ID "timezonelabel": + \code + + ... + + Select time zone + + ... + + \endcode + + \section2 Time Zone Manager + A \c QML_SINGLETON class in \c C++ responsible for handling + time zone changes. + + Upon selecting a time zone by the user, the instance of \c TimeZoneManager + remembers the chosen time zone: + \quotefromfile localizedclock-idbased/timezonemanager.cpp + \skipto connect + \printuntil connect + + Updating the time zone emits a TimeZoneManager::timeZoneChanged signal: + \quotefromfile localizedclock-idbased/timezonemanager.cpp + \skipto TimeZoneManager::setTimeZone + \printuntil } + \printuntil } + + \c TimeZoneManager::currentTimeZoneOffsetMs() is marked with + \l Q_INVOKABLE and returns the time offset of the selected time zone. + Since the \c TimeZoneManager class is declared with \l QML_ELEMENT + and \l QML_SINGLETON, the method can be directly accessed from QML + to update the presented time; also, see \l{Main.qml}. + + \quotefromfile localizedclock-idbased/timezonemanager.cpp + \skipto currentTimeZoneOffsetMs + \printuntil } + + \section2 Main.qml + The main QML file defines the application's user interface. + The UI presents time, date, current time zone, and a counter for seconds. + It also provides a button that opens a \l {Time Zone Dialog} to change the + time zone. + Below is an explanation of the relevant pieces of code: + + \b{Define the window and set its title using \l{Qt::}{qsTrId()} + for ID-based translation.} The text + for the source language is specified using the meta string + notation \c {//%} (see \l {Text ID-based translations}). + \l lupdate parses the + meta strings and writes the defined source texts to the TS file. + As the source text is specified using meta strings and in the form + of a comment, they are not visible to the application at runtime. + Hence, when the application is loaded in the English locale, even + though the source language is in English, it still needs to install + the English translation to present the English texts. + Otherwise, the raw ID "Main-Digital-Clock" is shown. This is also + why we specified "en" in \c I18N_TRANSLATED_LANGUAGES + by the setup in \l{CMakeLists.txt}. + + \qml + //% "Digital Clock" + title: qsTrId("Main-Digital-Clock") + \endqml + + \b{Show the number of seconds using \l{Qt::}{qsTrId()} with plural + support.} Again here the text in the source language is specified using + the \c{//%} meta string. + The plural form is enabled by using the special notation "%n" + in the source text in the meta string (see \l{Handle Plural Forms}). + Depending on the value of n, the translation function returns a + different translation, with the correct grammatical number for the + target language. + For instance in English, if the value of \c root.seconds is larger + than one, the plural form is used, otherwise the singular form is used. + In \l{Translation Rules for Plural Forms} you find the plural rules + for different languages. + + \qml + //% "%n second(s)" + text: qsTrId("Main-n-second-s", root.seconds) + \endqml + + \b{Display the currently selected time zone using ID-based + translation,} with \c { //% "Time zone: "} specifying the + text in the source language: + \qml + //% "Time zone: " + text: qsTrId("timezone") + TimeZoneManager.timeZone; + \endqml + + \b{A Button to open the \l{Time Zone Dialog}.} The text on the + button is specified using \l{Qt::}{qsTrId()} for ID-based + translation. In this case, the source text is not defined by meta + strings anymore since this is a reuse of the ID "timezonelabel", + which was previously used in the dialog.ui document + (see \l{Time Zone Dialog}). In ID-based translation, it + suffices to specify the source text per ID only once in the project: + \quotefromfile localizedclock-idbased/Main.qml + \skipto Button + \printuntil } + + Upon changing the time zone and receiving the + \c TimeZoneManager::timeZoneChanged() signal + (see \l{Time Zone Manager}), update the \c diff variable with + the time offset of the selected time zone: + \quotefromfile localizedclock-idbased/Main.qml + \skipto Connections + \printuntil } + \printuntil } + + \b{Declare a Timer} that triggers every second and updates the time, + date, and seconds properties. The timer calculates the time by adding the + current time to the time offset of the selected time zone: + \quotefromfile localizedclock-idbased/Main.qml + \skipto Timer + \printuntil } + \printuntil } + + The locale affects how dates and times are displayed. + These are formatted according to the current locale's country conventions. + For example, a German locale results in 24-hour time and + \e DD.MM.YYYY date format, while a US locale uses a 12-hour clock and + \e MM/DD/YYYY date format. +*/ diff --git a/examples/linguist/doc/src/localizedclock-switchlang.qdoc b/examples/linguist/doc/src/localizedclock-switchlang.qdoc new file mode 100644 index 0000000..bdfeba8 --- /dev/null +++ b/examples/linguist/doc/src/localizedclock-switchlang.qdoc @@ -0,0 +1,128 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example localizedclock-switchlocale + \ingroup examples-linguist + \examplecategory {User Interface Components} + \meta tags {qtquick,i18n,l10n,linguist,runtime,switch} + + \title Localized Clock with Runtime Language Switch + + \brief The example demonstrates best practices for using + Qt's translation and localization features in \l{Build with CMake}{CMake} + and \l{Qt Quick}, in particular changing the language of an application during + runtime. It extends the simpler \l{Localized Clock Example}. + + See the \l{Qt Linguist Manual} for more information about + translating Qt applications. + + \section1 User Interface + + The example shows the current time and date in your system's locale + and language. The UI texts are also localized for the following languages: + English, German, French, Spanish, Italian, Japanese, Korean, Portuguese, + Arabic, and Chinese. Users can also use a menu button to select different + languages and locales. + + The English version of the application: + \image linguist-localizedclock_switchlang_en.webp + + The German version after changing the language at runtime: + \image linguist-localizedclock_switchlang_de.webp + + + \section1 Implementation + + The application has four parts: + \list + \li \l {CMakeLists.txt} + \li \l main.cpp + \li \l {Translator Manager (translator.h, translator.cpp)} + \li \l{localizedclock-switchlocale#Main.qml}{Main.qml} + \endlist + + \section2 CMakeLists.txt + The CMake integration is the same as the + \l{localizedclock#CMakeLists.txt}{CMake integration} of + the \l{localizedclock}{Localized Clock} example. + Please refer to that page for more details. + + \section2 main.cpp + The starting point of the application, responsible for loading the + QML module. + + \quotefromfile localizedclock-switchlocale/main.cpp + \skipto main + \printuntil } + \printuntil } + + \section2 Translator Manager (translator.h, translator.cpp) + + \c TranslatorManager is a QML singleton class that manages language and + locale switching at runtime. The central method of the class is + \c TranslatorManager::switchLanguage(), which accepts a string + representing a language code. + + While the method is marked with \l Q_INVOKABLE, the TranslatorManager + class is declared with \l QML_ELEMENT and \l QML_SINGLETON. + This allows the method to be directly accessed from QML; also, see + \l{localizedclock-switchlocale#Main.qml}{Main.qml}. + + \quotefromfile localizedclock-switchlocale/translatormanager.cpp + \skipto TranslatorManager::switchLanguage + \printuntil } + \printuntil } + \printuntil } + + \section3 Updating Translations at Runtime + + Upon switching language, the previous translation + is removed (QCoreApplication::removeTranslator()) and a + new translation for the selected language is + loaded (QTranslator::load()) and installed + (QCoreApplication::installTranslator()). + \l QQmlEngine::retranslate() then triggers the retranslation of + the strings in the QML document. + + \section3 Updating Date and Time Formats + + Changing language could also affect how dates and times are displayed. + For instance, if QLocale on your system prefers locale "en_US" for + "en", and "de_DE" for "de": + \list + \li The application is initially loaded with "US" as the country code. + \li Due to the US locale conventions, the date is presented in \e MM/DD/YYYY + format, and the time uses a 12-hour clock. + \li After changing the language to German, "de_DE" is the first preferred + locale for the German language, so the country changes to Germany. + \li The German conventions bring the date in \e DD.MM.YYYY format and + use a 24-hour clock. + \endlist + + Upon switching the language, the application updates its QLocale + according to the selected language using \l QLocale::setDefault(). + When representing time, the function + \l[QML]{Date::toLocaleTimeString()}{Date.toLocaleTimeString()} takes care of localizing the time and date + according to the locale of the application (see \l{localizedclock-switchlocale#Main.qml}{Main.qml}). + + + \section2 Main.qml + The main QML file defines the application's user interface. + The UI presents time, date, and a counter for seconds. For the basic + translation (window title) and plural handling (number of seconds), + please refer to the \l{localizedclock#Main.qml}{Localized Clock} example. + + Upon selecting an item from the \c Menu \c TranslatorManager.switchLanguage() + is called with the respective language code. This call also takes care of + retranslating the translatable texts (see + \l{Translator Manager (translator.h, translator.cpp)}{Translator Manager}). + However, we still need to invoke \c updateClock() to + update the time representation in the new localization format + when the language is changed. + + \quotefromfile localizedclock-switchlocale/Main.qml + \skipto ListModel + \printuntil root.updateClock() + \printuntil /^ {8}\}/ + */ diff --git a/examples/linguist/doc/src/localizedclock.qdoc b/examples/linguist/doc/src/localizedclock.qdoc new file mode 100644 index 0000000..2df89da --- /dev/null +++ b/examples/linguist/doc/src/localizedclock.qdoc @@ -0,0 +1,214 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example localizedclock + \ingroup examples-linguist + \examplecategory {User Interface Components} + \meta tags {qtquick,i18n,l10n,linguist,plurals} + + \title Localized Clock Example + + \brief The example shows best practices for using Qt's translation and + localization features in CMake and Qt Quick including the handling + of plurals in different languages, and localized time and date formats. + + See the \l{Qt Linguist Manual} for more information about + translating Qt applications. + + \section1 User Interface + + The example shows the current time and date in the locale and language of + your system. The texts in the UI are furthermore localized for + the following languages: + English, German, French, Spanish, Italian, Japanese, Korean, Portuguese, + Arabic, and Chinese. + If your desktop is in another language, it falls back to English. + + To test different languages and locales without altering your system, the + example also accepts a locale as command line argument. For example, + starting localizedClock from the command line with the option + \c{--locale de} shows the clock in German, with a date and time + format as it is common in Germany. + + The screenshot shows the en_US version: + \image linguist-localizedclock_en_US.webp + + \section2 Internationalization + + In the application translation is used to set the main window + title and a few UI texts, including ones with placeholders + and plural forms. The application counts the seconds with + \e{plural forms} enabled (see \l{Handle Plural Forms}). As a + result, depending on the count of seconds, the translation function + returns a different translation, with the correct grammatical + number for the target language. + For instance, in English, if the count is larger + than \e one, the \e plural form is used, otherwise the \e singular + form is used. In \l{Translation Rules for Plural Forms} you find + the plural rules for different languages. + + The locale also affects how dates and times are displayed. + These are formatted according to the country conventions of the current + locale. For example, a German locale results in 24-hour time and + the day written before the month, while a US locale + uses a 12-hour clock, and the month is written before the day. + + This screenshot shows the en_GB version. Notice that the data format is + different than the en_US version above, while the same English plural + translation is loaded in both cases. + + \image linguist-localizedclock_en_GB.webp + + Here is the screenshot of the de_DE version, which similar to GB has a + different date format than US. Notice that the German translation for + regular and plural forms is loaded accordingly. + + \image linguist-localizedclock_de_DE.webp + + \section1 Implementation + + The implementation has three parts: + \list + \li \l{CMakeLists.txt} + \li \l{main.cpp} + \li \l{Main.qml} + \endlist + + \section2 CMakeLists.txt + + The CMake file of the application enables Qt's translation and localization support. + Here are the relevant pieces: + + \c find_package(Qt6 REQUIRED COMPONENTS Core Linguist Qml Quick): + Finds and links the required Qt 6 modules, including \c Linguist which + are essential for internationalization. + + \c qt_standard_project_setup(...): + Sets up the internationalization system with support for the listed + locales. \c I18N_SOURCE_LANGUAGE is left at its default value (English), + since the source code contains English texts. + \quotefromfile localizedclock/CMakeLists.txt + \skipto qt_standard_project_setup + \printuntil I18N_TRANSLATED_LANGUAGES + + \c qt_add_translations(...): + Bundles the functionality of \c lupdate and \c lrelease by generating the + translation source files (TS files) in the "i18n" directory using + \c clock as the base name, and compiling them into binary \c .qm files if + they contain translations. The following TS files are generated: + \list + \li "clock_{de, ar, ko, zh, ja, fr, it, es, pt}.ts": One TS file per language + listed in \c I18N_TRANSLATED_LANGUAGES of \c qt_standard_project_setup + containing the translations to that language. + \li "clock_en.ts": Contains English plural forms, since the source code + has plural form translation ("%n second(s)"). + The function \c qt_add_translations writes only the plural forms here, + as we specified the language of the texts in the source code as English, + by leaving \c I18N_SOURCE_LANGUAGE with the default value. So the reglular + texts do not need translations. + \endlist + \quotefromfile localizedclock/CMakeLists.txt + \skipto qt_add_translations + \printuntil ) + + \c qt_add_qml_module(...): + Adds a QML module under the URI \c qtexamples.localizedclock, including + the \l Main.qml file. + \quotefromfile localizedclock/CMakeLists.txt + \skipto qt_add_qml_module + \printuntil ) + + \section2 main.cpp + The starting point of the application. This part is responsible + for setting the locale, installing required translations, and + loading UI. Below is an explanation of the relevant pieces of code: + + Define the locale argument, e.g., \c {--locale en_US} or \c{--locale de_DE}: + + \quotefromfile localizedclock/main.cpp + \skipto QCommandLineParser parser + \printuntil parser.process + + Parse the arguments, fetch the provided locale, and set the input + locale as the default locale of the application: + + \quotefromfile localizedclock/main.cpp + \skipto QLocale locale + \printuntil QLocale::setDefault + + + Install the English translation regardless of the locale, to allow incomplete + translations for other languages. QTranslator queries translations + for texts in the reversed order the translations are installed: + + \quotefromfile localizedclock/main.cpp + \skipto QTranslator enPlurals + \printuntil app.installTranslator + + Install a translation according to the given locale. As English translation + is already installed in the previous step, we might end + up with two installed translations here. Qt uses the most recently installed + translation for any overlapping keys. Therefore, the locale-specific + translation will take precedence over English, and in case of any missing + translations, QTranslator falls back to English. + + \quotefromfile localizedclock/main.cpp + \skipto QTranslator translation + \printuntil } + \printuntil } + \printuntil } + + \section2 Main.qml + This QML file defines the main UI window of the application, which + presents time, date, the used locale, and a counter for seconds. + Below is an explanation of the relevant pieces of code: + + \b {Set the title of the Window using qsTr() for translation.} To + find the translation for this text QTranslator queries in the + TS file of the current language the text \e{"Digital Clock"} + within the context \e{"Main"} (the file name): + + \qml + title: qsTr("Digital Clock") + \endqml + + \b{Show the number of seconds using qsTr() with plural (numeral) support.} + The plural form is enabled by using the special notation "%n" + (see \l{Handle Plural Forms}). + Depending on the value of n, the translation function returns a + different translation, with the correct grammatical number for the + target language. + For instance in English, if the value of \c root.seconds is larger + than one, the plural form is used, otherwise the singular form is used. + In \l{Translation Rules for Plural Forms} you find the plural rules + for different languages. + + \qml + text: qsTr("%n second(s)", "seconds", root.seconds) + \endqml + + \b{Display the current locale and use qsTr() to translate + the source text "Locale: %1".} Also the translation needs + to contain the argument notation "%1". As a result, the + argument of the text (i.e. \c{Qt.locale().name}) can + correctly be used to format the text: + + \qml + text: qsTr("Locale: %1").arg(Qt.locale().name) + \endqml + + \b{Format the time and date according to the locale convensions.} + Different countires might have specific preferences on + how time and date should be presented. + For instance, the German locale uses a 24-hour clock and writes + the day before the month, whereas the US locale follows a + 12-hour clock and places the month before the day. + The method Date.toLocaleTimeString takes these considerations + into account and formats the time and date correctly based + on the given locale: + + \quotefromfile localizedclock/Main.qml + \skipto const now + \printuntil root.date +*/ diff --git a/examples/linguist/doc/src/trollprint.qdoc b/examples/linguist/doc/src/trollprint.qdoc new file mode 100644 index 0000000..ffbd57f --- /dev/null +++ b/examples/linguist/doc/src/trollprint.qdoc @@ -0,0 +1,229 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example trollprint + \ingroup examples-linguist + \examplecategory {User Interface Components} + + \title Troll Print Example + + \brief Updating translations for later releases. + + Troll Print is an example application that lets the user choose + printer settings. It comes in two versions: English and + Portuguese. + + \image linguist-trollprint_10_en.png + + We've included a translation file, \c trollprint_pt.ts, which contains some + Portuguese translations for this example. + + We will consider two releases of the same application: Troll Print + 1.0 and 1.1. We will learn to reuse the translations created for one + release in a subsequent release. (In this tutorial, you need to edit + some source files. It's probably best to copy all the files to a new + temporary directory and work from there.) + + See the \l{Qt Linguist Manual} for more information about + translating Qt application. + + \section1 Line by Line Walkthrough + + The \c PrintPanel class is defined in \c printpanel.h. + + \snippet trollprint/printpanel.h 0 + + \c PrintPanel is a QWidget. It needs the \c Q_OBJECT macro for \c + tr() to work properly. + + The implementation file is \c printpanel.cpp. + + \snippet trollprint/printpanel.cpp 0 + + Some of the code is commented out in Troll Print 1.0; you will + uncomment it later, for Troll Print 1.1. + + \snippet trollprint/printpanel.cpp 1 + \snippet trollprint/printpanel.cpp 2 + + Notice the two occurrences of \c tr("Enabled") and of \c + tr("Disabled") in PrintPanel. Since both "Enabled"s and "Disabled"s + appear in the same context \e {Qt Linguist} will only display one + occurrence of each and will use the same translations for the + duplicates that it doesn't display. Whilst this is a useful + timesaver, in some languages, such as Portuguese, the second + occurrence requires a separate translation. We will see how \e {Qt + Linguist} can be made to display all the occurrences for separate + translation shortly. + + The header file for \c MainWindow, \c mainwindow.h, contains no + surprises. In the implementation, \c mainwindow.cpp, we have some + user-visible source texts that must be marked for translation. + + \snippet trollprint/mainwindow.cpp 0 + + We must translate the window title. + + \snippet trollprint/mainwindow.cpp 1 + \snippet trollprint/mainwindow.cpp 3 + + We also need to translate the actions and menus. Note that the + two argument form of \c tr() is used for the keyboard + accelerator, "Ctrl+Q", since the second argument is the only clue + the translator has to indicate what function that accelerator + will perform. + + \snippet trollprint/main.cpp 0 + + The \c main() function in \c main.cpp is the same as the one in + the \l{arrowpad}{Arrow Pad} example. In particular, it + chooses a translation file based on the current locale. + + \section1 Running Troll Print 1.0 in English and in Portuguese + + We will use the translations in the \c trollprint_pt.ts file that is provided. + + Set the \c LANG environment variable to \c pt, and then run \c + trollprint. You should still see the English version. Now run \c + lrelease, e.g. \c {lrelease trollprint.pro}, and then run the + example again. Now you should see the Portuguese edition (Troll + Imprimir 1.0): + + \image linguist-trollprint_10_pt_bad.png + + Whilst the translation has appeared correctly, it is in fact wrong. In + good Portuguese, the second occurrence of "Enabled" should be + "Ativadas", not "Ativado" and the ending for the second translation of + "Disabled" must change similarly too. + + If you open \c trollprint_pt.ts using \e {Qt Linguist}, you will see that + there is just one occurrence of "Enabled" and of "Disabled" in the + translation source file, even though there are two of each in the + source code. This is because \e {Qt Linguist} tries to minimize the + translator's work by using the same translation for duplicate source + texts. In cases such as this where an identical translation is wrong, + the programmer must disambiguate the duplicate occurrences. This is + easily achieved by using the two argument form of \c tr(). + + We can easily determine which file must be changed because the + translator's "context" is in fact the class name for the class where + the texts that must be changed appears. In this case the file is \c + printpanel.cpp, where there are four lines to change. Add the + second argument "two-sided" in the appropriate \c tr() calls to the + first pair of radio buttons: + + \snippet doc/snippets/doc_src_examples_trollprint.cpp 0 + + and add the second argument "colors" in the appropriate \c tr() calls + for the second pair of radio buttons: + + \snippet doc/snippets/doc_src_examples_trollprint.cpp 1 + + Now recompile, run \c lupdate and open \c trollprint_pt.ts with + \e {Qt Linguist}. You should now see two changes. + + First, the translation source file now contains \e three "Enabled", + "Disabled" pairs. The first pair is marked "(obs.)" signifying that they + are obsolete. This is because these texts appeared in \c tr() calls that + have been replaced by new calls with two arguments. The second pair has + "two-sided" as their comment, and the third pair has "colors" as their + comment. The comments are shown in the \uicontrol {Source text and comments} + area in \e {Qt Linguist}. + + Second, the translation text "Ativado" and "Desativado" have been + automatically used as translations for the new "Enabled" and "Disabled" + texts, again to minimize the translator's work. Of course in this case + these are not correct for the second occurrence of each word, but they + provide a good starting point. + + Change the second "Ativado" into "Ativadas" and the second + "Desativado" into "Desativadas", then save and quit. Run \c lrelease + to obtain an up-to-date binary \c trollprint_pt.qm file, and run Troll Print + (or rather Troll Imprimir). + + \image linguist-trollprint_10_pt_good.png + + The second argument to \c tr() calls, called "comments" in \e {Qt + Linguist}, distinguish between identical source texts that occur in + the same context (class). They are also useful in other cases to give + clues to the translator, and in the case of Ctrl key accelerators are + the only means of conveying the function performed by the accelerator to + the translator. + + An additional way of helping the translator is to provide information on + how to navigate to the particular part of the application that contains + the source texts they must translate. This helps them see the context + in which the translation appears and also helps them to find and test + the translations. This can be achieved by using a \c TRANSLATOR comment + in the source code: + + \snippet doc/snippets/doc_src_examples_trollprint.cpp 2 + + Try adding these comments to some source files, particularly to + dialog classes, describing the navigation necessary to reach the + dialogs. You could also add them to the example files, e.g. \c + mainwindow.cpp and \c printpanel.cpp are appropriate files. Run \c + lupdate and then start \e {Qt Linguist} and load in \c trollprint_pt.ts. + You should see the comments in the \uicontrol {Source text and comments} area + as you browse through the list of source texts. + + Sometimes, particularly with large programs, it can be difficult for + the translator to find their translations and check that they're + correct. Comments that provide good navigation information can save + them time: + + \snippet doc/snippets/doc_src_examples_trollprint.cpp 3 + + \section1 Troll Print 1.1 + + We'll now prepare release 1.1 of Troll Print. Start your favorite text + editor and follow these steps: + + \list + \li Uncomment the two lines that create a QLabel with the text + "\TROLL PRINT\" in \c printpanel.cpp. + \li Word-tidying: Replace "2-sided" by "Two-sided" in \c printpanel.cpp. + \li Replace "1.0" with "1.1" everywhere it occurs in \c mainwindow.cpp. + \li Update the copyright year to 1999-2000 in \c mainwindow.cpp. + \endlist + + (Of course the version number and copyright year would be consts or + #defines in a real application.) + + Once finished, run \c lupdate, then open \c trollprint_pt.ts in \e {Qt + Linguist}. The following items are of special interest: + + \list + \li \c PrintPanel + \list + \li 2-sided - marked "(obs.)", obsolete + \li \TROLL PRINT\ - unmarked, i.e. untranslated + \li Two-sided - unmarked, i.e. untranslated. + \endlist + \endlist + + In order to make revisions easier, instead of tr("Troll Print 1.0") + you can use tr("Troll Print %1").arg("1.0") or such to avoid having to update + the string on every release. + + Go over the translations in \c MainWindow and mark these as "done". + Translate "\TROLL PRINT\" as "\TROLL IMPRIMIR\". + When you're translating "Two-sided", press the \uicontrol {Guess Again} + button to translate "Two-sided", but change the "2" into "Dois". + + Save and quit, then run \c lrelease. The Portuguese version + should look like this: + + \image linguist-trollprint_11_pt.png + + Choose \uicontrol{Ajuda|Sobre} (\uicontrol{Help|About}) to see the about box. + + If you choose \uicontrol {Ajuda|Sobre Qt} (\uicontrol {Help|About Qt}), you'll get + an English dialog. Oops! Qt itself needs to be translated. See + \l{Internationalization with Qt} for details. + + Now set \c LANG=en to get the original English version: + + \image linguist-trollprint_11_en.png +*/ diff --git a/examples/linguist/hellotr/CMakeLists.txt b/examples/linguist/hellotr/CMakeLists.txt new file mode 100644 index 0000000..0fff68a --- /dev/null +++ b/examples/linguist/hellotr/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(hellotr LANGUAGES CXX) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/linguist/hellotr") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +#! [0] +find_package(Qt6 REQUIRED COMPONENTS LinguistTools) + +qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES la) +#! [0] + +qt_add_executable(hellotr + main.cpp +) + +#! [1] +qt6_add_translations(hellotr + QM_FILES_OUTPUT_VARIABLE qm_files) +install(FILES ${qm_files} DESTINATION ${INSTALL_EXAMPLEDIR}) +#! [1] +set_target_properties(hellotr PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(hellotr PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) + +install(TARGETS hellotr + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/linguist/hellotr/hellotr.pro b/examples/linguist/hellotr/hellotr.pro new file mode 100644 index 0000000..766b536 --- /dev/null +++ b/examples/linguist/hellotr/hellotr.pro @@ -0,0 +1,12 @@ +#! [0] +SOURCES = main.cpp +#! [0] #! [1] +TRANSLATIONS = hellotr_la.ts +#! [1] + +target.path = $$[QT_INSTALL_EXAMPLES]/linguist/hellotr +INSTALLS += target + +QT += widgets + +simulator: warning(This example might not fully work on Simulator platform) diff --git a/examples/linguist/hellotr/hellotr_en.ts b/examples/linguist/hellotr/hellotr_en.ts new file mode 100644 index 0000000..fa9f21c --- /dev/null +++ b/examples/linguist/hellotr/hellotr_en.ts @@ -0,0 +1,10 @@ + + + + diff --git a/examples/linguist/hellotr/hellotr_la.ts b/examples/linguist/hellotr/hellotr_la.ts new file mode 100644 index 0000000..1f068c5 --- /dev/null +++ b/examples/linguist/hellotr/hellotr_la.ts @@ -0,0 +1,3 @@ + + + diff --git a/examples/linguist/hellotr/main.cpp b/examples/linguist/hellotr/main.cpp new file mode 100644 index 0000000..e655029 --- /dev/null +++ b/examples/linguist/hellotr/main.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +//! [0] +#include +//! [0] + +//! [1] //! [2] +int main(int argc, char *argv[]) +//! [1] //! [3] //! [4] +{ + QApplication app(argc, argv); +//! [3] + +//! [5] + QTranslator translator; +//! [5] //! [6] + Q_UNUSED(translator.load("hellotr_la")); +//! [6] //! [7] + app.installTranslator(&translator); +//! [4] //! [7] + +//! [8] + QPushButton hello(QPushButton::tr("Hello world!")); +//! [8] + hello.resize(100, 30); + + hello.show(); + return app.exec(); +} +//! [2] diff --git a/examples/linguist/i18n/CMakeLists.txt b/examples/linguist/i18n/CMakeLists.txt new file mode 100644 index 0000000..b5c8df5 --- /dev/null +++ b/examples/linguist/i18n/CMakeLists.txt @@ -0,0 +1,46 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(i18n LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/tools/i18n") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets LinguistTools) + +qt_standard_project_setup( + I18N_TRANSLATED_LANGUAGES ar cs de el en eo fr it ja ko nb ru sv zh +) + +qt_add_executable(i18n + languagechooser.cpp languagechooser.h + main.cpp + mainwindow.cpp mainwindow.h +) + +set_target_properties(i18n PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(i18n PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) + +qt_add_translations(i18n + TS_OUTPUT_DIRECTORY translations +) + +install(TARGETS i18n + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/linguist/i18n/i18n.pro b/examples/linguist/i18n/i18n.pro new file mode 100644 index 0000000..c15d780 --- /dev/null +++ b/examples/linguist/i18n/i18n.pro @@ -0,0 +1,32 @@ +CONFIG += lrelease embed_translations + +QT += widgets + +HEADERS = \ + languagechooser.h \ + mainwindow.h + +SOURCES = \ + languagechooser.cpp \ + main.cpp \ + mainwindow.cpp + +TRANSLATIONS += \ + translations/i18n_ar.ts \ + translations/i18n_cs.ts \ + translations/i18n_de.ts \ + translations/i18n_el.ts \ + translations/i18n_en.ts \ + translations/i18n_eo.ts \ + translations/i18n_fr.ts \ + translations/i18n_it.ts \ + translations/i18n_ja.ts \ + translations/i18n_ko.ts \ + translations/i18n_nb.ts \ + translations/i18n_ru.ts \ + translations/i18n_sv.ts \ + translations/i18n_zh.ts + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/widgets/tools/i18n +INSTALLS += target diff --git a/examples/linguist/i18n/languagechooser.cpp b/examples/linguist/i18n/languagechooser.cpp new file mode 100644 index 0000000..a8558cb --- /dev/null +++ b/examples/linguist/i18n/languagechooser.cpp @@ -0,0 +1,141 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "languagechooser.h" +#include "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +LanguageChooser::LanguageChooser(QWidget *parent) : QDialog(parent, Qt::WindowStaysOnTopHint) +{ + groupBox = new QGroupBox("Languages"); + + QGridLayout *groupBoxLayout = new QGridLayout; + + const QStringList qmFiles = findQmFiles(); + const QStringList uiLanguages = QLocale::system().uiLanguages(); + for (int i = 0; i < qmFiles.size(); ++i) { + const QString &qmFile = qmFiles.at(i); + QCheckBox *checkBox = new QCheckBox(languageName(qmFile)); + qmFileForCheckBoxMap.insert(checkBox, qmFile); + connect(checkBox, &QCheckBox::toggled, this, &LanguageChooser::checkBoxToggled); + if (std::find_if( + uiLanguages.begin(), uiLanguages.end(), + [qmFile](const QString &lang) -> bool { return languageMatch(lang, qmFile); }) + != uiLanguages.end()) + checkBox->setCheckState(Qt::Checked); + groupBoxLayout->addWidget(checkBox, i / 2, i % 2); + } + groupBox->setLayout(groupBoxLayout); + + buttonBox = new QDialogButtonBox; + showAllButton = buttonBox->addButton("Show All", QDialogButtonBox::ActionRole); + hideAllButton = buttonBox->addButton("Hide All", QDialogButtonBox::ActionRole); + + connect(showAllButton, &QAbstractButton::clicked, this, &LanguageChooser::showAll); + connect(hideAllButton, &QAbstractButton::clicked, this, &LanguageChooser::hideAll); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(groupBox); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); + + setWindowTitle("I18N"); +} + +bool LanguageChooser::languageMatch(QStringView lang, QStringView qmFile) +{ + // qmFile: i18n_xx.qm + const QStringView prefix{ u"i18n_" }; + const int langTokenLength = 2; /*FIXME: is checking two chars enough?*/ + return qmFile.mid(qmFile.indexOf(prefix) + prefix.length(), langTokenLength) + == lang.left(langTokenLength); +} + +bool LanguageChooser::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::Close) { + MainWindow *window = qobject_cast(object); + if (window) { + QCheckBox *checkBox = mainWindowForCheckBoxMap.key(window); + if (checkBox) + checkBox->setChecked(false); + } + } + return QDialog::eventFilter(object, event); +} + +void LanguageChooser::closeEvent(QCloseEvent * /* event */) +{ + QCoreApplication::quit(); +} + +void LanguageChooser::checkBoxToggled() +{ + QCheckBox *checkBox = qobject_cast(sender()); + MainWindow *window = mainWindowForCheckBoxMap.value(checkBox); + if (!window) { + QTranslator translator; + const QString qmlFile = qmFileForCheckBoxMap.value(checkBox); + if (translator.load(qmlFile)) + QCoreApplication::installTranslator(&translator); + else + qWarning("Unable to load %s", qPrintable(QDir::toNativeSeparators(qmlFile))); + + window = new MainWindow; + window->setPalette(colorForLanguage(checkBox->text())); + + window->installEventFilter(this); + mainWindowForCheckBoxMap.insert(checkBox, window); + } + window->setVisible(checkBox->isChecked()); +} + +void LanguageChooser::showAll() +{ + for (auto it = qmFileForCheckBoxMap.keyBegin(); it != qmFileForCheckBoxMap.keyEnd(); ++it) + (*it)->setChecked(true); +} + +void LanguageChooser::hideAll() +{ + for (auto it = qmFileForCheckBoxMap.keyBegin(); it != qmFileForCheckBoxMap.keyEnd(); ++it) + (*it)->setChecked(false); +} + +QStringList LanguageChooser::findQmFiles() +{ + QDir dir(":/i18n"); + QStringList fileNames = dir.entryList(QStringList("*.qm"), QDir::Files, QDir::Name); + for (QString &fileName : fileNames) + fileName = dir.filePath(fileName); + return fileNames; +} + +QString LanguageChooser::languageName(const QString &qmFile) +{ + QTranslator translator; + if (!translator.load(qmFile)) { + qWarning("Unable to load %s", qPrintable(QDir::toNativeSeparators(qmFile))); + return {}; + } + return translator.translate("MainWindow", "English"); +} + +QColor LanguageChooser::colorForLanguage(const QString &language) +{ + size_t hashValue = qHash(language); + int red = 156 + (hashValue & 0x3F); + int green = 156 + ((hashValue >> 6) & 0x3F); + int blue = 156 + ((hashValue >> 12) & 0x3F); + return QColor(red, green, blue); +} diff --git a/examples/linguist/i18n/languagechooser.h b/examples/linguist/i18n/languagechooser.h new file mode 100644 index 0000000..789f762 --- /dev/null +++ b/examples/linguist/i18n/languagechooser.h @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef LANGUAGECHOOSER_H +#define LANGUAGECHOOSER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QAbstractButton; +class QCheckBox; +class QDialogButtonBox; +class QGroupBox; +QT_END_NAMESPACE + +class MainWindow; + +class LanguageChooser : public QDialog +{ + Q_OBJECT + +public: + explicit LanguageChooser(QWidget *parent = nullptr); + +protected: + bool eventFilter(QObject *object, QEvent *event) override; + void closeEvent(QCloseEvent *event) override; + +private slots: + void checkBoxToggled(); + void showAll(); + void hideAll(); + +private: + static QStringList findQmFiles(); + static QString languageName(const QString &qmFile); + static QColor colorForLanguage(const QString &language); + static bool languageMatch(QStringView lang, QStringView qmFile); + + QGroupBox *groupBox; + QDialogButtonBox *buttonBox; + QAbstractButton *showAllButton; + QAbstractButton *hideAllButton; + QHash qmFileForCheckBoxMap; + QHash mainWindowForCheckBoxMap; +}; + +#endif // LANGUAGECHOOSER_H diff --git a/examples/linguist/i18n/main.cpp b/examples/linguist/i18n/main.cpp new file mode 100644 index 0000000..f931661 --- /dev/null +++ b/examples/linguist/i18n/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "languagechooser.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + LanguageChooser chooser; + chooser.show(); + return app.exec(); +} diff --git a/examples/linguist/i18n/mainwindow.cpp b/examples/linguist/i18n/mainwindow.cpp new file mode 100644 index 0000000..6b8f5b5 --- /dev/null +++ b/examples/linguist/i18n/mainwindow.cpp @@ -0,0 +1,62 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *const listEntries[] = { QT_TRANSLATE_NOOP("MainWindow", "First"), + QT_TRANSLATE_NOOP("MainWindow", "Second"), + QT_TRANSLATE_NOOP("MainWindow", "Third"), nullptr }; + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) +{ + centralWidget = new QWidget; + setCentralWidget(centralWidget); + + createGroupBox(); + + listWidget = new QListWidget; + for (const char *entry : listEntries) + listWidget->addItem(tr(entry)); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(groupBox); + mainLayout->addWidget(listWidget); + centralWidget->setLayout(mainLayout); + + exitAction = new QAction(tr("E&xit"), this); + connect(exitAction, &QAction::triggered, qApp, QCoreApplication::quit); + + fileMenu = menuBar()->addMenu(tr("&File")); + fileMenu->setPalette(QPalette(Qt::red)); + fileMenu->addAction(exitAction); + + setWindowTitle(tr("Language: %1").arg(tr("English"))); + statusBar()->showMessage(tr("Internationalization Example")); + + if (tr("LTR") == "RTL") + setLayoutDirection(Qt::RightToLeft); +} + +void MainWindow::createGroupBox() +{ + groupBox = new QGroupBox(tr("View")); + perspectiveRadioButton = new QRadioButton(tr("Perspective")); + isometricRadioButton = new QRadioButton(tr("Isometric")); + obliqueRadioButton = new QRadioButton(tr("Oblique")); + perspectiveRadioButton->setChecked(true); + + QVBoxLayout *groupBoxLayout = new QVBoxLayout; + groupBoxLayout->addWidget(perspectiveRadioButton); + groupBoxLayout->addWidget(isometricRadioButton); + groupBoxLayout->addWidget(obliqueRadioButton); + groupBox->setLayout(groupBoxLayout); +} diff --git a/examples/linguist/i18n/mainwindow.h b/examples/linguist/i18n/mainwindow.h new file mode 100644 index 0000000..93b75fd --- /dev/null +++ b/examples/linguist/i18n/mainwindow.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QGroupBox; +class QLabel; +class QListWidget; +class QMenu; +class QRadioButton; +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + +private: + void createGroupBox(); + + QWidget *centralWidget; + QLabel *label; + QGroupBox *groupBox; + QListWidget *listWidget; + QRadioButton *perspectiveRadioButton; + QRadioButton *isometricRadioButton; + QRadioButton *obliqueRadioButton; + QMenu *fileMenu; + QAction *exitAction; +}; + +#endif // MAINWINDOW_H diff --git a/examples/linguist/i18n/translations/i18n_ar.ts b/examples/linguist/i18n/translations/i18n_ar.ts new file mode 100644 index 0000000..f575f2b --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_ar.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + First + أول + + + Internationalization Example + مثال التدويل + + + Isometric + متماثل + + + Language: %1 + اللغة: %1 + + + English + العربية + + + Oblique + مصمت + + + Perspective + منظور + + + Second + ثانى + + + Third + ثالث + + + View + مرئى + + + E&xit + أخرج + + + &File + الملف + + + LTR + RTL + + + diff --git a/examples/linguist/i18n/translations/i18n_cs.ts b/examples/linguist/i18n/translations/i18n_cs.ts new file mode 100644 index 0000000..d05d5c8 --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_cs.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + View + Pohled + + + &File + &Soubor + + + E&xit + &Konec + + + First + První + + + Third + Třetí + + + Language: %1 + Jayzk: %1 + + + English + Český + + + Oblique + Nakloněný + + + Second + Druhý + + + Isometric + Isometrický + + + Perspective + Perspektivní + + + Internationalization Example + Ukázka lokalizace + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_de.ts b/examples/linguist/i18n/translations/i18n_de.ts new file mode 100644 index 0000000..155b6b5 --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_de.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + View + Ansicht + + + &File + &Datei + + + E&xit + Be&enden + + + First + Erstens + + + Third + Drittens + + + English + Deutsch + + + Language: %1 + Sprache: %1 + + + Oblique + Schief + + + Second + Zweitens + + + Isometric + Isometrisch + + + Perspective + Perspektivisch + + + Internationalization Example + Internationalisierungsbeispiel + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_el.ts b/examples/linguist/i18n/translations/i18n_el.ts new file mode 100644 index 0000000..e87ef24 --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_el.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + &File + &Αρχείο + + + E&xit + Έ&ξοδος + + + First + Πρώτο + + + Internationalization Example + Παράδειγμα διεθνοποίησης + + + Isometric + Ισομετρική + + + Language: %1 + Γλώσσα: %1 + + + English + Ελληνικά + + + Oblique + Πλάγια + + + Perspective + Προοπτική + + + Second + Δεύτερο + + + Third + Τρίτο + + + View + Όψη + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_en.ts b/examples/linguist/i18n/translations/i18n_en.ts new file mode 100644 index 0000000..9c6c0bf --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_en.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + E&xit + E&xit + + + &File + &File + + + Internationalization Example + Internationalization Example + + + Language: %1 + Language: %1 + + + English + English + + + View + View + + + Perspective + Perspective + + + Isometric + Isometric + + + Oblique + Oblique + + + First + First + + + Second + Second + + + Third + Third + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_eo.ts b/examples/linguist/i18n/translations/i18n_eo.ts new file mode 100644 index 0000000..0e95f9d --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_eo.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + &File + &Dosiero + + + First + Unue + + + Internationalization Example + Ekzemplo pri internaciigo + + + Isometric + Isometria + + + Language: %1 + Lingvo: %1 + + + English + Esperanto + + + Oblique + Oblikva + + + Perspective + Perspektiva + + + Second + Due + + + Third + Trie + + + View + Aspekto + + + E&xit + &Fini + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_fr.ts b/examples/linguist/i18n/translations/i18n_fr.ts new file mode 100644 index 0000000..58cdd7a --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_fr.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + View + Vue + + + &File + &Fichier + + + E&xit + &Quitter + + + First + Premier + + + Third + Troisième + + + Language: %1 + Langue : %1 + + + English + Français + + + Oblique + Oblique + + + Second + Deuxième + + + Isometric + Isométrique + + + Perspective + Perspective + + + Internationalization Example + Exemple d'internationalisation + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_it.ts b/examples/linguist/i18n/translations/i18n_it.ts new file mode 100644 index 0000000..62facf0 --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_it.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + First + Primo + + + Internationalization Example + Esempio di localizzazione + + + Isometric + Isometrica + + + Language: %1 + Lingua: %1 + + + English + Italiano + + + Oblique + Obliqua + + + Perspective + Prospettica + + + Second + Secondo + + + Third + Terzo + + + View + Vista + + + E&xit + &Esci + + + &File + &File + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_ja.ts b/examples/linguist/i18n/translations/i18n_ja.ts new file mode 100644 index 0000000..e7fe10a --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_ja.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + &File + ファイル(&F) + + + E&xit + 終了(&X) + + + First + 第一行 + + + Internationalization Example + 国際化(i18n)の例 + + + Isometric + 等角投影法 + + + Language: %1 + 言語: %1 + + + English + 日本語 + + + Oblique + 斜め投影法 + + + Perspective + 遠近法 + + + Second + 第二行 + + + Third + 第三行 + + + View + 表示方式 + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_ko.ts b/examples/linguist/i18n/translations/i18n_ko.ts new file mode 100644 index 0000000..ab921c8 --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_ko.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + &File + 파일&F + + + E&xit + 종료&X + + + First + 첫번째 + + + Internationalization Example + 국제화 예제 + + + Isometric + 등측도 + + + Language: %1 + 언어 : %1 + + + English + 한국어 + + + Oblique + 빗각 + + + Perspective + 원근화법 + + + Second + 두번째 + + + Third + 세번째 + + + View + 보기 + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_nb.ts b/examples/linguist/i18n/translations/i18n_nb.ts new file mode 100644 index 0000000..076ed3a --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_nb.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + View + Vis + + + &File + &Fil + + + E&xit + &Avslutt + + + First + Første + + + Third + Tredje + + + Language: %1 + SprÃ¥k: %1 + + + English + Norsk + + + Oblique + Skjevt + + + Second + Andre + + + Isometric + Isometrisk + + + Perspective + Perspektiv + + + Internationalization Example + Internasjonaliseringseksempel + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_ru.ts b/examples/linguist/i18n/translations/i18n_ru.ts new file mode 100644 index 0000000..7c7645d --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_ru.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + View + Вид + + + &File + Файл + + + E&xit + Выход + + + First + Первый + + + Third + Третий + + + Language: %1 + Язык: %1 + + + English + Русский + + + Oblique + Курсив + + + Second + Второй + + + Isometric + Изометрический + + + Perspective + Перспектива + + + Internationalization Example + Пример интернационализации + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_sv.ts b/examples/linguist/i18n/translations/i18n_sv.ts new file mode 100644 index 0000000..b7b3a45 --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_sv.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + View + Visa + + + &File + &Arkiv + + + E&xit + &Avsluta + + + First + Första + + + Third + Tredje + + + Language: %1 + SprÃ¥k: %1 + + + English + Svenska + + + Oblique + Skevt + + + Second + Andra + + + Isometric + Isometriskt + + + Perspective + Perspektivt + + + Internationalization Example + Internationaliseringsexempel + + + LTR + LTR + + + diff --git a/examples/linguist/i18n/translations/i18n_zh.ts b/examples/linguist/i18n/translations/i18n_zh.ts new file mode 100644 index 0000000..8701d76 --- /dev/null +++ b/examples/linguist/i18n/translations/i18n_zh.ts @@ -0,0 +1,59 @@ + + + + + MainWindow + + View + 视图 + + + &File + 文件[&F] + + + E&xit + 退出[&x] + + + First + 第一个 + + + Third + 第三个 + + + Language: %1 + 语言: %1 + + + English + 简体中文 + + + Oblique + 斜投影 + + + Second + 第二个 + + + Isometric + 等角投影 + + + Perspective + 透视投影 + + + Internationalization Example + 国际化范例 + + + LTR + LTR + + + diff --git a/examples/linguist/linguist.pro b/examples/linguist/linguist.pro new file mode 100644 index 0000000..2d30450 --- /dev/null +++ b/examples/linguist/linguist.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs +SUBDIRS = arrowpad \ + hellotr \ + i18n \ + trollprint diff --git a/examples/linguist/localizedclock-idbased/CMakeLists.txt b/examples/linguist/localizedclock-idbased/CMakeLists.txt new file mode 100644 index 0000000..a8cdef4 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/CMakeLists.txt @@ -0,0 +1,61 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) + +project(localizedClockIdBased LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/demos/localizedClockIdBased") + +find_package(Qt6 REQUIRED COMPONENTS Core Linguist Qml Quick Widgets) + +qt_standard_project_setup(REQUIRES 6.8 + I18N_TRANSLATED_LANGUAGES de ar ko zh ja fr it es pt en) + +qt_add_executable(localizedClockIdBased + main.cpp +) + +qt_add_translations(localizedClockIdBased + TS_FILE_BASE i18n/clock + MERGE_QT_TRANSLATIONS + QT_TRANSLATION_CATALOGS qtbase + RESOURCE_PREFIX i18n +) + +qt_add_qml_module(localizedClockIdBased + URI qtexamples.localizedclock + VERSION 1.0 + QML_FILES + Main.qml + RESOURCES dialog.ui + SOURCES + timezonemanager.h timezonemanager.cpp + dialog.h dialog.cpp +) + +target_compile_definitions(localizedClockIdBased PRIVATE + QT_NO_CAST_FROM_ASCII +) + +target_link_libraries(localizedClockIdBased PRIVATE + Qt6::Quick Qt6::Widgets +) + +set_target_properties(localizedClockIdBased PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +install(TARGETS localizedClockIdBased + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) + diff --git a/examples/linguist/localizedclock-idbased/Main.qml b/examples/linguist/localizedclock-idbased/Main.qml new file mode 100644 index 0000000..2ee1f09 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/Main.qml @@ -0,0 +1,77 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import qtexamples.localizedclock + +ApplicationWindow { + id: root + width: 320 + height: 320 + visible: true + minimumWidth: layout.Layout.minimumWidth + minimumHeight: layout.Layout.minimumHeight + //% "Digital Clock" + title: qsTrId("Main-Digital-Clock") + property string time + property string date + property int seconds + property int diff + + ColumnLayout { + anchors.fill: parent + id: layout + Label { + text: root.time + fontSizeMode: Text.Fit + font.pixelSize: 60 + minimumPixelSize: 10 + horizontalAlignment: Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + } + Label { + //% "%n second(s)" + text: qsTrId("Main-n-second-s", root.seconds) + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + Label { + text: root.date + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + Label { + //% "Time zone: " + text: qsTrId("timezone") + TimeZoneManager.timeZone; + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + Button { + text: qsTrId("timezonelabel") + onClicked: TimeZoneManager.openDialog() + Layout.alignment: Qt.AlignHCenter + } + } + Connections { + target: TimeZoneManager + function onTimeZoneChanged() { + root.diff = TimeZoneManager.currentTimeZoneOffsetMs(); + } + } + Timer { + interval: 1000 + running: true + repeat: true + triggeredOnStart: true + + onTriggered: { + const now = new Date(new Date().getTime() + root.diff); + const locale = Qt.locale(); + root.time = now.toLocaleTimeString(locale, Locale.ShortFormat); + root.date = now.toLocaleDateString(locale); + root.seconds = now.getSeconds(); + } + } +} diff --git a/examples/linguist/localizedclock-idbased/dialog.cpp b/examples/linguist/localizedclock-idbased/dialog.cpp new file mode 100644 index 0000000..e70a404 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/dialog.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "dialog.h" +#include + +Dialog::Dialog(QWidget *parent) : QDialog(parent), m_ui(std::make_unique()) +{ + m_ui->setupUi(this); + const QList timeZoneIds = QTimeZone::availableTimeZoneIds(); + for (const QByteArray &timeZone : timeZoneIds) + m_ui->comboBox->addItem(QLatin1String(timeZone)); + connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, + [&] { emit timeZoneSelected(m_ui->comboBox->currentText()); }); +} + +Dialog::~Dialog() { } diff --git a/examples/linguist/localizedclock-idbased/dialog.h b/examples/linguist/localizedclock-idbased/dialog.h new file mode 100644 index 0000000..45bb8d3 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/dialog.h @@ -0,0 +1,26 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef DIALOG_H +#define DIALOG_H + +#include "ui_dialog.h" + +#include +#include + +class Dialog final : public QDialog +{ + Q_OBJECT +public: + explicit Dialog(QWidget *parent = nullptr); + ~Dialog(); + +signals: + void timeZoneSelected(const QString &timeZone); + +private: + std::unique_ptr m_ui; +}; + +#endif // DIALOG_H diff --git a/examples/linguist/localizedclock-idbased/dialog.ui b/examples/linguist/localizedclock-idbased/dialog.ui new file mode 100644 index 0000000..8a6e54d --- /dev/null +++ b/examples/linguist/localizedclock-idbased/dialog.ui @@ -0,0 +1,100 @@ + + + Dialog + + + + 0 + 0 + 266 + 119 + + + + Time Zone + + + + + 10 + 80 + 241 + 32 + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + 10 + 40 + 241 + 32 + + + + + + + 20 + 10 + 200 + 20 + + + + + 200 + 20 + + + + Select time zone + + + comboBox + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_ar.ts b/examples/linguist/localizedclock-idbased/i18n/clock_ar.ts new file mode 100644 index 0000000..c5ae0c9 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_ar.ts @@ -0,0 +1,43 @@ + + + + + + + + Digital Clock + ساعة رقمية + + + + %n second(s) + + %n ثانية + %n ثانيتان + %n ثوانٍ + %n ثانية + %n ثانية + %n ثانية + + + + + Time zone: + المنطقة الزمنية: + + + + + Select time zone + اختر المنطقة الزمنية + + + + Dialog + + + Time Zone + المنطقة الزمنية + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_de.ts b/examples/linguist/localizedclock-idbased/i18n/clock_de.ts new file mode 100644 index 0000000..c2f869b --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_de.ts @@ -0,0 +1,39 @@ + + + + + + + + Digital Clock + Digitale Uhr + + + + %n second(s) + + %n Sekunde + %n Sekunden + + + + + Time zone: + Zeitzone: + + + + + Select time zone + Zeitzone auswählen + + + + Dialog + + + Time Zone + Zeitzone + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_en.ts b/examples/linguist/localizedclock-idbased/i18n/clock_en.ts new file mode 100644 index 0000000..a244d4f --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_en.ts @@ -0,0 +1,40 @@ + + + + + + + + Digital Clock + + + + + %n second(s) + + %n second + %n seconds + + + + + Time zone: + + + + + + Select time zone + + + + + Dialog + + + Time Zone + Dialog + + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_es.ts b/examples/linguist/localizedclock-idbased/i18n/clock_es.ts new file mode 100644 index 0000000..76e5698 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_es.ts @@ -0,0 +1,39 @@ + + + + + + + + Digital Clock + Reloj digital + + + + %n second(s) + + %n segundo + %n segundos + + + + + Time zone: + Zona horaria: + + + + + Select time zone + Seleccionar zona horaria + + + + Dialog + + + Time Zone + Zona horaria + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_fr.ts b/examples/linguist/localizedclock-idbased/i18n/clock_fr.ts new file mode 100644 index 0000000..9bec51b --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_fr.ts @@ -0,0 +1,39 @@ + + + + + + + + Digital Clock + Horloge numérique + + + + %n second(s) + + %n seconde + %n secondes + + + + + Time zone: + Fuseau horaire: + + + + + Select time zone + Sélectionner le fuseau horaire + + + + Dialog + + + Time Zone + Fuseau horaire + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_it.ts b/examples/linguist/localizedclock-idbased/i18n/clock_it.ts new file mode 100644 index 0000000..9d1aa14 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_it.ts @@ -0,0 +1,39 @@ + + + + + + + + Digital Clock + Orologio digitale + + + + %n second(s) + + %n secondo + %n secondi + + + + + Time zone: + Fuso orario: + + + + + Select time zone + Seleziona fuso orario + + + + Dialog + + + Time Zone + Fuso orario + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_ja.ts b/examples/linguist/localizedclock-idbased/i18n/clock_ja.ts new file mode 100644 index 0000000..8ffa8fe --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_ja.ts @@ -0,0 +1,38 @@ + + + + + + + + Digital Clock + デジタル時計 + + + + %n second(s) + + %n 秒 + + + + + Time zone: + タイムゾーン: + + + + + Select time zone + タイムゾーンを選択 + + + + Dialog + + + Time Zone + タイムゾーン + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_ko.ts b/examples/linguist/localizedclock-idbased/i18n/clock_ko.ts new file mode 100644 index 0000000..87d4a10 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_ko.ts @@ -0,0 +1,38 @@ + + + + + + + + Digital Clock + 디지털 시계 + + + + %n second(s) + + %n초 + + + + + Time zone: + 시간대: + + + + + Select time zone + 시간대 선택 + + + + Dialog + + + Time Zone + 시간대 + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_pt.ts b/examples/linguist/localizedclock-idbased/i18n/clock_pt.ts new file mode 100644 index 0000000..1212326 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_pt.ts @@ -0,0 +1,39 @@ + + + + + + + + Digital Clock + Relógio digital + + + + %n second(s) + + %n segundo + %n segundos + + + + + Time zone: + Fuso horário: + + + + + Select time zone + Selecionar fuso horário + + + + Dialog + + + Time Zone + Fuso horário + + + diff --git a/examples/linguist/localizedclock-idbased/i18n/clock_zh.ts b/examples/linguist/localizedclock-idbased/i18n/clock_zh.ts new file mode 100644 index 0000000..06ff867 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/i18n/clock_zh.ts @@ -0,0 +1,38 @@ + + + + + + + + Digital Clock + 数字时钟 + + + + %n second(s) + + %n 秒 + + + + + Time zone: + 时区: + + + + + Select time zone + 选择时区 + + + + Dialog + + + Time Zone + 时区 + + + diff --git a/examples/linguist/localizedclock-idbased/main.cpp b/examples/linguist/localizedclock-idbased/main.cpp new file mode 100644 index 0000000..c76d930 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/main.cpp @@ -0,0 +1,60 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include +#include + +using namespace Qt::Literals::StringLiterals; + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QCommandLineParser parser; + QCommandLineOption localeOption("locale"_L1, "Locale to be used in the user interface"_L1, + "locale"_L1); + parser.addOption(localeOption); + parser.addHelpOption(); + parser.process(app); + + if (parser.isSet(localeOption)) { + QLocale locale(parser.value(localeOption)); + qInfo() << "Setting locale to" << locale.name(); + QLocale::setDefault(locale); + } + + qInfo() << "Current locale:" << QLocale(); + qInfo() << "UI languages in locale:" << QLocale().uiLanguages().join(", "_L1); + + // Always install English plurals + QTranslator enPlurals; + const auto enPluralsPath = ":/i18n/clock_en.qm"_L1; + if (!enPlurals.load(enPluralsPath)) + qFatal("Could not load %s!", qUtf8Printable(enPluralsPath)); + app.installTranslator(&enPlurals); + + // Load translation based on QLocale::uiLanguages() + QTranslator translation; + if (QLocale().language() != QLocale::English) { + if (translation.load(QLocale(), "clock"_L1, "_"_L1, ":/i18n/"_L1)) { + qInfo("Loading translation %s", + qUtf8Printable(QDir::toNativeSeparators(translation.filePath()))); + if (!app.installTranslator(&translation)) + qWarning("Could not install %s!", + qUtf8Printable(QDir::toNativeSeparators(translation.filePath()))); + } else { + qInfo("Could not load translation to %s. Using English.", + qUtf8Printable(QLocale().name())); + } + } + + QQmlApplicationEngine engine; + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + [](const QUrl &) { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + engine.loadFromModule("qtexamples.localizedclock", "Main"); + return app.exec(); +} diff --git a/examples/linguist/localizedclock-idbased/timezonemanager.cpp b/examples/linguist/localizedclock-idbased/timezonemanager.cpp new file mode 100644 index 0000000..8493b3d --- /dev/null +++ b/examples/linguist/localizedclock-idbased/timezonemanager.cpp @@ -0,0 +1,45 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "timezonemanager.h" +#include "dialog.h" + +#include + +TimeZoneManager::TimeZoneManager(QObject *parent) : QObject(parent) +{ + setTimeZone(QString::fromLatin1(QTimeZone::systemTimeZoneId())); +} + +TimeZoneManager::~TimeZoneManager() { } + +void TimeZoneManager::openDialog() +{ + if (!m_dialog) { + m_dialog = std::make_unique

(); + m_dialog->setModal(true); + connect(m_dialog.get(), &Dialog::timeZoneSelected, this, &TimeZoneManager::setTimeZone); + } + m_dialog->show(); +} + +qint64 TimeZoneManager::currentTimeZoneOffsetMs() +{ + const QTimeZone tz(m_timeZone.toLatin1()); + if (!tz.isValid()) + return 0; + const QDateTime nowUtc = QDateTime::currentDateTimeUtc(); + + const int targetOffset = tz.offsetFromUtc(nowUtc); + const int systemOffset = QTimeZone::systemTimeZone().offsetFromUtc(nowUtc); + + return (targetOffset - systemOffset) * 1000; +} + +void TimeZoneManager::setTimeZone(const QString &timeZone) +{ + if (m_timeZone != timeZone) { + m_timeZone = timeZone; + emit timeZoneChanged(); + } +} diff --git a/examples/linguist/localizedclock-idbased/timezonemanager.h b/examples/linguist/localizedclock-idbased/timezonemanager.h new file mode 100644 index 0000000..a507640 --- /dev/null +++ b/examples/linguist/localizedclock-idbased/timezonemanager.h @@ -0,0 +1,38 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TIMEZONEMANAGER_H +#define TIMEZONEMANAGER_H + +#include +#include + +class Dialog; + +class TimeZoneManager : public QObject +{ + Q_OBJECT + QML_SINGLETON + QML_ELEMENT + Q_PROPERTY(QString timeZone READ timeZone NOTIFY timeZoneChanged) + +public: + explicit TimeZoneManager(QObject *parent = nullptr); + ~TimeZoneManager(); + Q_INVOKABLE void openDialog(); + + Q_INVOKABLE QString timeZone() const { return m_timeZone; } + Q_INVOKABLE qint64 currentTimeZoneOffsetMs(); + +signals: + void timeZoneChanged(); + +private slots: + void setTimeZone(const QString &timeZone); + +private: + QString m_timeZone; + std::unique_ptr m_dialog; +}; + +#endif // TIMEZONEMANAGER_H diff --git a/examples/linguist/localizedclock-switchlocale/CMakeLists.txt b/examples/linguist/localizedclock-switchlocale/CMakeLists.txt new file mode 100644 index 0000000..ccc2ac6 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/CMakeLists.txt @@ -0,0 +1,59 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) + +project(localizedClockSwitchLocale LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/demos/localizedclock-switchlocale") + +find_package(Qt6 REQUIRED COMPONENTS Core Linguist Qml Quick) + +qt_standard_project_setup(REQUIRES 6.8 + I18N_TRANSLATED_LANGUAGES de ar ko zh ja fr it es pt) + +qt_add_executable(localizedClockSwitchLocale + main.cpp +) + +qt_add_translations(localizedClockSwitchLocale + TS_FILE_BASE i18n/clock + RESOURCE_PREFIX i18n +) + +qt_add_qml_module(localizedClockSwitchLocale + URI qtexamples.localizedclockswitchlocale + VERSION 1.0 + QML_FILES + Main.qml + SOURCES + translatormanager.h translatormanager.cpp + RESOURCES + "globe.png" +) + +target_link_libraries(localizedClockSwitchLocale PRIVATE + Qt6::Quick +) + +target_compile_definitions(localizedClockSwitchLocale PRIVATE + QT_NO_CAST_FROM_ASCII +) + +set_target_properties(localizedClockSwitchLocale PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + + +install(TARGETS localizedClockSwitchLocale + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/linguist/localizedclock-switchlocale/Main.qml b/examples/linguist/localizedclock-switchlocale/Main.qml new file mode 100644 index 0000000..e97dd33 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/Main.qml @@ -0,0 +1,125 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls.Basic +import qtexamples.localizedclockswitchlocale + +pragma ComponentBehavior: Bound + +Window { + id: root + width: 320 + height: 320 + visible: true + minimumWidth: layout.Layout.minimumWidth + minimumHeight: layout.Layout.minimumHeight + + function updateClock() { + const now = new Date() + const locale = Qt.locale() + root.time = now.toLocaleTimeString(locale, Locale.ShortFormat) + root.date = now.toLocaleDateString(locale) + root.seconds = now.getSeconds() + } + + Component.onCompleted: { + TranslatorManager.switchLanguage("en") + updateClock() + } + + title: qsTr("Digital Clock") + property string time + property string date + property int seconds + property string selectedLanguage: "English" + + Button { + id: languageButton + anchors.top: parent.top + anchors.right: parent.right + width: 120 + height: 40 + anchors.margins: 5 + background: Rectangle { + color: "transparent" + } + RowLayout { + anchors.centerIn: parent + Image { + source: "globe.png" + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + } + + Text { + text: root.selectedLanguage + color: "gray" + font.pixelSize: 18 + verticalAlignment: Text.AlignVCenter + } + } + ListModel { + id: languageModel + ListElement { name: "English"; code: "en" } + ListElement { name: "Deutsch"; code: "de" } + ListElement { name: "العربية"; code: "ar" } + ListElement { name: "한국어"; code: "ko" } + ListElement { name: "中文"; code: "zh" } + ListElement { name: "日本語"; code: "ja" } + ListElement { name: "Français"; code: "fr" } + ListElement { name: "Italiano"; code: "it" } + ListElement { name: "Español"; code: "es" } + ListElement { name: "Português"; code: "pt" } + } + Menu { + id: languageMenu + width: languageButton.width + Repeater { + model: languageModel + delegate: MenuItem { + required property string name + required property string code + text: name + onTriggered: { + TranslatorManager.switchLanguage(code) + root.selectedLanguage = name + root.updateClock() + } + } + } + } + onClicked: languageMenu.open() + } + ColumnLayout { + anchors.fill: parent + id: layout + Text { + text: root.time + fontSizeMode: Text.Fit + font.pixelSize: 60 + minimumPixelSize: 10 + horizontalAlignment: Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + } + Text { + text: qsTr("%n second(s)", "seconds", root.seconds) + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + Text { + text: root.date + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + } + Timer { + interval: 1000 + running: true + repeat: true + triggeredOnStart: true + + onTriggered: root.updateClock() + } +} diff --git a/examples/linguist/localizedclock-switchlocale/globe.png b/examples/linguist/localizedclock-switchlocale/globe.png new file mode 100644 index 0000000000000000000000000000000000000000..174bcc56063a64bea5679ad4364b27c4e32bb4f6 GIT binary patch literal 985 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz#ygy!i=6lDj$G? z>?NMQuIw*aIC+Gu4td1)5S3);_%%WyAy942(;N-X$A#J2h6n&jadFO&l&s4patvQy}7h>D8WQEGredi-sXXa^2t7mac2$o>%-g|~wBq%I zJ@caI8w^HM>rWnAtuw5{vD^L3}W0RyX-%Y`W`=^__!s9Bht=+)9 zqIQ*R!FDT~!7F_IZL`}< zzs!an?;>}`;5%2={gq<2DP%pMYB>E~p?vkVTgEQQ-3i{|TrUi=N?%XAcy$5aoIQ3~ zleKafHrPom$o)}z+=wyEeOCMn{RRdP`(kYX@0Ff!FOFwr%z3^6paGBmd` zG1WFOure^HE>*scq9HdwB{QuOU4xmGu@OXrR>$!}Kn)sj8%i>BQ;SOya|_V*SXh}_ WK=d$rH>3mgFnGH9xvX + + + + Main + + + Digital Clock + ساعة رقمية + + + + %n second(s) + seconds + + لا ثواني + ثانية واحدة + ثانيتان + %n ثوانٍ + %n ثانية + %n ثانية + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_de.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_de.ts new file mode 100644 index 0000000..0cd4699 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_de.ts @@ -0,0 +1,21 @@ + + + + + Main + + + Digital Clock + Digitale Uhr + + + + %n second(s) + seconds + + %n Sekunde + %n Sekunden + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_en.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_en.ts new file mode 100644 index 0000000..a47b556 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_en.ts @@ -0,0 +1,16 @@ + + + + + Main + + + %n second(s) + seconds + + %n second + %n seconds + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_es.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_es.ts new file mode 100644 index 0000000..2fa348d --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_es.ts @@ -0,0 +1,21 @@ + + + + + Main + + + Digital Clock + Reloj digital + + + + %n second(s) + seconds + + %n segundo + %n segundos + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_fr.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_fr.ts new file mode 100644 index 0000000..e0b3478 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_fr.ts @@ -0,0 +1,21 @@ + + + + + Main + + + Digital Clock + Horloge numérique + + + + %n second(s) + seconds + + %n seconde + %n secondes + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_it.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_it.ts new file mode 100644 index 0000000..20e4c61 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_it.ts @@ -0,0 +1,21 @@ + + + + + Main + + + Digital Clock + Orologio digitale + + + + %n second(s) + seconds + + %n secondo + %n secondi + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_ja.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_ja.ts new file mode 100644 index 0000000..b30f7ac --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_ja.ts @@ -0,0 +1,20 @@ + + + + + Main + + + Digital Clock + デジタル時計 + + + + %n second(s) + seconds + + %n 秒 + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_ko.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_ko.ts new file mode 100644 index 0000000..477b775 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_ko.ts @@ -0,0 +1,20 @@ + + + + + Main + + + Digital Clock + 디지털 시계 + + + + %n second(s) + seconds + + %n초 + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_pt.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_pt.ts new file mode 100644 index 0000000..615c665 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_pt.ts @@ -0,0 +1,21 @@ + + + + + Main + + + Digital Clock + Relógio digital + + + + %n second(s) + seconds + + %n segundo + %n segundos + + + + diff --git a/examples/linguist/localizedclock-switchlocale/i18n/clock_zh.ts b/examples/linguist/localizedclock-switchlocale/i18n/clock_zh.ts new file mode 100644 index 0000000..2544946 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/i18n/clock_zh.ts @@ -0,0 +1,20 @@ + + + + + Main + + + Digital Clock + 数字时钟 + + + + %n second(s) + seconds + + %n 秒 + + + + diff --git a/examples/linguist/localizedclock-switchlocale/main.cpp b/examples/linguist/localizedclock-switchlocale/main.cpp new file mode 100644 index 0000000..43e8c1d --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/main.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + QQmlApplicationEngine engine; + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + [](const QUrl &) { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + engine.loadFromModule("qtexamples.localizedclockswitchlocale", "Main"); + return app.exec(); +} diff --git a/examples/linguist/localizedclock-switchlocale/translatormanager.cpp b/examples/linguist/localizedclock-switchlocale/translatormanager.cpp new file mode 100644 index 0000000..67619eb --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/translatormanager.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "translatormanager.h" + +#include + +using namespace Qt::Literals::StringLiterals; + +TranslatorManager::TranslatorManager(QQmlEngine &engine) : m_engine(engine) { } + +TranslatorManager *TranslatorManager::create(QQmlEngine *engine, QJSEngine *) +{ + return new TranslatorManager(*engine); +} + +void TranslatorManager::switchLanguage(const QString &lang) +{ + QLocale locale(lang); + QLocale::setDefault(locale); + qApp->removeTranslator(&m_translator); // not necessary from Qt 6.10 + if (m_translator.load(locale, "clock"_L1, "_"_L1, ":/i18n/"_L1) + && qApp->installTranslator(&m_translator)) { + m_engine.retranslate(); + } else { + qWarning("Could not load translation to %s!", qPrintable(locale.name())); + } +} diff --git a/examples/linguist/localizedclock-switchlocale/translatormanager.h b/examples/linguist/localizedclock-switchlocale/translatormanager.h new file mode 100644 index 0000000..6e35f55 --- /dev/null +++ b/examples/linguist/localizedclock-switchlocale/translatormanager.h @@ -0,0 +1,25 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TRANSLATORMANAGER_H +#define TRANSLATORMANAGER_H + +#include +#include + +class TranslatorManager : public QObject +{ + Q_OBJECT + QML_SINGLETON + QML_ELEMENT +public: + explicit TranslatorManager(QQmlEngine &engine); + + static TranslatorManager *create(QQmlEngine *engine, QJSEngine *); + Q_INVOKABLE void switchLanguage(const QString &lang); + +private: + QTranslator m_translator; + QQmlEngine &m_engine; +}; +#endif // TRANSLATORMANAGER_H diff --git a/examples/linguist/localizedclock/CMakeLists.txt b/examples/linguist/localizedclock/CMakeLists.txt new file mode 100644 index 0000000..0ecc321 --- /dev/null +++ b/examples/linguist/localizedclock/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) + +project(localizedClock LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/demos/localizedClock") + +find_package(Qt6 REQUIRED COMPONENTS Core Linguist Qml Quick) + +qt_standard_project_setup(REQUIRES 6.8 + I18N_TRANSLATED_LANGUAGES de ar ko zh ja fr it es pt) + +qt_add_executable(localizedClock + main.cpp +) + +qt_add_translations(localizedClock + TS_FILE_BASE i18n/clock + RESOURCE_PREFIX i18n +) + +qt_add_qml_module(localizedClock + URI qtexamples.localizedclock + VERSION 1.0 + QML_FILES + Main.qml +) + +target_link_libraries(localizedClock PRIVATE + Qt6::Quick +) + +target_compile_definitions(localizedClock PRIVATE + QT_NO_CAST_FROM_ASCII +) + +set_target_properties(localizedClock PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +install(TARGETS localizedClock + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/linguist/localizedclock/Main.qml b/examples/linguist/localizedclock/Main.qml new file mode 100644 index 0000000..90d4d15 --- /dev/null +++ b/examples/linguist/localizedclock/Main.qml @@ -0,0 +1,63 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts + +Window { + id: root + width: 320 + height: 320 + visible: true + minimumWidth: layout.Layout.minimumWidth + minimumHeight: layout.Layout.minimumHeight + + title: qsTr("Digital Clock") + + property string time + property string date + property int seconds + + ColumnLayout { + anchors.fill: parent + id: layout + Text { + text: root.time + fontSizeMode: Text.Fit + font.pixelSize: 60 + minimumPixelSize: 10 + horizontalAlignment: Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + } + Text { + text: qsTr("%n second(s)", "seconds", root.seconds) + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + Text { + text: root.date + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + Text { + text: qsTr("Locale: %1").arg(Qt.locale().name) + font.pixelSize: 20 + Layout.alignment: Qt.AlignHCenter + } + } + + Timer { + interval: 1000 + running: true + repeat: true + triggeredOnStart: true + + onTriggered: { + const now = new Date(); + const locale = Qt.locale(); + root.time = now.toLocaleTimeString(locale, Locale.ShortFormat); + root.date = now.toLocaleDateString(locale); + root.seconds = now.getSeconds(); + } + } +} diff --git a/examples/linguist/localizedclock/i18n/clock_ar.ts b/examples/linguist/localizedclock/i18n/clock_ar.ts new file mode 100644 index 0000000..8996dc0 --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_ar.ts @@ -0,0 +1,30 @@ + + + + + Main + + + Digital Clock + ساعة رقمية + + + + %n second(s) + seconds + + لا ثواني + ثانية واحدة + ثانيتان + %n ثوانٍ + %n ثانية + %n ثانية + + + + + Locale: %1 + الإعدادات المحلية: %1 + + + diff --git a/examples/linguist/localizedclock/i18n/clock_de.ts b/examples/linguist/localizedclock/i18n/clock_de.ts new file mode 100644 index 0000000..8a4bfb0 --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_de.ts @@ -0,0 +1,26 @@ + + + + + Main + + + Digital Clock + Digitale Uhr + + + + %n second(s) + seconds + + %n Sekunde + %n Sekunden + + + + + Locale: %1 + Gebietsschema: %1 + + + diff --git a/examples/linguist/localizedclock/i18n/clock_en.ts b/examples/linguist/localizedclock/i18n/clock_en.ts new file mode 100644 index 0000000..2a67648 --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_en.ts @@ -0,0 +1,16 @@ + + + + + Main + + + %n second(s) + seconds + + %n second + %n seconds + + + + diff --git a/examples/linguist/localizedclock/i18n/clock_es.ts b/examples/linguist/localizedclock/i18n/clock_es.ts new file mode 100644 index 0000000..ba4619f --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_es.ts @@ -0,0 +1,26 @@ + + + + + Main + + + Digital Clock + Reloj digital + + + + %n second(s) + seconds + + %n segundo + %n segundos + + + + + Locale: %1 + Configuración regional: %1 + + + diff --git a/examples/linguist/localizedclock/i18n/clock_fr.ts b/examples/linguist/localizedclock/i18n/clock_fr.ts new file mode 100644 index 0000000..5bd0dc3 --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_fr.ts @@ -0,0 +1,26 @@ + + + + + Main + + + Digital Clock + Horloge numérique + + + + %n second(s) + seconds + + %n seconde + %n secondes + + + + + Locale: %1 + Langue : %1 + + + diff --git a/examples/linguist/localizedclock/i18n/clock_it.ts b/examples/linguist/localizedclock/i18n/clock_it.ts new file mode 100644 index 0000000..67c7034 --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_it.ts @@ -0,0 +1,26 @@ + + + + + Main + + + Digital Clock + Orologio digitale + + + + %n second(s) + seconds + + %n secondo + %n secondi + + + + + Locale: %1 + Impostazioni locali: %1 + + + diff --git a/examples/linguist/localizedclock/i18n/clock_ja.ts b/examples/linguist/localizedclock/i18n/clock_ja.ts new file mode 100644 index 0000000..01d18e0 --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_ja.ts @@ -0,0 +1,25 @@ + + + + + Main + + + Digital Clock + デジタル時計 + + + + %n second(s) + seconds + + %n 秒 + + + + + Locale: %1 + ロケール: %1 + + + diff --git a/examples/linguist/localizedclock/i18n/clock_ko.ts b/examples/linguist/localizedclock/i18n/clock_ko.ts new file mode 100644 index 0000000..91171ab --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_ko.ts @@ -0,0 +1,25 @@ + + + + + Main + + + Digital Clock + 디지털 시계 + + + + %n second(s) + seconds + + %n초 + + + + + Locale: %1 + 로케일: %1 + + + diff --git a/examples/linguist/localizedclock/i18n/clock_pt.ts b/examples/linguist/localizedclock/i18n/clock_pt.ts new file mode 100644 index 0000000..082a8c8 --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_pt.ts @@ -0,0 +1,26 @@ + + + + + Main + + + Digital Clock + Relógio digital + + + + %n second(s) + seconds + + %n segundo + %n segundos + + + + + Locale: %1 + Localidade: %1 + + + diff --git a/examples/linguist/localizedclock/i18n/clock_zh.ts b/examples/linguist/localizedclock/i18n/clock_zh.ts new file mode 100644 index 0000000..0e73968 --- /dev/null +++ b/examples/linguist/localizedclock/i18n/clock_zh.ts @@ -0,0 +1,25 @@ + + + + + Main + + + Digital Clock + 数字时钟 + + + + %n second(s) + seconds + + %n 秒 + + + + + Locale: %1 + 区域设置:%1 + + + diff --git a/examples/linguist/localizedclock/main.cpp b/examples/linguist/localizedclock/main.cpp new file mode 100644 index 0000000..68a6079 --- /dev/null +++ b/examples/linguist/localizedclock/main.cpp @@ -0,0 +1,62 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include +#include + +using namespace Qt::Literals::StringLiterals; + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QCommandLineParser parser; + QCommandLineOption localeOption("locale"_L1, "Locale to be used in the user interface"_L1, + "locale"_L1); + parser.addOption(localeOption); + parser.addHelpOption(); + parser.process(app); + + if (parser.isSet(localeOption)) { + QLocale locale(parser.value(localeOption)); + qInfo() << "Setting locale to" << locale.name(); + QLocale::setDefault(locale); + } + + qInfo() << "Current locale:" << QLocale(); + qInfo() << "UI languages in locale:" << QLocale().uiLanguages().join(", "_L1); + + // Always install English plurals + QTranslator enPlurals; + const auto enPluralsPath = ":/i18n/clock_en.qm"_L1; + if (!enPlurals.load(enPluralsPath)) + qFatal("Could not load %s!", qUtf8Printable(enPluralsPath)); + app.installTranslator(&enPlurals); + + // Load translation based on QLocale::uiLanguages() + QTranslator translation; + if (QLocale().language() != QLocale::English) { + if (translation.load(QLocale(), "clock"_L1, "_"_L1, ":/i18n/"_L1)) { + qInfo("Loading translation %s", + qUtf8Printable(QDir::toNativeSeparators(translation.filePath()))); + if (!app.installTranslator(&translation)) + qWarning("Could not install %s!", + qUtf8Printable(QDir::toNativeSeparators(translation.filePath()))); + } else { + qInfo("Could not load translation to %s. Using English.", + qUtf8Printable(QLocale().name())); + } + } + + QQmlApplicationEngine engine; + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + [](const QUrl &) { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + engine.loadFromModule("qtexamples.localizedclock", "Main"); + + return app.exec(); +} diff --git a/examples/linguist/trollprint/CMakeLists.txt b/examples/linguist/trollprint/CMakeLists.txt new file mode 100644 index 0000000..8ced26c --- /dev/null +++ b/examples/linguist/trollprint/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(trollprint LANGUAGES CXX) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/linguist/trollprint") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +find_package(Qt6 REQUIRED COMPONENTS LinguistTools) + +qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES pt) + +qt_add_executable(trollprint + main.cpp + mainwindow.cpp mainwindow.h + printpanel.cpp printpanel.h +) + +qt6_add_translations(trollprint + QM_FILES_OUTPUT_VARIABLE qm_files) +install(FILES ${qm_files} DESTINATION ${INSTALL_EXAMPLEDIR}) + +set_target_properties(trollprint PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(trollprint PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) + +install(TARGETS trollprint + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/linguist/trollprint/main.cpp b/examples/linguist/trollprint/main.cpp new file mode 100644 index 0000000..6d7b35b --- /dev/null +++ b/examples/linguist/trollprint/main.cpp @@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "mainwindow.h" + +using namespace Qt::StringLiterals; + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + auto locale = QLocale::system(); + +//! [0] + QTranslator translator; + if (translator.load(locale, u"trollprint"_s, u"_"_s)) + app.installTranslator(&translator); +//! [0] + + MainWindow mainWindow; + mainWindow.show(); + return app.exec(); +} diff --git a/examples/linguist/trollprint/mainwindow.cpp b/examples/linguist/trollprint/mainwindow.cpp new file mode 100644 index 0000000..42bbf43 --- /dev/null +++ b/examples/linguist/trollprint/mainwindow.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "mainwindow.h" +#include "printpanel.h" + +static constexpr QLatin1StringView version("1.0"); +MainWindow::MainWindow() +{ + printPanel = new PrintPanel; + setCentralWidget(printPanel); + + createActions(); + createMenus(); + +//! [0] + setWindowTitle(tr("Troll Print %1").arg(version)); +//! [0] +} + +void MainWindow::about() +{ + QMessageBox::information(this, tr("About Troll Print %1").arg(version), + tr("Troll Print %1.\n\n" + "Copyright 1999 Software, Inc.").arg(version)); +} + +//! [1] +void MainWindow::createActions() +{ +//! [2] + exitAct = new QAction(tr("E&xit"), this); + exitAct->setShortcut(tr("Ctrl+Q", "Quit")); +//! [2] + connect(exitAct, &QAction::triggered, this, &MainWindow::close); + + aboutAct = new QAction(tr("&About"), this); + aboutAct->setShortcut(Qt::Key_F1); + connect(aboutAct, &QAction::triggered, this, &MainWindow::about); + + aboutQtAct = new QAction(tr("About &Qt"), this); + connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt); +} + +void MainWindow::createMenus() +//! [1] //! [3] +{ + QMenu *fileMenu = menuBar()->addMenu(tr("&File")); + fileMenu->addAction(exitAct); + + menuBar()->addSeparator(); + + QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); + helpMenu->addAction(aboutAct); + helpMenu->addAction(aboutQtAct); +} +//! [3] diff --git a/examples/linguist/trollprint/mainwindow.h b/examples/linguist/trollprint/mainwindow.h new file mode 100644 index 0000000..ed2d7b6 --- /dev/null +++ b/examples/linguist/trollprint/mainwindow.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QMenu; +QT_END_NAMESPACE +class PrintPanel; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + +private slots: + void about(); + +private: + void createActions(); + void createMenus(); + + PrintPanel *printPanel; + QMenu *fileMenu; + QMenu *helpMenu; + QAction *exitAct; + QAction *aboutAct; + QAction *aboutQtAct; +}; + +#endif diff --git a/examples/linguist/trollprint/printpanel.cpp b/examples/linguist/trollprint/printpanel.cpp new file mode 100644 index 0000000..1d0c30c --- /dev/null +++ b/examples/linguist/trollprint/printpanel.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "printpanel.h" + +//! [0] +PrintPanel::PrintPanel(QWidget *parent) + : QWidget(parent) +{ +/* + QLabel *label = new QLabel(tr("TROLL PRINT")); + label->setAlignment(Qt::AlignCenter); +*/ +//! [0] + +//! [1] + twoSidedGroupBox = new QGroupBox(tr("2-sided")); + twoSidedEnabledRadio = new QRadioButton(tr("Enabled")); + twoSidedDisabledRadio = new QRadioButton(tr("Disabled")); +//! [1] //! [2] + twoSidedDisabledRadio->setChecked(true); + + colorsGroupBox = new QGroupBox(tr("Colors")); + colorsEnabledRadio = new QRadioButton(tr("Enabled")); + colorsDisabledRadio = new QRadioButton(tr("Disabled")); +//! [2] + colorsDisabledRadio->setChecked(true); + + QHBoxLayout *twoSidedLayout = new QHBoxLayout; + twoSidedLayout->addWidget(twoSidedEnabledRadio); + twoSidedLayout->addWidget(twoSidedDisabledRadio); + twoSidedGroupBox->setLayout(twoSidedLayout); + + QHBoxLayout *colorsLayout = new QHBoxLayout; + colorsLayout->addWidget(colorsEnabledRadio); + colorsLayout->addWidget(colorsDisabledRadio); + colorsGroupBox->setLayout(colorsLayout); + + QVBoxLayout *mainLayout = new QVBoxLayout; +/* + mainLayout->addWidget(label); +*/ + mainLayout->addWidget(twoSidedGroupBox); + mainLayout->addWidget(colorsGroupBox); + setLayout(mainLayout); +} diff --git a/examples/linguist/trollprint/printpanel.h b/examples/linguist/trollprint/printpanel.h new file mode 100644 index 0000000..9a075dc --- /dev/null +++ b/examples/linguist/trollprint/printpanel.h @@ -0,0 +1,32 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef PRINTPANEL_H +#define PRINTPANEL_H + +#include + +QT_BEGIN_NAMESPACE +class QGroupBox; +class QRadioButton; +QT_END_NAMESPACE + +//! [0] +class PrintPanel : public QWidget +{ + Q_OBJECT +//! [0] + +public: + PrintPanel(QWidget *parent = 0); + +private: + QGroupBox *twoSidedGroupBox; + QGroupBox *colorsGroupBox; + QRadioButton *twoSidedEnabledRadio; + QRadioButton *twoSidedDisabledRadio; + QRadioButton *colorsEnabledRadio; + QRadioButton *colorsDisabledRadio; +}; + +#endif diff --git a/examples/linguist/trollprint/trollprint.pro b/examples/linguist/trollprint/trollprint.pro new file mode 100644 index 0000000..fa51751 --- /dev/null +++ b/examples/linguist/trollprint/trollprint.pro @@ -0,0 +1,13 @@ +HEADERS = mainwindow.h \ + printpanel.h +SOURCES = main.cpp \ + mainwindow.cpp \ + printpanel.cpp +TRANSLATIONS = trollprint_pt.ts + +target.path = $$[QT_INSTALL_EXAMPLES]/linguist/trollprint +INSTALLS += target + +QT += widgets + +simulator: warning(This example might not fully work on Simulator platform) diff --git a/examples/linguist/trollprint/trollprint_en.ts b/examples/linguist/trollprint/trollprint_en.ts new file mode 100644 index 0000000..fc9deb3 --- /dev/null +++ b/examples/linguist/trollprint/trollprint_en.ts @@ -0,0 +1,10 @@ + + + + diff --git a/examples/linguist/trollprint/trollprint_pt.ts b/examples/linguist/trollprint/trollprint_pt.ts new file mode 100644 index 0000000..b8e215c --- /dev/null +++ b/examples/linguist/trollprint/trollprint_pt.ts @@ -0,0 +1,67 @@ + + + + + MainWindow + + Troll Print %1 + Troll Imprimir %1 + + + E&xit + &Sair + + + &About + &Sobre + + + About &Qt + Sobre &Qt + + + &File + &Arquivo + + + &Help + A&juda + + + About Troll Print %1 + Sobre Troll Imprimir %1 + + + Troll Print %1. + +Copyright 1999 Software, Inc. + Troll Imprimir %1 + +Copyright 1999 Software, Inc. + + + Ctrl+Q + Quit + Ctrl+Q + + + + PrintPanel + + 2-sided + 2-lados + + + Enabled + Ativado + + + Disabled + Desativado + + + Colors + Cores + + + diff --git a/examples/uitools/CMakeLists.txt b/examples/uitools/CMakeLists.txt new file mode 100644 index 0000000..16d288c --- /dev/null +++ b/examples/uitools/CMakeLists.txt @@ -0,0 +1 @@ +qt_internal_add_example(textfinder) diff --git a/examples/uitools/doc/images/textfinder-example-userinterface.webp b/examples/uitools/doc/images/textfinder-example-userinterface.webp new file mode 100644 index 0000000000000000000000000000000000000000..e746c730470acd008f5a0cbe9770eb54b41f83c5 GIT binary patch literal 13754 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 R=IiepvH#a}NbmyizW_&gqvQYp literal 0 HcmV?d00001 diff --git a/examples/uitools/doc/src/textfinder.qdoc b/examples/uitools/doc/src/textfinder.qdoc new file mode 100644 index 0000000..4f982b0 --- /dev/null +++ b/examples/uitools/doc/src/textfinder.qdoc @@ -0,0 +1,155 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example textfinder + \examplecategory {Desktop} + \meta tags {widgets,designer} + \ingroup examples-qtuitools + \title Text Finder + + \brief Dynamically loading .ui files using QUiLoader. + + The TextFinder example shows how to load and setup a \c{.ui} file + dynamically using the \l{QUiLoader} class that is part of the + \l{Qt UI Tools} library. + + The program allows the user to look up a particular word within the + contents of a text. The visual elements and layout of the user interface is + loaded at runtime, from a the program resources. + + \table + \row \li \inlineimage {textfinder-example-find.webp} {TextFinder find button and input} + \li \inlineimage {textfinder-example-find2.webp} {TextFinder Highlighted results} + \endtable + + \section1 Setting Up The Resource File + + The resources required for the example are: + \list + \li \c{textfinder.ui} - the user interface file created in + \l{Qt Widgets Designer} + \li \c{input.txt} - a text file containing some text to be displayed in + a QTextEdit + \endlist + + \c textfinder.ui contains all the necessary QWidget objects for the Text + Finder. A QLineEdit is used for the user input, a QTextEdit is used to + display the contents of \c input.txt, a QLabel is used to display the text + "Keyword", and a QPushButton is used for the \uicontrol Find button. Note + that all the widgets have sensible \c{objectName}'s assigned. These are + used in code to identify them. + + The screenshot below shows the preview obtained in \l{Qt Widgets Designer}. + + \image {doc/images/textfinder-example-userinterface.webp} {Screenshot of TestFinder UI} + + In this example, we store both resources in the applicaton's executable by + including the \c{textfinder.qrc} file. Alternatively, the files could also + be loaded at runtime from the file system, or from an external binary + resource \c{.rcc} file. For more information on resource files, see + \l{The Qt Resource System}. + + The \c{textfinder.qrc} file lists all files that should be included as a + resource: + + \quotefile textfinder/textfinder.qrc + + To generate a form at run-time, the example is linked against the + \l{Qt Ui Tools} library. This is done in the \c{textfinder.pro} file: + + \snippet textfinder/textfinder.pro 0 + + \section1 TextFinder Class Definition + + The \c TextFinder class contains the main user interface. It declares + pointers to the QPushButton, QTextEdit and QLineEdit elements described + above. The QLabel in the user interface is not declared here as we do + not need to access it from code. + + \snippet textfinder/textfinder.h 0 + + The slot \c{on_findButton_clicked()} is a slot named according to the + \l{Using a Designer UI File in Your Application#Automatic Connections} + {Automatic Connection} naming convention required + by \c uic. + + \section1 Loading the Resources + + We use QFile to load the data from the program resources at runtime. The + code for this is in two method methods on top of \c{textfinder.cpp}: + \c{loadUiFile} and \c{loadTextFile}. + + The \c{loadUiFile} function loads the user interface file previously + created in \l{Qt Widgets Designer}. First, the \c{textfinder.ui} + file is located and opened from the resource system. + Then a QUiLoader instance is + created, and the QUiLoader::load() function is called, with the first + argument being the open file, and the second argument being the pointer of + the widget that should be set as the parent. The created QWidget is + returned. + + \snippet textfinder/textfinder.cpp 4 + + In a similar vein, the \c loadTextFile function locates and opens + \c{input.txt} from the resources. It then returns the file's content + using the QTextStream::readAll() function. + + \snippet textfinder/textfinder.cpp 5 + + \section1 TextFinder Class Implementation + + The \c TextFinder class's constructor does not instantiate any child widgets + directly. Instead, it calls the \c loadUiFile() function, and then uses + QObject::findChild() to locate the created \l{QWidget}s by object name. + + \snippet textfinder/textfinder.cpp 0 + + We then use QMetaObject::connectSlotsByName() to enable the automatic + calling of the \c on_findButton_clicked() slot. + + \snippet textfinder/textfinder.cpp 2 + + The \c loadTextFile function is called to get the text to be shown in the + QTextEdit. + + \snippet textfinder/textfinder.cpp 3a + + The dynamically loaded user interface in \c formWidget is now properly set + up. We now embed \c formWidget through a \c QVBoxLayout. + + \snippet textfinder/textfinder.cpp 3b + + At the end of the constructor we set a window title. + + \snippet textfinder/textfinder.cpp 3c + + The \c{on_findButton_clicked()} function is a slot that is connected to + \c{ui_findButton}'s \c clicked() signal. The \c searchString is extracted + from the \c ui_lineEdit and the \c document is extracted from + \c ui_textEdit. If there is an empty \c searchString, a QMessageBox is + used, requesting the user to enter a word. Otherwise, we traverse through + the words in \c ui_textEdit, and highlight all ocurrences of the + \c searchString. Two QTextCursor objects are used: One to traverse through + the words in \c line and another to keep track of the edit blocks. + + \snippet textfinder/textfinder.cpp 7 + + The \c found flag is used to indicate if the \c searchString was found + within the contents of \c ui_textEdit. If it was not found, a QMessageBox + is used to inform the user. + + \snippet textfinder/textfinder.cpp 9 + + \section1 \c main() Function + + The \c main() function instantiates and shows \c TextFinder. + + \snippet textfinder/main.cpp 0 + + There are various approaches to include forms into applications. Using + \l QUiLoader is just one of them. See \l{Using a Designer UI File in Your + Application} for more information on the other approaches available. + + \sa {Calculator Builder} + */ diff --git a/examples/uitools/textfinder/CMakeLists.txt b/examples/uitools/textfinder/CMakeLists.txt new file mode 100644 index 0000000..21e4f08 --- /dev/null +++ b/examples/uitools/textfinder/CMakeLists.txt @@ -0,0 +1,56 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(textfinder LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/uitools/textfinder") + +#! [0] +find_package(Qt6 REQUIRED COMPONENTS Core Gui UiTools Widgets) +#! [0] + +qt_add_executable(textfinder + main.cpp + textfinder.cpp textfinder.h +) + +set_target_properties(textfinder PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +#! [1] +target_link_libraries(textfinder PUBLIC + Qt::Core + Qt::Gui + Qt::UiTools + Qt::Widgets +) +#! [1] + +# Resources: +set(textfinder_resource_files + "forms/input.txt" + "forms/textfinder.ui" +) + +qt6_add_resources(textfinder "textfinder" + PREFIX + "/" + FILES + ${textfinder_resource_files} +) + +install(TARGETS textfinder + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/uitools/textfinder/forms/input.txt b/examples/uitools/textfinder/forms/input.txt new file mode 100644 index 0000000..4dc15ce --- /dev/null +++ b/examples/uitools/textfinder/forms/input.txt @@ -0,0 +1,3 @@ +This user interface was loaded and processed at run-time. + +Applications that want to load .ui files at run-time must be configured to be link against the QtUiTools module. A form loader object, provided by the QUiLoader class, is used to construct the user interface. This user interface can be retrieved from any QIODevice; for example, a QFile object can be used to obtain a form stored in a project's resources. The QUiLoader::load() function takes the user interface description contained in the file and constructs the form widget. diff --git a/examples/uitools/textfinder/forms/textfinder.ui b/examples/uitools/textfinder/forms/textfinder.ui new file mode 100644 index 0000000..f8cec91 --- /dev/null +++ b/examples/uitools/textfinder/forms/textfinder.ui @@ -0,0 +1,99 @@ + + + Form + + + + 0 + 0 + 378 + 158 + + + + Find Text + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + + + true + + + + + + + &Keyword: + + + lineEdit + + + + + + + &Find + + + + + + + + + + + + + + lineEdit + returnPressed() + findButton + animateClick() + + + 261 + 17 + + + 320 + 17 + + + + + diff --git a/examples/uitools/textfinder/main.cpp b/examples/uitools/textfinder/main.cpp new file mode 100644 index 0000000..c1e2e69 --- /dev/null +++ b/examples/uitools/textfinder/main.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "textfinder.h" + +#include + +//! [0] +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + TextFinder textFinder; + textFinder.show(); + + return app.exec(); +} +//! [0] diff --git a/examples/uitools/textfinder/textfinder.cpp b/examples/uitools/textfinder/textfinder.cpp new file mode 100644 index 0000000..2d3b6ea --- /dev/null +++ b/examples/uitools/textfinder/textfinder.cpp @@ -0,0 +1,119 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "textfinder.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace Qt::StringLiterals; + +//! [4] +static QWidget *loadUiFile(QWidget *parent) +{ + QFile file(u":/forms/textfinder.ui"_s); + if (!file.open(QIODevice::ReadOnly)) + qFatal("Cannot open resource file"); + + return QUiLoader().load(&file, parent); +} +//! [4] + +//! [5] +static QString loadTextFile() +{ + QFile inputFile(u":/forms/input.txt"_s); + if (!inputFile.open(QIODevice::ReadOnly)) + qFatal("Cannot open resource file"); + + return QTextStream(&inputFile).readAll(); +} +//! [5] + +//! [0] +TextFinder::TextFinder(QWidget *parent) + : QWidget(parent) +{ + QWidget *formWidget = loadUiFile(this); + +//! [1] + ui_findButton = findChild("findButton"); + ui_textEdit = findChild("textEdit"); + ui_lineEdit = findChild("lineEdit"); +//! [0] //! [1] + +//! [2] + QMetaObject::connectSlotsByName(this); +//! [2] + +//! [3a] + ui_textEdit->setText(loadTextFile()); +//! [3a] + +//! [3b] + auto *layout = new QVBoxLayout(this); + layout->addWidget(formWidget); +//! [3b] + +//! [3c] + setWindowTitle(tr("Text Finder")); +} +//! [3c] + +//! [6] //! [7] +void TextFinder::on_findButton_clicked() +{ + QString searchString = ui_lineEdit->text(); + QTextDocument *document = ui_textEdit->document(); + + bool found = false; + + // undo previous change (if any) + document->undo(); + + if (searchString.isEmpty()) { + QMessageBox::information(this, tr("Empty Search Field"), + tr("The search field is empty. " + "Please enter a word and click Find.")); + } else { + QTextCursor highlightCursor(document); + QTextCursor cursor(document); + + cursor.beginEditBlock(); +//! [6] + + QTextCharFormat plainFormat(highlightCursor.charFormat()); + QTextCharFormat colorFormat = plainFormat; + colorFormat.setForeground(Qt::red); + + while (!highlightCursor.isNull() && !highlightCursor.atEnd()) { + highlightCursor = document->find(searchString, highlightCursor, + QTextDocument::FindWholeWords); + + if (!highlightCursor.isNull()) { + found = true; + highlightCursor.movePosition(QTextCursor::WordRight, + QTextCursor::KeepAnchor); + highlightCursor.mergeCharFormat(colorFormat); + } + } + +//! [8] + cursor.endEditBlock(); +//! [7] //! [9] + + if (found == false) { + QMessageBox::information(this, tr("Word Not Found"), + tr("Sorry, the word cannot be found.")); + } + } +} +//! [8] //! [9] diff --git a/examples/uitools/textfinder/textfinder.h b/examples/uitools/textfinder/textfinder.h new file mode 100644 index 0000000..a9204c1 --- /dev/null +++ b/examples/uitools/textfinder/textfinder.h @@ -0,0 +1,33 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef TEXTFINDER_H +#define TEXTFINDER_H + +#include + +QT_BEGIN_NAMESPACE +class QLineEdit; +class QPushButton; +class QTextEdit; +QT_END_NAMESPACE + +//! [0] +class TextFinder : public QWidget +{ + Q_OBJECT + +public: + explicit TextFinder(QWidget *parent = nullptr); + +private slots: + void on_findButton_clicked(); + +private: + QPushButton *ui_findButton; + QTextEdit *ui_textEdit; + QLineEdit *ui_lineEdit; +}; +//! [0] + +#endif // TEXTFINDER_H diff --git a/examples/uitools/textfinder/textfinder.pro b/examples/uitools/textfinder/textfinder.pro new file mode 100644 index 0000000..8f66814 --- /dev/null +++ b/examples/uitools/textfinder/textfinder.pro @@ -0,0 +1,10 @@ +#! [0] +QT += widgets uitools + +HEADERS = textfinder.h +SOURCES = textfinder.cpp main.cpp +RESOURCES = textfinder.qrc +#! [0] + +target.path = $$[QT_INSTALL_EXAMPLES]/uitools/textfinder +INSTALLS += target diff --git a/examples/uitools/textfinder/textfinder.qrc b/examples/uitools/textfinder/textfinder.qrc new file mode 100644 index 0000000..a4cea8a --- /dev/null +++ b/examples/uitools/textfinder/textfinder.qrc @@ -0,0 +1,6 @@ + + + forms/textfinder.ui + forms/input.txt + + \ No newline at end of file diff --git a/examples/uitools/uitools.pro b/examples/uitools/uitools.pro new file mode 100644 index 0000000..e0d2206 --- /dev/null +++ b/examples/uitools/uitools.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = textfinder diff --git a/licenseRule.json b/licenseRule.json new file mode 100644 index 0000000..41816f5 --- /dev/null +++ b/licenseRule.json @@ -0,0 +1,249 @@ +[ + { + "comment": [ "file_pattern_ending: strings matched against the end of a file name.", + "location keys: regular expression matched against the beginning of", + "the file path (relative to the git submodule root).", + "spdx: list of SPDX-License-Expression's allowed in the matching files.", + "-------------------------------------------------------", + "Files with the following endings are Build System licensed,", + "unless they are examples or test data", + "Files with other endings can also be build system files" + ], + "file_pattern_ending": ["CMakeLists.txt", ".cmake", ".pro", ".prf", "configure", + ".pri", ".cfg", ".qrc", ".plist", + ".cmake.conf", ".tag", + "coin/axivion/ci_config_linux.json", ".yaml"], + "location": { + "": { + "comment": "File with other endings also belong to the build system file type", + "file type": "build system", + "spdx": ["BSD-3-Clause"] + }, + "(.*)(examples/|snippets/)": { + "comment": "Example takes precedence", + "file type": "examples and snippets", + "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] + }, + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/.*/expected/": { + "comment": "", + "file type": "test", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] + } + } + }, + { + "comments": ["Files with the following endings are infrastructure licensed"], + "file_pattern_ending": [".gitattributes", ".gitignore", ".gitmodules", ".gitreview", + "clang-format", "licenseRule.json", "REUSE.toml"], + "location":{ + "": { + "comment": "Default", + "file type": "infrastructure", + "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] + } + } + }, + { + "file_pattern_ending": [".qdoc", ".qdocinc" , ".qdocconf", ".qdoc.sample", "README.md", + "README", "qt_attribution.json", "README_test.chromium"], + "location":{ + "": { + "comment": "", + "file type": "documentation", + "spdx": ["LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only"] + }, + "src/qdoc/qdoc/tests/generatedoutput/testdata/(?!REUSE.toml)": { + "comment": "", + "file type": "test", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] + }, + "src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/(?!REUSE.toml)": { + "comment": "", + "file type": "test", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] + }, + "src/qdoc/qdoc/tests/config/testdata/(?!REUSE.toml)": { + "comment": "", + "file type": "test", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] + } + } + }, + { + "comments": ["Files with the following endings are tool licensed", + "unless they are in example or snippet, in which case", + "the should be accordingly licensed.", + "At the moment there is no such file in example not snippet."], + "file_pattern_ending": [".sh", ".py", ".pl", ".bat", ".ps1"], + "location":{ + "": { + "comment": "", + "file type": "tools", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"] + }, + "tests/auto/linguist/lupdate/testdata/good/parsepython/main.py": { + "comment": "", + "file type": "test", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] + }, + "(.*)(examples/|snippets/)": { + "comment": "Example takes precedence", + "file type": "examples and snippets", + "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] + } + } + }, + { + "comment": ["All other files", + "The licensing is defined only by the file location in the Qt module repository.", + "NO key for this case!", + "This needs to be the last entry of the file."], + "location": { + "": { + "comment": "tools repository", + "file type": "tools", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"] + }, + "dist/": { + "comment": "Default", + "file type": "documentation", + "spdx": ["LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only"] + }, + "src/designer/src/plugins/": { + "comment": "plugins for Qt Widgets Designer, but also Qt UI Tools", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/uiplugin/": { + "comment": "UiTools links UiPlugin", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/assistant/help/": { + "comment": "library", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/assistant/plugins/help/": { + "comment": "help engine plugin", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/assistant/qhelpgenerator/": { + "comment": "", + "file type": "tools", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"] + }, + "src/assistant/shared/": { + "comment": "used in library and tools", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/assistant/": { + "comment": "for a tool", + "file type": "tools", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"] + }, + "src/designer/src/lib/uilib/": { + "comment": "code used in src/uitools", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/designer/": { + "comment": "for a tool", + "file type": "tools", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"] + }, + "src/uilib/": { + "comment": "library", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/shared/": { + "comment": "used by some libraries", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/uitools/": { + "comment": "code for the Qt Ui Tools library", + "file type": "module and plugin", + "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] + }, + "src/qdoc/qdoc/src/qdoc/": { + "comment": "", + "file type": "tools", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"] + }, + "src/qdoc/catch/include/catch/catch.hpp": { + "comment": "src/qdoc/catch/LICENSE.CATCH.txt", + "file type": "module and plugin", + "spdx": ["BSL-1.0"] + }, + "tests/": { + "comment": "", + "file type": "test", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] + }, + "src/qdoc/qdoc/tests/": { + "comment": "", + "file type": "test", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] + }, + "src/qdoc/qdoc/tests/(validateqdocoutputfiles|generatedoutput)/.*/(examples|snippets)": { + "comment": "", + "file type": "test", + "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] + }, + "(.*)(examples/|snippets/)": { + "comment": "", + "file type": "examples and snippets", + "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] + }, + "(examples|.*).*/doc/images/": { + "comment": "Documentation asset", + "file type": "documentation", + "spdx": ["LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only"] + }, + "src/assistant/assistant/doc/images/": { + "comment": "Documentation asset", + "file type": "documentation", + "spdx": ["LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only"] + }, + "tests/auto/linguist/lconvert/data/test-kde-.*.po": { + "comment": "", + "file type": "public domain", + "spdx": ["CC0-1.0"] + }, + "tests/auto/linguist/lconvert/data/wrapping.po": { + "comment": "", + "file type": "public domain", + "spdx": ["CC0-1.0"] + }, + "tests/auto/linguist/lconvert/data/test1-.*.po": { + "comment": "", + "file type": "public domain", + "spdx": ["CC0-1.0"] + }, + "tests/auto/linguist/lconvert/data/test-translator-comment.po": { + "comment": "", + "file type": "public domain", + "spdx": ["CC0-1.0"] + }, + "tests/auto/linguist/lconvert/data/test-empty-comment.po": { + "comment": "", + "file type": "public domain", + "spdx": ["CC0-1.0"] + }, + "tests/auto/linguist/lconvert/data/test-developer-comment.po": { + "comment": "", + "file type": "public domain", + "spdx": ["CC0-1.0"] + }, + "src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h": { + "comment": "", + "file type": "3rd party", + "spdx": ["Apache-2.0 WITH LLVM-exception"] + } + } + } +] diff --git a/qt_cmdline.cmake b/qt_cmdline.cmake new file mode 100644 index 0000000..e69de29 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..5339498 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,58 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Need to stop building these apps by default because they would fail +# in device_and_simulator and webassembly builds. +if(UIKIT OR WASM) + set(_qt_additional_tools_to_exclude qtplugininfo) +endif() + +qt_exclude_tool_directories_from_default_target( + distancefieldgenerator + pixeltool + ${_qt_additional_tools_to_exclude} +) + +# Evaluate features to decide what to build. +# The config files will be written in the src/global module. +qt_feature_evaluate_features("${CMAKE_CURRENT_SOURCE_DIR}/../configure.cmake") + +option(QT_INSTALL_XDG_DESKTOP_ENTRIES "Enable XDG desktop entries installation" OFF) + +if(TARGET Qt::Widgets) + add_subdirectory(uiplugin) + add_subdirectory(uitools) +endif() + +add_subdirectory(global) # special case add as first directory +if(QT_FEATURE_linguist) + add_subdirectory(linguist) +endif() +# add_subdirectory(global) # special case remove +if(QT_FEATURE_designer) + add_subdirectory(designer) +endif() +if(QT_FEATURE_pixeltool) + add_subdirectory(pixeltool) +endif() +if(QT_FEATURE_assistant) + add_subdirectory(assistant) +endif() +if(QT_FEATURE_distancefieldgenerator) + add_subdirectory(distancefieldgenerator) +endif() +if(QT_FEATURE_qtattributionsscanner) + add_subdirectory(qtattributionsscanner) +endif() +if(QT_FEATURE_qtplugininfo) + add_subdirectory(qtplugininfo) +endif() + +add_subdirectory(qdoc) + +if(QT_FEATURE_qdbus) + add_subdirectory(qdbus) +endif() +if(QT_FEATURE_qtdiag) + add_subdirectory(qtdiag) +endif() diff --git a/src/assistant/CMakeLists.txt b/src/assistant/CMakeLists.txt new file mode 100644 index 0000000..eff944e --- /dev/null +++ b/src/assistant/CMakeLists.txt @@ -0,0 +1,61 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_FEATURE_fullqthelp) + add_subdirectory(help) + add_subdirectory(plugins) + return() +endif() + +if(NOT TARGET Qt::Sql OR NOT TARGET Qt::PrintSupport) + return() +endif() + +qt_exclude_tool_directories_from_default_target( + assistant + qhelpgenerator +) + +if(NOT TARGET Qt::Sql) + return() +endif() +if(NOT QT_FEATURE_assistant) + return() +endif() +add_subdirectory(help) +add_subdirectory(assistant) +add_subdirectory(qhelpgenerator) +add_subdirectory(plugins) + +set(QLITEHTML_BIN_PATH ${INSTALL_BINDIR}) +set(QLITEHTML_LIBRARY_PATH ${INSTALL_LIBDIR}) +set(QLITEHTML_LIBRARY_TYPE STATIC) +set(BUILD_SHARED_LIBS OFF) +set(BUILD_TESTING OFF) +if(QT_FEATURE_static_runtime AND MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() +add_subdirectory(qlitehtml/src EXCLUDE_FROM_ALL) +if(TARGET qlitehtml) + qt_autogen_tools_initial_setup(qlitehtml) + # The litehtml and gumbo targets will not be available here if they are not built by Qt + # but found in the system, because they are imported only to the subdirectory scope + # where find_package was called. But that's fine, we wouldn't be able to set compiler flags + # on them anyway. + if(TARGET litehtml) + qt_internal_set_exceptions_flags(litehtml OFF) + qt_disable_warnings(litehtml) + endif() + if(TARGET gumbo) + qt_disable_warnings(gumbo) + endif() + qt_disable_warnings(qlitehtml) + target_link_libraries(qlitehtml PRIVATE Qt::PlatformCommonInternal) + set_target_properties(qlitehtml PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${QT_BUILD_DIR}/${INSTALL_BINDIR}" + LIBRARY_OUTPUT_DIRECTORY "${QT_BUILD_DIR}/${INSTALL_LIBDIR}" + ARCHIVE_OUTPUT_DIRECTORY "${QT_BUILD_DIR}/${INSTALL_LIBDIR}") + qt_handle_multi_config_output_dirs(qlitehtml) +else() + message(FATAL_ERROR "qlitehtml not found. Did you run git submodule update --init --recursive?") +endif() diff --git a/src/assistant/assistant/CMakeLists.txt b/src/assistant/assistant/CMakeLists.txt new file mode 100644 index 0000000..5bac76d --- /dev/null +++ b/src/assistant/assistant/CMakeLists.txt @@ -0,0 +1,204 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## assistant App: +##################################################################### + +qt_internal_add_app(assistant + SOURCES + ../../shared/fontpanel/fontpanel.cpp ../../shared/fontpanel/fontpanel_p.h + ../shared/collectionconfiguration.cpp ../shared/collectionconfiguration.h + aboutdialog.cpp aboutdialog.h + bookmarkdialog.cpp bookmarkdialog.h bookmarkdialog.ui + bookmarkfiltermodel.cpp bookmarkfiltermodel.h + bookmarkitem.cpp bookmarkitem.h + bookmarkmanager.cpp bookmarkmanager.h + bookmarkmanagerwidget.cpp bookmarkmanagerwidget.h bookmarkmanagerwidget.ui + bookmarkmodel.cpp bookmarkmodel.h + bookmarkwidget.ui + centralwidget.cpp centralwidget.h + cmdlineparser.cpp cmdlineparser.h + contentwindow.cpp contentwindow.h + findwidget.cpp findwidget.h + globalactions.cpp globalactions.h + helpbrowsersupport.cpp helpbrowsersupport.h + helpdocsettings.cpp helpdocsettings.h + helpdocsettingswidget.cpp helpdocsettingswidget.h helpdocsettingswidget.ui + helpenginewrapper.cpp helpenginewrapper.h + helpviewer.cpp helpviewer.h helpviewerimpl.cpp helpviewerimpl.h helpviewerimpl_p.h + indexwindow.cpp indexwindow.h + main.cpp + mainwindow.cpp mainwindow.h + openpagesmanager.cpp openpagesmanager.h + openpagesmodel.cpp openpagesmodel.h + openpagesswitcher.cpp openpagesswitcher.h + openpageswidget.cpp openpageswidget.h + preferencesdialog.cpp preferencesdialog.h preferencesdialog.ui + qtdocinstaller.cpp qtdocinstaller.h + remotecontrol.cpp remotecontrol.h + searchwidget.cpp searchwidget.h + topicchooser.cpp topicchooser.h topicchooser.ui + tracer.h + xbelsupport.cpp xbelsupport.h + INCLUDE_DIRECTORIES + ../../shared/fontpanel + LIBRARIES + Qt::Gui + Qt::Help + Qt::Network + Qt::Sql + Qt::Widgets + Qt::PrintSupport + qlitehtml + ATTRIBUTION_FILE_DIR_PATHS + ../qlitehtml/src/3rdparty +) + +qt_add_ui(assistant SOURCES + bookmarkdialog.ui + bookmarkmanagerwidget.ui + bookmarkwidget.ui + helpdocsettingswidget.ui + preferencesdialog.ui + topicchooser.ui +) + +# Resources: +set(assistant_images_resource_files + "images/assistant-128.png" + "images/assistant.png" + "images/bookmark.png" + "images/closebutton.png" + "images/darkclosebutton.png" + "images/mac/book.png" + "images/mac/closetab.png" + "images/mac/editcopy.png" + "images/mac/find.png" + "images/mac/home.png" + "images/mac/next.png" + "images/mac/previous.png" + "images/mac/print.png" + "images/mac/resetzoom.png" + "images/mac/synctoc.png" + "images/mac/zoomin.png" + "images/mac/zoomout.png" + "images/win/book.png" + "images/win/closetab.png" + "images/win/editcopy.png" + "images/win/find.png" + "images/win/home.png" + "images/win/next.png" + "images/win/previous.png" + "images/win/print.png" + "images/win/resetzoom.png" + "images/win/synctoc.png" + "images/win/zoomin.png" + "images/win/zoomout.png" + "images/wrap.png" +) + +qt_internal_add_resource(assistant "assistant_images" + PREFIX + "/qt-project.org/assistant" + FILES + ${assistant_images_resource_files} +) + +set_target_properties(assistant PROPERTIES + QT_TARGET_DESCRIPTION "Qt Assistant" +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(assistant CONDITION TARGET Qt::PrintSupport + PUBLIC_LIBRARIES + Qt::PrintSupport +) + +qt_internal_extend_target(assistant CONDITION BROWSER___equals___qtwebkit + SOURCES + helpviewerimpl_qwv.cpp + DEFINES + BROWSER_QTWEBKIT + PUBLIC_LIBRARIES + Qt::WebKitWidgets +) + +qt_internal_extend_target(assistant CONDITION NOT BROWSER___equals___qtwebkit + SOURCES + helpviewerimpl_qtb.cpp + DEFINES + BROWSER_QTEXTBROWSER +) + +if(WIN32) + set_target_properties(assistant PROPERTIES + QT_TARGET_RC_ICONS "${CMAKE_CURRENT_SOURCE_DIR}/assistant.ico" + ) +endif() + +if(WIN32) + set_target_properties(assistant PROPERTIES + QT_TARGET_VERSION "${PROJECT_VERSION}.0" + ) +endif() + +qt_internal_extend_target(assistant CONDITION WIN32 + SOURCES + stdinlistener_win.cpp stdinlistener_win.h + PUBLIC_LIBRARIES + shell32 +) + +if(UNIX) + set_target_properties(assistant PROPERTIES + QT_TARGET_VERSION "${PROJECT_VERSION}" + ) +endif() + +qt_internal_extend_target(assistant CONDITION UNIX + SOURCES + stdinlistener.cpp stdinlistener.h +) + +if(QT_INSTALL_XDG_DESKTOP_ENTRIES) + if(UNIX AND NOT APPLE) + qt_path_join(xdg_install_dir ${QT_INSTALL_DIR} ${CMAKE_INSTALL_DATAROOTDIR}) + + qt_install(FILES assistant.desktop + DESTINATION "${xdg_install_dir}/applications" + ) + qt_install(FILES assistant.metainfo.xml + RENAME io.qt.Assistant.metainfo.xml + DESTINATION "${xdg_install_dir}/metainfo" + ) + qt_install(FILES images/assistant-128.png + RENAME assistant.png + DESTINATION "${xdg_install_dir}/icons/hicolor/128x128/apps" + ) + endif() +endif() + +if(APPLE) + set_target_properties(assistant PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info_mac.plist" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_ICON_FILE "assistant.icns" + OUTPUT_NAME "Assistant" + ) + set_source_files_properties(assistant.icns PROPERTIES + MACOSX_PACKAGE_LOCATION Resources + ) + target_sources(assistant PRIVATE + assistant.icns + ) + # Set values to be replaced in the custom Info_mac.plist. + set(ICON "assistant.icns") + set(EXECUTABLE "Assistant") +endif() +qt_internal_add_docs(assistant + doc/qtassistant.qdocconf +) + diff --git a/src/assistant/assistant/Info_mac.plist b/src/assistant/assistant/Info_mac.plist new file mode 100644 index 0000000..cf74d05 --- /dev/null +++ b/src/assistant/assistant/Info_mac.plist @@ -0,0 +1,20 @@ + + + + + NSPrincipalClass + NSApplication + CFBundleIconFile + @ICON@ + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleIdentifier + org.qt-project.assistant + CFBundleExecutable + @EXECUTABLE@ + NSHumanReadableCopyright + (C) 2017 The Qt Company Ltd + + diff --git a/src/assistant/assistant/aboutdialog.cpp b/src/assistant/assistant/aboutdialog.cpp new file mode 100644 index 0000000..2c7ea63 --- /dev/null +++ b/src/assistant/assistant/aboutdialog.cpp @@ -0,0 +1,149 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "aboutdialog.h" + +#include "helpviewer.h" +#include "tracer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +AboutLabel::AboutLabel(QWidget *parent) + : QTextBrowser(parent) +{ + TRACE_OBJ + setFrameStyle(QFrame::NoFrame); + QPalette p; + p.setColor(QPalette::Base, p.color(QPalette::Window)); + setPalette(p); +} + +void AboutLabel::setText(const QString &text, const QByteArray &resources) +{ + TRACE_OBJ + QDataStream in(resources); + in >> m_resourceMap; + + QTextBrowser::setText(text); +} + +QSize AboutLabel::minimumSizeHint() const +{ + TRACE_OBJ + QTextDocument *doc = document(); + doc->adjustSize(); + return QSize(int(doc->size().width()), int(doc->size().height())); +} + +QVariant AboutLabel::loadResource(int type, const QUrl &name) +{ + TRACE_OBJ + if (type == 2 || type == 3) { + if (m_resourceMap.contains(name.toString())) { + return m_resourceMap.value(name.toString()); + } + } + return QVariant(); +} + +void AboutLabel::doSetSource(const QUrl &url, QTextDocument::ResourceType type) +{ + TRACE_OBJ + Q_UNUSED(type); + if (url.isValid() && (!HelpViewer::isLocalUrl(url) + || !HelpViewer::canOpenPage(url.path()))) { + if (!QDesktopServices::openUrl(url)) { + QMessageBox::warning(this, tr("Warning"), + tr("Unable to launch external application."), QMessageBox::Close); + } + } +} + +AboutDialog::AboutDialog(QWidget *parent) + : QDialog(parent, Qt::MSWindowsFixedSizeDialogHint | + Qt::WindowTitleHint|Qt::WindowSystemMenuHint) +{ + TRACE_OBJ + m_pixmapLabel = nullptr; + m_aboutLabel = new AboutLabel(); + + m_closeButton = new QPushButton(); + m_closeButton->setText(tr("&Close")); + connect(m_closeButton, &QAbstractButton::clicked, this, &QWidget::close); + + m_layout = new QGridLayout(this); + m_layout->addWidget(m_aboutLabel, 1, 0, 1, -1); + m_layout->addItem(new QSpacerItem(20, 10, QSizePolicy::Minimum, + QSizePolicy::Fixed), 2, 1, 1, 1); + m_layout->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding), 3, 0, 1, 1); + m_layout->addWidget(m_closeButton, 3, 1, 1, 1); + m_layout->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding), 3, 2, 1, 1); +} + +void AboutDialog::setText(const QString &text, const QByteArray &resources) +{ + TRACE_OBJ + m_aboutLabel->setText(text, resources); + updateSize(); +} + +void AboutDialog::setPixmap(const QPixmap &pixmap) +{ + TRACE_OBJ + if (!m_pixmapLabel) { + m_pixmapLabel = new QLabel(); + m_layout->addWidget(m_pixmapLabel, 0, 0, 1, -1, Qt::AlignCenter); + } + m_pixmapLabel->setPixmap(pixmap); + updateSize(); +} + +QString AboutDialog::documentTitle() const +{ + TRACE_OBJ + return m_aboutLabel->documentTitle(); +} + +void AboutDialog::updateSize() +{ + TRACE_OBJ + auto screen = QGuiApplication::screenAt(QCursor::pos()); + if (!screen) + screen = QGuiApplication::primaryScreen(); + const QSize screenSize = screen->availableSize(); + int limit = qMin(screenSize.width()/2, 500); + +#ifdef Q_OS_MAC + limit = qMin(screenSize.width()/2, 420); +#endif + + layout()->activate(); + int width = layout()->totalMinimumSize().width(); + + if (width > limit) + width = limit; + + QFontMetrics fm(qApp->font("QWorkspaceTitleBar")); + int windowTitleWidth = qMin(fm.horizontalAdvance(windowTitle()) + 50, limit); + if (windowTitleWidth > width) + width = windowTitleWidth; + + layout()->activate(); + int height = (layout()->hasHeightForWidth()) + ? layout()->totalHeightForWidth(width) + : layout()->totalMinimumSize().height(); + setFixedSize(width, height); + QCoreApplication::removePostedEvents(this, QEvent::LayoutRequest); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/aboutdialog.h b/src/assistant/assistant/aboutdialog.h new file mode 100644 index 0000000..dd3db0c --- /dev/null +++ b/src/assistant/assistant/aboutdialog.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QLabel; +class QPushButton; +class QGridLayout; + +class AboutLabel : public QTextBrowser +{ + Q_OBJECT + +public: + AboutLabel(QWidget *parent = nullptr); + void setText(const QString &text, const QByteArray &resources); + QSize minimumSizeHint() const override; + +private: + QVariant loadResource(int type, const QUrl &name) override; + + void doSetSource(const QUrl &name, QTextDocument::ResourceType type) override; + + QMap m_resourceMap; +}; + +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + AboutDialog(QWidget *parent = nullptr); + void setText(const QString &text, const QByteArray &resources); + void setPixmap(const QPixmap &pixmap); + QString documentTitle() const; + +private: + void updateSize(); + + QLabel *m_pixmapLabel; + AboutLabel *m_aboutLabel; + QPushButton *m_closeButton; + QGridLayout *m_layout; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/assistant/assistant/assistant.desktop b/src/assistant/assistant/assistant.desktop new file mode 100644 index 0000000..0458c07 --- /dev/null +++ b/src/assistant/assistant/assistant.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Qt Assistant +GenericName=Documentation viewer +Comment=View documentation in Qt help file format +Exec=assistant6 +Icon=assistant +Terminal=false +Type=Application +Categories=Qt;Development;Documentation; diff --git a/src/assistant/assistant/assistant.icns b/src/assistant/assistant/assistant.icns new file mode 100644 index 0000000000000000000000000000000000000000..cf763571bd79cfede41decdc1bb22a1199b7e626 GIT binary patch literal 118925 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

w&^Cabk6uF}~L(Iq$3O-O#;65AX1!q>i z@I$+68tjq^(VOAQ8nr~0bT~D-15dgM1$r;NiXgA=6qujOA1v%DjS>4clYbcKjSq`4 zz-y!qcVBuIq~WE!7WE1>7L>OS{rNSn5i&^_Y?cI$9U<2UOV6K78v93>7~TdzQN}Ri zy94LI3;#ML4P~xYCf=gN&xLM1<*XH7$t!e03Ov*fzgwHKMRAW!D(ndk z8pqhD#ozD{VI6e&MRy{YqjZ0UwONGQP(5S|cYX&%KZL;uOY3XW3&C91;d_g1JLZKq zZ*+vVB2bQ3_80%dY`}2_N>7{JPtYdP@3h?mB0=lNbq_#ei0HJ0(Sc!Kd2(+gkHIbN zSzGlUx9=nWzG$Zt8^&K;0bzJH5eAa-W4SCQo|7UI$5=+^6vbX7!XX&H{y}pv4 zWUF=Cs<%C$US%1G>Wx(T>F;m)3{xL~)}V$u{lQ8efIv^|`f5K(EflU;QQJ1rQr7FY z@Fcm}Op! zw|Kx=x5V3X2i+u54Q_JTs`>IrP=7X^PG6#f-C)wx&^pf5t%L57)hYt;1^E&24c%5$ z!IBIgV*_pih)KI%gU) zB@AX7TH{a*6Y%iCRvesze0@heLt}b_aj3l+f#p-^OjdJ4wlxCNY7!Al5U_!V^pj~C zOt6xEwF%uE;&Kgti#7Rgd|<21zyajL9Y2@0%R)4L4qj6(*c#ryX(M+GiRB zwq}4U46?5NMWpZUp!*6#DML{Iy^7vV0RG^Zibl>M5_^bX=Moy|E|uDHu5%@jW`A!S zuZ$j%@cziidMyri+D_)=zI=ykNU#(y8gOvS5R9_f8@DhT?1V2BkPm;;cGvH&Ft&W5 zTNS>jml%%Kma94dH7@WfLqh20rVyxgJPBiHU)81w-5K+V7ENB-VG0YNb@lq*?sD4Q z+=p?2a?-w`E^BFIQ|;ue7aT%VB_k>34GB44q-~7T+R07yeHV<-oXFt}r+*_4)EeCz zsm-A@)VC(A6dt^Y18%7d*l;=N0hOu5!^e=yHFB@+A9V<10s%6ZD6fLUZbg4J?h*%v z-EBss=A8&WjUQV~4BHAYiTG_ghqn-`C9P-nD2TQn{mxW^i9$8+3ec?nej89MlEvob z3Wdog0M9PrVvWm*|G$s)yQ9-RNxZB=N3{d6g$ufQLtniTXBF~49qqk7 literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/assistant.metainfo.xml b/src/assistant/assistant/assistant.metainfo.xml new file mode 100644 index 0000000..eb2b0f1 --- /dev/null +++ b/src/assistant/assistant/assistant.metainfo.xml @@ -0,0 +1,40 @@ + + + io.qt.Assistant + Qt Assistant +

View documentation in Qt help file format + + Qt Project + + CC0-1.0 + LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + Qt + +

The Qt Assistant is a tool for viewing documentation in Qt help file format.

+
+ + + The main window + https://doc.qt.io/qt-6/images/assistant-dockwidgets.png + + + https://www.qt.io/ + https://bugreports.qt.io/ + https://doc.qt.io/qt-6/qtassistant-index.html + https://code.qt.io/cgit/qt/qttools.git/tree/src/assistant/assistant + https://www.qt.io/community/contribute-to-qt + + Development + Documentation + Qt + + + pointing + keyboard + + + assistant.desktop + + assistant6 + + diff --git a/src/assistant/assistant/bookmarkdialog.cpp b/src/assistant/assistant/bookmarkdialog.cpp new file mode 100644 index 0000000..cbe2d67 --- /dev/null +++ b/src/assistant/assistant/bookmarkdialog.cpp @@ -0,0 +1,199 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "bookmarkdialog.h" +#include "bookmarkfiltermodel.h" +#include "bookmarkitem.h" +#include "bookmarkmodel.h" +#include "helpenginewrapper.h" +#include "tracer.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +BookmarkDialog::BookmarkDialog(BookmarkModel *sourceModel, const QString &title, + const QString &url, QWidget *parent) + : QDialog(parent) + , m_url(url) + , m_title(title) + , bookmarkModel(sourceModel) +{ + TRACE_OBJ + ui.setupUi(this); + + ui.bookmarkEdit->setText(m_title); + ui.newFolderButton->setVisible(false); + ui.buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + + connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &BookmarkDialog::accepted); + connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &BookmarkDialog::rejected); + connect(ui.newFolderButton, &QAbstractButton::clicked, this, &BookmarkDialog::addFolder); + connect(ui.toolButton, &QAbstractButton::clicked, this, &BookmarkDialog::toolButtonClicked); + connect(ui.bookmarkEdit, &QLineEdit::textChanged, this, &BookmarkDialog::textChanged); + + bookmarkProxyModel = new BookmarkFilterModel(this); + bookmarkProxyModel->setSourceModel(bookmarkModel); + ui.bookmarkFolders->setModel(bookmarkProxyModel); + connect(ui.bookmarkFolders, &QComboBox::currentIndexChanged, + this, QOverload::of(&BookmarkDialog::currentIndexChanged)); + + bookmarkTreeModel = new BookmarkTreeModel(this); + bookmarkTreeModel->setSourceModel(bookmarkModel); + ui.treeView->setModel(bookmarkTreeModel); + + ui.treeView->expandAll(); + ui.treeView->setVisible(false); + ui.treeView->installEventFilter(this); + ui.treeView->viewport()->installEventFilter(this); + ui.treeView->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(ui.treeView, &QWidget::customContextMenuRequested, + this, &BookmarkDialog::customContextMenuRequested); + connect(ui.treeView->selectionModel(), &QItemSelectionModel::currentChanged, + this, QOverload::of(&BookmarkDialog::currentIndexChanged)); + + ui.bookmarkFolders->setCurrentIndex(ui.bookmarkFolders->count() > 1 ? 1 : 0); + + const HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + if (helpEngine.usesAppFont()) + setFont(helpEngine.appFont()); +} + +BookmarkDialog::~BookmarkDialog() +{ + TRACE_OBJ +} + +bool BookmarkDialog::isRootItem(const QModelIndex &index) const +{ + return !bookmarkTreeModel->parent(index).isValid(); +} + +bool BookmarkDialog::eventFilter(QObject *object, QEvent *event) +{ + TRACE_OBJ + if (object != ui.treeView && object != ui.treeView->viewport()) + return QWidget::eventFilter(object, event); + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + switch (ke->key()) { + case Qt::Key_F2: { + const QModelIndex &index = ui.treeView->currentIndex(); + if (!isRootItem(index)) { + bookmarkModel->setItemsEditable(true); + ui.treeView->edit(index); + bookmarkModel->setItemsEditable(false); + } + } break; + default: break; + } + } + + return QObject::eventFilter(object, event); +} + +void BookmarkDialog::currentIndexChanged(int row) +{ + TRACE_OBJ + QModelIndex next = bookmarkProxyModel->index(row, 0, QModelIndex()); + if (next.isValid()) { + next = bookmarkProxyModel->mapToSource(next); + ui.treeView->setCurrentIndex(bookmarkTreeModel->mapFromSource(next)); + } +} + +void BookmarkDialog::currentIndexChanged(const QModelIndex &index) +{ + TRACE_OBJ + const QModelIndex current = bookmarkTreeModel->mapToSource(index); + if (current.isValid()) { + const int row = bookmarkProxyModel->mapFromSource(current).row(); + ui.bookmarkFolders->setCurrentIndex(row); + } +} + +void BookmarkDialog::accepted() +{ + TRACE_OBJ + QModelIndex index = ui.treeView->currentIndex(); + if (index.isValid()) { + index = bookmarkModel->addItem(bookmarkTreeModel->mapToSource(index)); + bookmarkModel->setData(index, DataVector() << m_title << m_url << false); + } else + rejected(); + + accept(); +} + +void BookmarkDialog::rejected() +{ + TRACE_OBJ + for (const QPersistentModelIndex &index : std::as_const(cache)) + bookmarkModel->removeItem(index); + reject(); +} + +void BookmarkDialog::addFolder() +{ + TRACE_OBJ + QModelIndex index = ui.treeView->currentIndex(); + if (index.isValid()) { + index = bookmarkModel->addItem(bookmarkTreeModel->mapToSource(index), + true); + cache.append(index); + + index = bookmarkTreeModel->mapFromSource(index); + if (index.isValid()) { + bookmarkModel->setItemsEditable(true); + ui.treeView->edit(index); + ui.treeView->expand(index); + ui.treeView->setCurrentIndex(index); + bookmarkModel->setItemsEditable(false); + } + } +} + +void BookmarkDialog::toolButtonClicked() +{ + TRACE_OBJ + const bool visible = !ui.treeView->isVisible(); + ui.treeView->setVisible(visible); + ui.newFolderButton->setVisible(visible); + + if (visible) { + resize(QSize(width(), 400)); + ui.toolButton->setText("-"_L1); + } else { + resize(width(), minimumHeight()); + ui.toolButton->setText("+"_L1); + } +} + +void BookmarkDialog::textChanged(const QString& text) +{ + m_title = text; +} + +void BookmarkDialog::customContextMenuRequested(const QPoint &point) +{ + TRACE_OBJ + const QModelIndex &index = ui.treeView->currentIndex(); + if (isRootItem(index)) + return; // check if we go to rename the "Bookmarks Menu", bail + + QMenu menu(QString(), this); + QAction *renameItem = menu.addAction(tr("Rename Folder")); + + QAction *picked = menu.exec(ui.treeView->mapToGlobal(point)); + if (picked == renameItem) { + bookmarkModel->setItemsEditable(true); + ui.treeView->edit(index); + bookmarkModel->setItemsEditable(false); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/bookmarkdialog.h b/src/assistant/assistant/bookmarkdialog.h new file mode 100644 index 0000000..3b5aecf --- /dev/null +++ b/src/assistant/assistant/bookmarkdialog.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef BOOKMARKDIALOG_H +#define BOOKMARKDIALOG_H + +#include "ui_bookmarkdialog.h" + +QT_BEGIN_NAMESPACE + +class BookmarkModel; +class BookmarkFilterModel; +class BookmarkTreeModel; + +class BookmarkDialog : public QDialog +{ + Q_OBJECT +public: + BookmarkDialog(BookmarkModel *bookmarkModel, const QString &title, + const QString &url, QWidget *parent = nullptr); + ~BookmarkDialog() override; + +private: + bool isRootItem(const QModelIndex &index) const; + bool eventFilter(QObject *object, QEvent *event) override; + +private slots: + void currentIndexChanged(int index); + void currentIndexChanged(const QModelIndex &index); + + void accepted(); + void rejected(); + + void addFolder(); + void toolButtonClicked(); + void textChanged(const QString& text); + void customContextMenuRequested(const QPoint &point); + +private: + QString m_url; + QString m_title; + Ui::BookmarkDialog ui; + QList cache; + + BookmarkModel *bookmarkModel; + BookmarkTreeModel *bookmarkTreeModel; + BookmarkFilterModel *bookmarkProxyModel; +}; + +QT_END_NAMESPACE + +#endif // BOOKMARKDIALOG_H diff --git a/src/assistant/assistant/bookmarkdialog.ui b/src/assistant/assistant/bookmarkdialog.ui new file mode 100644 index 0000000..5131531 --- /dev/null +++ b/src/assistant/assistant/bookmarkdialog.ui @@ -0,0 +1,156 @@ + + + BookmarkDialog + + + + 0 + 0 + 450 + 133 + + + + + 0 + 0 + + + + Add Bookmark + + + + + + + + + + Bookmark: + + + + + + + Add in Folder: + + + + + + + + + + + + + + + + + + + + + + + + 25 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + + 0 + 0 + + + + true + + + true + + + true + + + + + + + + + New Folder + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + BookmarkDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + BookmarkDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/assistant/assistant/bookmarkfiltermodel.cpp b/src/assistant/assistant/bookmarkfiltermodel.cpp new file mode 100644 index 0000000..89fe5ca --- /dev/null +++ b/src/assistant/assistant/bookmarkfiltermodel.cpp @@ -0,0 +1,278 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "bookmarkfiltermodel.h" + +#include "bookmarkitem.h" +#include "bookmarkmodel.h" + +BookmarkFilterModel::BookmarkFilterModel(QObject *parent) + : QAbstractProxyModel(parent) +{ +} + +void BookmarkFilterModel::setSourceModel(QAbstractItemModel *_sourceModel) +{ + beginResetModel(); + + if (sourceModel) { + disconnect(sourceModel, &QAbstractItemModel::dataChanged, + this, &BookmarkFilterModel::changed); + disconnect(sourceModel, &QAbstractItemModel::rowsInserted, + this, &BookmarkFilterModel::rowsInserted); + disconnect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &BookmarkFilterModel::rowsAboutToBeRemoved); + disconnect(sourceModel, &QAbstractItemModel::rowsRemoved, + this, &BookmarkFilterModel::rowsRemoved); + disconnect(sourceModel, &QAbstractItemModel::layoutAboutToBeChanged, + this, &BookmarkFilterModel::layoutAboutToBeChanged); + disconnect(sourceModel, &QAbstractItemModel::layoutChanged, + this, &BookmarkFilterModel::layoutChanged); + disconnect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, + this, &BookmarkFilterModel::modelAboutToBeReset); + disconnect(sourceModel, &QAbstractItemModel::modelReset, + this, &BookmarkFilterModel::modelReset); + } + + sourceModel = qobject_cast (_sourceModel); + QAbstractProxyModel::setSourceModel(sourceModel); + + if (sourceModel) { + connect(sourceModel, &QAbstractItemModel::dataChanged, + this, &BookmarkFilterModel::changed); + connect(sourceModel, &QAbstractItemModel::rowsInserted, + this, &BookmarkFilterModel::rowsInserted); + connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &BookmarkFilterModel::rowsAboutToBeRemoved); + connect(sourceModel, &QAbstractItemModel::rowsRemoved, + this, &BookmarkFilterModel::rowsRemoved); + connect(sourceModel, &QAbstractItemModel::layoutAboutToBeChanged, + this, &BookmarkFilterModel::layoutAboutToBeChanged); + connect(sourceModel, &QAbstractItemModel::layoutChanged, + this, &BookmarkFilterModel::layoutChanged); + connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, + this, &BookmarkFilterModel::modelAboutToBeReset); + connect(sourceModel, &QAbstractItemModel::modelReset, + this, &BookmarkFilterModel::modelReset); + + setupCache(sourceModel->index(0, 0, QModelIndex()).parent()); + } + endResetModel(); +} + +int BookmarkFilterModel::rowCount(const QModelIndex &index) const +{ + Q_UNUSED(index); + return cache.size(); +} + +int BookmarkFilterModel::columnCount(const QModelIndex &index) const +{ + Q_UNUSED(index); + if (sourceModel) + return sourceModel->columnCount(); + return 0; +} + +QModelIndex BookmarkFilterModel::mapToSource(const QModelIndex &proxyIndex) const +{ + const int row = proxyIndex.row(); + if (proxyIndex.isValid() && row >= 0 && row < cache.size()) + return cache[row]; + return QModelIndex(); +} + +QModelIndex BookmarkFilterModel::mapFromSource(const QModelIndex &sourceIndex) const +{ + return index(cache.indexOf(sourceIndex), 0, QModelIndex()); +} + +QModelIndex BookmarkFilterModel::parent(const QModelIndex &child) const +{ + Q_UNUSED(child); + return QModelIndex(); +} + +QModelIndex BookmarkFilterModel::index(int row, int column, + const QModelIndex &index) const +{ + Q_UNUSED(index); + if (row < 0 || column < 0 || cache.size() <= row + || !sourceModel || sourceModel->columnCount() <= column) { + return QModelIndex(); + } + return createIndex(row, 0); +} + +Qt::DropActions BookmarkFilterModel::supportedDropActions () const +{ + if (sourceModel) + return sourceModel->supportedDropActions(); + return Qt::IgnoreAction; +} + +Qt::ItemFlags BookmarkFilterModel::flags(const QModelIndex &index) const +{ + if (sourceModel) + return sourceModel->flags(index); + return Qt::NoItemFlags; +} + +QVariant BookmarkFilterModel::data(const QModelIndex &index, int role) const +{ + if (sourceModel) + return sourceModel->data(mapToSource(index), role); + return QVariant(); +} + +bool BookmarkFilterModel::setData(const QModelIndex &index, const QVariant &value, + int role) +{ + if (sourceModel) + return sourceModel->setData(mapToSource(index), value, role); + return false; +} + +void BookmarkFilterModel::filterBookmarks() +{ + if (sourceModel) { + beginResetModel(); + hideBookmarks = true; + setupCache(sourceModel->index(0, 0, QModelIndex()).parent()); + endResetModel(); + } +} + +void BookmarkFilterModel::filterBookmarkFolders() +{ + if (sourceModel) { + beginResetModel(); + hideBookmarks = false; + setupCache(sourceModel->index(0, 0, QModelIndex()).parent()); + endResetModel(); + } +} + +void BookmarkFilterModel::changed(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight)); +} + +void BookmarkFilterModel::rowsInserted(const QModelIndex &parent, int start, + int end) +{ + if (!sourceModel) + return; + + QModelIndex cachePrevious = parent; + if (BookmarkItem *parentItem = sourceModel->itemFromIndex(parent)) { + BookmarkItem *newItem = parentItem->child(start); + + // iterate over tree hirarchie to find the previous folder + for (int i = 0; i < parentItem->childCount(); ++i) { + if (BookmarkItem *child = parentItem->child(i)) { + const QModelIndex &tmp = sourceModel->indexFromItem(child); + if (tmp.data(UserRoleFolder).toBool() && child != newItem) + cachePrevious = tmp; + } + } + + const QModelIndex &newIndex = sourceModel->indexFromItem(newItem); + const bool isFolder = newIndex.data(UserRoleFolder).toBool(); + if ((isFolder && hideBookmarks) || (!isFolder && !hideBookmarks)) { + beginInsertRows(mapFromSource(parent), start, end); + const int index = cache.indexOf(cachePrevious) + 1; + if (cache.value(index, QPersistentModelIndex()) != newIndex) + cache.insert(index, newIndex); + endInsertRows(); + } + } +} + +void BookmarkFilterModel::rowsAboutToBeRemoved(const QModelIndex &parent, + int start, int end) +{ + if (!sourceModel) + return; + + if (BookmarkItem *parentItem = sourceModel->itemFromIndex(parent)) { + if (BookmarkItem *child = parentItem->child(start)) { + indexToRemove = sourceModel->indexFromItem(child); + if (cache.contains(indexToRemove)) + beginRemoveRows(mapFromSource(parent), start, end); + } + } +} + +void BookmarkFilterModel::rowsRemoved(const QModelIndex &/*parent*/, int, int) +{ + if (cache.contains(indexToRemove)) { + cache.removeAll(indexToRemove); + endRemoveRows(); + } +} + +void BookmarkFilterModel::layoutAboutToBeChanged() +{ + // TODO: ??? +} + +void BookmarkFilterModel::layoutChanged() +{ + // TODO: ??? +} + +void BookmarkFilterModel::modelAboutToBeReset() +{ + beginResetModel(); +} + +void BookmarkFilterModel::modelReset() +{ + if (sourceModel) + setupCache(sourceModel->index(0, 0, QModelIndex()).parent()); + endResetModel(); +} + +void BookmarkFilterModel::setupCache(const QModelIndex &parent) +{ + cache.clear(); + for (int i = 0; i < sourceModel->rowCount(parent); ++i) + collectItems(sourceModel->index(i, 0, parent)); +} + +void BookmarkFilterModel::collectItems(const QModelIndex &parent) +{ + if (parent.isValid()) { + bool isFolder = sourceModel->data(parent, UserRoleFolder).toBool(); + if ((isFolder && hideBookmarks) || (!isFolder && !hideBookmarks)) + cache.append(parent); + + if (sourceModel->hasChildren(parent)) { + for (int i = 0; i < sourceModel->rowCount(parent); ++i) + collectItems(sourceModel->index(i, 0, parent)); + } + } +} + +// -- BookmarkTreeModel + +BookmarkTreeModel::BookmarkTreeModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +int BookmarkTreeModel::columnCount(const QModelIndex &parent) const +{ + return qMin(1, QSortFilterProxyModel::columnCount(parent)); +} + +bool BookmarkTreeModel::filterAcceptsRow(int row, const QModelIndex &parent) const +{ + Q_UNUSED(row); + BookmarkModel *model = qobject_cast (sourceModel()); + if (model->rowCount(parent) > 0 + && model->data(model->index(row, 0, parent), UserRoleFolder).toBool()) + return true; + return false; +} diff --git a/src/assistant/assistant/bookmarkfiltermodel.h b/src/assistant/assistant/bookmarkfiltermodel.h new file mode 100644 index 0000000..38bfde7 --- /dev/null +++ b/src/assistant/assistant/bookmarkfiltermodel.h @@ -0,0 +1,80 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef BOOKMARKFILTERMODEL_H +#define BOOKMARKFILTERMODEL_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class BookmarkItem; +class BookmarkModel; + +typedef QList PersistentModelIndexCache; + +class BookmarkFilterModel : public QAbstractProxyModel +{ + Q_OBJECT +public: + explicit BookmarkFilterModel(QObject *parent = nullptr); + + void setSourceModel(QAbstractItemModel *sourceModel) override; + + int rowCount(const QModelIndex &index) const override; + int columnCount(const QModelIndex &index) const override; + + QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; + QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; + + QModelIndex parent(const QModelIndex &child) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + + Qt::DropActions supportedDropActions () const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + + void filterBookmarks(); + void filterBookmarkFolders(); + +private slots: + void changed(const QModelIndex &topLeft, const QModelIndex &bottomRight); + void rowsInserted(const QModelIndex &parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void rowsRemoved(const QModelIndex &parent, int start, int end); + void layoutAboutToBeChanged(); + void layoutChanged(); + void modelAboutToBeReset(); + void modelReset(); + +private: + void setupCache(const QModelIndex &parent); + void collectItems(const QModelIndex &parent); + +private: + BookmarkModel *sourceModel = nullptr; + PersistentModelIndexCache cache; + QPersistentModelIndex indexToRemove; + bool hideBookmarks = true; +}; + +// -- BookmarkTreeModel + +class BookmarkTreeModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + BookmarkTreeModel(QObject *parent = nullptr); + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; +}; + +QT_END_NAMESPACE + +#endif // BOOKMARKFILTERMODEL_H diff --git a/src/assistant/assistant/bookmarkitem.cpp b/src/assistant/assistant/bookmarkitem.cpp new file mode 100644 index 0000000..f114f83 --- /dev/null +++ b/src/assistant/assistant/bookmarkitem.cpp @@ -0,0 +1,148 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "bookmarkitem.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +BookmarkItem::BookmarkItem(const DataVector &data, BookmarkItem *parent) + : m_data(data) + , m_parent(parent) +{ +} + +BookmarkItem::~BookmarkItem() +{ + qDeleteAll(m_children); +} + +BookmarkItem* +BookmarkItem::parent() const +{ + return m_parent; +} + +void +BookmarkItem::setParent(BookmarkItem *parent) +{ + m_parent = parent; +} + +void +BookmarkItem::addChild(BookmarkItem *child) +{ + child->setParent(this); + m_children.append(child); +} + +BookmarkItem* +BookmarkItem::child(int number) const +{ + if (number >= 0 && number < m_children.size()) + return m_children[number]; + return nullptr; +} + +int BookmarkItem::childCount() const +{ + return m_children.size(); +} + +int BookmarkItem::childNumber() const +{ + if (m_parent) + return m_parent->m_children.indexOf(const_cast(this)); + return 0; +} + +QVariant +BookmarkItem::data(int column) const +{ + if (column == 0) + return m_data[0]; + + if (column == 1 || column == UserRoleUrl) + return m_data[1]; + + if (column == UserRoleFolder) + return m_data[1].toString() == "Folder"_L1; + + if (column == UserRoleExpanded) + return m_data[2]; + + return QVariant(); +} + +void +BookmarkItem::setData(const DataVector &data) +{ + m_data = data; +} + +bool +BookmarkItem::setData(int column, const QVariant &newValue) +{ + int index = -1; + if (column == 0 || column == 1) + index = column; + + if (column == UserRoleUrl || column == UserRoleFolder) + index = 1; + + if (column == UserRoleExpanded) + index = 2; + + if (index < 0) + return false; + + m_data[index] = newValue; + return true; +} + +bool +BookmarkItem::insertChildren(bool isFolder, int position, int count) +{ + if (position < 0 || position > m_children.size()) + return false; + + for (int row = 0; row < count; ++row) { + m_children.insert(position, new BookmarkItem(DataVector() + << (isFolder + ? QCoreApplication::translate("BookmarkItem", "New Folder") + : QCoreApplication::translate("BookmarkItem", "Untitled")) + << (isFolder ? "Folder" : "about:blank") << false, this)); + } + + return true; +} + +bool +BookmarkItem::removeChildren(int position, int count) +{ + if (position < 0 || position > m_children.size()) + return false; + + for (int row = 0; row < count; ++row) + delete m_children.takeAt(position); + + return true; +} + +void +BookmarkItem::dumpTree(int indent) const +{ + const QString tree(indent, ' '); + qDebug() << tree + (data(UserRoleFolder).toBool() ? "Folder" : "Bookmark") + << "Label:" << data(0).toString() << "parent:" << m_parent << "this:" + << this; + + for (BookmarkItem *item : m_children) + item->dumpTree(indent + 4); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/bookmarkitem.h b/src/assistant/assistant/bookmarkitem.h new file mode 100644 index 0000000..391a703 --- /dev/null +++ b/src/assistant/assistant/bookmarkitem.h @@ -0,0 +1,53 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BOOKMARKITEM_H +#define BOOKMARKITEM_H + +#include +#include + +QT_BEGIN_NAMESPACE + +enum { + UserRoleUrl = Qt::UserRole + 50, + UserRoleFolder = Qt::UserRole + 100, + UserRoleExpanded = Qt::UserRole + 150 +}; + +typedef QList DataVector; + +class BookmarkItem +{ +public: + explicit BookmarkItem(const DataVector &data, BookmarkItem *parent = nullptr); + ~BookmarkItem(); + + BookmarkItem *parent() const; + void setParent(BookmarkItem *parent); + + void addChild(BookmarkItem *child); + BookmarkItem *child(int number) const; + + int childCount() const; + int childNumber() const; + + QVariant data(int column) const; + void setData(const DataVector &data); + bool setData(int column, const QVariant &value); + + bool insertChildren(bool isFolder, int position, int count); + bool removeChildren(int position, int count); + + void dumpTree(int indent) const; + +private: + DataVector m_data; + + BookmarkItem *m_parent; + QList m_children; +}; + +QT_END_NAMESPACE + +#endif // BOOKMARKITEM_H diff --git a/src/assistant/assistant/bookmarkmanager.cpp b/src/assistant/assistant/bookmarkmanager.cpp new file mode 100644 index 0000000..28c534e --- /dev/null +++ b/src/assistant/assistant/bookmarkmanager.cpp @@ -0,0 +1,530 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" + +#include "bookmarkmanager.h" +#include "bookmarkmanagerwidget.h" +#include "bookmarkdialog.h" +#include "bookmarkfiltermodel.h" +#include "bookmarkitem.h" +#include "bookmarkmodel.h" +#include "centralwidget.h" +#include "helpenginewrapper.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// -- BookmarkManager::BookmarkWidget + +void BookmarkManager::BookmarkWidget::focusInEvent(QFocusEvent *event) +{ + TRACE_OBJ + if (event->reason() != Qt::MouseFocusReason) { + ui.lineEdit->selectAll(); + ui.lineEdit->setFocus(); + + // force the focus in event on bookmark manager + emit focusInEventOccurred(); + } +} + +// -- BookmarkManager::BookmarkTreeView + +BookmarkManager::BookmarkTreeView::BookmarkTreeView(QWidget *parent) + : QTreeView(parent) +{ + TRACE_OBJ + setAcceptDrops(true); + setDragEnabled(true); + setAutoExpandDelay(1000); + setUniformRowHeights(true); + setDropIndicatorShown(true); + setExpandsOnDoubleClick(true); + + connect(this, &QTreeView::expanded, this, &BookmarkTreeView::setExpandedData); + connect(this, &QTreeView::collapsed, this, &BookmarkTreeView::setExpandedData); +} + +void BookmarkManager::BookmarkTreeView::subclassKeyPressEvent(QKeyEvent *event) +{ + TRACE_OBJ + QTreeView::keyPressEvent(event); +} + +void BookmarkManager::BookmarkTreeView::commitData(QWidget *editor) +{ + QTreeView::commitData(editor); + emit editingDone(); +} + +void BookmarkManager::BookmarkTreeView::setExpandedData(const QModelIndex &index) +{ + TRACE_OBJ + if (BookmarkModel *treeModel = qobject_cast (model())) + treeModel->setData(index, isExpanded(index), UserRoleExpanded); +} + +// -- BookmarkManager + +QMutex BookmarkManager::mutex; +BookmarkManager* BookmarkManager::bookmarkManager = nullptr; + +// -- public + +BookmarkManager* BookmarkManager::instance() +{ + TRACE_OBJ + if (!bookmarkManager) { + QMutexLocker _(&mutex); + if (!bookmarkManager) + bookmarkManager = new BookmarkManager(); + } + return bookmarkManager; +} + +void BookmarkManager::destroy() +{ + TRACE_OBJ + delete bookmarkManager; + bookmarkManager = nullptr; +} + +QWidget* BookmarkManager::bookmarkDockWidget() const +{ + TRACE_OBJ + if (bookmarkWidget) + return bookmarkWidget; + return nullptr; +} + +void BookmarkManager::setBookmarksMenu(QMenu* menu) +{ + TRACE_OBJ + bookmarkMenu = menu; + refreshBookmarkMenu(); +} + +void BookmarkManager::setBookmarksToolbar(QToolBar *toolBar) +{ + TRACE_OBJ + m_toolBar = toolBar; + refreshBookmarkToolBar(); +} + +// -- public slots + +void BookmarkManager::addBookmark(const QString &title, const QString &url) +{ + TRACE_OBJ + showBookmarkDialog(title.isEmpty() ? tr("Untitled") : title, + url.isEmpty() ? "about:blank"_L1 : url); + + storeBookmarks(); +} + +// -- private + +BookmarkManager::BookmarkManager() + : bookmarkModel(new BookmarkModel) + , bookmarkWidget(new BookmarkWidget) + , bookmarkTreeView(new BookmarkTreeView) +{ + TRACE_OBJ + bookmarkWidget->installEventFilter(this); + connect(bookmarkWidget->ui.add, &QAbstractButton::clicked, + this, &BookmarkManager::addBookmarkActivated); + connect(bookmarkWidget->ui.remove, &QAbstractButton::clicked, + this, &BookmarkManager::removeBookmarkActivated); + connect(bookmarkWidget->ui.lineEdit, &QLineEdit::textChanged, + this, &BookmarkManager::textChanged); + connect(bookmarkWidget, &BookmarkWidget::focusInEventOccurred, + this, &BookmarkManager::focusInEventOccurred); + + bookmarkTreeView->setModel(bookmarkModel); + bookmarkTreeView->installEventFilter(this); + bookmarkTreeView->viewport()->installEventFilter(this); + bookmarkTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + bookmarkWidget->ui.stackedWidget->addWidget(bookmarkTreeView); + + connect(bookmarkTreeView, &QAbstractItemView::activated, + this, [this](const QModelIndex &index) { setSourceFromIndex(index, false); }); + connect(bookmarkTreeView, &QWidget::customContextMenuRequested, + this, &BookmarkManager::customContextMenuRequested); + connect(bookmarkTreeView, &BookmarkTreeView::editingDone, + this, &BookmarkManager::storeBookmarks); + + connect(&HelpEngineWrapper::instance(), &HelpEngineWrapper::setupFinished, + this, &BookmarkManager::setupFinished); + + connect(bookmarkModel, &QAbstractItemModel::rowsRemoved, + this, &BookmarkManager::refreshBookmarkMenu); + connect(bookmarkModel, &QAbstractItemModel::rowsInserted, + this, &BookmarkManager::refreshBookmarkMenu); + connect(bookmarkModel, &QAbstractItemModel::dataChanged, + this, &BookmarkManager::refreshBookmarkMenu); + + connect(bookmarkModel, &QAbstractItemModel::rowsRemoved, + this, &BookmarkManager::refreshBookmarkToolBar); + connect(bookmarkModel, &QAbstractItemModel::rowsInserted, + this, &BookmarkManager::refreshBookmarkToolBar); + connect(bookmarkModel, &QAbstractItemModel::dataChanged, + this, &BookmarkManager::refreshBookmarkToolBar); + +} + +BookmarkManager::~BookmarkManager() +{ + TRACE_OBJ + delete bookmarkManagerWidget; + storeBookmarks(); + delete bookmarkModel; +} + +void BookmarkManager::removeItem(const QModelIndex &index) +{ + TRACE_OBJ + QModelIndex current = index; + if (typeAndSearch) { // need to map because of proxy + current = typeAndSearchModel->mapToSource(current); + current = bookmarkFilterModel->mapToSource(current); + } else if (!bookmarkModel->parent(index).isValid()) { + return; // check if we should delete the "Bookmarks Menu", bail + } + + if (bookmarkModel->hasChildren(current)) { + int value = QMessageBox::question(bookmarkTreeView, tr("Remove"), + tr("You are going to delete a Folder, this will also
" + "remove it's content. Are you sure to continue?"), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); + if (value == QMessageBox::Cancel) + return; + } + bookmarkModel->removeItem(current); + + storeBookmarks(); +} + +bool BookmarkManager::eventFilter(QObject *object, QEvent *event) +{ + if (object != bookmarkTreeView && object != bookmarkTreeView->viewport() + && object != bookmarkWidget) + return QObject::eventFilter(object, event); + + TRACE_OBJ + const bool isWidget = object == bookmarkWidget; + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + switch (ke->key()) { + case Qt::Key_F2: + renameBookmark(bookmarkTreeView->currentIndex()); + break; + + case Qt::Key_Delete: + removeItem(bookmarkTreeView->currentIndex()); + return true; + + case Qt::Key_Up: // needs event filter on widget + case Qt::Key_Down: + if (isWidget) + bookmarkTreeView->subclassKeyPressEvent(ke); + break; + + case Qt::Key_Escape: + emit escapePressed(); + break; + + default: break; + } + } + + if (event->type() == QEvent::MouseButtonRelease && !isWidget) { + QMouseEvent *me = static_cast(event); + switch (me->button()) { + case Qt::LeftButton: + if (me->modifiers() & Qt::ControlModifier) + setSourceFromIndex(bookmarkTreeView->currentIndex(), true); + break; + + case Qt::MiddleButton: + setSourceFromIndex(bookmarkTreeView->currentIndex(), true); + break; + + default: break; + } + } + + return QObject::eventFilter(object, event); +} + +void BookmarkManager::buildBookmarksMenu(const QModelIndex &index, QMenu* menu) +{ + TRACE_OBJ + if (!index.isValid()) + return; + + const QString &text = index.data().toString(); + const QIcon &icon = qvariant_cast(index.data(Qt::DecorationRole)); + if (index.data(UserRoleFolder).toBool()) { + if (QMenu* subMenu = menu->addMenu(icon, text)) { + for (int i = 0; i < bookmarkModel->rowCount(index); ++i) + buildBookmarksMenu(bookmarkModel->index(i, 0, index), subMenu); + } + } else { + QAction *action = menu->addAction(icon, text); + action->setData(index.data(UserRoleUrl).toString()); + connect(action, &QAction::triggered, + this, &BookmarkManager::setSourceFromAction); + } +} + +void BookmarkManager::showBookmarkDialog(const QString &name, const QString &url) +{ + TRACE_OBJ + BookmarkDialog dialog(bookmarkModel, name, url, bookmarkTreeView); + dialog.exec(); +} + +// -- private slots + +void BookmarkManager::setupFinished() +{ + TRACE_OBJ + bookmarkModel->setBookmarks(HelpEngineWrapper::instance().bookmarks()); + bookmarkModel->expandFoldersIfNeeeded(bookmarkTreeView); + + refreshBookmarkMenu(); + refreshBookmarkToolBar(); + + bookmarkTreeView->hideColumn(1); + bookmarkTreeView->header()->setVisible(false); + bookmarkTreeView->header()->setStretchLastSection(true); + + if (!bookmarkFilterModel) + bookmarkFilterModel = new BookmarkFilterModel(this); + bookmarkFilterModel->setSourceModel(bookmarkModel); + bookmarkFilterModel->filterBookmarkFolders(); + + if (!typeAndSearchModel) + typeAndSearchModel = new QSortFilterProxyModel(this); + typeAndSearchModel->setDynamicSortFilter(true); + typeAndSearchModel->setSourceModel(bookmarkFilterModel); +} + +void BookmarkManager::storeBookmarks() +{ + HelpEngineWrapper::instance().setBookmarks(bookmarkModel->bookmarks()); +} + +void BookmarkManager::addBookmarkActivated() +{ + TRACE_OBJ + if (CentralWidget *widget = CentralWidget::instance()) + addBookmark(widget->currentTitle(), widget->currentSource().toString()); +} + +void BookmarkManager::removeBookmarkActivated() +{ + TRACE_OBJ + removeItem(bookmarkTreeView->currentIndex()); +} + +void BookmarkManager::manageBookmarks() +{ + TRACE_OBJ + if (bookmarkManagerWidget == nullptr) { + bookmarkManagerWidget = new BookmarkManagerWidget(bookmarkModel); + connect(bookmarkManagerWidget, &BookmarkManagerWidget::setSource, + this, &BookmarkManager::setSource); + connect(bookmarkManagerWidget, &BookmarkManagerWidget::setSourceInNewTab, + this, &BookmarkManager::setSourceInNewTab); + connect(bookmarkManagerWidget, &BookmarkManagerWidget::managerWidgetAboutToClose, + this, &BookmarkManager::managerWidgetAboutToClose); + } + bookmarkManagerWidget->show(); + bookmarkManagerWidget->raise(); +} + +void BookmarkManager::refreshBookmarkMenu() +{ + TRACE_OBJ + if (!bookmarkMenu) + return; + + bookmarkMenu->clear(); + + bookmarkMenu->addAction(tr("Manage Bookmarks..."), this, + &BookmarkManager::manageBookmarks); + bookmarkMenu->addAction(QIcon::fromTheme("bookmark-new"), tr("Add Bookmark..."), + QKeySequence(tr("Ctrl+D")), + this, &BookmarkManager::addBookmarkActivated); + + bookmarkMenu->addSeparator(); + + QModelIndex root = bookmarkModel->index(0, 0, QModelIndex()).parent(); + buildBookmarksMenu(bookmarkModel->index(0, 0, root), bookmarkMenu); + + bookmarkMenu->addSeparator(); + + root = bookmarkModel->index(1, 0, QModelIndex()); + for (int i = 0; i < bookmarkModel->rowCount(root); ++i) + buildBookmarksMenu(bookmarkModel->index(i, 0, root), bookmarkMenu); +} + +void BookmarkManager::refreshBookmarkToolBar() +{ + TRACE_OBJ + if (!m_toolBar) + return; + + m_toolBar->clear(); + m_toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + + const QModelIndex &root = bookmarkModel->index(0, 0, QModelIndex()); + for (int i = 0; i < bookmarkModel->rowCount(root); ++i) { + const QModelIndex &index = bookmarkModel->index(i, 0, root); + if (index.data(UserRoleFolder).toBool()) { + QToolButton *button = new QToolButton(m_toolBar); + button->setPopupMode(QToolButton::InstantPopup); + button->setText(index.data().toString()); + QMenu *menu = new QMenu(button); + for (int j = 0; j < bookmarkModel->rowCount(index); ++j) + buildBookmarksMenu(bookmarkModel->index(j, 0, index), menu); + button->setMenu(menu); + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + button->setIcon(qvariant_cast(index.data(Qt::DecorationRole))); + QAction *a = m_toolBar->addWidget(button); + a->setText(index.data().toString()); + } else { + QAction *action = m_toolBar->addAction( + qvariant_cast(index.data(Qt::DecorationRole)), + index.data().toString(), this, &BookmarkManager::setSourceFromAction); + action->setData(index.data(UserRoleUrl).toString()); + } + } +} + +void BookmarkManager::renameBookmark(const QModelIndex &index) +{ + // check if we should rename the "Bookmarks Menu", bail + if (!typeAndSearch && !bookmarkModel->parent(index).isValid()) + return; + + bookmarkModel->setItemsEditable(true); + bookmarkTreeView->edit(index); + bookmarkModel->setItemsEditable(false); +} + + +void BookmarkManager::setSourceFromAction() +{ + TRACE_OBJ + const QAction *action = qobject_cast(sender()); + if (!action) + return; + + const QVariant &data = action->data(); + if (data.canConvert()) + emit setSource(data.toUrl()); +} + +void BookmarkManager::setSourceFromIndex(const QModelIndex &index, bool newTab) +{ + TRACE_OBJ + QAbstractItemModel *base = bookmarkModel; + if (typeAndSearch) + base = typeAndSearchModel; + + if (base->data(index, UserRoleFolder).toBool()) + return; + + const QVariant &data = base->data(index, UserRoleUrl); + if (data.canConvert()) { + if (newTab) + emit setSourceInNewTab(data.toUrl()); + else + emit setSource(data.toUrl()); + } +} + +void BookmarkManager::customContextMenuRequested(const QPoint &point) +{ + TRACE_OBJ + QModelIndex index = bookmarkTreeView->indexAt(point); + if (!index.isValid()) + return; + + // check if we should open the menu on "Bookmarks Menu", bail + if (!typeAndSearch && !bookmarkModel->parent(index).isValid()) + return; + + QAction *remove = nullptr; + QAction *rename = nullptr; + QAction *showItem = nullptr; + QAction *showItemInNewTab = nullptr; + + QMenu menu; + if (!typeAndSearch && bookmarkModel->data(index, UserRoleFolder).toBool()) { + remove = menu.addAction(tr("Delete Folder")); + rename = menu.addAction(tr("Rename Folder")); + } else { + showItem = menu.addAction(tr("Show Bookmark")); + showItemInNewTab = menu.addAction(tr("Show Bookmark in New Tab")); + menu.addSeparator(); + remove = menu.addAction(tr("Delete Bookmark")); + rename = menu.addAction(tr("Rename Bookmark")); + } + + QAction *pickedAction = menu.exec(bookmarkTreeView->mapToGlobal(point)); + if (pickedAction == rename) + renameBookmark(index); + else if (pickedAction == remove) + removeItem(index); + else if (pickedAction == showItem || pickedAction == showItemInNewTab) + setSourceFromIndex(index, pickedAction == showItemInNewTab); +} + +void BookmarkManager::focusInEventOccurred() +{ + TRACE_OBJ + const QModelIndex &index = bookmarkTreeView->indexAt(QPoint(2, 2)); + if (index.isValid()) + bookmarkTreeView->setCurrentIndex(index); +} + +void BookmarkManager::managerWidgetAboutToClose() +{ + if (bookmarkManagerWidget) + bookmarkManagerWidget->deleteLater(); + bookmarkManagerWidget = nullptr; + + storeBookmarks(); +} + +void BookmarkManager::textChanged(const QString &text) +{ + TRACE_OBJ + if (!bookmarkWidget->ui.lineEdit->text().isEmpty()) { + if (!typeAndSearch) { + typeAndSearch = true; + bookmarkTreeView->setItemsExpandable(false); + bookmarkTreeView->setRootIsDecorated(false); + bookmarkTreeView->setModel(typeAndSearchModel); + } + typeAndSearchModel->setFilterRegularExpression(text); + } else { + typeAndSearch = false; + bookmarkTreeView->setModel(bookmarkModel); + bookmarkTreeView->setItemsExpandable(true); + bookmarkTreeView->setRootIsDecorated(true); + bookmarkModel->expandFoldersIfNeeeded(bookmarkTreeView); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/bookmarkmanager.h b/src/assistant/assistant/bookmarkmanager.h new file mode 100644 index 0000000..1ffb079 --- /dev/null +++ b/src/assistant/assistant/bookmarkmanager.h @@ -0,0 +1,128 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef BOOKMARKMANAGER_H +#define BOOKMARKMANAGER_H + +#include +#include + +#include "ui_bookmarkwidget.h" + +QT_BEGIN_NAMESPACE + +class BookmarkManagerWidget; +class BookmarkModel; +class BookmarkFilterModel; +class QKeyEvent; +class QSortFilterProxyModel; +class QToolBar; + +class BookmarkManager : public QObject +{ + Q_OBJECT + class BookmarkWidget; + class BookmarkTreeView; + class BookmarkListView; + Q_DISABLE_COPY(BookmarkManager) + +public: + static BookmarkManager* instance(); + static void destroy(); + + QWidget* bookmarkDockWidget() const; + void setBookmarksMenu(QMenu* menu); + void setBookmarksToolbar(QToolBar *toolBar); + +public slots: + void addBookmark(const QString &title, const QString &url); + +signals: + void escapePressed(); + void setSource(const QUrl &url); + void setSourceInNewTab(const QUrl &url); + +private: + BookmarkManager(); + ~BookmarkManager() override; + + void removeItem(const QModelIndex &index); + bool eventFilter(QObject *object, QEvent *event) override; + void buildBookmarksMenu(const QModelIndex &index, QMenu *menu); + void showBookmarkDialog(const QString &name, const QString &url); + +private slots: + void setupFinished(); + void storeBookmarks(); + + void addBookmarkActivated(); + void removeBookmarkActivated(); + void manageBookmarks(); + void refreshBookmarkMenu(); + void refreshBookmarkToolBar(); + void renameBookmark(const QModelIndex &index); + + void setSourceFromAction(); + void setSourceFromIndex(const QModelIndex &index, bool newTab); + + void focusInEventOccurred(); + void managerWidgetAboutToClose(); + void textChanged(const QString &text); + void customContextMenuRequested(const QPoint &point); + +private: + bool typeAndSearch = false; + + static QMutex mutex; + static BookmarkManager *bookmarkManager; + + QMenu *bookmarkMenu = nullptr; + QToolBar *m_toolBar = nullptr; + + BookmarkModel *bookmarkModel; + BookmarkFilterModel *bookmarkFilterModel = nullptr; + QSortFilterProxyModel *typeAndSearchModel = nullptr; + + BookmarkWidget *bookmarkWidget; + BookmarkTreeView *bookmarkTreeView; + BookmarkManagerWidget *bookmarkManagerWidget = nullptr; +}; + +class BookmarkManager::BookmarkWidget : public QWidget +{ + Q_OBJECT +public: + BookmarkWidget(QWidget *parent = nullptr) + : QWidget(parent) { ui.setupUi(this); } + virtual ~BookmarkWidget() override {} + + Ui::BookmarkWidget ui; + +signals: + void focusInEventOccurred(); + +private: + void focusInEvent(QFocusEvent *event) override; +}; + +class BookmarkManager::BookmarkTreeView : public QTreeView +{ + Q_OBJECT +public: + BookmarkTreeView(QWidget *parent = nullptr); + ~BookmarkTreeView() override {} + + void subclassKeyPressEvent(QKeyEvent *event); + +signals: + void editingDone(); + +protected slots: + void commitData(QWidget *editor) override; + +private slots: + void setExpandedData(const QModelIndex &index); +}; + +QT_END_NAMESPACE + +#endif // BOOKMARKMANAGER_H diff --git a/src/assistant/assistant/bookmarkmanagerwidget.cpp b/src/assistant/assistant/bookmarkmanagerwidget.cpp new file mode 100644 index 0000000..fc2f24d --- /dev/null +++ b/src/assistant/assistant/bookmarkmanagerwidget.cpp @@ -0,0 +1,291 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "bookmarkmanagerwidget.h" +#include "bookmarkitem.h" +#include "bookmarkmodel.h" +#include "tracer.h" +#include "xbelsupport.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +BookmarkManagerWidget::BookmarkManagerWidget(BookmarkModel *sourceModel, + QWidget *parent) + : QWidget(parent) + , bookmarkModel(sourceModel) +{ + TRACE_OBJ + ui.setupUi(this); + + ui.treeView->setModel(bookmarkModel); + + ui.treeView->expandAll(); + ui.treeView->installEventFilter(this); + ui.treeView->viewport()->installEventFilter(this); + ui.treeView->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(ui.treeView, &QWidget::customContextMenuRequested, + this, &BookmarkManagerWidget::customContextMenuRequested); + + connect(ui.remove, &QAbstractButton::clicked, + this, [this]() { removeItem(); }); + connect(ui.lineEdit, &QLineEdit::textChanged, + this, &BookmarkManagerWidget::textChanged); + QShortcut *shortcut = new QShortcut(QKeySequence::Find, ui.lineEdit); + connect(shortcut, &QShortcut::activated, + ui.lineEdit, QOverload<>::of(&QWidget::setFocus)); + + importExportMenu.addAction(tr("Import..."), this, + &BookmarkManagerWidget::importBookmarks); + importExportMenu.addAction(tr("Export..."), this, + &BookmarkManagerWidget::exportBookmarks); + ui.importExport->setMenu(&importExportMenu); + + shortcut = new QShortcut(QKeySequence::FindNext, this); + connect(shortcut, &QShortcut::activated, + this, &BookmarkManagerWidget::findNext); + shortcut = new QShortcut(QKeySequence::FindPrevious, this); + connect(shortcut, &QShortcut::activated, + this, &BookmarkManagerWidget::findPrevious); + + connect(bookmarkModel, &QAbstractItemModel::rowsRemoved, + this, &BookmarkManagerWidget::refeshBookmarkCache); + connect(bookmarkModel, &QAbstractItemModel::rowsInserted, + this, &BookmarkManagerWidget::refeshBookmarkCache); + connect(bookmarkModel, &QAbstractItemModel::dataChanged, + this, &BookmarkManagerWidget::refeshBookmarkCache); + + ui.treeView->setCurrentIndex(ui.treeView->indexAt(QPoint(2, 2))); +} + +BookmarkManagerWidget::~BookmarkManagerWidget() +{ + TRACE_OBJ +} + +void BookmarkManagerWidget::closeEvent(QCloseEvent *event) +{ + TRACE_OBJ + event->accept(); + emit managerWidgetAboutToClose(); +} + +void BookmarkManagerWidget::renameItem(const QModelIndex &index) +{ + TRACE_OBJ + // check if we should rename the "Bookmarks Menu", bail + if (!bookmarkModel->parent(index).isValid()) + return; + + bookmarkModel->setItemsEditable(true); + ui.treeView->edit(index); + bookmarkModel->setItemsEditable(false); +} + +static int nextIndex(int current, int count, bool forward) +{ + TRACE_OBJ + if (current >= 0) + return (forward ? (current + 1) : ((current - 1) + count)) % count; + return 0; +} + +void BookmarkManagerWidget::selectNextIndex(bool direction) const +{ + QModelIndex current = ui.treeView->currentIndex(); + if (current.isValid() && !cache.isEmpty()) { + current = cache.at(nextIndex(cache.indexOf(current), cache.size(), + direction)); + } + ui.treeView->setCurrentIndex(current); +} + +bool BookmarkManagerWidget::eventFilter(QObject *object, QEvent *event) +{ + TRACE_OBJ + if (object != ui.treeView && object != ui.treeView->viewport()) + return QWidget::eventFilter(object, event); + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + switch (ke->key()) { + case Qt::Key_F2: + renameItem(ui.treeView->currentIndex()); + break; + + case Qt::Key_Delete: + removeItem(ui.treeView->currentIndex()); + break; + + default: break; + } + } + + if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *me = static_cast(event); + switch (me->button()) { + case Qt::LeftButton: + if (me->modifiers() & Qt::ControlModifier) + setSourceFromIndex(ui.treeView->currentIndex(), true); + break; + + case Qt::MiddleButton: + setSourceFromIndex(ui.treeView->currentIndex(), true); + break; + + default: break; + } + } + return QObject::eventFilter(object, event); +} + +void BookmarkManagerWidget::findNext() +{ + TRACE_OBJ + selectNextIndex(true); +} + +void BookmarkManagerWidget::findPrevious() +{ + TRACE_OBJ + selectNextIndex(false); +} + +void BookmarkManagerWidget::importBookmarks() +{ + TRACE_OBJ + const QString &fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), + QDir::currentPath(), tr("Files (*.xbel)")); + + if (fileName.isEmpty()) + return; + + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + XbelReader reader(bookmarkModel); + reader.readFromFile(&file); + } +} + +void BookmarkManagerWidget::exportBookmarks() +{ + TRACE_OBJ + QString fileName = QFileDialog::getSaveFileName(nullptr, tr("Save File"), "untitled.xbel"_L1, + tr("Files (*.xbel)")); + + const QLatin1StringView suffix(".xbel"); + if (!fileName.endsWith(suffix)) + fileName.append(suffix); + + QFile file(fileName); + if (file.open(QIODevice::WriteOnly)) { + XbelWriter writer(bookmarkModel); + writer.writeToFile(&file); + } else { + QMessageBox::information(this, tr("Qt Assistant"), + tr("Unable to save bookmarks."), QMessageBox::Ok); + } +} + +void BookmarkManagerWidget::refeshBookmarkCache() +{ + TRACE_OBJ + cache.clear(); + + const QString &text = ui.lineEdit->text(); + if (!text.isEmpty()) + cache = bookmarkModel->indexListFor(text); +} + +void BookmarkManagerWidget::textChanged(const QString &/*text*/) +{ + TRACE_OBJ + refeshBookmarkCache(); + if (!cache.isEmpty()) + ui.treeView->setCurrentIndex(cache.at(0)); +} + +void BookmarkManagerWidget::removeItem(const QModelIndex &index) +{ + TRACE_OBJ + QModelIndex current = index.isValid() ? index : ui.treeView->currentIndex(); + if (!current.parent().isValid() && current.row() < 2) + return; // check if we should delete the "Bookmarks Menu", bail + + if (bookmarkModel->hasChildren(current)) { + int value = QMessageBox::question(this, tr("Remove"), tr("You are going" + "to delete a Folder, this will also
remove it's content. Are " + "you sure to continue?"), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); + if (value == QMessageBox::Cancel) + return; + } + bookmarkModel->removeItem(current); +} + +void BookmarkManagerWidget::customContextMenuRequested(const QPoint &point) +{ + TRACE_OBJ + const QModelIndex &index = ui.treeView->indexAt(point); + if (!index.isValid()) + return; + + // check if we should open the menu on "Bookmarks Menu", bail + if (!bookmarkModel->parent(index).isValid()) + return; + + QAction *remove = nullptr; + QAction *rename = nullptr; + QAction *showItem = nullptr; + QAction *showItemInNewTab = nullptr; + + QMenu menu; + if (bookmarkModel->data(index, UserRoleFolder).toBool()) { + remove = menu.addAction(tr("Delete Folder")); + rename = menu.addAction(tr("Rename Folder")); + } else { + showItem = menu.addAction(tr("Show Bookmark")); + showItemInNewTab = menu.addAction(tr("Show Bookmark in New Tab")); + menu.addSeparator(); + remove = menu.addAction(tr("Delete Bookmark")); + rename = menu.addAction(tr("Rename Bookmark")); + } + + QAction *pickedAction = menu.exec(ui.treeView->mapToGlobal(point)); + if (pickedAction == rename) + renameItem(index); + else if (pickedAction == remove) + removeItem(index); + else if (pickedAction == showItem || pickedAction == showItemInNewTab) + setSourceFromIndex(index, pickedAction == showItemInNewTab); +} + +void +BookmarkManagerWidget::setSourceFromIndex(const QModelIndex &index, bool newTab) +{ + TRACE_OBJ + if (bookmarkModel->data(index, UserRoleFolder).toBool()) + return; + + const QVariant &data = bookmarkModel->data(index, UserRoleUrl); + if (data.canConvert()) { + if (newTab) + emit setSourceInNewTab(data.toUrl()); + else + emit setSource(data.toUrl()); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/bookmarkmanagerwidget.h b/src/assistant/assistant/bookmarkmanagerwidget.h new file mode 100644 index 0000000..dc56eea --- /dev/null +++ b/src/assistant/assistant/bookmarkmanagerwidget.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef BOOKMARKMANAGERWIDGET_H +#define BOOKMARKMANAGERWIDGET_H + +#include "ui_bookmarkmanagerwidget.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class BookmarkModel; +class QCloseEvent; +class QString; + +class BookmarkManagerWidget : public QWidget +{ + Q_OBJECT +public: + explicit BookmarkManagerWidget(BookmarkModel *bookmarkModel, + QWidget *parent = nullptr); + ~BookmarkManagerWidget() override; + +protected: + void closeEvent(QCloseEvent *event) override; + +signals: + void setSource(const QUrl &url); + void setSourceInNewTab(const QUrl &url); + + void managerWidgetAboutToClose(); + +private: + void renameItem(const QModelIndex &index); + void selectNextIndex(bool direction) const; + bool eventFilter(QObject *object, QEvent *event) override; + +private slots: + void findNext(); + void findPrevious(); + + void importBookmarks(); + void exportBookmarks(); + + void refeshBookmarkCache(); + void textChanged(const QString &text); + + void removeItem(const QModelIndex &index = QModelIndex()); + + void customContextMenuRequested(const QPoint &point); + void setSourceFromIndex(const QModelIndex &index, bool newTab = false); + +private: + QMenu importExportMenu; + Ui::BookmarkManagerWidget ui; + QList cache; + + BookmarkModel *bookmarkModel; +}; + +QT_END_NAMESPACE + +#endif // BOOKMARKMANAGERWIDGET_H diff --git a/src/assistant/assistant/bookmarkmanagerwidget.ui b/src/assistant/assistant/bookmarkmanagerwidget.ui new file mode 100644 index 0000000..dc965d9 --- /dev/null +++ b/src/assistant/assistant/bookmarkmanagerwidget.ui @@ -0,0 +1,137 @@ + + + BookmarkManagerWidget + + + + 0 + 0 + 517 + 348 + + + + Manage Bookmarks + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Search: + + + + + + + + + + + + true + + + true + + + true + + + 1000 + + + true + + + true + + + 225 + + + 50 + + + 225 + + + 50 + + + + + + + + + Remove + + + + + + + Import and Backup + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + OK + + + + + + + + + + + pushButton_5 + clicked() + BookmarkManagerWidget + close() + + + 445 + 328 + + + 340 + 313 + + + + + diff --git a/src/assistant/assistant/bookmarkmodel.cpp b/src/assistant/assistant/bookmarkmodel.cpp new file mode 100644 index 0000000..a7bb9cc --- /dev/null +++ b/src/assistant/assistant/bookmarkmodel.cpp @@ -0,0 +1,425 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "bookmarkmodel.h" +#include "bookmarkitem.h" + +#include +#include +#include + +#include +#include +#include + +using namespace Qt::StringLiterals; + +const quint32 VERSION = 0xe53798; +const QLatin1StringView MIMETYPE("application/bookmarks.assistant"); + +BookmarkModel::BookmarkModel() + : QAbstractItemModel() + , m_folder(false) + , m_editable(false) + , rootItem(nullptr) +{ +} + +BookmarkModel::~BookmarkModel() +{ + delete rootItem; +} + +QByteArray +BookmarkModel::bookmarks() const +{ + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << qint32(VERSION); + + const QModelIndex &root = index(0,0, QModelIndex()).parent(); + for (int i = 0; i < rowCount(root); ++i) + collectItems(index(i, 0, root), 0, &stream); + + return ba; +} + +void +BookmarkModel::setBookmarks(const QByteArray &bookmarks) +{ + beginResetModel(); + + delete rootItem; + folderIcon = QApplication::style()->standardIcon(QStyle::SP_DirClosedIcon); + bookmarkIcon = QIcon(":/qt-project.org/assistant/images/bookmark.png"_L1); + + rootItem = new BookmarkItem(DataVector() << tr("Name") << tr("Address") + << true); + + QStack parents; + QDataStream stream(bookmarks); + + quint32 version; + stream >> version; + if (version < VERSION) { + stream.device()->seek(0); + BookmarkItem *toolbar = + new BookmarkItem(DataVector() << tr("Bookmarks Toolbar") << "Folder"_L1 << true); + rootItem->addChild(toolbar); + + BookmarkItem *menu = + new BookmarkItem(DataVector() << tr("Bookmarks Menu") << "Folder"_L1 << true); + rootItem->addChild(menu); + parents.push(menu); + } else { + parents.push(rootItem); + } + + qint32 depth; + bool expanded; + QString name, url; + while (!stream.atEnd()) { + stream >> depth >> name >> url >> expanded; + while ((parents.size() - 1) != depth) + parents.pop(); + + BookmarkItem *item = new BookmarkItem(DataVector() << name << url << expanded); + if (url == "Folder"_L1) { + parents.top()->addChild(item); + parents.push(item); + } else { + parents.top()->addChild(item); + } + } + + cache.clear(); + setupCache(index(0,0, QModelIndex().parent())); + endResetModel(); +} + +void +BookmarkModel::setItemsEditable(bool editable) +{ + m_editable = editable; +} + +void +BookmarkModel::expandFoldersIfNeeeded(QTreeView *treeView) +{ + for (QModelIndex index : std::as_const(cache)) + treeView->setExpanded(index, index.data(UserRoleExpanded).toBool()); +} + +QModelIndex +BookmarkModel::addItem(const QModelIndex &parent, bool isFolder) +{ + m_folder = isFolder; + QModelIndex next; + if (insertRow(rowCount(parent), parent)) + next = index(rowCount(parent) - 1, 0, parent); + m_folder = false; + + return next; +} + +bool +BookmarkModel::removeItem(const QModelIndex &index) +{ + if (!index.isValid()) + return false; + + QModelIndexList indexes; + if (rowCount(index) > 0) + indexes = collectItems(index); + indexes.append(index); + + for (const QModelIndex &itemToRemove : std::as_const(indexes)) { + if (!removeRow(itemToRemove.row(), itemToRemove.parent())) + return false; + cache.remove(itemFromIndex(itemToRemove)); + } + return true; +} + +int +BookmarkModel::rowCount(const QModelIndex &index) const +{ + if (BookmarkItem *item = itemFromIndex(index)) + return item->childCount(); + return 0; +} + +int +BookmarkModel::columnCount(const QModelIndex &/*index*/) const +{ + return 2; +} + +QModelIndex +BookmarkModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + if (BookmarkItem *childItem = itemFromIndex(index)) { + if (BookmarkItem *parent = childItem->parent()) { + if (parent != rootItem) + return createIndex(parent->childNumber(), 0, parent); + } + } + return QModelIndex(); +} + +QModelIndex +BookmarkModel::index(int row, int column, const QModelIndex &index) const +{ + if (index.isValid() && (index.column() != 0 && index.column() != 1)) + return QModelIndex(); + + if (BookmarkItem *parent = itemFromIndex(index)) { + if (BookmarkItem *childItem = parent->child(row)) + return createIndex(row, column, childItem); + } + return QModelIndex(); +} + +Qt::DropActions +BookmarkModel::supportedDropActions () const +{ + return /* Qt::CopyAction | */Qt::MoveAction; +} + +Qt::ItemFlags +BookmarkModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + Qt::ItemFlags defaultFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + if (m_editable) + defaultFlags |= Qt::ItemIsEditable; + + if (itemFromIndex(index) && index.data(UserRoleFolder).toBool()) { + if (index.column() > 0) + return defaultFlags &~ Qt::ItemIsEditable; + return defaultFlags | Qt::ItemIsDropEnabled; + } + + return defaultFlags | Qt::ItemIsDragEnabled; +} + +QVariant +BookmarkModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid()) { + if (BookmarkItem *item = itemFromIndex(index)) { + switch (role) { + case Qt::EditRole: + case Qt::DisplayRole: + if (index.data(UserRoleFolder).toBool() && index.column() == 1) + return QString(); + return item->data(index.column()); + + case Qt::DecorationRole: + if (index.column() == 0) + return index.data(UserRoleFolder).toBool() + ? folderIcon : bookmarkIcon; + break; + + default: + return item->data(role); + } + } + } + return QVariant(); +} + +void BookmarkModel::setData(const QModelIndex &index, const DataVector &data) +{ + if (BookmarkItem *item = itemFromIndex(index)) { + item->setData(data); + emit dataChanged(index, index); + } +} + +bool +BookmarkModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + bool result = false; + if (role != Qt::EditRole && role != UserRoleExpanded) + return result; + + if (BookmarkItem *item = itemFromIndex(index)) { + if (role == Qt::EditRole) { + const bool isFolder = index.data(UserRoleFolder).toBool(); + if (!isFolder || index.column() == 0) + result = item->setData(index.column(), value); + } else if (role == UserRoleExpanded) { + result = item->setData(UserRoleExpanded, value); + } + } + + if (result) + emit dataChanged(index, index); + return result; +} + +QVariant +BookmarkModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (rootItem && orientation == Qt::Horizontal && role == Qt::DisplayRole) + return rootItem->data(section); + return QVariant(); +} + +QModelIndex +BookmarkModel::indexFromItem(BookmarkItem *item) const +{ + return cache.value(item, QModelIndex()); +} + +BookmarkItem* +BookmarkModel::itemFromIndex(const QModelIndex &index) const +{ + if (index.isValid()) + return static_cast(index.internalPointer()); + return rootItem; +} + +QList +BookmarkModel::indexListFor(const QString &label) const +{ + QList hits; + const QModelIndexList &list = collectItems(QModelIndex()); + for (const QModelIndex &index : list) { + if (index.data().toString().contains(label, Qt::CaseInsensitive)) + hits.prepend(index); // list is reverse sorted + } + return hits; +} + +bool +BookmarkModel::insertRows(int position, int rows, const QModelIndex &parent) +{ + if (parent.isValid() && !parent.data(UserRoleFolder).toBool()) + return false; + + bool success = false; + if (BookmarkItem *parentItem = itemFromIndex(parent)) { + beginInsertRows(parent, position, position + rows - 1); + success = parentItem->insertChildren(m_folder, position, rows); + if (success) { + const QModelIndex ¤t = index(position, 0, parent); + cache.insert(itemFromIndex(current), current); + } + endInsertRows(); + } + return success; +} + +bool +BookmarkModel::removeRows(int position, int rows, const QModelIndex &index) +{ + bool success = false; + if (BookmarkItem *parent = itemFromIndex(index)) { + beginRemoveRows(index, position, position + rows - 1); + success = parent->removeChildren(position, rows); + endRemoveRows(); + } + return success; +} + +QStringList +BookmarkModel::mimeTypes() const +{ + return QStringList() << MIMETYPE; +} + +QMimeData* +BookmarkModel::mimeData(const QModelIndexList &indexes) const +{ + if (indexes.isEmpty()) + return nullptr; + + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + + for (const QModelIndex &index : indexes) { + if (index.column() == 0) + collectItems(index, 0, &stream); + } + + QMimeData *mimeData = new QMimeData(); + mimeData->setData(MIMETYPE, data); + return mimeData; +} + +bool +BookmarkModel::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) + return true; + + if (!data->hasFormat(MIMETYPE) || column > 0) + return false; + + QByteArray ba = data->data(MIMETYPE); + QDataStream stream(&ba, QIODevice::ReadOnly); + while (stream.atEnd()) + return false; + + qint32 depth; + bool expanded; + QString name, url; + while (!stream.atEnd()) { + stream >> depth >> name >> url >> expanded; + if (insertRow(qMax(0, row), parent)) { + const QModelIndex ¤t = index(qMax(0, row), 0, parent); + if (current.isValid()) { + BookmarkItem* item = itemFromIndex(current); + item->setData(DataVector() << name << url << expanded); + } + } + } + return true; +} + +void +BookmarkModel::setupCache(const QModelIndex &parent) +{ + const QModelIndexList &list = collectItems(parent); + for (const QModelIndex &index : list) + cache.insert(itemFromIndex(index), index); +} + +QModelIndexList +BookmarkModel::collectItems(const QModelIndex &parent) const +{ + QModelIndexList list; + for (int i = rowCount(parent) - 1; i >= 0 ; --i) { + const QModelIndex &next = index(i, 0, parent); + if (data(next, UserRoleFolder).toBool()) + list += collectItems(next); + list.append(next); + } + return list; +} + +void +BookmarkModel::collectItems(const QModelIndex &parent, qint32 depth, + QDataStream *stream) const +{ + if (parent.isValid()) { + *stream << depth; + *stream << parent.data().toString(); + *stream << parent.data(UserRoleUrl).toString(); + *stream << parent.data(UserRoleExpanded).toBool(); + + for (int i = 0; i < rowCount(parent); ++i) { + if (parent.data(UserRoleFolder).toBool()) + collectItems(index(i, 0 , parent), depth + 1, stream); + } + } +} diff --git a/src/assistant/assistant/bookmarkmodel.h b/src/assistant/assistant/bookmarkmodel.h new file mode 100644 index 0000000..1d1edb2 --- /dev/null +++ b/src/assistant/assistant/bookmarkmodel.h @@ -0,0 +1,78 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef BOOKMARKMODEL_H +#define BOOKMARKMODEL_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class BookmarkItem; +class QMimeData; +class QTreeView; + +typedef QMap ItemModelIndexCache; + +class BookmarkModel : public QAbstractItemModel +{ + Q_OBJECT +public: + BookmarkModel(); + ~BookmarkModel() override; + + QByteArray bookmarks() const; + void setBookmarks(const QByteArray &bookmarks); + + void setItemsEditable(bool editable); + void expandFoldersIfNeeeded(QTreeView *treeView); + + QModelIndex addItem(const QModelIndex &parent, bool isFolder = false); + bool removeItem(const QModelIndex &index); + + int rowCount(const QModelIndex &index = QModelIndex()) const override; + int columnCount(const QModelIndex &index = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &index) const override; + QModelIndex index(int row, int column, const QModelIndex &index) const override; + + Qt::DropActions supportedDropActions () const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + QVariant data(const QModelIndex &index, int role) const override; + void setData(const QModelIndex &index, const QList &data); + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + QModelIndex indexFromItem(BookmarkItem *item) const; + BookmarkItem *itemFromIndex(const QModelIndex &index) const; + QList indexListFor(const QString &label) const; + + bool insertRows(int position, int rows, const QModelIndex &parent) override; + bool removeRows(int position, int rows, const QModelIndex &parent) override; + + QStringList mimeTypes() const override; + QMimeData* mimeData(const QModelIndexList &indexes) const override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, + int column, const QModelIndex &parent) override; + +private: + void setupCache(const QModelIndex &parent); + QModelIndexList collectItems(const QModelIndex &parent) const; + void collectItems(const QModelIndex &parent, qint32 depth, + QDataStream *stream) const; + +private: + bool m_folder; + bool m_editable; + QIcon folderIcon; + QIcon bookmarkIcon; + BookmarkItem *rootItem; + ItemModelIndexCache cache; +}; + +QT_END_NAMESPACE + +#endif // BOOKMARKMODEL_H diff --git a/src/assistant/assistant/bookmarkwidget.ui b/src/assistant/assistant/bookmarkwidget.ui new file mode 100644 index 0000000..a31a277 --- /dev/null +++ b/src/assistant/assistant/bookmarkwidget.ui @@ -0,0 +1,85 @@ + + + BookmarkWidget + + + + 0 + 0 + 235 + 606 + + + + Bookmarks + + + + 4 + + + + + + + Filter: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add + + + + + + + Remove + + + + + + + + + + diff --git a/src/assistant/assistant/centralwidget.cpp b/src/assistant/assistant/centralwidget.cpp new file mode 100644 index 0000000..ce733e9 --- /dev/null +++ b/src/assistant/assistant/centralwidget.cpp @@ -0,0 +1,612 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "centralwidget.h" + +#include "findwidget.h" +#include "helpenginewrapper.h" +#include "helpviewer.h" +#include "openpagesmanager.h" +#include "tracer.h" + +#include +#include + +#include +#include +#ifndef QT_NO_PRINTER +#include +#include +#include +#include +#endif +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + CentralWidget *staticCentralWidget = nullptr; +} + +// -- TabBar + +TabBar::TabBar(QWidget *parent) + : QTabBar(parent) +{ + TRACE_OBJ +#ifdef Q_OS_MAC + setDocumentMode(true); +#endif + setMovable(true); + setShape(QTabBar::RoundedNorth); + setContextMenuPolicy(Qt::CustomContextMenu); + setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred, + QSizePolicy::TabWidget)); + connect(this, &QTabBar::currentChanged, + this, &TabBar::slotCurrentChanged); + connect(this, &QTabBar::tabCloseRequested, + this, &TabBar::slotTabCloseRequested); + connect(this, &QWidget::customContextMenuRequested, + this, &TabBar::slotCustomContextMenuRequested); +} + +TabBar::~TabBar() +{ + TRACE_OBJ +} + +int TabBar::addNewTab(const QString &title) +{ + TRACE_OBJ + const int index = addTab(title); + setTabsClosable(count() > 1); + return index; +} + +void TabBar::setCurrent(HelpViewer *viewer) +{ + TRACE_OBJ + for (int i = 0; i < count(); ++i) { + HelpViewer *data = tabData(i).value(); + if (data == viewer) { + setCurrentIndex(i); + break; + } + } +} + +void TabBar::removeTabAt(HelpViewer *viewer) +{ + TRACE_OBJ + for (int i = 0; i < count(); ++i) { + HelpViewer *data = tabData(i).value(); + if (data == viewer) { + removeTab(i); + break; + } + } + setTabsClosable(count() > 1); +} + +void TabBar::titleChanged() +{ + TRACE_OBJ + for (int i = 0; i < count(); ++i) { + HelpViewer *data = tabData(i).value(); + QString title = data->title(); + title.replace(u'&', "&&"_L1); + setTabText(i, title.isEmpty() ? tr("(Untitled)") : title); + } +} + +void TabBar::slotCurrentChanged(int index) +{ + TRACE_OBJ + emit currentTabChanged(tabData(index).value()); +} + +void TabBar::slotTabCloseRequested(int index) +{ + TRACE_OBJ + OpenPagesManager::instance()->closePage(tabData(index).value()); +} + +void TabBar::slotCustomContextMenuRequested(const QPoint &pos) +{ + TRACE_OBJ + const int tab = tabAt(pos); + if (tab < 0) + return; + + QMenu menu(QString(), this); + menu.addAction(tr("New &Tab"), OpenPagesManager::instance(), + &OpenPagesManager::createBlankPage); + + const bool enableAction = count() > 1; + QAction *closePage = menu.addAction(tr("&Close Tab")); + closePage->setEnabled(enableAction); + + QAction *closePages = menu.addAction(tr("Close Other Tabs")); + closePages->setEnabled(enableAction); + + menu.addSeparator(); + + HelpViewer *viewer = tabData(tab).value(); + QAction *newBookmark = menu.addAction(tr("Add Bookmark for this Page...")); + const QString &url = viewer->source().toString(); + if (url.isEmpty() || url == "about:blank"_L1) + newBookmark->setEnabled(false); + + QAction *pickedAction = menu.exec(mapToGlobal(pos)); + if (pickedAction == closePage) + slotTabCloseRequested(tab); + else if (pickedAction == closePages) { + for (int i = count() - 1; i >= 0; --i) { + if (i != tab) + slotTabCloseRequested(i); + } + } else if (pickedAction == newBookmark) + emit addBookmark(viewer->title(), url); +} + +// -- CentralWidget + +CentralWidget::CentralWidget(QWidget *parent) + : QWidget(parent) +#ifndef QT_NO_PRINTER + , m_printer(nullptr) +#endif + , m_findWidget(new FindWidget(this)) + , m_stackedWidget(new QStackedWidget(this)) + , m_tabBar(new TabBar(this)) +{ + TRACE_OBJ + staticCentralWidget = this; + QVBoxLayout *vboxLayout = new QVBoxLayout(this); + + vboxLayout->setContentsMargins(QMargins()); + vboxLayout->setSpacing(0); + vboxLayout->addWidget(m_tabBar); + m_tabBar->setVisible(HelpEngineWrapper::instance().showTabs()); + vboxLayout->addWidget(m_stackedWidget); + vboxLayout->addWidget(m_findWidget); + m_findWidget->hide(); + + connect(m_findWidget, &FindWidget::findNext, this, &CentralWidget::findNext); + connect(m_findWidget, &FindWidget::findPrevious, this, &CentralWidget::findPrevious); + connect(m_findWidget, &FindWidget::find, this, &CentralWidget::find); + connect(m_findWidget, &FindWidget::escapePressed, this, &CentralWidget::activateTab); + connect(m_tabBar, &TabBar::addBookmark, this, &CentralWidget::addBookmark); +} + +CentralWidget::~CentralWidget() +{ + TRACE_OBJ + QStringList zoomFactors; + QStringList currentPages; + for (int i = 0; i < m_stackedWidget->count(); ++i) { + const HelpViewer * const viewer = viewerAt(i); + const QUrl &source = viewer->source(); + if (source.isValid()) { + currentPages << source.toString(); + zoomFactors << QString::number(viewer->scale()); + } + } + + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + helpEngine.setLastShownPages(currentPages); + helpEngine.setLastZoomFactors(zoomFactors); + helpEngine.setLastTabPage(m_stackedWidget->currentIndex()); + +#ifndef QT_NO_PRINTER + delete m_printer; +#endif +} + +CentralWidget *CentralWidget::instance() +{ + TRACE_OBJ + return staticCentralWidget; +} + +QUrl CentralWidget::currentSource() const +{ + TRACE_OBJ + return currentHelpViewer()->source(); +} + +QString CentralWidget::currentTitle() const +{ + TRACE_OBJ + return currentHelpViewer()->title(); +} + +bool CentralWidget::hasSelection() const +{ + TRACE_OBJ + return !currentHelpViewer()->selectedText().isEmpty(); +} + +bool CentralWidget::isForwardAvailable() const +{ + TRACE_OBJ + return currentHelpViewer()->isForwardAvailable(); +} + +bool CentralWidget::isBackwardAvailable() const +{ + TRACE_OBJ + return currentHelpViewer()->isBackwardAvailable(); +} + +HelpViewer* CentralWidget::viewerAt(int index) const +{ + TRACE_OBJ + return static_cast(m_stackedWidget->widget(index)); +} + +HelpViewer* CentralWidget::currentHelpViewer() const +{ + TRACE_OBJ + return static_cast(m_stackedWidget->currentWidget()); +} + +void CentralWidget::addPage(HelpViewer *page, bool fromSearch) +{ + TRACE_OBJ + page->installEventFilter(this); + page->setFocus(Qt::OtherFocusReason); + connectSignals(page); + const int index = m_stackedWidget->addWidget(page); + m_tabBar->setTabData(m_tabBar->addNewTab(page->title()), + QVariant::fromValue(viewerAt(index))); + connect(page, &HelpViewer::titleChanged, m_tabBar, &TabBar::titleChanged); + + if (fromSearch) { + connect(currentHelpViewer(), &HelpViewer::loadFinished, + this, &CentralWidget::highlightSearchTerms); + } +} + +void CentralWidget::removePage(int index) +{ + TRACE_OBJ + const bool currentChanged = index == currentIndex(); + m_tabBar->removeTabAt(viewerAt(index)); + m_stackedWidget->removeWidget(m_stackedWidget->widget(index)); + if (currentChanged) + emit currentViewerChanged(); +} + +int CentralWidget::currentIndex() const +{ + TRACE_OBJ + return m_stackedWidget->currentIndex(); +} + +void CentralWidget::setCurrentPage(HelpViewer *page) +{ + TRACE_OBJ + m_tabBar->setCurrent(page); + m_stackedWidget->setCurrentWidget(page); + emit currentViewerChanged(); +} + +void CentralWidget::connectTabBar() +{ + TRACE_OBJ + connect(m_tabBar, &TabBar::currentTabChanged, OpenPagesManager::instance(), + QOverload::of(&OpenPagesManager::setCurrentPage)); +} + +// -- public slots + +#if QT_CONFIG(clipboard) +void CentralWidget::copy() +{ + TRACE_OBJ + currentHelpViewer()->copy(); +} +#endif + +void CentralWidget::home() +{ + TRACE_OBJ + currentHelpViewer()->home(); +} + +void CentralWidget::zoomIn() +{ + TRACE_OBJ + currentHelpViewer()->scaleUp(); +} + +void CentralWidget::zoomOut() +{ + TRACE_OBJ + currentHelpViewer()->scaleDown(); +} + +void CentralWidget::resetZoom() +{ + TRACE_OBJ + currentHelpViewer()->resetScale(); +} + +void CentralWidget::forward() +{ + TRACE_OBJ + currentHelpViewer()->forward(); +} + +void CentralWidget::nextPage() +{ + TRACE_OBJ + m_stackedWidget->setCurrentIndex((m_stackedWidget->currentIndex() + 1) + % m_stackedWidget->count()); +} + +void CentralWidget::backward() +{ + TRACE_OBJ + currentHelpViewer()->backward(); +} + +void CentralWidget::previousPage() +{ + TRACE_OBJ + m_stackedWidget->setCurrentIndex((m_stackedWidget->currentIndex() - 1) + % m_stackedWidget->count()); +} + +void CentralWidget::print() +{ + TRACE_OBJ +#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG) + initPrinter(); + QPrintDialog dlg(m_printer, this); + + if (!currentHelpViewer()->selectedText().isEmpty()) + dlg.setOption(QAbstractPrintDialog::PrintSelection); + dlg.setOption(QAbstractPrintDialog::PrintPageRange); + dlg.setOption(QAbstractPrintDialog::PrintCollateCopies); + dlg.setWindowTitle(tr("Print Document")); + if (dlg.exec() == QDialog::Accepted) + currentHelpViewer()->print(m_printer); +#endif +} + +void CentralWidget::pageSetup() +{ + TRACE_OBJ +#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG) + initPrinter(); + QPageSetupDialog dlg(m_printer); + dlg.exec(); +#endif +} + +void CentralWidget::printPreview() +{ + TRACE_OBJ +#if !defined(QT_NO_PRINTER) && !defined(QT_NO_PRINTDIALOG) + initPrinter(); + QPrintPreviewDialog preview(m_printer, this); + preview.resize(m_printer->width(), m_printer->height()); + connect(&preview, &QPrintPreviewDialog::paintRequested, + this, &CentralWidget::printPreviewToPrinter); + preview.exec(); +#endif +} + +void CentralWidget::setSource(const QUrl &url) +{ + TRACE_OBJ + HelpViewer *viewer = currentHelpViewer(); + viewer->setSource(url); + viewer->setFocus(Qt::OtherFocusReason); +} + +void CentralWidget::setSourceFromSearch(const QUrl &url) +{ + TRACE_OBJ + connect(currentHelpViewer(), &HelpViewer::loadFinished, + this, &CentralWidget::highlightSearchTerms); + currentHelpViewer()->setSource(url); + currentHelpViewer()->setFocus(Qt::OtherFocusReason); +} + +void CentralWidget::findNext() +{ + TRACE_OBJ + find(m_findWidget->text(), true, false); +} + +void CentralWidget::findPrevious() +{ + TRACE_OBJ + find(m_findWidget->text(), false, false); +} + +void CentralWidget::find(const QString &ttf, bool forward, bool incremental) +{ + TRACE_OBJ + bool found = false; + if (HelpViewer *viewer = currentHelpViewer()) { + HelpViewer::FindFlags flags; + if (!forward) + flags |= HelpViewer::FindBackward; + if (m_findWidget->caseSensitive()) + flags |= HelpViewer::FindCaseSensitively; + found = viewer->findText(ttf, flags, incremental, false); + } + + if (!found && ttf.isEmpty()) + found = true; // the line edit is empty, no need to mark it red... + + if (!m_findWidget->isVisible()) + m_findWidget->show(); + m_findWidget->setPalette(found); +} + +void CentralWidget::activateTab() +{ + TRACE_OBJ + currentHelpViewer()->setFocus(); +} + +void CentralWidget::showTextSearch() +{ + TRACE_OBJ + m_findWidget->show(); +} + +void CentralWidget::updateBrowserFont() +{ + TRACE_OBJ + const int count = m_stackedWidget->count(); + const QFont &font = viewerAt(count - 1)->viewerFont(); + for (int i = 0; i < count; ++i) + viewerAt(i)->setViewerFont(font); +} + +void CentralWidget::updateUserInterface() +{ + m_tabBar->setVisible(HelpEngineWrapper::instance().showTabs()); +} + +// -- protected + +void CentralWidget::keyPressEvent(QKeyEvent *e) +{ + TRACE_OBJ + const QString &text = e->text(); + if (text.startsWith(u'/')) { + if (!m_findWidget->isVisible()) { + m_findWidget->showAndClear(); + } else { + m_findWidget->show(); + } + } else { + QWidget::keyPressEvent(e); + } +} + +void CentralWidget::focusInEvent(QFocusEvent * /* event */) +{ + TRACE_OBJ + // If we have a current help viewer then this is the 'focus proxy', + // otherwise it's the central widget. This is needed, so an embedding + // program can just set the focus to the central widget and it does + // The Right Thing(TM) + QWidget *receiver = m_stackedWidget; + if (HelpViewer *viewer = currentHelpViewer()) + receiver = viewer; + QTimer::singleShot(1, receiver, + QOverload<>::of(&QWidget::setFocus)); +} + +// -- private slots + +void CentralWidget::highlightSearchTerms() +{ + TRACE_OBJ + QHelpSearchEngine *searchEngine = + HelpEngineWrapper::instance().searchEngine(); + const QString searchInput = searchEngine->searchInput(); + const bool wholePhrase = searchInput.startsWith(u'"') && + searchInput.endsWith(u'"'); + const QStringList &words = wholePhrase ? QStringList(searchInput.mid(1, searchInput.size() - 2)) : + searchInput.split(QRegularExpression("\\W+"), Qt::SkipEmptyParts); + HelpViewer *viewer = currentHelpViewer(); + for (const QString &word : words) + viewer->findText(word, {}, false, true); + disconnect(viewer, &HelpViewer::loadFinished, + this, &CentralWidget::highlightSearchTerms); +} + +void CentralWidget::printPreviewToPrinter(QPrinter *p) +{ + TRACE_OBJ +#ifndef QT_NO_PRINTER + currentHelpViewer()->print(p); +#endif +} + +void CentralWidget::handleSourceChanged(const QUrl &url) +{ + TRACE_OBJ + if (sender() == currentHelpViewer()) + emit sourceChanged(url); +} + +void CentralWidget::slotHighlighted(const QUrl &link) +{ + TRACE_OBJ + QUrl resolvedLink = m_resolvedLinks.value(link); + if (!link.isEmpty() && resolvedLink.isEmpty()) { + resolvedLink = HelpEngineWrapper::instance().findFile(link); + m_resolvedLinks.insert(link, resolvedLink); + } + emit highlighted(resolvedLink); +} + +// -- private + +void CentralWidget::initPrinter() +{ + TRACE_OBJ +#ifndef QT_NO_PRINTER + if (!m_printer) + m_printer = new QPrinter(QPrinter::ScreenResolution); +#endif +} + +void CentralWidget::connectSignals(HelpViewer *page) +{ + TRACE_OBJ +#if defined(BROWSER_QTWEBKIT) + connect(page, &HelpViewer::printRequested, + this, &CentralWidget::print); +#endif +#if QT_CONFIG(clipboard) + connect(page, &HelpViewer::copyAvailable, + this, &CentralWidget::copyAvailable); +#endif + connect(page, &HelpViewer::forwardAvailable, + this, &CentralWidget::forwardAvailable); + connect(page, &HelpViewer::backwardAvailable, + this, &CentralWidget::backwardAvailable); + connect(page, &HelpViewer::sourceChanged, + this, &CentralWidget::handleSourceChanged); + connect(page, QOverload::of(&HelpViewer::highlighted), + this, &CentralWidget::slotHighlighted); +} + +bool CentralWidget::eventFilter(QObject *object, QEvent *e) +{ + TRACE_OBJ + if (e->type() != QEvent::KeyPress) + return QWidget::eventFilter(object, e); + + HelpViewer *viewer = currentHelpViewer(); + QKeyEvent *keyEvent = static_cast (e); + if (viewer == object && keyEvent->key() == Qt::Key_Backspace) { + if (viewer->isBackwardAvailable()) { +#if defined(BROWSER_QTWEBKIT) + // this helps in case there is an html field + if (!viewer->hasFocus()) +#endif // BROWSER_QTWEBKIT + viewer->backward(); + } + } + return QWidget::eventFilter(object, e); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/centralwidget.h b/src/assistant/assistant/centralwidget.h new file mode 100644 index 0000000..2ff379b --- /dev/null +++ b/src/assistant/assistant/centralwidget.h @@ -0,0 +1,146 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CENTRALWIDGET_H +#define CENTRALWIDGET_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class FindWidget; +class HelpViewer; +class QStackedWidget; +class QPrinter; + +class TabBar : public QTabBar +{ + Q_OBJECT + friend class CentralWidget; + +public: + ~TabBar(); + + int addNewTab(const QString &title); + void setCurrent(HelpViewer *viewer); + void removeTabAt(HelpViewer *viewer); + +public slots: + void titleChanged(); + +signals: + void currentTabChanged(HelpViewer *viewer); + void addBookmark(const QString &title, const QString &url); + +private: + TabBar(QWidget *parent = nullptr); + +private slots: + void slotCurrentChanged(int index); + void slotTabCloseRequested(int index); + void slotCustomContextMenuRequested(const QPoint &pos); +}; + +class CentralWidget : public QWidget +{ + Q_OBJECT + friend class OpenPagesManager; + +public: + CentralWidget(QWidget *parent = nullptr); + ~CentralWidget() override; + + static CentralWidget *instance(); + + QUrl currentSource() const; + QString currentTitle() const; + + bool hasSelection() const; + bool isForwardAvailable() const; + bool isBackwardAvailable() const; + + HelpViewer *viewerAt(int index) const; + HelpViewer *currentHelpViewer() const; + int currentIndex() const; + + void connectTabBar(); + +public slots: +#if QT_CONFIG(clipboard) + void copy(); +#endif + void home(); + + void zoomIn(); + void zoomOut(); + void resetZoom(); + + void forward(); + void nextPage(); + + void backward(); + void previousPage(); + + void print(); + void pageSetup(); + void printPreview(); + + void setSource(const QUrl &url); + void setSourceFromSearch(const QUrl &url); + + void findNext(); + void findPrevious(); + void find(const QString &text, bool forward, bool incremental); + + void activateTab(); + void showTextSearch(); + void updateBrowserFont(); + void updateUserInterface(); + +signals: + void currentViewerChanged(); +#if QT_CONFIG(clipboard) + void copyAvailable(bool yes); +#endif + void sourceChanged(const QUrl &url); + void highlighted(const QUrl &link); + void forwardAvailable(bool available); + void backwardAvailable(bool available); + void addBookmark(const QString &title, const QString &url); + +protected: + void keyPressEvent(QKeyEvent *) override; + void focusInEvent(QFocusEvent *event) override; + +private slots: + void highlightSearchTerms(); + void printPreviewToPrinter(QPrinter *printer); + void handleSourceChanged(const QUrl &url); + void slotHighlighted(const QUrl& link); + +private: + void initPrinter(); + void connectSignals(HelpViewer *page); + bool eventFilter(QObject *object, QEvent *e) override; + + void removePage(int index); + void setCurrentPage(HelpViewer *page); + void addPage(HelpViewer *page, bool fromSearch = false); + +private: +#ifndef QT_NO_PRINTER + QPrinter *m_printer; +#endif + FindWidget *m_findWidget; + QStackedWidget *m_stackedWidget; + TabBar *m_tabBar; + QHash m_resolvedLinks; +}; + +QT_END_NAMESPACE + +#endif // CENTRALWIDGET_H diff --git a/src/assistant/assistant/cmdlineparser.cpp b/src/assistant/assistant/cmdlineparser.cpp new file mode 100644 index 0000000..7172c37 --- /dev/null +++ b/src/assistant/assistant/cmdlineparser.cpp @@ -0,0 +1,334 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" + +#include +#include +#include + +#include "cmdlineparser.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static const char helpMessage[] = QT_TRANSLATE_NOOP("CmdLineParser", + "Usage: assistant [Options]\n\n" + "-collectionFile file Uses the specified collection\n" + " file instead of the default one\n" + "-showUrl url Shows the document with the\n" + " url.\n" + "-enableRemoteControl Enables Assistant to be\n" + " remotely controlled.\n" + "-show widget Shows the specified dockwidget\n" + " which can be \"contents\", \"index\",\n" + " \"bookmarks\" or \"search\".\n" + "-activate widget Activates the specified dockwidget\n" + " which can be \"contents\", \"index\",\n" + " \"bookmarks\" or \"search\".\n" + "-hide widget Hides the specified dockwidget\n" + " which can be \"contents\", \"index\"\n" + " \"bookmarks\" or \"search\".\n" + "-register helpFile Registers the specified help file\n" + " (.qch) in the given collection\n" + " file.\n" + "-unregister helpFile Unregisters the specified help file\n" + " (.qch) from the give collection\n" + " file.\n" + "-setCurrentFilter filter Set the filter as the active filter.\n" + "-remove-search-index Removes the full text search index.\n" + "-rebuild-search-index Obsolete. Use -remove-search-index instead.\n" + " Removes the full text search index.\n" + " It will be rebuilt on next Assistant run.\n" + "-quiet Does not display any error or\n" + " status message.\n" + "-help Displays this help.\n" + ); + + +CmdLineParser::CmdLineParser(const QStringList &arguments) + : m_pos(0), + m_enableRemoteControl(false), + m_contents(Untouched), + m_index(Untouched), + m_bookmarks(Untouched), + m_search(Untouched), + m_register(None), + m_removeSearchIndex(false), + m_quiet(false) +{ + TRACE_OBJ + for (int i = 1; i < arguments.size(); ++i) { + const QString &arg = arguments.at(i); + if (arg.toLower() == "-quiet") + m_quiet = true; + else + m_arguments.append(arg); + } +} + +CmdLineParser::Result CmdLineParser::parse() +{ + TRACE_OBJ + bool showHelp = false; + + while (m_error.isEmpty() && hasMoreArgs()) { + const QString &arg = nextArg().toLower(); + if (arg == "-collectionfile"_L1) + handleCollectionFileOption(); + else if (arg == "-showurl"_L1) + handleShowUrlOption(); + else if (arg == "-enableremotecontrol"_L1) + m_enableRemoteControl = true; + else if (arg == "-show"_L1) + handleShowOption(); + else if (arg == "-hide"_L1) + handleHideOption(); + else if (arg == "-activate"_L1) + handleActivateOption(); + else if (arg == "-register"_L1) + handleRegisterOption(); + else if (arg == "-unregister"_L1) + handleUnregisterOption(); + else if (arg == "-setcurrentfilter"_L1) + handleSetCurrentFilterOption(); + else if (arg == "-remove-search-index"_L1) + m_removeSearchIndex = true; + else if (arg == "-rebuild-search-index"_L1) + m_removeSearchIndex = true; + else if (arg == "-help"_L1) + showHelp = true; + else + m_error = tr("Unknown option: %1").arg(arg); + } + + if (!m_error.isEmpty()) { + showMessage(m_error + "\n\n\n"_L1 + tr(helpMessage), true); + return Error; + } else if (showHelp) { + showMessage(tr(helpMessage), false); + return Help; + } + return Ok; +} + +bool CmdLineParser::hasMoreArgs() const +{ + TRACE_OBJ + return m_pos < m_arguments.size(); +} + +const QString &CmdLineParser::nextArg() +{ + TRACE_OBJ + Q_ASSERT(hasMoreArgs()); + return m_arguments.at(m_pos++); +} + +void CmdLineParser::handleCollectionFileOption() +{ + TRACE_OBJ + if (hasMoreArgs()) { + const QString &fileName = nextArg(); + m_collectionFile = getFileName(fileName); + if (m_collectionFile.isEmpty()) + m_error = tr("The collection file '%1' does not exist."). + arg(fileName); + } else { + m_error = tr("Missing collection file."); + } +} + +void CmdLineParser::handleShowUrlOption() +{ + TRACE_OBJ + if (hasMoreArgs()) { + const QString &urlString = nextArg(); + QUrl url(urlString); + if (url.isValid()) { + m_url = url; + } else + m_error = tr("Invalid URL '%1'.").arg(urlString); + } else { + m_error = tr("Missing URL."); + } +} + +void CmdLineParser::handleShowOption() +{ + TRACE_OBJ + handleShowOrHideOrActivateOption(Show); +} + +void CmdLineParser::handleHideOption() +{ + TRACE_OBJ + handleShowOrHideOrActivateOption(Hide); +} + +void CmdLineParser::handleActivateOption() +{ + TRACE_OBJ + handleShowOrHideOrActivateOption(Activate); +} + +void CmdLineParser::handleShowOrHideOrActivateOption(ShowState state) +{ + TRACE_OBJ + if (hasMoreArgs()) { + const QString &widget = nextArg().toLower(); + if (widget == "contents"_L1) + m_contents = state; + else if (widget == "index"_L1) + m_index = state; + else if (widget == "bookmarks"_L1) + m_bookmarks = state; + else if (widget == "search"_L1) + m_search = state; + else + m_error = tr("Unknown widget: %1").arg(widget); + } else { + m_error = tr("Missing widget."); + } +} + +void CmdLineParser::handleRegisterOption() +{ + TRACE_OBJ + handleRegisterOrUnregisterOption(Register); +} + +void CmdLineParser::handleUnregisterOption() +{ + TRACE_OBJ + handleRegisterOrUnregisterOption(Unregister); +} + +void CmdLineParser::handleRegisterOrUnregisterOption(RegisterState state) +{ + TRACE_OBJ + if (hasMoreArgs()) { + const QString &fileName = nextArg(); + m_helpFile = getFileName(fileName); + if (m_helpFile.isEmpty()) + m_error = tr("The Qt help file '%1' does not exist.").arg(fileName); + else + m_register = state; + } else { + m_error = tr("Missing help file."); + } +} + +void CmdLineParser::handleSetCurrentFilterOption() +{ + TRACE_OBJ + if (hasMoreArgs()) + m_currentFilter = nextArg(); + else + m_error = tr("Missing filter argument."); +} + +QString CmdLineParser::getFileName(const QString &fileName) +{ + TRACE_OBJ + QFileInfo fi(fileName); + if (!fi.exists()) + return QString(); + return fi.absoluteFilePath(); +} + +void CmdLineParser::showMessage(const QString &msg, bool error) +{ + TRACE_OBJ + if (m_quiet) + return; +#ifdef Q_OS_WIN + QString message = "
"_L1 % msg % "
"_L1; + if (error) + QMessageBox::critical(0, tr("Error"), message); + else + QMessageBox::information(0, tr("Notice"), message); +#else + fprintf(error ? stderr : stdout, "%s\n", qPrintable(msg)); +#endif +} + +void CmdLineParser::setCollectionFile(const QString &file) +{ + TRACE_OBJ + m_collectionFile = file; +} + +QString CmdLineParser::collectionFile() const +{ + TRACE_OBJ + return m_collectionFile; +} + +bool CmdLineParser::collectionFileGiven() const +{ + TRACE_OBJ + return m_arguments.contains("-collectionfile"_L1, Qt::CaseInsensitive); +} + +QUrl CmdLineParser::url() const +{ + TRACE_OBJ + return m_url; +} + +bool CmdLineParser::enableRemoteControl() const +{ + TRACE_OBJ + return m_enableRemoteControl; +} + +CmdLineParser::ShowState CmdLineParser::contents() const +{ + TRACE_OBJ + return m_contents; +} + +CmdLineParser::ShowState CmdLineParser::index() const +{ + TRACE_OBJ + return m_index; +} + +CmdLineParser::ShowState CmdLineParser::bookmarks() const +{ + TRACE_OBJ + return m_bookmarks; +} + +CmdLineParser::ShowState CmdLineParser::search() const +{ + TRACE_OBJ + return m_search; +} + +QString CmdLineParser::currentFilter() const +{ + TRACE_OBJ + return m_currentFilter; +} + +bool CmdLineParser::removeSearchIndex() const +{ + TRACE_OBJ + return m_removeSearchIndex; +} + +CmdLineParser::RegisterState CmdLineParser::registerRequest() const +{ + TRACE_OBJ + return m_register; +} + +QString CmdLineParser::helpFile() const +{ + TRACE_OBJ + return m_helpFile; +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/cmdlineparser.h b/src/assistant/assistant/cmdlineparser.h new file mode 100644 index 0000000..04ca012 --- /dev/null +++ b/src/assistant/assistant/cmdlineparser.h @@ -0,0 +1,78 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CMDLINEPARSER_H +#define CMDLINEPARSER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class CmdLineParser +{ + Q_DECLARE_TR_FUNCTIONS(CmdLineParser) +public: + enum Result {Ok, Help, Error}; + enum ShowState {Untouched, Show, Hide, Activate}; + enum RegisterState {None, Register, Unregister}; + + CmdLineParser(const QStringList &arguments); + Result parse(); + + void setCollectionFile(const QString &file); + QString collectionFile() const; + bool collectionFileGiven() const; + QString cloneFile() const; + QUrl url() const; + bool enableRemoteControl() const; + ShowState contents() const; + ShowState index() const; + ShowState bookmarks() const; + ShowState search() const; + QString currentFilter() const; + bool removeSearchIndex() const; + bool rebuildSearchIndex() const; + RegisterState registerRequest() const; + QString helpFile() const; + + void showMessage(const QString &msg, bool error); + +private: + QString getFileName(const QString &fileName); + bool hasMoreArgs() const; + const QString &nextArg(); + void handleCollectionFileOption(); + void handleShowUrlOption(); + void handleShowOption(); + void handleHideOption(); + void handleActivateOption(); + void handleShowOrHideOrActivateOption(ShowState state); + void handleRegisterOption(); + void handleUnregisterOption(); + void handleRegisterOrUnregisterOption(RegisterState state); + void handleSetCurrentFilterOption(); + + QStringList m_arguments; + int m_pos; + QString m_collectionFile; + QString m_cloneFile; + QString m_helpFile; + QUrl m_url; + bool m_enableRemoteControl; + + ShowState m_contents; + ShowState m_index; + ShowState m_bookmarks; + ShowState m_search; + RegisterState m_register; + QString m_currentFilter; + bool m_removeSearchIndex; + bool m_quiet; + QString m_error; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/assistant/assistant/contentwindow.cpp b/src/assistant/assistant/contentwindow.cpp new file mode 100644 index 0000000..d9fc185 --- /dev/null +++ b/src/assistant/assistant/contentwindow.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "contentwindow.h" + +#include "centralwidget.h" +#include "helpenginewrapper.h" +#include "helpviewer.h" +#include "openpagesmanager.h" +#include "tracer.h" + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +ContentWindow::ContentWindow() + : m_contentWidget(HelpEngineWrapper::instance().contentWidget()) + , m_expandDepth(-2) +{ + TRACE_OBJ + m_contentWidget->viewport()->installEventFilter(this); + m_contentWidget->setContextMenuPolicy(Qt::CustomContextMenu); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(4, 4, 4, 4); + layout->addWidget(m_contentWidget); + + connect(m_contentWidget, &QWidget::customContextMenuRequested, + this, &ContentWindow::showContextMenu); + connect(m_contentWidget, &QHelpContentWidget::linkActivated, + this, &ContentWindow::linkActivated); + + QHelpContentModel *contentModel = + qobject_cast(m_contentWidget->model()); + connect(contentModel, &QHelpContentModel::contentsCreated, + this, &ContentWindow::expandTOC); +} + +ContentWindow::~ContentWindow() +{ + TRACE_OBJ +} + +bool ContentWindow::syncToContent(const QUrl& url) +{ + TRACE_OBJ + QModelIndex idx = m_contentWidget->indexOf(url); + if (!idx.isValid()) + return false; + m_contentWidget->setCurrentIndex(idx); + m_contentWidget->scrollTo(idx); + return true; +} + +void ContentWindow::expandTOC() +{ + TRACE_OBJ + Q_ASSERT(m_expandDepth >= -2); + if (m_expandDepth > -2) { + expandToDepth(m_expandDepth); + m_expandDepth = -2; + } +} + +void ContentWindow::expandToDepth(int depth) +{ + TRACE_OBJ + Q_ASSERT(depth >= -2); + m_expandDepth = depth; + if (depth == -1) + m_contentWidget->expandAll(); + else if (depth == 0) + m_contentWidget->collapseAll(); + else + m_contentWidget->expandToDepth(depth - 1); +} + +void ContentWindow::focusInEvent(QFocusEvent *e) +{ + TRACE_OBJ + if (e->reason() != Qt::MouseFocusReason) + m_contentWidget->setFocus(); +} + +void ContentWindow::keyPressEvent(QKeyEvent *e) +{ + TRACE_OBJ + if (e->key() == Qt::Key_Escape) + emit escapePressed(); +} + +bool ContentWindow::eventFilter(QObject *o, QEvent *e) +{ + TRACE_OBJ + if (m_contentWidget && o == m_contentWidget->viewport() + && e->type() == QEvent::MouseButtonRelease) { + QMouseEvent *me = static_cast(e); + const QModelIndex &index = m_contentWidget->indexAt(me->pos()); + if (!index.isValid()) + return QWidget::eventFilter(o, e); + + const Qt::MouseButtons button = me->button(); + QItemSelectionModel *sm = m_contentWidget->selectionModel(); + if (sm->isSelected(index)) { + if ((button == Qt::LeftButton && (me->modifiers() & Qt::ControlModifier)) + || (button == Qt::MiddleButton)) { + QHelpContentModel *contentModel = + qobject_cast(m_contentWidget->model()); + if (contentModel) { + QHelpContentItem *itm = contentModel->contentItemAt(index); + if (itm && HelpViewer::canOpenPage(itm->url().path())) + OpenPagesManager::instance()->createPage(itm->url()); + } + } else if (button == Qt::LeftButton) { + itemClicked(index); + } + } + } + return QWidget::eventFilter(o, e); +} + + +void ContentWindow::showContextMenu(const QPoint &pos) +{ + TRACE_OBJ + if (!m_contentWidget->indexAt(pos).isValid()) + return; + + QHelpContentModel *contentModel = + qobject_cast(m_contentWidget->model()); + QHelpContentItem *itm = + contentModel->contentItemAt(m_contentWidget->currentIndex()); + + QMenu menu; + QAction *curTab = menu.addAction(tr("Open Link")); + QAction *newTab = menu.addAction(tr("Open Link in New Tab")); + if (!HelpViewer::canOpenPage(itm->url().path())) + newTab->setEnabled(false); + + menu.move(m_contentWidget->mapToGlobal(pos)); + + QAction *action = menu.exec(); + if (curTab == action) + emit linkActivated(itm->url()); + else if (newTab == action) + OpenPagesManager::instance()->createPage(itm->url()); +} + +void ContentWindow::itemClicked(const QModelIndex &index) +{ + TRACE_OBJ + QHelpContentModel *contentModel = + qobject_cast(m_contentWidget->model()); + + if (contentModel) { + if (QHelpContentItem *itm = contentModel->contentItemAt(index)) { + const QUrl &url = itm->url(); + if (url != CentralWidget::instance()->currentSource()) + emit linkActivated(url); + } + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/contentwindow.h b/src/assistant/assistant/contentwindow.h new file mode 100644 index 0000000..5dae053 --- /dev/null +++ b/src/assistant/assistant/contentwindow.h @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONTENTWINDOW_H +#define CONTENTWINDOW_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpContentWidget; + +class ContentWindow : public QWidget +{ + Q_OBJECT + +public: + ContentWindow(); + ~ContentWindow() override; + + bool syncToContent(const QUrl &url); + void expandToDepth(int depth); + +signals: + void linkActivated(const QUrl &link); + void escapePressed(); + +private slots: + void showContextMenu(const QPoint &pos); + void expandTOC(); + void itemClicked(const QModelIndex &index); + +private: + void focusInEvent(QFocusEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + bool eventFilter(QObject *o, QEvent *e) override; + + QHelpContentWidget * const m_contentWidget; + int m_expandDepth; +}; + +QT_END_NAMESPACE + +#endif // CONTENTWINDOW_H diff --git a/src/assistant/assistant/doc/images/assistant-assistant.png b/src/assistant/assistant/doc/images/assistant-assistant.png new file mode 100644 index 0000000000000000000000000000000000000000..368b5c9325f1d988d5b1973e38affd5d04f0ab4c GIT binary patch literal 47313 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

vdZ|ZR{iM`D3-n_=v8^hlJ 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 X;iRQlm>sn^57AQByH=$7XUP8mc72Rx literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/doc/images/assistant-dockwidgets.png b/src/assistant/assistant/doc/images/assistant-dockwidgets.png new file mode 100644 index 0000000000000000000000000000000000000000..c657426e99206fa5d9ec5a36d57313669dee7fa9 GIT binary patch literal 42222 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

oeu|4MZTy@f)?oN&&Y38Kf8)N zZm99#4;L1i^tG}38!>^0%_LMnuRPo}m;uWrq7~`?JHj|e^TX=8a=8|~67}O!{06B_ zC*4uCvO3;$0t4m zMsJ|k!^J-fa^21G(~q|Wav6S`3X)<(CZH?~qZZs70YRfQ`X!ecY`&*pG|~j6;dC*d zVj(^?khyqy4u`VrP}#tZ->{`UXmh>{!fT&kO9b%O8=xKbv)5L?9ZWXl(ZaI+1OeOa z8Hu~hMhS5)5fO=AgDuo+=M@o8h(ST3sldJCx59=!r!MLlBtw|eJe}7PI#D-|<7;V+ z?*!R|oDWt&R|oo!I0+yilU|@G@T2~MjC!yo!^kb4$22Iphc!$2~vEJ|dM{_CblyT&~Qkq_=BRkS>!ypAivW`n^Z+;$r;K8^4nkf>R zbY1(?bT~1>+_3Yry_ED7PzqQhFFIoA3D0CCDy4y1m45Yy4GLP@Skt{t-mhi96$Q5$ zU65juh8x8or=_pmgA1zM+o<0uKXm&2LX9onUT7HF(DgQ5>bLpVbm!-ywW~ZE!s`X= z$46!-KbnmEwFET(EC@C2ub}&xMLkxcMb|=?-LkT`eh;bs=^`cqmgz;rIEID1d=ioc z6Ny6d2VyyDO(QJr9m?3aE_OK#-Yg)RMo|Hj)+ z{uuF{Q7XACN(e!k{mcEPD^ zh`s7IKG}0WxHbUS>#v({6^nS(U$bvw5Te1BZWm)j^Eh2zxD6-l(x2|i+PXypf=Hha z|Ir`Wx-H^TgIUN$@etj?1J-U{PA}1Bng^62M}`z#y)I5szqB{OTl3>v;O5UvSyhsO zOFT0w2EJQA5iztk(tvq%r)wA#(C93QiEsRBp_0?F53m7jCrK7YL@7}EMcztcZKv2b zv7_((Y}McL6I`;=f{;H90{TE%FWpujk4(D{sl+;G(1*R#5aWnbj`av9cg^ylF>7zm zyEo&Vjrio)@qpqqY1IwDhB;ouH2wKvdTikh;$5+smJ)D<{1&)sk$y=eRvl=%opHZM zY@?ONEW(U}yVO;SOeawIK;r?fMAm2%=t84?2Y;odO@Qc1T^e&!_r>5<2>Ap!5Zb|9 z*_4GTwF?sMuiNnzlI{#!90+<^)Peu0!&A!`dVGT4e1;3>ex{NM#3+8rkr21De6~ z_~;bvpe%vwT1-ukh)ix|LGI~E@1@)eA4WlRuW&9uiTWy#GUOcT@~mQGErac|D|qp6yqzl=$Uq+B zrQa;}b$knOpS5=9bUu;41{^cWCN(j#!;_4Bx}2^M zJ%2+0dK?A-kncd_ih|@v#=N4j(ef;a+ao^yn_er>Km2%U1(4$vdXGmz6Qcfsni0A2`VBs~A18Mm0%WxOBNl8}Y@FplL?xg&!^3?8|QX!|a39jMpI1Ei zbFS+azv7eK=t&_c*of>^sf7{EK1ibNaL7jWH!S<1D!ll1LP0tzsmvW zM*?z|IdBw#*#zu0Y!FF9!n!!=H5*mU=9q?T%K$R1N>zibEke*GLrmn$j+ai0{|k;y zzR}Y4x$PLy_qES;1#0Dw4U>$WZ-~yV)n6l z5|4GpyS~lk(%rI@4fW=i^=X3Je*+@QEX-jRHw{I9HlgV+6 zzjE8X-&we+izT7wUE=HZ7Y@&3QY|yaT_HDByUXNOPkx`xNwrDjvdN-Em`_Xb+* zb1`7xLU=+udcbE5Dw!u=5^G~+QP~eolBo}CBn|gn`NT&`@C5ver*aP^v2A^wgH2nIF1Z(EYg29Om|NSM+=Kvd2f6R@CmTt zQ~dQ7>>~M;4#;6qtDqhWpuQfIh90!yE0G_D2kphiDkPUIyRCa42(_fB40JC7n^*vQ z-%$9zCjio}r{pXESj7>YA=F7g=+j*r+8TU@b!P&Lj(}Vf+&jssYX;DB613sZL;w;d z;L!cD@C)EHqtv3`KZXOx6&A^GX~%>^O9K&@bJbUw1RV8-QpGKEV~<^Oyp`b5He60B zPB$(S5YRE|f4b?e`Kome=OG7o?PaO|9SLq})4~wIXyHEqJZqB&WusdRw;9VpVB)cs zvQAgzo*PT5rSJ+NEI(TMoAG5!^mkv8Xv|2=%w1PrmI@h`dl1h~*YNdwA2T}u8x{uq z-nl#{P+qXDK3VDqLJX9LZn8Tm4iYpLunXTUKK;CKJ#0lhuzBCejB0Ms$0x= z#6d{((d4%Gwp~E&&gd*OkJi1sW5ZucW?48bWO&yY+vQgejLganTXQ&BF4Ms!;Xb8K zbr`4nz<;`w6}q?yB(R3-dQHp4brDIPQ?QY`;lNd3-;m!svT$WsRsdE)BzxU4{xQF& zrtGbTY+gQHW2D=}1xP@X{Cp)(E z4+>vQBZoH1TYSIyLL(0`o()`skhyfv`q@LT{bMRW6MY0^;)gdKZ8&0YY&(AZJRG{H zrY@Jt{p2x^vIHnM1yfRXbu0%(n{UfraLC6^53}Xs!4oy>(!<^o&rSB!UF7D%Yi73a zj<_Aj!F|^oc3+V{h}`H8Tok2Z_LCFyUYA*q2&$uo*|u28!kI(%ht!qOy?k(61nVpF zS87N63?p<}q~=L4Hjo-jI?RU{>jZoa$&JTK797(PU@4o$sdaDFN2?cZ8etRNv_lw z)**R}IVp+#;lwPIJ=V2?PSxiv>MFCUG>odz{q&`eZ67*RzH`lO6F?V$MHyiNz%%{O zJUOk!TATZH(Ly{0?<*75?%CQ@pYI}7A&qG-VJq%ZPR&ecy)znA;BG4d8ZT;Cd_~^9 z?BUU%|H=qzwc`@dgn~GR`|Mr-d_Q0*i2w*;1fN}F!6!fyWho1n6->dHQ@iK^WMHTU z+Z`Mez)VT&S9+$OU^WlZH*4WPUpx3BAWfKD{_63i8y4*}CDm<_n0<^vcrRAG1gEQr z9T*b;){jC^&J{`2q*%`&t7?T$1?L`L9`>8Y-YkzAJI-?4ge0i_y9W(!CqH! zxo+xw>$Me2tdWE8q(xq2vMrh6b~T1Kay?`esWWmg6zOdwyGB&Ho&}la-KJ=mn=XD) ziGZwg11*;EE2}`%ZA*b!01zDnP%H?wijjr;6bN61=>g3OD1jr)5tzr#9^+NOO1Xn~ zmxZsBh{^!w=Zu^*A%`wXn27$rERU5Lwq_@I$ak!MCU^uA0x^F7R%pjQTK~4y0fg_k zCdp}{aL|)~JqWnA^RNv31tz~eY@=WpIh4TOo}ZuoYts#6CS+CJs;jd2I9+k{)JZKP zAm*=a?QboH+T`RN>_qzUsmhLL=+1+P0Ymt`FdWQuAx@WKRF6c){|n3G#~OXER$7!R zu^$LrDljX7?x$#_N2@pI>|xqKNJlVqLj}YWy+CP-M~~?0HS=Zk;7bbNQcticIG)eK z0_G2XJ@|9+{x^r{#Me=Fl3r^SIzjDPlKOBhzD7qf0v#$1FUI5!C94@I$Jac+$AnJh z!2=NN9iL@1DzXG^Lm)$;AYXE*N)urW0A^{WM;GgL`d*Gu$N_ja3H+#0shMc@sgMaa z`ON$hJV2xaGuZk2*9D`Dy99%*9|?AOZ!}$Q>GNsYXGMa(Vey6kgz^H#MV{Hkr275} zIQc{0s=h86CZH^6?D+$a@a?FE*sVW-juUYu6`&Gf=dl}UTaA?oW93_t-#L1|;`il^jcpCybkIClS9KK;Zp@Kc@0%X7 zg|g-l$=|>9)&kg3#^16;2Hvf|s?>NhtL=-M{x%X_NHw0&QnyC6S|j&6228$UTFK6B z)E@W%C_4M)($y|_wBxaWG`TGeFY^28A;%OOc60+qPf9#H<)k;-sr_KE%NC(+1o2qF z3vWY*ZHI0PyIcEFR|%7+873#PtDg8_LzE$_?e>xqbkAetYYZST;~O_scsFkdC~( zQ5?Ac1%zHzl8GW<^JsPxgsrkI~BAiY$WY?Z&r8h{g>a_&+0|RRZ4fd zjRP!}$6mWz6{c$^_vV#}O)Vw!Ms$qbcl!)JYOqr-A8 zN<-ar`oR5zF0a{-&yu2ynT2=0n)u8d7KLP_D+@DkZdP_PzO@<_ifMoq@g1kT6)^q8 zPNP&L{B$!gO)na}rWx}jR?RDVcy*h&bvMjA54tu?V+7>`G7K4b(3g2=1a&?hH(<3< z27B#(iH*N%wj6Oo9yR=qeSyZQpaHPu=5b1w2#y*rQ{0Rn{VAqD{=YiA?y#n^Z7-rSBMK@? zPy`gg3<5F`1QSX|rHBZKC@7%~y+;XEN_12hY6J!lNl}m@Rp|l}l`16&gc7O(5(&MA zn!KIhE$@Br-uu1xPrfAQoPE~Wd#$zC`mNtS+lh}f^RjC&;K^V~%A#KN@X=w%(OJQ% z_$(6|Jkuezq)F3aYf(9TR{~jZ8I$kUdhy!UEK4}e&|`BT_Nq^5d>td}utQiu`bt+i z&lEtp&ktT(yJ@s1+HO@j+KBZ^Z1zh{P>~hhzxP&K#uDi2e#%oEBD!;L*JxGKf;UNa z!m4sy;(?cRAy#LBL=$_bTHms&6jP%#B*g=Z zmKaZEE+rL0d^ z5a&O1@B7RE!wFdynC76W-SDXOrt^kXA-}aK_BKEA`xdU2MWK%GRW&3P;C!9V#Qt?N88(8)CqMn zrSAvFo@MI$sxqjoHHFC?9+=Chj8=Sql`#|}cPP3*M>l^^;!Ryvr_-BC-AVJ|firT7 z47t)dP6TJxdRCD!*5i4|KjNSxtJKM}El-D#h=c?izVL)I_90lcC8cZ$!hoJx(FTrQ zWo<&T4p~IGZHRANog@XAN;0Sd#EyrNBeS}J%+5M%nhUJ6a9%gPZhDET@8J2ZrI}d| zF)!ORc%^W4BhIL;gzES-N@F_lM2`z5IjT>|28dbTBZI)xl+1g=7o+4w1|>9@N@h2hrC-DsCxG{W>|v z_1cK9OZO(%R8t<$#vmf)f|iR>Js*~$^mXD*9J}J3Ee!?*il-Dm2^I_pJOqT3q7b-| z)f-LL2Q7-6i6?2;m%h@%_DBKkvf8u@OtM;G2?=&28uk}P!^I+_d%~@uIfMZf(bpve z`RV0zH(A7}6werJ)~6dKowD-7cC3DP{4?5v5Nn%B5G_tT(l9Jiq{L;#BL|=4trlBQ zP$_4IlScZ?=@a$cV8CA_meBxYx*_H5 zuQXVBK%ta zOpylPa_z59WxMi#izQt2Aii?nfSo(VSkjzG*4Q1-l2SpmB~mt@d2IyJNVVSCeP!J% z8l~qX@(g4dAOnWdP-bUUb7$gCF&a0Wn)?#O1al>)Zz5Q0G=ANCm6am0Tm71D=g1|j z;WRY!{i1} z+bJYSFuA{Y|0l)jKjol55>^iC@y`uxC#;;$?Sz%X-TLPw_Q%~g0nz^_arHb}Z5bUhQH#QUEd?B}S~%H%#B9W2UsRdxE^Tpc0u5tS&3)v@ zE|6HOJ;Oe%epX!po|%JY58^f{%h)x*`U_H2TQG4VvU=0Zg1$e{7aZD}0fm)2uDCxn z6xEg5HdK)odx-wEk20}AeN29kyV!&cyv?R0d6u(E3PQf+Sg+xl8s4%u@$t}E_ZYZm zh3~yyZ~2c`wVwSluFPw6dranx`uyrv7}9qGlgS!SC(j=N+d2R=L9OS0Ub1h<%6p?> z?@_b9dfeyh#rBf|>e`Q?;^>OeX~+N@RmT4Em#GIggr&q-<-{AP8L7`}HAp#p2^Wd? zXfut}=-?z4_r<&bnDM8QVQq@?w8iEFW-I>8PNQ8SE}Yu_eajex#Mahu;8?6~A$ss$ z^L%lsP@A1z;#1#LX_Q5o(7VH{`8+!~1TE>ntu}VNYW3G;4;LuHUY}o0%xp^TiSI}S z!FetN3--{W0w(QR#6F=1PMAg3c%^Z#UK#}whGaLNCh@G%(=X|RTg3AD7^}S?`Ug^e%d(6XdY51K#JNm(H{P5Q-cUfE+-yXO zVIrS*An7l0*5w8y_w7!*j+Bmomt5|g|9)AGpME^JkcZp%&3;?fA#bqM@P+{L`U-em zqGvu{NIS(^eO@-!euO+z%)33%F90zCQ_{Ep68(xA3`1G-vlJ046x*7C^w-N^VgzX0 z0y#vWCO5nJ6lNs+BZ?PCA{Bg@m#&TBQ%xrPYvaq#-FqH$9S2?{$gw;3v~mF`s12R= zu-;%8fWfQ{RPgshQNv|}Y+L9=Y`zgirvIhJ>gK8T;b-@LjIC~(VJkrJpBSjcVk8AL zw#>lx@0g1N7eRU5i+f5|k(wlT+~x6@)W;}9SwqND?Zzzoj$-(a`v%>CBuR~2v8ack zDd7iQ;Z9EmY?SZW#e-M6bGlS;X67S6FQU=@TOXJ4=6&HC^hWr05Ayj12S`@9dk(Q% z1c?OZI^G=|ebn^hD5x0#!$5dZ9y2Wq6Tr>5$_NSoBbqN@7(;L5bR{oG8O{M-HeRT; zf5i0VUBC9#0`vinW@?D}MBrhI>v&MH=-J(FjM_Bo>EgpmT=!Yj38zDhHm|hdi*EG=%;dv&BWO6#WY%9{*CFV&60mov^uKh6Q zZ-G;~MnN`Et|vh0X3-@9YUR#;h&Kh*Rs!_VuW4jWt#r; z2C5--e*3w~DrYT~JRa>WOo9K-vG)C!itOrxeFiQc=0X;>93h;b5s@Qn*G z@cgeSKL-H#ms8aF!b4nM_}`o0;tQbp?fLT`Q{sP`Q@=D`0d9-~bnt99u`S-~OA-H9 z9Ql+iFh>r`!38t4emvoO0U0jr%$c|U{k;13Sv)3poB6&n8{75aAs^WET1AWFYB~7x z3*ImlKR=%CZq=3V73kc8>6z0zcPJS{TUAPuQU!8#s4{84QMz%}Om8Ypbh0`jt0?k< zz>XEbQQ_kJ4{m}Ycg?s%ecX{(jH&U8NW_lU^qAa^N1ZKz#0FwH^e zqnP8hqY0#Ui0PD6X@z+Y7?$}F% z5&_JQ`bs+aC?9*dspX8Bk8l(Mrc!5NoH<<2w?h}8Xj3IXVc$~#qAy4Y+_A&vHkYK= za8%5B^aMw}0H(Yu{9KR9!_`@!DZsZ9vle?u8@0)PTcb%`;I<7htNj2+YVc0Y{sx=`d&ZAkNyZ7f3&jKOdh(ix4z)4RaYOH z2}sOKS=GPypBy1eq=h41R~kc`j)CuQuR_gpG45>L;m8u ztKaL=YZ@TEc6I_1gZ?&Vmke`n=q5(_EN#ePG#hyuvf}Az^N}Z zh6_PQ$jkwZ(cRBjFDWaX4Z54?e*=Myuz2d}K%9>!o zvCXe-`>QwR{GV~Gz@}=s5}2YRHDKWr6s!$e2UR827}o5u@Q18l{M5nD>~}x2-?jzk z%QIevEyXLFIQ}v5wDUq;(fr)+D!+_gQc_P=3D;{7RG{9<*I?9U*X^x$t#1-`6U&hh zJ*lGY{$zYTrpLBoZn>)^x1M6=(>HSAdiT*I`VpIPk0MHLoRv#3QPwp?dT&-KvP<23 zPPYS%h~L@XA`L8}7h?>~Zv!h5zV;evDhEBv)a?PwDqi;+L)Rq(zrP4eH86|yO%*dM zo{Er~Bg9_4nOOz4VXQSe)l~P&Sp4bOAERN=glCfVH!`kdYYZC+)uo#4YK=hD_0<6p z>(4;oP?d}B5m(3d1(G{3=4z2Wswza%N^QnUz( zYfd2Za7rtTo7oF^#$aKJHgD-9tKe(aaDdI+hn{`FP-}nN-)mErW*F;Ry_|;@)3~?9 z0>SA@8Ck3+r*?TAt9hu55dQ1uj{A9vQrJ5Zmrm`Ail?X>XXYN3ht1V8YOfq|g8+RM z%{eYB4!ZEg5ZSlnGR8mc&D^W@xEWXKCm;PMV_|n0@VgTqt}GpOj0@sPzbqLYVc;<4 zNPX4H#^`;NQE1c?o&ft-$D2*-?*ttNNi%Ghzp#slWY5j5;{l-=_>+(^hYIQY?+*A> zBN<6@0!wMR#>$iHn{=eLMfIU?F-`BZD$@)*%%m3QYv zbjh5hzTH~sw>uY^aqrE@oAqQ$Ny>|yin*@muq)n>O!-Y=BWHd;6-$@hDxTshE>*%W zZ2R8J^&4EsQ@cMPTJk$sta`dr>eOL$RHgLOkc)Ai+a`w7CVZ9G!j7T^1OKY__U0$7 zqAf^5xCMu*Wl?Ouyw>kMfW6fZRCBZe*MLpCYc?*N3QIN2e~g-bP6o^i`}Z%~ic*P1 z16Dt0$coC|%6HEo##>e@HB!xBKO;*r)oAiOAg}2E*aG4In!yc*O<|_x$9*UghM73EaD)SNgE3#!tG#tk*%eF3pIqW%>O5;ftkW*!B zM{5B4;ATrGDo^3@UEursy}F#GWXg+ zEw9C+X9PXc{fv{iLqGb{`@GYm!10p>k&eTa=1%+CV|UVBhtqir-y(_@2nAhvE;K%%beEF0f?Csq(-rU^0GC#el^}XgtWsQDjALS;!4upSQ z;Ertl_3PJbSy|cc+4{Qa&!HjtB4xTmG7^$Qi~n5<)&OgQwZXa}ul?N6pqG~y{>aG4 z_wPcX|C>%#89jsQw2teA&+#K?nga|<-{daYUMFo-V~SYgm`2?(VB zO2hf*=2Zz&a3A1=Ei5cBuD7;me}BIGk|7#AH>I5CQ$0I3_j>zYiZsqZa>e-m>zlnt zW~QfcW#!}s_^0SqcD-xy@}DN_KN^2xnY{>*N(jkoE8W9~Uic_ISzl7@%$1S)txL7c z!0iFIz(Dx{JF2+8sA$tU6({PECC;QT4KHRuzlMv8>ue{>xv;(6XTcVcJy`$%fy2hZ z5#tlrw{u6X&dbP%N)UZOtgf#1gM6y8`caN0z=|K@K-n};ARX-|;=Hd3J^|bk2lqeN zsIPBJLqwU6iu2f6@nf0^f%^vsk}fVBHzzAGCr_4=+K zmYO5tt0SzVrlxjveSHq!YtE`M9pJzSWy#&lZ4ir#JP3=|3y5>{}$lOEhB002OJ zc6ECjGszo56TCYi~#*; zY}8b?_VUuJQXT&FOTx~sf)Jik!sNkL$!Z$@SpB)=jn&NKHe7 zg@?y$|6o9uERu4y-}O;Tizkcxxock(5cuH6?xppB3ShxM%&6?>APIb)3q*5vK3!;4 zvI~^txafYFdzEx_WQ`~N5KoZtZ@q zMpWOb_1k8qp1}OQKp7ttAs%IVzV`aOcK&=CNb&ytdkigwiH7c{k#1>4#ppZ!74^XT z5tHcBrkw0-={@S9FP@$RMfQ33zpXJTC=#lwSo}6g)-dPZLltM$syI10-9-l8A#m1S zt$%8^OG(EFVPBj}2z;{O;pL6uKdlXXKJ+?no&#H;qoLt-m-%ac!YqCnFnJ0GIVmg0 zrptPJf8cYNr%e4d9&p2rMOgCjyB?pG&bJ!_=S~^4ocj=qPAz>!gXZm~ID_(P;!exS zk@aP_z4s$GeQn=b09WSjfp4B9{p2|H9Cku*RVaI#5q|Mnu8RJz^Lo~f38C?t$qYI?SOoU2VLq~yZd3C`=Jh& z`F9pXmcW05}ue-o+NjEJP?);0Vhb4&eG0H)mI!CIb6~(aQ1%XG=P3}>w6U<&-eD!MXWy_THA`4avBma#`uR* zaDW)w#v5TN(&wH1lDHEM+$0O2MZMm5$sV1Y22F)*v4<5L+4T1yN=r5{R`FyzoFuL- z4X~(u$`E*YeT}yiv_K7ji+~d}Ha5mQQ8K5E&yQV9n@~xkw!1wH0Mbwv9*iobu|;V& z5wIKM7FkalCltCgQNVE9y-IT2cAolU@H?+cfBKY>E|-OP!JrbIu93nlbK{W2Ndle> z4UUf-4(efHWmS2~b32@6<$QG$mP;VVs|**rZ5xj|hS-QaERBz=Re2QHS@?h^Ti{|) z0aa<*DB5Lzo`W*gCq}YHulQw(@^svpR;wpd zHOv_{ug?|2l7BwX?Z#ee#rmec6>Az5qzcLbVtVdPP+>XjReN3TjG8wyLt|^d{}?v* z4ghEH?Om{vt80T-=42a`D-~Aw+vjX^BS8Dnr1VrcHT#~uZ1C6yvl>pld>=}`t*E! zIvhSGrGqBOWblYv}?Uh#`z_ho8!f6V&tY%&5C@)Q{ zHLXZ?@*?5dcDLk~(89NbN8D^esHKS&-D`Z3$ye+TwXOaPWQSJ2oavTPMI9_tc}~%nf#LyC4r00 z>bQ1{{HkwPUH}VMW8e$6m1CN}*>>U!vCbN@T9xA}26Bpw5<&h&N}ZV0E`=HDzwe)`|9jJVV{uOKf1vVzgZf-g;A6Hj-9LWz_qGpk(xfdcz{58vc{4GQ z__h7%KVaA>W{eYwmB31mee0N`T2|~-_YbygTsTUY1Dk;@C`iB>bNc6vXWiQtg7QnE z`>zYnV%lPrZYC@SxeFhjEl1MES>M2Dqu4d3HQ&v~k3cwDS5_aTO?7Ur^COBL*$)lo z$(*LSk`_x#5=%e)6l9^%u>n#dgUE8 z$o%wBR2%%SuqbE3zcYGq6VF!Vjk?*PyKxn0Ni@N_80bO4UW}7z}Pa!%1op_z6cdr?NyJM zEV}v>E#KnPb1P7c;Q$Y|fbP%G$fykh^Lg!WKIA?5$gS4WZ}m)!i<+^SSOwZaUeAPohs#RqF{TZ2!qxR8`gjYZlfn$FJe8LoxL|?d;nEkdf?kE&1a5e2*ni{66iYSp)eQ!3ms0h3wOT`PgS&1R& zwO4(0hU_O6K0fb+n`nR{$H*An76M0YWMYr|idrP4lf);{XCT=ZLo!!sY8hYEOF$9*dI0wJU=n_I$DTKrhl#j%n`Yb zY=chB6T&V4&`qce@heDkT2bk1=T9^O#JX^u-kgtf>os%4hC@!B1TvY>Sd9t^2jIr} zc26<%_R}B(FZ`d)J2H~lSaUgi)U^_9M;6Ej8!!%aUu2ewp*o^P4F!*2YWwBtzl=E1 zYTfoT9BGPZC2TsW3V0nMwg`|*eo8PDAZG?^doWIZ@<4fvU+E3=!3NMD*F8@t+)=pn z`5JU??0Z9%#Z&Y|_!CfJjWt@?;kK$hZeHyt&$s8hDg z^!48k!C$GV6tQm*-b9v26!7 z)eX5eLgMHNbL&2quOKv-ekpTzoVT)${8RA3g%2m?Pwci?r~w=;jwI0m?$CTUdaIJ) zt`?CfF{H-v1y;w|BRrv%p0E2=2qdSAlxQLG1?&_?mm*QkFV4!?TY6n%LpDe0vGxv3 za4qi4V_HRrZx63W##xDVV48qJrQvcO{}cF=vgeAnP~=FxYQkceBe?PAFma7C^cP1| z`uZqnzG|Rp0JT$+A?iZG8I+i_9C2lpi3N@; zAVl}Q$h1n)t;8~7F&VGdZZoTZ?+NKxt_brTSA}J=QY?7?vv00`>FAI^I|7!i|K%cB z1PTDn4==B$Z%^yU6o`F!yPd#`X;ZxRFPUz$a#r^6_K2xjg^J*6J zSp5|`=3pe6v>o@%_#S4Rg$i4jJyHg$@O;(ouId3(#*xc#%`EaqyT;*586M(F9ahO` zvx`wH+Ds_-Jr|#OWe!f}&lJS_Gy1#0*KOG1mV`cK4av9omapuH*}_jVxrxji! zwWDp4J)|qeZJ1!8D&#_a#}ePs4Lg3M&kSD330kztbi}!BtbJvjeE?g0bPm_+w?Sb; zvTD0s_tx@6XA`9U9P?(Q!hYPS%ToDla8LzJcfU>^&FrB%h@5>*wLe{!4+C;T*E&{g zC3%pWQhtzn5f}EBNf+{P!7A7ZtVEE-#l^0wxkEV$qnyllQ`z!!hQP{hkV2S%j57^i zpcfqW)W<^eFH~bVQk#n(X5uw%x_)2`7NQIwJW66(r~g2Cw3vgV)UylDnA9eYi5qMH zEIHHL6*JYt_BhZwNd}|X&lNfu^43}eE#&t)QV>|m2yV)%GTZfg^sha#wB)b^1AN{% zL#No3-`u8TL}H*FEL~5g@DxeFuab1a*a5jg>;kHu`g9G72BNe^5`40#juQ}coBJmB zBPXTVEt93zP5xCnnR)aQ*o|e8@90x2UbB>iz@ceE2wfZDGuPF&`SEAxt9@<66%f{b z#;19+-G~bEdu7@aaIfC$K4c=>=&%I=VdGgCyR`P0P` z9o$CS`nyRYTYRsb)#fB`iv;N({DSol4nly4+H(*-P#^Zw&gN{nil(tZz*(|(IdQYNP1~xkt_~LBrU)di;o)mJ+EXx9lPbWifii^V81BJZlMHxnsQv8dR0Gbnl0G zKQ{j%|A%J6uQ470P^6hSUwmbay@p*3xUEKF5*2JE>%A#ogWR8@FyC~|5M4o%=s5x( z@Zp*od|xH%g8_M!ylR%hMtcu|b0E3CxW zVQU)JgjOVruT%h`EGyS=LdHp^K`Npaw&d7Y3AvH2qC_?QC64b~e=?p`Sj+-v{Z(V* zfyZmV8$}qe2~+ehRkUj?J_49fQI%x#bE9sEc$i{ihw@vi*^?xl^&j-y1!uxs3}+jN ziBl2RZQ>*kc%w76`w2DoQoogc}jFt+CYQ`@r6rjqc5ZYjbTDVOrFC>T;Y}ar8V^M6$<<9!0DkmG8Alb_+;$ThR z(y_5Igd3^;SmBruVhV~tQxnU~j-yQhjiRC=l#{&S851T}CE9nN&7=LL z{0F|Eak%99{4v7fxVi2}<~4zT?tURj8rOVH5O_{-_t^dV_z5M;)YNqSyH$2Xq|28t zS*;kZmX=%GjO7gtTu;YqFUPQtF>!Ivda7DFIgt3@?oQQsjqp@w(!XRPpp6DF#l6`-dOejJ`tiuK&l<4`{ceta(#_8{d>*6Q-1qzw!C+4m;2MyTeMP4DIa?tw%q9 z5&OTgK1BzxACBCB9Gsy{_m?LAmHMmH#KC4G6{=Q$WsiSY-v0*vq;COMGfvjxwcqEl z^GGL7eqaGVSI38PGne2YV8NDMazw@8yH;UsEykPlj?t`4=Ud?&h`)Pjn>Ny@oF;9t zs6yPZ@!b;klNN=eK!P8T1ydm3jNh27Z=4yjf8Xth!jdUResM9Jf`S5uySI0n zfa#g*pW5>B9@9XIpR#iJO>UNiZ;G$dI|4yxrW9mGMn*)W53sMv^jIDzq!*W$0~S7+ zyUFdgIB+s@CNkpULF<~p=xA(c+XE!=oR;IgJ>o?p7P#&`(djh)(?i~s=6AHpK_ zzsz8>$y&{C$ByTJ*ur|8uCm4(l+PXB@BDl-Jt}|#1N^D=GN`ZGSVPOWtRuxH})4#k9<5I+9pG}neoP`(phi}B%rZbB<0 z?}_-_vkUDmt$*~+T^<2aF*qqw5qdc)YRLXAI(b#>$V-K8DrBv>sR`Jqx98RluY&RP z@)Cf>l8R$iD3Rz+jHmo=C4`UUnp%(LJ&a7K3{DmN9fj;?A7TBh%E_5Y;7$ZGE!ap( zy@zvpZjPPUoZ?z!6pZE1i@aebQO+_VWo^*8cM$Doh%6R51XO}J5GjSLXP!fbQxFY* zV8&KlJT16Y4qsvhyzms(NJk&CyjQ8#iMb?TGcB9ak(LWDs1iVN9Od1!sl>S?p&)_p zekK=iG3}ne&USYByU8(I8QwHBB<*wcYvSkbZ_2S-tG(i2x}hoI2~ThCriLW?_$o{| zb=q+98(v;sHDkTL?nQfk^_Bou{g(Nnm;P3bo1e~!k3UZ}@O_;2vAB}q2`)}|HR}I)lqMYCR(!FtGB@BJJ?_>gPrT<=|WsWd5+Df6~o=7ez2NE$m0H#d%qj7$tn+TESNa(e)h z6zmEI5!!8G1*9iJ5Ni*y-9{&Bus6k!EpX41XGUizgA$jZSmJwP%$L3~c!d<#k8maH zQ_xBT@Y>*C+=t$?<_)#hlA>|x%A6A65wbD7l6cYE3$&l{ZVUmY4z#iq&j>2%`z zqg*FQMIoo4K}XH@u773aO|rXCiGOo+B1rBhTGdJ8$XpJDAy};y;k#TO zY+Y^b_b{ZRF`xAHnPhizi|v{@*~n9}`0_30(0Qs~pzxfZ&}Rbu$OZ!M&>Bo$(T3T> zVsL)4BXhA%$(IxNcau8&WJag}(6nl1EBiY&9XO35zRc7j1s+;l2jYr5$=8_RgD0QL zdA5mCpff7&97?~JBSOlJX3Pt%Xki)-4(tF~>FI=f&30I`_!r-PQL2W|PEXV3##~)p z$?3d*8$&JzFo$F#KVM2sMPv0~p6Oj$O9VQTj77kidg(Pd0E-T$LY6yVP;ISmc^N1q z^Nx%I$wDRAd-O^PwGEE0r8HU~A#Q|lLq6$y9eqzcR2UjD(LR58kB&g_ZJ(lGfC-`% zNz6>l$Szxnt$K0x##_9<{>H|5Fe@W=83lzzRyWyCNH(XBBdmVHkGNl{iuL}>$>fCF zq0};PoN!-oGYp(yANwu|)K8npj2;aqf85Y)y`EzXxw%Q77zE-F*deU>AF@~$;qK}#=dBCHAY!Re7sipG&Y{6bYj>wRn?&# ziHU_K4$@c=VP_(leYP<8sEJ2`p%T0OOY6`uu5V&3+`qQKgk#~klAfNo7BqQ}x2JE@y6wp=wy$ zUz?z99&YgAh`oi48Cwoh?ij*AEQ{47r+UG*l|$`_F+va(ln@mB$2c14soa0LpVG8&$6`M>D zDE%H&PCGxPqz&YKxkE!0FFmMh{1q+v7YJChAzR;m10WOITmIm%Oj)CJ43S@0STNV- z)4oK7js7F4O6c9u|kz4y!WYc00chve4)_w;UJKpr3rgdwJ z^04z33^d$1HI`Yd4T#U~m!o)4=QAj+KQTnmIkFc|lfJ&gvCIrHEE$|A$41(Io*-5p z2@$A>rY$dt6wINhC^Q^`1Dv0qCxi(^gRwBNheT#!K)k=9NT2<8$8sViNulRvh9wCk{O(p^+J=iKztG1A%gD*0+%wgCIH||=U;h+6igO*TEDMOC$a#V{ z3?HwVyET&+WvQ8(H4JaG9v8)juo)N2o44^c2vEy3W)Tn@-i~s!PND3E&DuIK;4Gnv zG8sh^pUk%?$hUqJ)*%67HECQ6H1jq+J8G)9e_%64^kMO8_`CP0oT40OOVzs{2MN)= zQ9|iWq-K7e_6u-6D5EDXB5F*Bug7I)WBipwFMRTAFBKvJ;LTlpjm!4Bt^p%vw9Hpc|mE2NvG?@usVJu0uP z>_-chLBX`f6#s)CF7#yw_2n#5%ycx9*X#LuZsYKfStsu+Zn7(>$kFAHhY3dI8*J~2 zh*-qtWDVB|W^U63yca^YA6wX{Ucbdm@z*w_(~OUDJk!sra5-5vr#$%z^;rkVJxo7A z;*PeWTQkPZ%8jh9^5|3%C=8x#1Gp|z2U%Vp)vr-R`C>wtT((_-h|7LOf7>l0KnRqo zUhB6@KIn_oE-kOS@l!WJwog^uj}sJUa;W#uxMXZ%l7SF3z27@>&aJ38Kjs&Gws&@- zG3BJ}g`I?aXz5QcC07;`63=gz$`zC)yP-b7K0g;CAiM*6$X{hd?AZ*BNgT*g6gkVt&P+Si7qzsB@vz=4lsB&sY(F{{93knHGd;a|GWC!~t;NSc zXCX-`n1Rhey@h$olNSK&z97SpO7LWUo=J|+E$Ndvx`_I*f_ANz_CuX(eMwegVi4rX ztzP$dQyOcq?@8h1rYC1&2Eo{oVsXk=&zQ$^bww^{G#CbJ$; zoGTF3euau#LJw(JK{i~dgGst!P9!Fa$X_?jj*0Edh)GLIOb3462qrq*hJ8P}bC$?c z2Dg;w*oQnW`^|%CfMLAof`j*cHtp0mcaAM9anftV5(_$;q5~5W@Mv7mf-*-tA+X3! zCWMk0=vm)Vtp;_+0W4xzPZ0NVhI4K2FMFao*_VMhV3dvF z^9cB1V7H!Hc%9y=cW?8S`z+=S2T#K0^#oH3T#3oZBO0b=s#-{e-UaNbTGL9f*`a6ca8GVs341eKcPWo5mJF1Gy*( zCj)%SRxlk&Wd-@(p1cKn<(ax(33?9Uc^cWAE1%~d1!{ZFar$v%H$K_I07C4jt)%T+ z4j>IZ;$ViyZ7P(=uo94p}7MYRH9{u{{ z=5}<4RUE(8Q+LU1a{&m`DDf){EsSL41(q?82E#V@7n-sC9x1OW`FV#sI;;wm2woE_ zgxq!KAMJHhGD1*QA09W4ss+N9L9Bd~^z%hbOM@lfXNk`rlV|RhU+4_SD6k|>UVx;} zE6h`7GXrc-ooY1HUwq8C)ZRqCom>*1d`$#c82;!VSZ-e#`z%8R6c67(j3cPr@O$Rv z?+nbnW?}%8L53FB_(i>j-fvk$kc-4}1T|n|W8*EoEV82^NO6-*#G)NQl7(0^M@ zLWJ5I!e`F!w2ZkC7Zet>se$nzCkufKD%#WjnRCb>c8xX-ABf=ZO#wL>fT|aaSx;0x zoM?{;7O42hYD?bB?>~GV9y)sVV=8jkI7QDJJSIi?U%F zRJ>rCFk7LFH>H=TpN$yAytc4{g}{9VzMSEgq}#Oj+vY^I4pE~y@t}oR)UGc!??vXW z$w_3hn!myEprB;A#H^IC%wfN29Xwzt6aqZy!1dvr%TYN+!<5B}wwnC(Lfc5|$@Ut=Vl2nfXFio`iwsU%jGR8q?ahXZ$&Ea>8n&XdA?z zx%R}&BTyY2;0lSN0l;bElF=j<6>z6!;rGJHTsOd_+aNv?Ez$(WwJ3Eiq=4BD!V%qv_#?x;IFf1@_^XVfDjEtY~!3#CI6{(}c!}vn%>RCd> zsSy{_n> z%Z`bnzf=nnVx)+aDEl;lTSCLpC*^Y}8ijO$xP|_3pI_GMxp6pb>BIC` zhz_%AOfEn|Ol@qSc1Vaw-g(nT#d5SgKe97dqK2?0fJDyIhaw?y}D`)@FHk0 zH!1d;q8#EmS(0=lv$4VdN8}?fwURQlw%)adAKEXA#6;tI;L7|NP+3)lyRq(#KdLIx z2=$|C*Nn6DFp)C#-MD|!g$la_4>z3DKmD?zd_Ww&hbkz6=POE|X-W`#`}g6mlqcTR z@idb`?d*@c&55zN8A!eRm*WO!u6#0OY}z^KxFebqCfe})Pe ze+Myca?O8gX=&m5w>O09q%Bgwo4?Q*PxAJg_{nCt03+?!BQJhWMH1O|3k0VVN?UTR-vNT)r5r~INsUPNRNT|d4tFpa zL_Y(QN9|+YhdK~hO|BWC(YM;5Di*5|1Qb7m0H5jmm?FaLw-+RrlZ|5%lzp2>k_MMu zIslfF+yAX$mjELBkVJxmynDP<=eXo?B;rS2Eg}^YD1eNy(t9QDmDfoV^yd#nRSw?5 z{0SE9KG%|>2sxxQajzMzKAfgbSuF~uVM&&ApSc z@ob?zN9N8UH`B))NJ73Rxn6@d-{s%2PUa%y^-h~iN{KC|tH!lz4J*8NqYMq;55jJG zt2&2{<3oz27=C;q9}{YcvD)yy;L?B0U9BG~CPTb34j&@^SKaMA!=#s4)fN*V6O4JP z_61VL8N+Y97J^}J8g2xu_X9)2@&qj-(lTH25Uu)QS%b;h3FR&j(_!L64-e_x|>i)5~PuchC+5rc?I(HVv>^hK;4~0-@}{Bf44_9Q#i^6tP#G02QInZ zQ4=pMP0k1f6{O-iENn$wH4rby2>?~(G{bqaHg@ksB|lb9swYLPoX*p9SIdQg68N&E z4CD|51${M=zM$JTC11zoJg~1$)YGX=pW73p*YIIVn4S)WSzv6 z`$9j>WhRVfN#$n}|5PYL8#?;fYdVq7!-;78i>s$lYV4QenaAZoY7xqDaayCm%Wa5UpIb;Ina=bK8*j890v)G2Dnj4|dKqZocg4eX?# z%QGLQhlAovC6AhKB>?sm#k}WUfh9N%hnY!VR&d0;X0XohwHacO`XW^aCuxcL&rF3X zDh@SFQr``x_^Pvtdn&mK=s2Uu8fl7D93EnMFG82a2qt3gKqti96MjXfvK#ja8zdbup2i`ob0Y=cBPz`b|tT z1}o2oHr5;hMD6v$Kt_F19^wvDJkf@M^bRWo(bHFM#jA*ec>&FM%`~HDE9Y1zJfqT3 z&Vn225-5UjK!fJv3POd#ZQgr=dY?etXeJV>Pm=Bj{j#%p5ud75JB*JXxw-Mw7{&ZtLC%=>)n3X2 zI@jFM8#-pel~RZ}69>Nu7*2=mt5WJNh?1ddW~V#ah*8W0K{c?s&O${=)ET(8_EG&y zY;%*7si7Er*@HpSOe_>qanyfE<2J=ga!thd9ibMNfcvL5Fn|YyvPzPKEnWlWS`K-0k<7j5dS7}7 z5XMW)^@PWzfD7G7Wi(&ar~c6BREl|Lfv|XROCZ0IOpZh_V`Cw9s0zgOkIkMg=RRC- z#{)3CoN+wJ)Nq#ewQ;wASk09Zha1V-!7r(HM|$q0Qr!D^@U?vlA?*tln)dDocG9!) z!0k^tOi;Fpc>ktU;~x2CQ$A6jHI!?ZZ=(jH-$J$RF^aw$*oAkl3VB%%Dl}cSAY<;v z$znm?VQsg;Q+fJwru%uJgB7q^0};?8tO`4eq_VXO-m00{wm5!>5aMc#!#*D84d}%% zRcMHgb4+Hi0L&0olTPVI(3FqEZAa8wEv_*b^j*};c@22MiRJ`y#OnT=jf9=p?^OO44AjaeVcBJU%_S2mnPmsK}P8dD~!>ag!ED(9z@uJs$>j) znvTs2XLYe1nIwK78dLs0>B^ImpAR)__5&q#*-jFfLFyl&l$zk&cqY;p7cy67h}mmd z^$?RYPVIYFPiHa3+PsKRdxnavr6k`Kq}+WBNyE~)4v^-mmq`|ArqK~%rxf4+p)}-g zGkd)rJ25ye+Hv`W6wtF#PRrwdW9DT8!lCJ06nGgtxbpc* zch_{~px=g!P*%;8;@vwRY57DC6priOyu?4(TbOCYf!N*FrH}m_u zTcK6>bC~fxwm3xm_j17PdCRVrM{o*iO33k;A5?QF{mI@8g{1Z$Wlp!n;@uL&;i| zG`D@vCe#^sc1Mkhcz<$ugl&u;k%YO9+1p`384*nNk65v1>GXI3wmkHN>&LJZud-1M`k zkmW>*8w+D>#7|a?JA*3SmSv!$FW{csWwg;wPE1I!Fgy#Bq>)`?(UfJN--0v%+9ZQ` zx!n#F%27Wb((cLf(tVyX5Cx-Md%gjfwLY_{&jJp0q0c&=#kwBiOeuw}q9jh}hxKOt zJ`Mn(xb|E#PW`#;Koo$5Y=S8YErI6HOrTJ|FF%-aLLA-#j zXqbiqJ==%s!5j#+;c6>UR*MtPMT<<()=&C)@O2AbuWzLb8S-x6=EIB&1}NNV1LTvX zSQj`b=zuwF-7l-Qb*sQRosE{pOJR1|a$IL}O)hzP_y)gvS5<-=G5O!ZGxMNf-Om6V zJ|PDwLv0dC49A}{Z~bdCDe!%)Enok0NII~Hp99-MXZ^@dljqk_qK=qFi-m~n{P8by zIaxbLf>+*Sq7?I~izcI^Bh(ROpHRX6UyV`0B0kxm@bUh=kZsxy&lgMXJgWxp&FWF1 zz?MtS)2H!IbiPAvxf!iGe9q)cWt^(nN5O3X0w#)u=WQL_AkMCn5U|#s;bH(7k?v( z?A7q<{wGv5<>iR~k*s|~th*mJ_oax`y`=wfGkl=Ez5Qbq6#w(vaY^<@cR{khCk{~n zTWQave)_-vV`6BKFa)*NOH_V`n;3_Gxq(R9TTgt3Dc@!28@K%zR??#aW>>$)_=;yt z8fC}UaN>zoEwF#P_URNrU`P8VJjXQCUu)HTmSgGV)%Cs~MW^6j0(WQ;DNxcxFsDfZ zzLy|?1+aRu7s*tZa+r3jpPU>>dV3RPW@V}C8ZfuFloF@lZDXvI7XBbriHqAiIucj& zzrghG9$_EAUxHjj8j?XG8zxNRxP-y5XnTAwVQty_P9C^F4X5vNLW1 zWH>!LonI=5(OXt)u51+|!5Le*+Xwg{h~-+H1Re6cEKaH&{;`9vXc9R6tz&3>#Q&|w z(!~^tAdDb$Dj3>ax~8@@?O%Tzrve)_Ns_Ws*r^TVw424cZLhl;zxq2u=q8OwJw=b4c{ z3*P3(J=?c4oYa&&#Lqay=_>k7TZ2>r98ke!QiSrBjjPNTb;O(`la z#i83Ee{`cgs;$K<+2$qGCBpC2b~YkXR8-RN$x~t=Yc~7UHlc!A&{#wg{m9o$IBiqK zD5q^ff2Dbw%KW~X0q16pyFy$|6K&YupnzBZ?!oLIy)8mumR{0@kO6&gN$i!p>*m^d zon-A!u+%mTG!o&r#U?4GqIQ3wat;&8|OPnWtKXBv7ReiJ9h#EVeJ{`BJu>5a|({SQ)eS@ z^|rSMjJlzsRAf4`G<-z8MJdw z%*FMAIi940Xa_IxV4R551DH|EY6--hIi(h-LjFV(qq=z(7FOZn@v87f`~jmv_B@eQW=(lQ`?BPzTjcZ(qHIT$X~(RgV;q(0 zgrtlD2iofI+&kHq@Lw?c@tA^FcNs0-c;>0LM%g5Y_n?0`AUw_IA#RlpQ+l=MP$j8g z%Uf&*%kmB_({5N@e}zb=XDmL> zTj~qNMnlY}ud)49C1%&+hcCFaboj-&3K~x|YsLLj)Jw_y2}$G?wwLq|u~lJ%`UjqaXj znY{svLyUtU_H}ksBnF@#D&oWLT*Dypw17B7i^{$ulP!0p& zO`dPq0UR~xnp2llW5ub|%l3F(m&d0YZ%1OGyKa;Du~MhvBhRBhenjYSuI?G@;Q@jy zCJQ(o7y|B}benwtxU5mIreSJ4( zom{ZpLlHyuCN{sN*UyrG@+g#_S+!4|&`dh(OWgq&Y+dwN$zKyh+a%sNcGv#l7s?Zk zmy0iAGIuMkS1ofr)}zdKPvg4O)rGLtDU4pPBh2f5rBHOJJ*Sa*z0rX_1g(nt6KkJh zrn0(X@J!q!8yA7?2Dg+>b+m9MhX04Fw+@S{Yu~@=j-fk8N$DE81Qbc>E(K|Z4gu+* zK}EVd1?g@OY3YVRx~2QKx$ozBzVGq=HwVK2Yp=~->pItYea;h&#WIRe6}}kF#{}Jz z_X_-Mq@HjLF?Otw}VW;|F zsYT2v7F-sbdj$Ve4Khu9_H5d{j^YIWNJsnTP^cH(Mfn|Kn#>{AYI3Zk--XN7P5Vch z@#jHBfPo|;^FTh;4ugQ|5p89~k01z(rmy*Q?Kq!A*S`wOvJnA#^pVK_vV^9Bc`K{U z0%Oy<&0OzEtq1l&0vc|R_qv|E8F1Z*$vc)!zJIbX<&2s@>bYNaJZVpNkaDd0+%GlFfJiJEcBLY| zYY&={4EVh&6teLM6VhJjTwbOMm#9oaoXvP53M0yVmp@9AHaHK*E#VU{I>>N%IBkTK&Pv z)j6=#AIp^#P59}CL+wUTX}dJKJ92D~UQM+wDk_tjFIT(US?~`@IE;@=6&MU_paVLz z?l#zuBVWnsjd0lA%~_p{+IDkW3J%*6F&8%l>pZL{LfNt@f5#g?LxHS=Z-YK)D|2DN z`E%`O&llQ*?gLm{-{chyw**JC&#cPtU=*AoV;Eiw+;!94Iqgg;-erDoiH_o|C4v9G zKh|My3ruRlRMI1zO_U2?2%|YRdF+3T^uMY?7jdoI5QK?xY8QSpnTL&jyqA48b!M7Z zPZ}VqV?Gf8ffV(*uo^3n63LD%78i`sR3PchZV$PAI(k$PGMx^*9CE`Lwo>4Ca!R`i7+Y>#sExPNl z$;ZjFr*^LyMxUO7c50Ib5%i4R- z`XpkR=LUH4(x6m>tEoMLuKct+ZdyORX)lbua!%5RiSUx*_n51$}zO$ zYe<*-z6qHS)Ia_$Ed_a(VqgW~g^*oh4Ymcmd48{ktJD2}&VJlCgw19Ob?3s=rrsQQ!lfqza)cRCzOa&Ye804HLBt zf>4sIG%eOWNNM0rXBO4OxPKRl&~TwR6G7ZAvnC%ty)2xogSFWN8;?Phpz!%33lhhHW-w{K$##wIi<6iX7ams5d34bc&rR`XCwV zlO-hn{EwW)aJxKub#IDfy1cKWq|8GNyy`>GQDJZU6nsSke{QX2j=rE+U?`tu4_X?$ zUgsc)TYbE*rg4qgpCy#EiC83EN_c8YtNhO6_pS8i;YWRv)vpPJ!_f}! z2ZoUi3D^Ygx^|Z6UEm1#_!B#lc|?eiG&WA|FviJzsD>FC^rtU##ikpU5Qx1;jx@?5 zi;1A(kc_8na=2iMOKQ>J0udJ78l9IT!ScQwvQ~rqu+_EJJcT$ecQBT!RMgyvE{ept zs(;2YIfw?jnx<2S@OJV^R03`9WzV5zk{AAa?Q6CYtlX-6ft9+MoG_y0xSKCXYfuqP z#kKOjBHE7PR`*xCYGCF`WW@Rux$WPIDQj)x1aA7`gs}&qd&MaGb1t;VHr*}g`u-Y% z`$3dtG;~Gc@@IHy#rq-SEG^@#B9$J)(y#giiZB*F^#t&k(Nsf(p0^(CC{u@9@BzoF zZ-OAG5l21r6_xk#>HIx_A2Ps2Jr` z1yF;d0AaJhE3w@u|GNhR$d_e=ZeJR*P3g1&7*2gcQQXh+FP~G-8mi{-jNS^fbqg|j zeTV`({zp*Y>EUQoHpGf@VT6i%sC4bwQSvkK4T)f?G=J|-Cw=}q$dG_~g|$wLFRHHF zWt~a0n3b>|eT#?^wA)If^P}l7zgFs51)Dk1$P9kOxL|7m*&(|xk63|xuNaKz_m&{^ zf4@eY8lGq5b~TYI@#?j*T*+!piw#Skx6{rTEsA^(J!1!17^kHS&-(zsBFg?HYUqyT z3zgf0&c;7XRmz5o49tc@Ivrx;5k#W(xp^i~d^jav)vtCxck0IQHE-%!c+_!fJ=CTi zne*MWtQCK@m@5Y%dO667_ZRf0)kBS+cOd?sB>)hM$>F4dkT2s1`7($h!-@h?1+3(* zyUFhrw{Z&Q(238#{irAml(wt34E^SPvR$sum#&XFoOWI;RO)OjD!g}7$8UGAd`4*| z$+Y0kR7B$P_+;w;S5XqehZk5`X*5{*fu7n*p1N|pM;J!t<|srx4%6I}lyF{%l_CLc z6i?^uFJk`P+CQE2ivwLe|NVqH@RH)cS^`vxI(o*EpOWDP@O~`My|z;9j=;a4FN;Nm zhlYlZO-=@vl&}HqL*zn&f-+aQ;ZptoRf}vUqTY)8w}8)N>Cw{E zglUy%%Jo=Q(;bbEt4GDgKHYr)cIZn;0|1BeG zZ~t6Kz$*B!o<$Cy{og$Qr(}5=*Z+T1|6Hc0ubO;-Rd-~~NEbF+-4Ee_EC(6=&nJLK z8tz!+0;O3pT7eQ8Ia&HAEe%U>l*R=9{W2qUkg<^D_gHaBbs& zpWD|d@r73cBi&a;?ol@Ai}&V1=?rkVI;l7ASAR4$eA5tO5hJQD*3>|E|Hl+p<%ULf zyHSn$R2(+c$8h#Xj@}(Gyiqw+ZPqOo_-qi3704 z!7E7k*-h14`|TlZU)2H$_qdCLeu(bTISi&EKbC%@dbe3@Gu*RjG1qWOuCw-Sfl2d% ziZngck{(-KxLggVVD)D-)kg8vY02g~K>UQ9y`haDA8@`IdC z#Dmjgnw@s!sbdZokRNRu)vQyTTu<>Dn)U;>ic86pCbI=3apIABio;St-m}OKVcp|` z1?fb`seNDPf;aD~AzwvU%?4V+et;PbwvVgJH(>PkBASx zRuFy>cJicP^c{&dy!RJhO7mJlPryHXg==EjEvQoxFIu6oF-WB zbtoPNHN9SlQ78O}xS$S$D9IL@R}_)><*+h4jf3)JlQv zJFjIcoFqTum4hjj@ZN#X?6@mJO{(ApWyE+Zw`_IPt$}p~Z>h;H_MrSo=`zMvqnWQ` zh9VUzZ4nG!*1zWEqz zg~c`KGaUQRmMGu-aV7owLjpT;T>m;4imZjF`?SQViNjvMrcucd3lFMjUOn_jf4df_ zb%AwF_-lo06`}3`tD^`5>P@VWE~S)^c8q&X7L&ufPK_75C6k-L$a99^-b^cHX@vf^ z8xLHo9M+lU=ll_@dVivtQc;&5P+p*7yeu!gB8(BU@C&a6oI6c{Ft?7|i%*dVLOoi2TlNCq6 z68@=~>hA6WOmAT-$lLmO?g;m|xQ+ix7CVs~RMg}QlH+|YzYyWHIDvW_aw?P~D8unp z@29lK{)I^NP1rblBx;6e!muC1mP)!Hf9J$Ce;>alnMeT4*S#TW0as6fS1d*$cXx#K z%80e&SWSo#&ojqbi4Qw~=sw|i!rV zisB0AhJqx;CL>7uAn)-LjumQd|uHnw&y=_ zy=2+WzAXiB4U_&{z^*!QMf7HK=mh6r5WeQhxv=jzx2>-_|7kQyY}8NH{s@jJwOOs| zB^$67s^t2itNR^2o)(YA&Y@?#mX4vK1^-)e$qQ?1@)&tbvsWzN-=Md~FDxN(iQ@C) zMdgSc631BLWfuxk>ABsL7pe5zC|iw`(M2t&O2xXHXnpr}H**bJqmHVjw z7LL03Z404J4@VijB#2k%%8O^0+5p)piO=~N%vNBnBePa|v<7yL6D*4CL^MnzrY-v; zows&>P!z@AitCztHKFOq>Av5N9I^6>S9Mk8JzmVmN<0dI)}m2W=>=1v3NJfZZC}Zz zy&tQDUjyS0J!cOh`{F(_?OJHBseBUpEfNat^O5}JBR|IeXKKfZT9YqXV{)-8jB{&z zc_uKbd5)a8qi0~9u_^R9OVov|(t82MMe*?^y`cLT8R0WUwNx+Khk^I6AU}v2qmj3Z zN2NUFvC-JTAnh?PF5a26khCV1GeP2x&THNql%H<1erV#nte=tH85!%58E9|gCM1HF zBldLH?SdfLLpBwiV16R~_e6JMvOJocpEl-D@=VV9k|!-+-B3ZDR}lXw&;~vpm_I{p zf(uyrGdvDBiTYueP|#`%^qU`zr*2cxzEn&SdOMFE@4taDf_^A1Wk_>I^!`|sASmXH z-cEuvC}SBd&nnR(?~3rQ;+vGZ2T52?5&v0@D#R)5PY}F|CkRQLYx$=Ko zbLgf2g0(j%Yv|&_Cz&+2|6G^pi*&Y{7zr%$Y&j^005N$|3-a+9Il;2%@vaY^le2S= zf`6s##ql3;7!^fFo<8#V9~QRXU8^6TQNE9t0LrdbDyW-clERNwqBM-?fvDyH^f8II zQTGx$S^P@f^wxo=BTj!d!`999C@vm%eVk^SkVOT{yEC;jy2j4BK!w{c`hD8{Xcp4a zsw~#SX1xSaDbvxy(M|dTYW$!Fp+&6?8WOyYw;4ak8w@L6%FiMaD+;L9keC{n;<0_k zTdTzv?3|k;uwC{=$E4VqZ6Cnxmlw7IgO_FNW!&4(u|x|O zIbQH(V>a2D)s+=vbcKGJtK(?NHFGcI7@B6hbf-I`>lvqQ2gzsA6iy4Z8kNY)`aW3p z%8j$`SDY*r1w^!0Z45#L8$W2C&n0`VZ+$lp%H^wb)Qi+&{>(G^6|-GjhT`BtT0P8; z^lict6hpD3_m3%M%yJQEq#03ICx-Dy4$gxAHM^aMYmfUae&zT&8x!fLhP9~m2BV4a zixzgLxhj%oU(|;E7roP&_vCwmqGq?+=q+{kyiLt;C}YKgaq|yWT*AN$aB(fVF?xLJ_p!Su*R*uATYRRjO=GnDBC}9Fe@R#0I7*J(l9ze7; z>9$m0wwtRYq#Bin|BMMD*CY&14R)~$&bwgidgE<5=+gqmZ)>Kh@+kyAEIlAK-LkJj z+miHmv*8hRD*f7>gM3I>zrGjahFvTMLC9}G+feF_`Vg`JgY&W4#ehVAl)KCF&eS~* zjF~&Ume$`S%5Ip)a~p{L;@J((Cm@G3=boLsI=xR0?Jg)Q{s*!rp407*%)!KOxW|n` z-P)IeQ(j}gh~TA$_-S~K@AghvILtB`u9F>3OBoXcbk7WCm&De|82;u7UbgT4+8f;S zDQLTdBVj=DVqoSq`jswjD2Ep-hfQm(DeiMCmaq(0Q&=6(-bdoGx zMUYOktO8h#+>M;s!j_wdC$Q}5WmHLYHU8%as2K4J?)iHQA%XRZSxPG_^xdN5J)m4L6StuV(g<~6Li?o=u{ z#S-Xr^E5IOl{mw~*%&In2*6OSbGPF;cw|#HCQkhyhSndXs56|o1sZ_`E!9qqn*0lx za&vCz%npgt)0q)OJb)k=7h?PCq-1-FZ05fMGO>I+a~A^ zy1Wj`2c#Ta*d2~MWTa2IZz!vSos0X`hDm}g>qowMtXZQkmhT1Cw4xZ)h2+#0QfH7U zzJ!$$)5qj^Sc$d7tq2DO%{lWI#`BEw8x0#pS;w%)oM9!M|{kd+M5og%w1a9th zcqSY{+A8r4sa@x^VGV(nIBp5%vFqxtR-s+YIS53x>eV0*!F;3OhCBqxvojrdBSQ}dJzkP6`*4}J_&|A!~0?* z%*#dBc_>Z`8*pr%VTwVbP{ce8rU|LeB@(*}R2vr!Kb$mOWZRXhs#LbArraEtL6Lta z!!D(C(K})8Rpk_De9o zs^TAn*Y<=T4c$A1Rw-}7e4r`_4L3Ysc||TTqzy@yWTt9B6p8YFaTD(S2c>z<(58I0 zQ+iwvZDo+8GKu(SxJZam^99`8yIOY%q7KE(i1!-%@D5TvfnClPgwR6tGvbVFra5iS z+@7fZ5hC){#o1buaZ99@;j9A}(Yr|5aC`3Mwz9@HN3`s#3wrsKAFGWss1Iw2PKsBO z5A!6DCc3U{U(RFfwh)47B-TQ_EI-}Ysu(e9=mP2yQQ{a_NNV|82(nMw+NKAkTTsN@ z&j`O9iZG9cH3wx8&Sz5*ARSjx6pDM1D1MBEhNFPON8Gd0O;_5=qBxHo`Z`CKt~OuV zSN6Md3dSfGBBJY3YW!%#Xe{Wq1p9kPm$D2Wz7_EZ$Ax1fnU2aV)?nfmug!YwvguPr zLML6g7Gn)%!tRVO5h5K>st}ExmNy~>S!>6VqdyFXe_E(m!if6SlV=>Wz^sy^I%`=Z zfqHv+*wqQjrVef?d8UjZ{_0?VB-cd=yH^dhh1%D0?PCicou7}#`js`}afmPa%CDZ{ zJuSwbsb$iN-d#)0;>H27@Zz{l*zvGSK*@3ev_ZzUBanOvd3umFoz z5Xh=Zch>HL@IFWCpH>rNa~L}pmx7y{KO|9&@__$%OP)Y1+>4swfZgSlN?YXQnH~R`Doi8hI0$IZ z`Pc2_g7st_^0N8}Wh)?I>xJyPIG8To8mQ%irulZVwTrU|%f@%jGI(VR)g%2&1@x0X z%yZ^e#!P?kjZ4Pm1I!!*l(m%d)tK|i_L?@hQSb+4m*3-^c9v46|4Lwy3=RXJM5_JB zxIDy^5~|&JG*7!l7`%#=e8FK|yxFKB;VZ1{w>*x}R@cHfX8cTrd3z)g;a*-`&E^_7 z_C*rM1UKGtC;m)&7pw~Ks}mn9#Z0aSx5)d>53*=@4F~-PqC%^C zKkyU4Y@sOsQ_{ZSK_#ws==Q5z3i+tD+oR6|c9Qs&q)mwM3McBb@?IsI5t}XVL0h#E zzs#X{DUj67qno?d5M@wZ=5&9M-a1FVx-~hFn!r#t(fDL9twrv@-lw1F`BOljJW5_S ziZIRHC~UR>%98M4g5*21l13{TI4^&!gRQ?>qySS+qU8A64w3H zz=~b)we@(R4y|`Sx#~w>^QwJ4Caye*1MOmf0f{)qB~q!TU?Fs|aJ1=8C>^#cQQh5pey#+Bz^k1_xn~4)tbGEvFTfeUmZ}ADi2K zn{fUe({|~oh3%@c?XwZykdx_;`yS3cy+s&TP5WI-q3XK9M?*J$S07K?*OWH63zXL} zR&HtCLl;Bl`BC*uI52GJIrng_@h$j?SFO{hYdG?e2Ee{jbulIQy2pNBlw0k zQPD%l3bb(0T=hJj3D&PY1+L8f>c+U>MB)3wK!j zK%;jNG-}5ykn7_K!vIZ^!LY12!L?&rx|jX^>qYDCi>yZ>&|k3K1;kn3Kh*Gb2ZlD8z~vjymUU;` zIlW{SJL>MdJVZYOY6ABIrWjGtRdaW+D-+*5TX&_+j{RP z4pKudN{hJvdtR8qQmy+fry0+{KCXDmSq$cUhi6fyMFMnts?$Rs9c7IExWCk_H+!4? zsq9{4KStint_8z=q>zW-8Z^xl$;YeqY-H_iX03?UY=ww3U)L^L@6p*VZA1AYez;*! z-*1a!cIwdgzh2bkvJ2P0o;@NjaBDqwnU7Hn36T!`-@T#O8R}Ij zs%2}W@Ttl$b_V3ihXPeBwf1wX>d0vPU*v85TrB0VSl9#HG3c)V%$Lk2F^SG<(nBe9Zn{Qs)9HB zw9-T>$$+NqG0t%q0gpjyL+3ZcGBIO5s&Wm*RH)~mJT-1}V9_z(&yp{ggV9AXqR|J& z%uWI#go$tUvlHAp`yIeCU`8lIHh=;orFLBdY9#uTi;n_dDM3%N2m5n!#mY`gLmc;+)0P;Z$C4^cgbBtp+00^|W$H zjh{05JVf!+zYVOSdzMJbSRpbk=o9ye42Q4ncy+;UBCX`{7N1Pp_dyB>%wGn2ggSS2 ztSekh|AEN4KGseJibeXIF!Ors{uUw8?LpoOlK_w{(3~|-7JJ2@r}m1>78V(W_(n1+ z_=m&EW21&;5u`zLI`;^~G*KubvL!8HFn|UlAE%;|TYXwa&hKE1&Z^Be=gC9qB9m$n z!B>!;zGZ2dx0Jiy8`a}N*HWUP32%}_wWecyFMP(#fD<8L&+*pL=jpR~IhM5i{Ihn2 z3Tu9H0_du5;58}U_b%xVv$L}r<6Hw+k*5 z9~f9%tiZ+TmI_T#Lqz&RVtjIZ)YWH1CFKkFYM%!|N?b&65d!2;IJ)BXFf}YS^z||< zxPkv&PnbpX9_mkGo1O#X=#qHMsyLqtpY_RMHgXEkY^Dq>*M7U83%KH=kww<%0w)c0 zex!j6NNM@M0P>-TZ$uggvu~4*WcgXKScawjK);~J?p&iwAr@$cu%7GzwOq}*X8P5< zC)r2A!xjvn2-(>zq+d{nh?Sw|U(P-0adP2AYr=PPu^6uwDyECdJD z$S2VvXebwjgz)W6DIr4AJ!@^c{K7&2pmrMWJ*_U91)Md$7tfO%`JgT%gH%#da@8aG zd`?799PblgpfyPR{2()4rON9Y*w*`X02>vmv8jI5x&g}&b}6)B3X7+5LE}91d-Z}2 zXyP>bkZOG^IF=R^dqLJIh73(-5-))Vt+`+E{&4;QYIyJ^#sn4amv`3LZ}Bi41#iXN zZwojt2wxNu!R;KQUQ9d7;lBt>0xbAdH!wytI^Q#b{ey#E`MJTVMwhK)AaP)eoR-$_ zkN4T9eBZ=x-$q>A+|EDeuv}kX!`&}SfBYbP|Ni~yr<&GQBg4cTVE_>6&p^wyH}IZH z-+~q4rLoQw;4?-bP43!(5|=8>MR@s@c+7DS0NJ2U~4;ZdlBZdc5Xo z&urCc4yiOSCBOWdsQB(FQst#eEP43pVkH%g2O0@+>XJWfm^`2ohQD(cpLaMEdDi3ybkZDGXWrvTUUlLh1A!$bcP%zk zY|uqngIv#rt1!5d1mC#0zC3~C&7;A}4XL4#QSXs`Jc0ho;2^dnkd@QBMG{_@f4;4| zgJ@`A5E&2kEX4?FN1f)IKE2Miq_q8Bq;Lm^PhEi$9k`MGSJ6ts@#vUBFyA~ zow_{mFG3b&wtn6#!%^RWsPEk<Aa9BXpjA-ve113v&%QFfU(05#Ik z(6G{SnDV5#rA6!{TYwC{jp%W@smv3-)a((tH`E%6NzR&HYdZsPyFhcxJe&ahg*ac-bvq6NvF3Aj@(BHz2(}*XUJWm+wfP9V3BOn1NGr~6FlSdgohCva)nVghooKH6gzXZI1yclljYJgqVxOgoJaco7f0_$x9IPh=I-6 zXj?MOk=~3R)s}*>fG28sa4NqF*&=yM==cjuAnN4#jq} z1lU9s7&&^9-w&)Mm){?6zME5iaH2dHTbPg9USMldCSWvH5u{M@Pq&i+NjEWF*l*?(sv7p8u zZ2CEa0wv^o%U`Uv+ohwNsw(x55dWvND{$ypYD0T{R(aF;Q|3{2B<)?@hdf;PTMdog z79htafNCSYu3GM)4G0Aa_X6T(+O*Bj48rZ2w-U(A#u$ z^qMp*U6f6DdZ=-1%xBk`PBKMh!Ac%jWSGS0D=qqCyg&-3e_3E)?i_chroDBkNK%OHF03yUy9wxXBD%$`XY3 z$IuY1MKU^}@J%+P&Z7|okIR0Rie*$M=BNfBOWq1u7E~nxA}V9Z2~sfHN)%ZDLTxUv zagT53Ev4Z`qNO!Y3!8L^$%>VV{si;>{FV~Mrbc`t|+5hjqy`F&{rDuje}WAwIB zon*i8AVuFnYuDU6mUB`xp(6spwqgsQzt(uL$EzT!8Dt!G~NsP3=^C!%P$p31nryA!2cF) zH@fd_iL8)mrwesZdQoZzDf;bsG|(jmAg+&vwa3zjrK75(ksn+_M^a8wIORGPBy8o^Oi@ z7J9=$_4hY8J;_<5)A?u2BsNt-rM36zh`(Ml7-iD~Go8(oM9%h9*G&^e%wyOuwrX#5Jj`6ja7WB%LUjQ&tbhOS^|Md`LaW?sW-bhi~9%7pET zya74fh?)JW8GI$YE#GvNH8{)1%(N&F9r~TKW9V@@O27N}m-C==6&h)G zxX~ZWzD}1^q4^CPr4gw#>-oU=l4lKvuKfpB3&$={nUCo>6Qsx~yo{BD8!U&GdEi;c z=OY49Cs)q@3M$^e7(eI@Ie}56-Xs&+I^0A8dWrB3m;0ek(M^&f@0k zse7Yo-;%^OPsT>*0f?!TDGA2DTy`cuN9Juta(+)R!= zwUSzyr4K$Ng8R+r(By8*GZMqFg*J_!LxcVK-E^x;rNmeU0N@zY1Kme+x1b!qcia*P zX`s=faht8%MCr3EG1$Yx2P}gB2#$Lcky2rY8pH&-eUE3?uUB{FlpcZY_UcuJ@M+R= z5G1mN>Xs&L+-4HYK8wp-F(qTg-}#QegS${Ut@oL1!=<+&D+Nj78-pzc_>a&j*!e;L ztc53AO{r+~_$7w4p_PE3V=GdS7jd@39!P@^F?RqKeDM`bu;FVov#JX?rI zq9*~k*#6T%>H?_SiA3As9imI8^dr+uBRPo-zZYl=t8KE$t-Gvse5gfy5y@mm7(Ar> zju@EiM8~y4!6SGW7|l@==u4G9(Q-5r%re?}JQ&i~wcLWKt)6P)e=M03c+lx< zb=>J|wJnwS_f2Hhe7`UGTyi5$b9T!GQ-<-QxP&f!_NdD&%?$KsLqG z9Jux;#J*HW7lwSZ^d4i0^XNv^Y3^^dK>nYCGg{4|!S?56*xG(RI}AfK+&2u&ptVQA zHp?Z}qb`a|2}(fA0?6-n7pGQ~FOmxAEMNw8!{*PN{9I!L0`K9nX)0+7y3B1mPyDaj z>$3@Jg%hG=(W$?|E~)yoLYF0#bc0BIg1S8$*u%9V!}jo5F4Ug!NX*2ibT7&Fg6!AE z0P(U><9_YSyL8d>#j5IUKD}>xG$Jv3hzjZw7fzqP5X&=gw)@oKrje{h6}3=ArI8-o zolW{e);3{_v@t)cBtt+}(-txnAgedyr3qtxNb%OW#>n}7KW9*$P7H2)7)BTo46HdF z78_!KjoX_3nN|-ep7e7%sqc2zE`{@A_OyM_`tzTkljsOfsH{-)=8a7&y zh2A7wiucrP+dK9*#Bja1c!LKewVm{;7!Eb2_P`Bxm5D6DAbW==?!}yW3*Ig8iPOs>P35Kv+LZg&{_$<=yxLnWpM)0w0bNv@o8Up z^*qXXo?Gy{A2JpohC`HSA|Yg`cXZcH>qjL0pw}NwYcN4%#9F~T(CcTe^3^2pk9F(L zn;kr&ri}@Lj0nW>t$&!x8w}XnwayMI zQ$Cq|7f+F(v6-T7l=Ba7sn7pPee^~fp>gi3RUBw()KIY1Slj#)D7quu;66=dP~nQG z!3Ppz6yw%Bz_?fpsZ0%r;T(5%9=|9mz=SWJ7>IK9Or({eU(z3|&~1Za@P+ z=O73(s$cg`e)-A~;kN#}%poi;GX>jobO3*+Q5ZwC1-p>Yp!dS(a|EPe|Kg>&B_5;7 zLGMVC^wU5gFe(Sue#%k9u|M3^@sKY_!o_lGMYxv$HJ9LbY#rmyw3}fw6#G4gEH@`2 z9PUveyG{+WTP;hTa+v~oR%6er`Tv5 zZudK)KfmS?m-7)8{4N>Ja|*&csFBS;fTsLL*pE~UnifN~*FmkD$6TQbgAPGC-s+dT zRMsW-neT1*g+eZ{F`?`p%u-C5;Vh*!+>{EHj_a&ZKivRuLq26kxcCBr!7{dD)JQjms*A&EfoqZRdA5tppj;62RAOZX7#F|yy!FQxCAn^@~qQp zPd~3t)wHFZmbt#XC&8OS;RzNiyVy??F6vgQl_sQ>J0w)*xECkUKtxI?j+lvy8E<)D>h>>L0iHLOb+v6FmSUrL#s~ELYvEc z&+F7mp9Ae0@}r=J(w(v2mtfpk>!X0>6Yc%^O3d0DYuhQcIT~QRE|}0dGZ$JsD2eA* zchD;(MHzE*!e5lPStbZ@`H=Hve|x?PwdU#V51#Gk?0I-x73I{@hJmMap!9FbLS&u2 z4bV>Xy-|(>#f#jgm^1)(fmoBLsatYRLGklx3< zKRogeYH-x_aB`o|0YYoUaRKu&J=Dx4&ZaGewoZ#&T3cx$?aRTwV1VjrDS%b7k1ohj zm+dPW7$<`B>Y!4=^84$kp7h*TX>%y=9iO|;HfHG+*2}{qn0x2Ip*BA%%@iQS8nM8| zJKGI&T7;4irWT=RA}U|cRE99b+^5M<<6hksy;`)1eZeEc5u@`me)pE1Q|lAtwF`x| z=u&8k-xst}Wy@0{ox)uowkof4zq6gF>K#hug1OyEACQ&u8|_T&=7aIEC$s*3;!hwK z8)#SBKoPe&oaA|VJqu>3ro^(w9%-0cm+BBs6Q>DGdjNFC6rTCd$d}QnP9kSGhBV7P zqDVc;s-^KHKd^nkky)BJV5Tve+xyW~SU+vrn~DJKhPBH+y52CVONP5HJz$R|Z7wJI zV#51fhI3&08&KCMs_B-4$yZm^2zxl*P)+#6*R){Uce^+}Qx^R;WUi`u^u?+ng9Qf= z7eVso>DJKNM0CJ@KT6Z1j*j{l%-~=mIOO6T0qhgR*|_EyN_>e6s@xmO^uzK}j6a;~ z|IB;4hPpSf(4)ME2JKq&H5{@J&_@{IQPlAa*n9CNa4DQ^ZFwC1yD}Zzy%vRXisf@~ zVheKd;#vSeJT=s2OBlUgEI#ArS(5bFZGCS(X}@V^k#bt+|F~Gp6ojW=j^~|@h8vQ& zMmfW^JT}}_SJUdnv~-(`>XI1lQj=0hJDX_lvw(xgq#yS80~Pb2T}JxKR9<1D_PLD2 zWYH_^0G*Prz*Kfgr`41qhK+o~1PPHfy@Pn{v-J_n!Lp9rC&shN8*xRau_tq>}m@A7Lven&-#@XDM_BkA~B2spO6q z<)Z0-Vk9?i?He=SzsX)X{18*(C1;Jl2eewn}#em33-&r zg_3wY=LKdb-uj7zfC?anbHz&l&HY>D^7`W`RXD z*!iJ#((m&QpIiB_r5xto0>!i#)@J|ghk+{&98DQN<6PCIsu3xN=pN;6s)!RCd4ESY z@EvV!)Yy_){7Rwh)pq82Dthh*8(hJEq-5_Y{vF%R#fwh) zo^@Z4TDD%3W8_Q8#17B%#j|~$d=Qcc_*dygMEa%r%Eyb zWEEh>jhcF?U}k7x{LXKf++m>qWL8ck==mM3!Qb5Y=UQSnLY9I#yu;Y&*xt~O>kLt> zi9&I8b6LGhxvBADdHU0i^H~XCr$GtV;`7MaWuz%n(hAba?yKOLPujw;Q@~9|wY7L# z5aP2PFwF9*>63CZewKpJa61`rkq?D|jXZZ~X&V(N&KR}#1C@;U{+iEUoRKt<=>3Ni zFA$l>7(wD`Ip9~_ie8`Mi*J|&AGdx*O+Q8`QWeGzOP2t$9dTkp3uK;48Scpmr8R4b z5`~urx5^SLO=ys_w1!6Sxij+bzW~;e-g8c2S?b&TFi1iT-M}18*O7Yk@1f=ol+4@l zxrH8{<1RT76INQxX6th?PkE>tRm~utY^-kDU} z(1;?x){3brT}_{yRJjv=iXCo&Fu7X}BcsfPtMO(*OHciO(J zq*_WYY(fNpMW7`9ccaIb^mKzMS4W>}r&Ga^JhRlx zNimPdE}(@p+BdQt%M7nKm6 zi0ity{ucB(MP*foDHB+L&Kg@Dm~o{^dTB9=gx4xL)3F;pCuS25&YUhu}Owf7xk0optHsMqz0Y;x+l_}CET8ecmZ zwHpo*?VTz ztUV*p)Kf*vf+Ksa=yGTF+Vd8TmR%D4p`09Vyzuwg*;`*FzIa`ONW_C7UYY?`+Y_Lj zye_?;QD|Sjf2se{^I6%hMz`)aRJuy2Am~rP5GE*%KB^VR^NT;0&%VghtBJ-YXLkF% zj}VjHmrZg}Q?2CQ&bkb*qE1~FtqK2P8K7+mWU~rUT=)&sh_e$0wJZf2D)F4Xr0loA zF-MHDE5x)!yhg^BPkpOqa>=V_*iDRi}+mj#t3~~dX_nHn+og`GT@HPbyBaLY`}dgtN@2v;p~ z9v(`#FqDhGY4%F{X^K$<@4$BChSnNMn#i``zeTb{HHzwb0p@WOO|Mq#^j%!Q1lfJh z+6-PUdGyd^eOf-Kis_}&wyb5NJPbI)0j|}*&)ErWG2~}rVsf#`DZl`o|Il=4ALC95 zw{OMU^*@vn77ofjZyC**K!uo0>C#;a^J`Y*f{1W)3#J{WzS+0{Fd@lqTWHxz(?}6v zgm5)JiXi^`YhUnOwv$2$SsFDMkwGJO%aJePUEw7&L^C`*_;~DlxfLw#&UQPeI$rcV z0V%P5be1(6DmkQKIiriE)Qj^92*FF#e;SIEKoRo^5E=FZ!7IM3Xat<8l`D?eZ#SBp zkTx`G6y>(h`;**g)x!QXKPx@%Cln0NsRnN4T)wC{%{4%=EWkvdu2wlE0aB-Ba=s*17OB?Zt0OK^2I|AN=ZbE zFiHveni-XSke-rlT>LC;XF)?}&%5kFOEA`tM-j&!B=@zf39muxDMEZsDAWF9ku07S_K;e|UDZjBuuhM%uKho^SfUK zf@tKy89d?wyyN4Bj2CC+Ds${|$O-YunQD7U5kdp=CaSy!&&c@3pt(=}wY&8sWxp

RD=X80No{DD~ClJc=z91BjQ{ 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 S*{IFR|2X0V(z?_Nd*z?Hlmf^A literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/doc/images/assistant-preferences-documentation.png b/src/assistant/assistant/doc/images/assistant-preferences-documentation.png new file mode 100644 index 0000000000000000000000000000000000000000..92e8e0568b27db11f519df73410c349de44b59cb GIT binary patch literal 9533 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 literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/doc/images/assistant-preferences-options.png b/src/assistant/assistant/doc/images/assistant-preferences-options.png new file mode 100644 index 0000000000000000000000000000000000000000..38233b7cfa7b779e3d8f603c7a3cd7855fdb44a0 GIT binary patch literal 5730 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

;?I`UNFZC=g_o#plr{TI>-NlEyakCyc z;1IRXoz3bw!-aHh6s9my{sIaEU*xea zKIMiJqQ9afeoCk*MmE02<^d6qWxUT=CfJWw&orQ5o`vdeQC`eXwY}KMKg6^Kg>`EZ zKaE_*?52lPL*jhL?v>;BVu_N_aFnrQ^)Bhk)IwjIk&q(;$C6P#v^1Nz>;-X2koTl> zN$p9x%)^D(zq<`Fc=5cMll5?&sfbI6KeifFF?=E-+d0ces^kJ4C}}qgnUS0Smn0#y zD$)EOEHULqQ&6<*pEOhPUmTOEt7OcXP@@epIvP%hN#$#jF&DrGsU*@mdz4)HRE&o5 zaqZszvqS~_tyKRNP)+`^KlI\n" +qhp.extraFiles = style/offline-simple.css +buildversion = " " diff --git a/src/assistant/assistant/doc/internal/internal.pro b/src/assistant/assistant/doc/internal/internal.pro new file mode 100644 index 0000000..d6c3cea --- /dev/null +++ b/src/assistant/assistant/doc/internal/internal.pro @@ -0,0 +1,2 @@ +TEMPLATE = aux +QMAKE_DOCS = $$PWD/assistant.qdocconf diff --git a/src/assistant/assistant/doc/qtassistant.qdocconf b/src/assistant/assistant/doc/qtassistant.qdocconf new file mode 100644 index 0000000..a468182 --- /dev/null +++ b/src/assistant/assistant/doc/qtassistant.qdocconf @@ -0,0 +1,43 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qttools.qdocconf) + +project = QtAssistant +description = Qt Assistant Manual +examplesinstallpath = assistant +moduleheader = + +qhp.projects = QtAssistant + +qhp.QtAssistant.file = qtassistant.qhp +qhp.QtAssistant.namespace = org.qt-project.qtassistant.$QT_VERSION_TAG +qhp.QtAssistant.virtualFolder = qtassistant +qhp.QtAssistant.indexTitle = Qt Assistant Manual + +qhp.QtAssistant.subprojects = manual examples +qhp.QtAssistant.subprojects.manual.title = Manual +qhp.QtAssistant.subprojects.manual.indexTitle = Qt Assistant Manual +qhp.QtAssistant.subprojects.manual.selectors = fake:page +qhp.QtAssistant.subprojects.examples.title = Examples +qhp.QtAssistant.subprojects.examples.indexTitle = Qt Assistant Examples +qhp.QtAssistant.subprojects.examples.selectors = fake:example +qhp.QtAssistant.subprojects.examples.sortPages = true + +language = Cpp + +sourcedirs += .. + +exampledirs = ../../../../examples/assistant \ + snippets + +imagedirs = images + +depends += qtdoc qmake + +# Use a generic thumbnail image for examples that have no images in their docs +manifestmeta.thumbnail.names += "QtAssistant/Remote Control Example" + +# Highlighted example for Desktop category +manifestmeta.highlighted.names = "QtAssistant/Simple Text Viewer Example" + +navigation.landingpage = "Qt Assistant Manual" + diff --git a/src/assistant/assistant/doc/snippets/doc_src_assistant-manual.qdoc b/src/assistant/assistant/doc/snippets/doc_src_assistant-manual.qdoc new file mode 100644 index 0000000..7f52adc --- /dev/null +++ b/src/assistant/assistant/doc/snippets/doc_src_assistant-manual.qdoc @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +//! [0] +assistant -collectionFile file +//! [0] + + +//! [1] + + + + My Application Help + qthelp://com.mycompany.1_0_0/doc/index.html + myfilter + application.png + false + false + true + mycompany/myapplication + + About My Application + Über meine Applikation... + + + about.txt + ueber.txt + about.png + + + + + + myapplication-manual.qhp + myapplication-manual.qch + + + + myapplication-manual.qch + + + +//! [1] + + +//! [2] +QProcess *process = new QProcess; +QStringList args; +args << QLatin1String("-collectionFile") + << QLatin1String("mycollection.qhc") + << QLatin1String("-enableRemoteControl"); +process->start(QLatin1String("assistant"), args); +if (!process->waitForStarted()) + return; +//! [2] + + +//! [3] +QByteArray ba; +ba.append("setSource qthelp://com.mycompany.1_0_0/doc/index.html\n"); +process->write(ba); +//! [3] + + +//! [4] +QByteArray ba; +ba.append("hide bookmarks;"); +ba.append("hide index;"); +ba.append("setSource qthelp://com.mycompany.1_0_0/doc/index.html\n"); +process->write(ba); +//! [4] + +//! [5] + + + ... + + + myapplication-manual.qch + another-manual.qch + + + +//! [5] + +//! [6] +assistant -collectionFile mycollection.qhc -register myapplication-manual.qch +//! [6] + +//! [7] + + + + My Application Help + mycompany/myapplication + ... + + +//! [7] + +//! [8] +assistant -collectionFile mycollection.qhc +//! [8] + +//! [9] +%QDesktopServices::AppDataLocation%/mycompany/myapplication/mycollection.qhc +//! [9] + +//! [10] +qhelpgenerator mycollection.qhcp -o mycollection.qhc +//! [10] diff --git a/src/assistant/assistant/doc/src/assistant-example.qdoc b/src/assistant/assistant/doc/src/assistant-example.qdoc new file mode 100644 index 0000000..8a3fc5e --- /dev/null +++ b/src/assistant/assistant/doc/src/assistant-example.qdoc @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \group examples-qtassistant + \ingroup all-examples + \title Qt Assistant Examples + \brief Using Qt Assistant as a help viewer for Qt applications. + + \image assistant-examples.png + + Qt Assistant provides support for interactive help and enables you to + display customer documentation to users of your Qt applications. + + The following examples illustrate how to use Qt Assistant as a help viewer + for applications. + +*/ + +/* + \list + \li \l{simpletextviewer}{Simple Text Viewer}\raisedaster + \li \l{remotecontrol}{Remote Control} + \endlist + + Examples marked with an asterisk (*) are fully documented. +*/ diff --git a/src/assistant/assistant/doc/src/assistant-manual.qdoc b/src/assistant/assistant/doc/src/assistant-manual.qdoc new file mode 100644 index 0000000..bc7707d --- /dev/null +++ b/src/assistant/assistant/doc/src/assistant-manual.qdoc @@ -0,0 +1,418 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtassistant-index.html + \title Qt Assistant Manual + \ingroup qttools + + \startpage {Qt Reference Pages} + \nextpage Qt Assistant Quick Guide + + \keyword Qt Assistant + + \QA is a tool for viewing on-line documentation in Qt help file format. + For more information about basic \QA functions, see + \l{Qt Assistant Quick Guide}. + + Qt installations include a set of reference and tools documentation that + you can view in the Qt Creator IDE and in \QA. You can add custom + documentation to the set of documents displayed in the \QA main view. For + detailed information about all \QA functions, see \l{Using Qt Assistant}. + + You can use \QA as the help viewer in your applications. You can display + your own documentation and customize \QA to look and feel like part of your + application. You can change the window title or icon, as well as menu texts + and actions. For more information, see \l{Customizing Qt Assistant}. + + \section1 Table of Contents + + \list + \li \l{Qt Assistant Quick Guide} + \li \l{Using Qt Assistant} + \li \l{Customizing Qt Assistant} + \li \l{Licenses and Attributions} + \endlist +*/ + +/*! + \page assistant-custom-help-viewer.html + \title Customizing Qt Assistant + + \previouspage Using Qt Assistant + \nextpage Licenses and Attributions + + Using \QA as custom help viewer requires more than just being able to + display custom documentation. It is equally important that the + appearance of \QA can be customized so that it is seen as a + application-specific help viewer rather than \QA. This is achieved by + changing the window title or icon, as well as some application-specific + menu texts and actions. For a complete list of possible customizations, + see \l{Creating a Custom Help Collection File}. + + Another requirement of a custom help viewer is the ability to receive + actions or commands from the application it provides help for. This is + especially important when the application offers context sensitive help. + When used in this way, the help viewer may need to change its contents + depending on the state the application is currently in. This means that + the application has to communicate the current state to the help viewer. + For more information, see \l{Using Qt Assistant Remotely}. + + The \l{Simple Text Viewer Example}{Simple Text Viewer} example uses the + techniques described in this document to show how to use \QA as a custom + help viewer for an application. + + \warning In order to ship Qt Assistant in your application, it is crucial + that you include the sqlite plugin. For more information on how to include + plugins in your application, refer to the \l{Deploying Qt Applications} + {deployment documentation}. + + \section1 Qt Help Collection Files + + The first important point to know about \QA is that it stores all + settings related to its appearance \e and a list of installed + documentation in a help collection file. This means, when starting \QA + with different collection files, \QA may look totally different. This + complete separation of settings makes it possible to deploy \QA as a + custom help viewer for more than one application on one machine + without risk of interference between different instances of \QA. + + To apply a certain help collection to \QA, specify the respective + collection file on the command line when starting it. For example: + + \snippet doc_src_assistant-manual.qdoc 8 + + However, storing all settings in one collection file raises some problems. + The collection file is usually installed in the same directory as the + application itself, or one of its subdirectories. Depending on the + directory and the operating system, the user may not have any permissions + to modify this file which would happen when the user settings are stored. + Also, it may not even be possible to give the user write permissions, for + example when the file is located on a read-only medium like a CD-ROM. + + Even if it is possible to give everybody the right to store their settings + in a globally available collection file, the settings from one user would + be overwritten by another user when exiting \QA. + + To solve this dilemma, \QA creates user specific collection files which + are more or less copied from the original collection file. The user-specific + collection file will be saved in a subdirectory of the path returned by + QDesktopServices::AppDataLocation. The subdirectory, or \e{cache directory} + within this user-specific location, can be defined in the help collection + project file. For example: + + \snippet doc_src_assistant-manual.qdoc 7 + + So, when calling + + \snippet doc_src_assistant-manual.qdoc 8 + + \QA actually uses the collection file: + + \snippet doc_src_assistant-manual.qdoc 9 + + There is no need ever to start \QA with the user specific collection + file. Instead, the collection file shipped with the application should + always be used. Also, when adding or removing documentation from the + collection file (see next section) always use the normal collection file. + \QA will take care of synchronizing the user collection files when the + list of installed documentation has changed. + + \section1 Displaying Custom Documentation + + Before \QA is able to show documentation, it has to know where it can + find the actual documentation files, meaning that it has to know the + location of the Qt compressed help file (*.qch). As already mentioned, + \QA stores references to the compressed help files in the currently used + collection file. So, when creating a new collection file you can list + all compressed help files \QA should display. + + \snippet doc_src_assistant-manual.qdoc 5 + + Sometimes, depending on the application for which \QA acts as a + help viewer, more documentation needs to be added over time; for + example, when installing more application components or plugins. + This can be done manually in \QA by selecting \gui Edit > \gui Preferences + > \gui Documentation. However, this approach has the disadvantage + that every user has to do it manually to get access to the new + documentation. + + The preferred way of adding documentation to an already existing collection + file is to use the \c{-register} command line flag of \QA. When starting + \QA with this flag, the documentation will be added and \QA will + exit right away displaying a message if the registration was successful + or not. + + The search indexing will only index your custom *.html, *.htm, + and *.txt files. + + \snippet doc_src_assistant-manual.qdoc 6 + + The \c{-quiet} flag can be passed on to \QA to prevent it from writing + out the status message. + + \note \QA shows the documentation in the \gui Contents view in the same + order as it was registered. + + + \section1 Changing the Appearance of Qt Assistant + + The appearance of \QA can be changed by passing different command line options + on startup. However, these command line options only allow to show or hide + specific widgets, like the contents or index view. Other customizations, such + as changing the application title or icon, or disabling the filter functionality, + can be done by creating a custom help collection file. + + \section2 Creating a Custom Help Collection File + + The help collection file (*.qhc) used by \QA is created when running the + \c qhelpgenerator tool on a help collection project file (*.qhcp). + The project file format is XML and it supports the following tags: + + \table + \header + \li Tag + \li Brief Description + \row + \li \c{} + \li Specifies a window title for \QA. + \row + \li \c{<homePage>} + \li Specifies the page to display when selecting \gui Home in the + \QA main window. + \row + \li \c{<startPage>} + \li Specifies the page to display initially when the help collection + is used. + \row + \li \c{<currentFilter>} + \li Specifies the filter that is initially used. + If this filter is not specified, the documentation will not be filtered. This has + no impact if only one documentation set is installed. + \row + \li \c{<applicationIcon>} + \li Describes an icon that will be used instead of the normal \QA + application icon. This is specified as a relative path from the directory + containing the collection file. + \row + \li \c{<enableFilterFunctionality>} + \li Enables or disables user accessible filter functionality, + making it possible to prevent the user from changing any filter when running \QA. + It does not mean that the internal filter functionality is completely disabled. + Set the value to \c{false} if you want to disable the filtering. If the filter + toolbar should be shown by default, set the attribute \c{visible} to \c{true}. + \row + \li \c{<enableDocumentationManager>} + \li Shows or hides the \gui Documentation tab in the \gui Preferences + dialog. Disabling the \gui Documentation tab allows you to limit + \QA to display a specific documentation set or make it impossible for the end user + to accidentally remove or install documentation. To hide the \gui Documentation tab, + set the tag value to \c{false}. + \row + \li \c{<enableAddressBar>} + \li Enables or disables the address bar functionality. By default it + is enabled. To disable it, set the tag value to \c{false}. If the + address bar functionality is enabled, the address bar can be shown by setting the + tag attribute \c{visible} to \c{true}. + \row + \li \c{<aboutMenuText>, <text>} + \li Lists localized versions for the \gui About menu item in the + \gui Help menu. For example, \gui {About Application}. The text is + specified within the \c{text} tags. The \c{language} attribute takes the two + letter language name. The text is used as the default text if no language + attribute is specified. + \row + \li \c{<aboutDialog>, <file>, <icon>} + \li Specifies the text for the \gui About dialog that can be opened + from the \gui Help menu. The text is taken from the + file in the \c{file} tags. It is possible to specify a different file or any + language. The icon defined by the \c{icon} tags is applied to any language. + \row + \li \c{<cacheDirectory>, <cacheDirectory base="collection">} + \li Specifies the cache directory that is used to store index files + needed for the full text search and a copy of the collection file. + The copy is needed because \QA stores all its settings in the + collection file, and therefore, it must be writable for the user. + The directory is specified as a relative path. + If the \c{base} attribute is set to "collection", the path is + relative to the directory the collection file resides in. + If the attribute is set to "default" or if it is missing, + the path is relative to the directory given by + QDesktopServices::AppDataLocation. The first form is useful for + collections that are used in a \e mobile way, such as carried around + on a USB stick. + \row + \li \c{<enableFullTextSearchFallback>} + \li Enables or disables the ability to fallback and use the full text + search if a keyword cannot be found in the index. This functionality + can be used while remote controlling \QA. To make it available for + remote control, set the tag value to \c{true}. + \endtable + + In addition to those \QA specific tags, the tags for generating and registering + documentation can be used. See \l{Qt Help Collection Files} documentation for more information. + + An example of a help collection file that uses all the available tags is shown below: + + \snippet doc_src_assistant-manual.qdoc 1 + + To create the binary collection file, run the \c qhelpgenerator tool: + + \snippet doc_src_assistant-manual.qdoc 10 + + To test the generated collection file, start \QA in the following way: + + \snippet doc_src_assistant-manual.qdoc 8 + + \section1 Using Qt Assistant Remotely + + Even though the help viewer is a standalone application, it will mostly + be launched by the application it provides help for. This approach + gives the application the possibility to ask for specific help contents + to be displayed as soon as the help viewer is started. Another advantage + with this approach is that the application can communicate with the + help viewer process and can therefore request other help contents to be + shown depending on the current state of the application. + + So, to use \QA as the custom help viewer of your application, simply + create a QProcess and specify the path to the \QA executable. In order + to make \QA listen to your application, turn on its remote control + functionality by passing the \c{-enableRemoteControl} command line option. + + The following example shows how this can be done: + + \snippet doc_src_assistant-manual.qdoc 2 + + Once \QA is running, you can send commands by using the stdin channel of + the process. The code snippet below shows how to tell \QA to show a certain + page in the documentation. + + \snippet doc_src_assistant-manual.qdoc 3 + + \note The trailing newline character is required to mark the end + of the input. + + The following commands can be used to control \QA: + + \table + \header + \li Command + \li Brief Description + \row + \li \c{show <Widget>} + \li Shows the sidebar window (dock widget) specified by <Widget>. If the widget + is already shown and this command is sent again, the widget will be + activated, meaning that it will be raised and given the input focus. + Possible values for <Widget> are "contents", "index", "bookmarks" or "search". + \row + \li \c{hide <Widget>} + \li Hides the dock widget specified by <Widget>. Possible values for + <Widget> are "contents", "index", "bookmarks" and "search". + \row + \li \c{setSource <Url>} + \li Displays the given <Url>. The URL can be absolute or relative + to the currently displayed page. If the URL is absolute, it has to + be a valid Qt help system URL. That is, starting with "qthelp://". + \row + \li \c{activateKeyword <Keyword>} + \li Inserts the specified <Keyword> into the line edit of the + index dock widget and activates the corresponding item in the + index list. If such an item has more than one link associated + with it, a topic chooser will be shown. + \row + \li \c{activateIdentifier <Id>} + \li Displays the help contents for the given <Id>. An ID is unique + in each namespace and has only one link associated to it, so the + topic chooser will never pop up. + \row + \li \c{syncContents} + \li Selects the item in the contents widget which corresponds to + the currently displayed page. + \row + \li \c{setCurrentFilter <filter>} + \li Selects the specified filter and updates the visual representation + accordingly. + \row + \li \c{expandToc <Depth>} + \li Expands the table of contents tree to the given depth. If depth + is 0, the tree will be collapsed completely. If depth is -1, + the tree will be expanded completely. + \row + \li \c{register <help file>} + \li Adds the given Qt compressed help file to the collection. + \row + \li \c{unregister <help file>} + \li Removes the given Qt compressed help file from the collection. + \endtable + + If you want to send several commands within a short period of time, it is + recommended that you write only a single line to the stdin of the process + instead of one line for every command. The commands have to be separated by + a semicolon, as shown in the following example: + + \snippet doc_src_assistant-manual.qdoc 4 +*/ + +/* +\section2 Modifying \QA with Command Line Options + + Different help collections can be shown by simply passing the help collection + path to \QA. For example: + + \snippet doc_src_assistant-manual.qdoc 0 + + Other available options the can be passed on the command line. + + \table + \header + \li Command Line Option + \li Brief Description + \row + \li -collectionFile <file.qhc> + \li Uses the specified collection file instead of the default one. + \row + \li -showUrl URL + \li Shows the document referenced by URL. + \row + \li -enableRemoteControl + \li Enables \QA to be remotly controlled. + \row + \li -show <widget> + \li Shows the specified dockwidget which can be "contents", "index", + "bookmarks" or "search". + \row + \li -hide <widget> + \li Hides the specified dockwidget which can be "contents", "index", + "bookmarks" or "search. + \row + \li -activate <widget> + \li Activates the specified dockwidget which can be "contents", + "index", "bookmarks" or "search. + \row + \li -register <doc.qch> + \li Registers the specified compressed help file in the given help + collection. + \row + \li -unregister <doc.qch> + \li Unregisters the specified compressed help file from the given + collection file. + \row + \li -quiet + \li Doesn't show any error, warning or success messages. + \endtable + */ + +/*! + \page assistant-licenses.html + \title Licenses and Attributions + + \previouspage Customizing Qt Assistant + + \QA is available under commercial licenses from \l{The Qt Company}. In + addition, it is available under the \l{GNU General Public License, version 3}. + + Furthermore, \QA \QtVersion may contain third party modules + under following permissive licenses: + + \annotatedlist attributions-qtassistant-tools +*/ diff --git a/src/assistant/assistant/doc/src/assistant-quick-guide.qdoc b/src/assistant/assistant/doc/src/assistant-quick-guide.qdoc new file mode 100644 index 0000000..5fa53a2 --- /dev/null +++ b/src/assistant/assistant/doc/src/assistant-quick-guide.qdoc @@ -0,0 +1,289 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \if !defined(ASSISTANT_INTERNAL) + \previouspage Qt Assistant Manual + \endif + \page assistant-quick-guide.html + \nextpage Using Qt Assistant + + \title Qt Assistant Quick Guide + + Once you have installed Qt, you can start Qt Assistant in the same way as + any other application on the development host. + + The \QA main window contains a sidebar (1) with navigation windows for: + + \list + \li Viewing a list of documents in Qt help format that are installed on + the development host. + \li Managing bookmarks. + \li Searching for keywords in the index. + \li Searching for information using a free text search function. + \li Switching between open topics. + \endlist + + The selected topic in the selected document is displayed in the + \b Documentation window (2). + + \image assistant-assistant.png + + Click the \gui Previous and \gui Next toolbar buttons (3) to navigate within + the topics you have visited. + + \section1 Managing Bookmarks + + \image assistant-bookmarks.png + + To bookmark topics of particular interest, select \gui Bookmarks > + \gui {Add Bookmark} (or press \key{Ctrl+B}). A bookmark for the + page that is currently showing in the \gui Documentation window is added. + + You can view and manage bookmarks in the \gui Bookmarks window. + Double-click a bookmark to open the topic in the \gui Documentation window. + You can also right-click the bookmark and select \gui{Show Bookmark} in the + context menu. + + To rename or delete the highlighted bookmark, select \gui {Rename Bookmark} + or \gui {Delete Bookmark} in the context menu. + + \section1 Searching for Keywords + + \image assistant-index.png + + To perform an index search, click the \gui{Index} tab on the sidebar + (or press \key{Alt+I}). In the \gui{Look For} field, enter the search term. + As you type, words are found and highlighted in a list beneath the field. + If the highlighted text matches what you are + looking for, double-click it or press \key{Enter}. The + \gui Documentation window displays the relevant topic. You rarely have + to type in the whole word before \QA finds a match. Note that for some + words there may be more than one possible topic that is relevant. + + \section1 Using Free Text Search + + \image assistant-search.png + + \QA also provides full text searching for finding specific words in the + documentation. To activate the full text search, either press \key(Alt+S) + or click the \gui{Search} tab on the sidebar. Then enter the term you are + looking for and click \gui{Search}. All documents containing the specified + term are listed. +*/ + +/*! + \if !defined(ASSISTANT_INTERNAL) + \nextpage Customizing Qt Assistant + \endif + \page assistant-details.html + \title Using Qt Assistant + \previouspage Qt Assistant Quick Guide + + \image assistant-dockwidgets.png + + You can read documentation in the \gui Documentation window. To open a topic + in a new tab, right click it in the \gui Contents window to open a context + menu and select \gui {Open Link in New Tab}. All open topics are listed in + the \gui {Open Pages} window. Select a topic to view it in the + \gui Documentation window. + + If you want the \gui{Documentation} window to use as much space as possible, + you can easily group, move or hide the sidebar windows. To group the windows, + drag one on top of the other and release the mouse. If one or all sidebar + windows are not shown, select keyboard shortcuts to display them. You can + view the keyboard shortcuts in the \gui View menu. + + The sidebar windows can be docked into the main window, so you can drag them + to the top, left, right or bottom of the main window, or you can + drag them outside \QA to float them as independent windows. + + To change the font family and font sizes of the browser window displaying + the documentation or the application itself, select \gui Edit > + \gui Preferences > \gui Fonts. + + \image assistant-preferences-fonts.png + + To temporarily increase or decrease the font size in the \gui Documentation + window, select \gui View > \gui {Zoom in} or \gui {Zoom out}. To reset the + font size, select \gui View > \gui {Normal Size}. + + To navigate between pages, select \gui Go > \gui Previous or \gui Next. This + takes you to the previous or next page in the history. To return to the home + page, select \gui Go > \gui Home. To specify the home page, select + \gui Edit > \gui Preferences > \gui Options. + + To synchronize the \gui{Contents} window with the page currently shown in + the \gui{Documentation} window, select \gui Go > + \gui {Sync with Table of Contents}. + + The address toolbar provides a fast way to enter a specific URL for a + documentation file. To show the address toolbar, select \gui View > + \gui Toolbars > \gui {Address Toolbar}. + + \section1 Searching from Page Contents + + To find text on the current page, select \gui Edit > \gui {Find in Text}. + Enter the search term in the field. The search is incremental, meaning that + the most relevant result is shown as you enter characters into the field. + + If you select the \gui{Case sensitive} checkbox, the search considers the + case of the search term. For example, if you search for \b spin, it matches + \b spin but not \b Spin. + + To search forwards or backwards from your current position on the page, + click \gui Previous or \gui Next. + + To hide the find control, either click \gui Close or press \key Esc. + + \section1 Full Text Searching + + \img assistant-search.png + + \QA provides a powerful full text search engine. You can search for certain + words or text in the \gui Search window. Enter the text you want to look for + and press \key{Enter} or click \gui{Search}. The search is not case sensitive. + For example, \b Foo, \b fOo and \b FOO are all treated as the same. + + You can create complex queries using the + \l{https://sqlite.org/fts5.html#full_text_query_syntax}{FTS query syntax}. + + The following are examples of common search patterns: + + \list + \li \c deep -- lists all the documents that contain the word \b deep + \li \c{deep*} -- lists all the documents that contain a word beginning + with \b deep + \li \c{deep copy} -- lists all documents that contain both \b deep \e + and \b copy + \li \c{"deep copy"} -- list all documents that contain the phrase + \b {deep copy} + \endlist + + The list of documents found is ordered according to the number of + occurrences of the search text which they contain, with those containing + the highest number of occurrences appearing first. Simply click any + document in the list to display it in the \gui{Documentation} window. + + If the documentation has changed \mdash for example, if documents have been added + or removed \mdash \QA will index them again. + + \section1 Filtering Help Contents + + \QA allows you to install any kind of documentation as long as it is organized + in Qt compressed help files (*.qch). For example, you can view + Qt reference documentation for several Qt versions at the same time. In many + respects, this is very convenient since only one version of \QA is needed. + However, at the same time it becomes more complicated when performing tasks like + searching the index because most keywords are defined in more than one Qt + version. This means that \QA will always ask the user to choose which one + should be displayed. + + We use documentation filters to solve this issue. A filter is identified by its + name, and contains a list of filter attributes. An attribute is just a string and + can be freely chosen. Attributes are defined by the documentation itself, + which means that every documentation set usually has one or more attributes. + + For example, the \QA documentation defines the attributes \c {assistant} and + \c{tools}, whereas \QD defines \c{designer} and \c{tools}. + The filter to display all tools would then define only the attribute + \c{tools} since this attribute is part of both documentation sets. + Adding the attribute \c{assistant} to the filter would then only show \QA + documentation since the \QD documentation does not contain this + attribute. Having an empty list of attributes in a filter will match all + documentation. That is, it is equivalent to requesting unfiltered documentation. + + To create and remove documentation filters, select \gui Edit > + \gui Preferences > \gui Filters. + + \image assistant-preferences-filters.png + + To add a new filter, click \gui Add, + specify a filter name, and click \gui OK. Then select the filter attributes + in the \gui Attributes field. + + To delete a filter, select it and click \gui Remove. + + The filter toolbar allows you to apply a filter to the currently installed + documentation. To show the filter toolbar, select \gui View > \gui Toolbars + > \gui {Filter Toolbar}. + + \section1 Adding Documentation + + To install and remove compressed help files, select \gui Edit > + \gui Preferences > \gui Documentation. + + \image assistant-preferences-documentation.png + + Click the \gui{Install} button and + choose the path of the compressed help file (*.qch) you would like to install. + To delete a help file, select a documentation set in the list and click + \gui{Remove}. + + \section1 Specifying Home Page + + To specify the homepage \QA displays when you click the \gui{Home} button, + select \gui Edit > \gui Preferences > \gui Options. + + \image assistant-preferences-options.png + + Enter the URL of the home page or select \gui{Current Page} to set the + currently displayed page as your home page. To leave the home page blank, + select \gui {Blank page}. Select \gui{Restore to default} to reset your home + page to the default home page. + + \section1 Using Command Line Options + + \QA handles the following command line options: + + \table + \header + \li Command Line Option + \li Brief Description + \row + \li -collectionFile <file.qhc> + \li Uses the specified collection file instead of the default one. + \row + \li -showUrl <URL> + \li Shows the document referenced by URL. + \row + \li -enableRemoteControl + \li Enables \QA to be remotely controlled. + \row + \li -show <widget> + \li Shows the specified sidebar window which can be "contents", "index", + "bookmarks" or "search". + \row + \li -hide <widget> + \li Hides the specified sidebar window which can be "contents", "index", + "bookmarks" or "search". + \row + \li -activate <widget> + \li Activates the specified sidebar window which can be "contents", + "index", "bookmarks" or "search". + \row + \li -register <doc.qch> + \li Registers the specified compressed help file in the given help + collection. + \row + \li -unregister <doc.qch> + \li Unregisters the specified compressed help file from the given + collection file. + \row + \li -remove-search-index + \li Purges the help search engine's index. This option is + useful in case the associated index files get corrupted. + \QA will re-index the documentation at the next start-up. + \row + \li -rebuild-search-index + \li Rebuilds the help search engine's index. + Note that this operation may take a while to finish. + \row + \li -setCurrentFilter <filter> + \li Sets the given filter as the active filter. + \row + \li -quiet + \li Does not show any error, warning or success messages. + \endtable + +*/ diff --git a/src/assistant/assistant/findwidget.cpp b/src/assistant/assistant/findwidget.cpp new file mode 100644 index 0000000..5595456 --- /dev/null +++ b/src/assistant/assistant/findwidget.cpp @@ -0,0 +1,194 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" +#include "findwidget.h" + +#include <QtWidgets/QApplication> +#include <QtWidgets/QCheckBox> +#include <QtGui/QHideEvent> +#include <QtGui/QKeyEvent> +#include <QtWidgets/QLabel> +#include <QtWidgets/QLayout> +#include <QtWidgets/QLineEdit> +#include <QtWidgets/QToolButton> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +FindWidget::FindWidget(QWidget *parent) + : QWidget(parent) + , appPalette(qApp->palette()) +{ + TRACE_OBJ + installEventFilter(this); + QHBoxLayout *hboxLayout = new QHBoxLayout(this); + QString resourcePath = ":/qt-project.org/assistant/images/"_L1; + +#ifndef Q_OS_MAC + hboxLayout->setContentsMargins({}); + hboxLayout->setSpacing(6); + resourcePath.append("win"_L1); +#else + resourcePath.append("mac"_L1); +#endif + + toolClose = setupToolButton({}, resourcePath + "/closetab.png"_L1); + hboxLayout->addWidget(toolClose); + connect(toolClose, &QAbstractButton::clicked, this, &QWidget::hide); + + editFind = new QLineEdit(this); + hboxLayout->addWidget(editFind); + editFind->setMinimumSize(QSize(150, 0)); + connect(editFind, &QLineEdit::textChanged, this, &FindWidget::textChanged); + connect(editFind, &QLineEdit::returnPressed, this, &FindWidget::findNext); + connect(editFind, &QLineEdit::textChanged, this, &FindWidget::updateButtons); + + toolPrevious = setupToolButton(tr("Previous"), resourcePath + "/previous.png"_L1); + connect(toolPrevious, &QAbstractButton::clicked, this, &FindWidget::findPrevious); + + hboxLayout->addWidget(toolPrevious); + + toolNext = setupToolButton(tr("Next"), resourcePath + "/next.png"_L1); + hboxLayout->addWidget(toolNext); + connect(toolNext, &QAbstractButton::clicked, this, &FindWidget::findNext); + + checkCase = new QCheckBox(tr("Case Sensitive"), this); + hboxLayout->addWidget(checkCase); + + labelWrapped = new QLabel(this); + labelWrapped->setScaledContents(true); + labelWrapped->setTextFormat(Qt::RichText); + labelWrapped->setMinimumSize(QSize(0, 20)); + labelWrapped->setMaximumSize(QSize(105, 20)); + labelWrapped->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + labelWrapped->setText(tr("<img src=\":/qt-project.org/assistant/images/wrap.png\"" + "> Search wrapped")); + hboxLayout->addWidget(labelWrapped); + + QSpacerItem *spacerItem = new QSpacerItem(20, 20, QSizePolicy::Expanding, + QSizePolicy::Minimum); + hboxLayout->addItem(spacerItem); + setMinimumWidth(minimumSizeHint().width()); + labelWrapped->hide(); + + updateButtons(); +} + +FindWidget::~FindWidget() +{ + TRACE_OBJ +} + +void FindWidget::show() +{ + TRACE_OBJ + QWidget::show(); + editFind->selectAll(); + editFind->setFocus(Qt::ShortcutFocusReason); +} + +void FindWidget::showAndClear() +{ + TRACE_OBJ + show(); + editFind->clear(); +} + +QString FindWidget::text() const +{ + TRACE_OBJ + return editFind->text(); +} + +bool FindWidget::caseSensitive() const +{ + TRACE_OBJ + return checkCase->isChecked(); +} + +void FindWidget::setPalette(bool found) +{ + TRACE_OBJ + QPalette palette = editFind->palette(); + palette.setColor(QPalette::Active, QPalette::Base, found ? Qt::white + : QColor(255, 102, 102)); + editFind->setPalette(palette); +} + +void FindWidget::setTextWrappedVisible(bool visible) +{ + TRACE_OBJ + labelWrapped->setVisible(visible); +} + +void FindWidget::hideEvent(QHideEvent* event) +{ + TRACE_OBJ +#if defined(BROWSER_QTWEBKIT) + // TODO: remove this once webkit supports setting the palette + if (!event->spontaneous()) + qApp->setPalette(appPalette); +#else // BROWSER_QTWEBKIT + Q_UNUSED(event); +#endif +} + +void FindWidget::showEvent(QShowEvent* event) +{ + TRACE_OBJ +#if defined(BROWSER_QTWEBKIT) + // TODO: remove this once webkit supports setting the palette + if (!event->spontaneous()) { + QPalette p = appPalette; + p.setColor(QPalette::Inactive, QPalette::Highlight, + p.color(QPalette::Active, QPalette::Highlight)); + p.setColor(QPalette::Inactive, QPalette::HighlightedText, + p.color(QPalette::Active, QPalette::HighlightedText)); + qApp->setPalette(p); + } +#else // BROWSER_QTWEBKIT + Q_UNUSED(event); +#endif +} + +void FindWidget::updateButtons() +{ + TRACE_OBJ + const bool enable = !editFind->text().isEmpty(); + toolNext->setEnabled(enable); + toolPrevious->setEnabled(enable); +} + +void FindWidget::textChanged(const QString &text) +{ + TRACE_OBJ + emit find(text, true, true); +} + +bool FindWidget::eventFilter(QObject *object, QEvent *e) +{ + TRACE_OBJ + if (e->type() == QEvent::KeyPress) { + if ((static_cast<QKeyEvent*>(e))->key() == Qt::Key_Escape) { + hide(); + emit escapePressed(); + } + } + return QWidget::eventFilter(object, e); +} + +QToolButton* FindWidget::setupToolButton(const QString &text, const QString &icon) +{ + TRACE_OBJ + QToolButton *toolButton = new QToolButton(this); + + toolButton->setText(text); + toolButton->setAutoRaise(true); + toolButton->setIcon(QIcon(icon)); + toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + + return toolButton; +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/findwidget.h b/src/assistant/assistant/findwidget.h new file mode 100644 index 0000000..a9647b0 --- /dev/null +++ b/src/assistant/assistant/findwidget.h @@ -0,0 +1,62 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef FINDWIDGET_H +#define FINDWIDGET_H + +#include <QtWidgets/QWidget> + +QT_BEGIN_NAMESPACE + +class QCheckBox; +class QLabel; +class QLineEdit; +class QToolButton; + +class FindWidget : public QWidget +{ + Q_OBJECT +public: + FindWidget(QWidget *parent = nullptr); + ~FindWidget() override; + + void show(); + void showAndClear(); + + QString text() const; + bool caseSensitive() const; + + void setPalette(bool found); + void setTextWrappedVisible(bool visible); + +signals: + void findNext(); + void findPrevious(); + void escapePressed(); + void find(const QString &text, bool forward, bool incremental); + +protected: + void hideEvent(QHideEvent* event) override; + void showEvent(QShowEvent * event) override; + +private slots: + void updateButtons(); + void textChanged(const QString &text); + +private: + bool eventFilter(QObject *object, QEvent *e) override; + QToolButton* setupToolButton(const QString &text, const QString &icon); + +private: + QPalette appPalette; + + QLineEdit *editFind; + QCheckBox *checkCase; + QLabel *labelWrapped; + QToolButton *toolNext; + QToolButton *toolClose; + QToolButton *toolPrevious; +}; + +QT_END_NAMESPACE + +#endif // FINDWIDGET_H diff --git a/src/assistant/assistant/globalactions.cpp b/src/assistant/assistant/globalactions.cpp new file mode 100644 index 0000000..cd9d187 --- /dev/null +++ b/src/assistant/assistant/globalactions.cpp @@ -0,0 +1,227 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "globalactions.h" + +#include "centralwidget.h" +#include "helpviewer.h" +#include "tracer.h" + +#include <QtWidgets/QMenu> + +#include <QtGui/QAction> + +#if defined(BROWSER_QTWEBKIT) +# include <QWebHistory> +#endif + +using namespace Qt::StringLiterals; + +GlobalActions *GlobalActions::instance(QObject *parent) +{ + Q_ASSERT(!m_instance != !parent); + if (!m_instance) + m_instance = new GlobalActions(parent); + return m_instance; +} + +GlobalActions::GlobalActions(QObject *parent) : QObject(parent) +{ + TRACE_OBJ + + // TODO: Put resource path in misc class + QString resourcePath = ":/qt-project.org/assistant/images/"_L1; +#ifdef Q_OS_MAC + resourcePath.append("mac"_L1); +#else + resourcePath.append("win"_L1); +#endif + CentralWidget *centralWidget = CentralWidget::instance(); + + m_backAction = new QAction(tr("&Back"), parent); + m_backAction->setEnabled(false); + m_backAction->setShortcuts(QKeySequence::Back); + m_backAction->setIcon(QIcon(resourcePath + "/previous.png"_L1)); + connect(m_backAction, &QAction::triggered, centralWidget, &CentralWidget::backward); + m_actionList << m_backAction; + + m_nextAction = new QAction(tr("&Forward"), parent); + m_nextAction->setPriority(QAction::LowPriority); + m_nextAction->setEnabled(false); + m_nextAction->setShortcuts(QKeySequence::Forward); + m_nextAction->setIcon(QIcon(resourcePath + "/next.png"_L1)); + connect(m_nextAction, &QAction::triggered, centralWidget, &CentralWidget::forward); + m_actionList << m_nextAction; + + setupNavigationMenus(m_backAction, m_nextAction, centralWidget); + + m_homeAction = new QAction(tr("&Home"), parent); + m_homeAction->setShortcut(tr("ALT+Home")); + m_homeAction->setIcon(QIcon(resourcePath + "/home.png"_L1)); + connect(m_homeAction, &QAction::triggered, centralWidget, &CentralWidget::home); + m_actionList << m_homeAction; + + QAction *separator = new QAction(parent); + separator->setSeparator(true); + m_actionList << separator; + + m_zoomInAction = new QAction(tr("Zoom &in"), parent); + m_zoomInAction->setPriority(QAction::LowPriority); + m_zoomInAction->setIcon(QIcon(resourcePath + "/zoomin.png"_L1)); + m_zoomInAction->setShortcut(QKeySequence::ZoomIn); + connect(m_zoomInAction, &QAction::triggered, centralWidget, &CentralWidget::zoomIn); + m_actionList << m_zoomInAction; + + m_zoomOutAction = new QAction(tr("Zoom &out"), parent); + m_zoomOutAction->setPriority(QAction::LowPriority); + m_zoomOutAction->setIcon(QIcon(resourcePath + "/zoomout.png"_L1)); + m_zoomOutAction->setShortcut(QKeySequence::ZoomOut); + connect(m_zoomOutAction, &QAction::triggered, centralWidget, &CentralWidget::zoomOut); + m_actionList << m_zoomOutAction; + + separator = new QAction(parent); + separator->setSeparator(true); + m_actionList << separator; + +#if QT_CONFIG(clipboard) + m_copyAction = new QAction(tr("&Copy selected Text"), parent); + m_copyAction->setPriority(QAction::LowPriority); + m_copyAction->setIconText("&Copy"); + m_copyAction->setIcon(QIcon(resourcePath + "/editcopy.png"_L1)); + m_copyAction->setShortcuts(QKeySequence::Copy); + m_copyAction->setEnabled(false); + connect(m_copyAction, &QAction::triggered, centralWidget, &CentralWidget::copy); + m_actionList << m_copyAction; +#endif + + m_printAction = new QAction(tr("&Print..."), parent); + m_printAction->setPriority(QAction::LowPriority); + m_printAction->setIcon(QIcon(resourcePath + "/print.png"_L1)); + m_printAction->setShortcut(QKeySequence::Print); + connect(m_printAction, &QAction::triggered, centralWidget, &CentralWidget::print); + m_actionList << m_printAction; + + m_findAction = new QAction(tr("&Find in Text..."), parent); + m_findAction->setIconText(tr("&Find")); + m_findAction->setIcon(QIcon(resourcePath + "/find.png"_L1)); + m_findAction->setShortcuts(QKeySequence::Find); + connect(m_findAction, &QAction::triggered, centralWidget, &CentralWidget::showTextSearch); + m_actionList << m_findAction; + +#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) + m_backAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoPrevious, + m_backAction->icon())); + m_nextAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoNext, + m_nextAction->icon())); + m_zoomInAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomIn, + m_zoomInAction->icon())); + m_zoomOutAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ZoomOut, + m_zoomOutAction->icon())); +#if QT_CONFIG(clipboard) + m_copyAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::EditCopy, + m_copyAction->icon())); +#endif + m_findAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::EditFind, + m_findAction->icon())); + m_homeAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoHome, + m_homeAction->icon())); + m_printAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::DocumentPrint, + m_printAction->icon())); +#endif +} + +void GlobalActions::updateActions() +{ + TRACE_OBJ + CentralWidget *centralWidget = CentralWidget::instance(); +#if QT_CONFIG(clipboard) + m_copyAction->setEnabled(centralWidget->hasSelection()); +#endif + m_nextAction->setEnabled(centralWidget->isForwardAvailable()); + m_backAction->setEnabled(centralWidget->isBackwardAvailable()); +} + +#if QT_CONFIG(clipboard) +void GlobalActions::setCopyAvailable(bool available) +{ + TRACE_OBJ + m_copyAction->setEnabled(available); +} +#endif + +#if defined(BROWSER_QTWEBKIT) + +void GlobalActions::slotAboutToShowBackMenu() +{ + TRACE_OBJ + m_backMenu->clear(); + if (QWebHistory *history = CentralWidget::instance()->currentHelpViewer()->history()) { + const int currentItemIndex = history->currentItemIndex(); + QList<QWebHistoryItem> items = history->backItems(history->count()); + for (int i = items.count() - 1; i >= 0; --i) { + QAction *action = new QAction(this); + action->setText(items.at(i).title()); + action->setData(-1 * (currentItemIndex - i)); + m_backMenu->addAction(action); + } + } +} + +void GlobalActions::slotAboutToShowNextMenu() +{ + TRACE_OBJ + m_nextMenu->clear(); + if (QWebHistory *history = CentralWidget::instance()->currentHelpViewer()->history()) { + const int count = history->count(); + QList<QWebHistoryItem> items = history->forwardItems(count); + for (int i = 0; i < items.count(); ++i) { + QAction *action = new QAction(this); + action->setData(count - i); + action->setText(items.at(i).title()); + m_nextMenu->addAction(action); + } + } +} + +void GlobalActions::slotOpenActionUrl(QAction *action) +{ + TRACE_OBJ + if (HelpViewer* viewer = CentralWidget::instance()->currentHelpViewer()) { + const int offset = action->data().toInt(); + QWebHistory *history = viewer->history(); + if (offset > 0) { + history->goToItem(history->forwardItems(history->count() + - offset + 1).back()); // forward + } else if (offset < 0) { + history->goToItem(history->backItems(-1 * offset).first()); // back + } + } +} + +#endif // BROWSER_QTWEBKIT + +void GlobalActions::setupNavigationMenus(QAction *back, QAction *next, + QWidget *parent) +{ +#if defined(BROWSER_QTWEBKIT) + m_backMenu = new QMenu(parent); + connect(m_backMenu, &QMenu::aboutToShow, + this, &GlobalActions::slotAboutToShowBackMenu); + connect(m_backMenu, &QMenu::triggered, + this, &GlobalActions::slotOpenActionUrl); + back->setMenu(m_backMenu); + + m_nextMenu = new QMenu(parent); + connect(m_nextMenu, &QMenu::aboutToShow, + this, &GlobalActions::slotAboutToShowNextMenu); + connect(m_nextMenu, &QMenu::triggered, + this, &GlobalActions::slotOpenActionUrl); + next->setMenu(m_nextMenu); +#else + Q_UNUSED(back); + Q_UNUSED(next); + Q_UNUSED(parent); +#endif +} + +GlobalActions *GlobalActions::m_instance = nullptr; diff --git a/src/assistant/assistant/globalactions.h b/src/assistant/assistant/globalactions.h new file mode 100644 index 0000000..a408321 --- /dev/null +++ b/src/assistant/assistant/globalactions.h @@ -0,0 +1,77 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef GLOBALACTIONS_H +#define GLOBALACTIONS_H + +#include <QtCore/QList> +#include <QtCore/QObject> +#include <QtGui/qtguiglobal.h> + +QT_BEGIN_NAMESPACE + +class QAction; +class QMenu; + +class GlobalActions : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(GlobalActions) +public: + static GlobalActions *instance(QObject *parent = nullptr); + + QList<QAction *> actionList() const { return m_actionList; } + QAction *backAction() const { return m_backAction; } + QAction *nextAction() const { return m_nextAction; } + QAction *homeAction() const { return m_homeAction; } + QAction *zoomInAction() const { return m_zoomInAction; } + QAction *zoomOutAction() const { return m_zoomOutAction; } +#if QT_CONFIG(clipboard) + QAction *copyAction() const { return m_copyAction; } +#endif + QAction *printAction() const { return m_printAction; } + QAction *findAction() const { return m_findAction; } + +public slots: +#if QT_CONFIG(clipboard) + void setCopyAvailable(bool available); +#endif + void updateActions(); + +#if defined(BROWSER_QTWEBKIT) +private slots: + void slotAboutToShowBackMenu(); + void slotAboutToShowNextMenu(); + void slotOpenActionUrl(QAction *action); +#endif // BROWSER_QTWEBKIT + +private: + void setupNavigationMenus(QAction *back, QAction *next, QWidget *parent); + +private: + GlobalActions(QObject *parent); + + static GlobalActions *m_instance; + + QAction *m_backAction; + QAction *m_nextAction; + QAction *m_homeAction; + QAction *m_zoomInAction; + QAction *m_zoomOutAction; +#if QT_CONFIG(clipboard) + QAction *m_copyAction; +#endif + QAction *m_printAction; + QAction *m_findAction; + + QList<QAction *> m_actionList; + +#if defined(BROWSER_QTWEBKIT) + QMenu *m_backMenu; + QMenu *m_nextMenu; +#endif +}; + +QT_END_NAMESPACE + +#endif // GLOBALACTIONS_H diff --git a/src/assistant/assistant/helpbrowsersupport.cpp b/src/assistant/assistant/helpbrowsersupport.cpp new file mode 100644 index 0000000..2109f90 --- /dev/null +++ b/src/assistant/assistant/helpbrowsersupport.cpp @@ -0,0 +1,207 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "helpbrowsersupport.h" +#include "helpenginewrapper.h" +#include "helpviewer.h" +#include "tracer.h" + +#include <QtHelp/QHelpEngineCore> + +#include <QtNetwork/QNetworkAccessManager> +#include <QtNetwork/QNetworkReply> +#include <QtNetwork/QNetworkRequest> + +#include <QtCore/QTimer> +#include <QtCore/QCoreApplication> +#include <QtCore/QUrl> +#include <QtCore/QDebug> + +QT_BEGIN_NAMESPACE + +// -- messages + +static const char g_htmlPage[] = "<html><head><meta http-equiv=\"content-type\" content=\"text/html; " + "charset=UTF-8\"><title>%1

%2

%3

%4

"; + +QString HelpBrowserSupport::msgError404() +{ + return QCoreApplication::translate("HelpViewer", "Error 404..."); +} + +QString HelpBrowserSupport::msgPageNotFound() +{ + return QCoreApplication::translate("HelpViewer", "The page could not be found"); +} + +QString HelpBrowserSupport::msgAllDocumentationSets() +{ + return QCoreApplication::translate("HelpViewer", + "Please make sure that you have all " + "documentation sets installed."); +} + +QString HelpBrowserSupport::msgLoadError(const QUrl &url) +{ + return HelpViewer::tr("Error loading: %1").arg(url.toString()); +} + +QString HelpBrowserSupport::msgHtmlErrorPage(const QUrl &url) +{ + return QString::fromLatin1(g_htmlPage) + .arg(HelpBrowserSupport::msgError404(), HelpBrowserSupport::msgPageNotFound(), + HelpBrowserSupport::msgLoadError(url), HelpBrowserSupport::msgAllDocumentationSets()); +} + +// A QNetworkAccessManager implementing unhandled URL schema handling for WebKit-type browsers. + +// -- HelpNetworkReply + +class HelpNetworkReply : public QNetworkReply +{ +public: + HelpNetworkReply(const QNetworkRequest &request, const QByteArray &fileData, + const QString &mimeType); + + void abort() override; + + qint64 bytesAvailable() const override + { return data.size() + QNetworkReply::bytesAvailable(); } + +protected: + qint64 readData(char *data, qint64 maxlen) override; + +private: + QByteArray data; + const qint64 origLen; +}; + +HelpNetworkReply::HelpNetworkReply(const QNetworkRequest &request, + const QByteArray &fileData, const QString& mimeType) + : data(fileData), origLen(fileData.size()) +{ + TRACE_OBJ + setRequest(request); + setUrl(request.url()); + setOpenMode(QIODevice::ReadOnly); + + setHeader(QNetworkRequest::ContentTypeHeader, mimeType); + setHeader(QNetworkRequest::ContentLengthHeader, QByteArray::number(origLen)); + QTimer::singleShot(0, this, &QNetworkReply::metaDataChanged); + QTimer::singleShot(0, this, &QNetworkReply::readyRead); + QTimer::singleShot(0, this, &QNetworkReply::finished); +} + +void HelpNetworkReply::abort() +{ + TRACE_OBJ +} + +qint64 HelpNetworkReply::readData(char *buffer, qint64 maxlen) +{ + TRACE_OBJ + qint64 len = qMin(qint64(data.size()), maxlen); + if (len) { + memcpy(buffer, data.constData(), len); + data.remove(0, len); + } + if (!data.size()) + QTimer::singleShot(0, this, &QNetworkReply::finished); + return len; +} + +// -- HelpRedirectNetworkReply + +class HelpRedirectNetworkReply : public QNetworkReply +{ +public: + HelpRedirectNetworkReply(const QNetworkRequest &request, const QUrl &newUrl) + { + setRequest(request); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 301); + setAttribute(QNetworkRequest::RedirectionTargetAttribute, newUrl); + + QTimer::singleShot(0, this, &QNetworkReply::finished); + } + +protected: + void abort() override { TRACE_OBJ } + qint64 readData(char*, qint64) override { TRACE_OBJ return qint64(-1); } +}; + +// -- HelpNetworkAccessManager + +class HelpNetworkAccessManager : public QNetworkAccessManager +{ +public: + HelpNetworkAccessManager(QObject *parent); + +protected: + QNetworkReply *createRequest(Operation op, + const QNetworkRequest &request, QIODevice *outgoingData = nullptr) override; +}; + +HelpNetworkAccessManager::HelpNetworkAccessManager(QObject *parent) + : QNetworkAccessManager(parent) +{ + TRACE_OBJ +} + +QNetworkReply *HelpNetworkAccessManager::createRequest(Operation, const QNetworkRequest &request, QIODevice*) +{ + TRACE_OBJ + + QByteArray data; + const QUrl url = request.url(); + QUrl redirectedUrl; + switch (HelpBrowserSupport::resolveUrl(url, &redirectedUrl, &data)) { + case HelpBrowserSupport::UrlRedirect: + return new HelpRedirectNetworkReply(request, redirectedUrl); + case HelpBrowserSupport::UrlLocalData: { + const QString mimeType = HelpViewer::mimeFromUrl(url); + return new HelpNetworkReply(request, data, mimeType); + } + case HelpBrowserSupport::UrlResolveError: + break; + } + return new HelpNetworkReply(request, HelpBrowserSupport::msgHtmlErrorPage(request.url()).toUtf8(), + QStringLiteral("text/html")); +} + +QByteArray HelpBrowserSupport::fileDataForLocalUrl(const QUrl &url) +{ + return HelpEngineWrapper::instance().fileData(url); +} + +HelpBrowserSupport::ResolveUrlResult HelpBrowserSupport::resolveUrl(const QUrl &url, + QUrl *targetUrlP, + QByteArray *dataP) +{ + const HelpEngineWrapper &engine = HelpEngineWrapper::instance(); + + const QUrl targetUrl = engine.findFile(url); + if (!targetUrl.isValid()) + return UrlResolveError; + + if (targetUrl != url) { + if (targetUrlP) + *targetUrlP = targetUrl; + return UrlRedirect; + } + + if (dataP) + *dataP = HelpBrowserSupport::fileDataForLocalUrl(targetUrl); + return UrlLocalData; +} + +QNetworkAccessManager *HelpBrowserSupport::createNetworkAccessManager(QObject *parent) +{ + return new HelpNetworkAccessManager(parent); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/helpbrowsersupport.h b/src/assistant/assistant/helpbrowsersupport.h new file mode 100644 index 0000000..08920e6 --- /dev/null +++ b/src/assistant/assistant/helpbrowsersupport.h @@ -0,0 +1,44 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HELPBROWSERSUPPORT_H +#define HELPBROWSERSUPPORT_H + +#include + +QT_BEGIN_NAMESPACE + +class QNetworkAccessManager; +class QObject; +class QString; +class QByteArray; +class QUrl; + +// Provide helper functions for feeding the QtHelp data stored in the help database +// into various browsers. +class HelpBrowserSupport +{ +public: + enum ResolveUrlResult { + UrlRedirect, + UrlLocalData, + UrlResolveError + }; + + static QString msgError404(); + static QString msgPageNotFound(); + static QString msgAllDocumentationSets(); + static QString msgLoadError(const QUrl &url); + static QString msgHtmlErrorPage(const QUrl &url); + + static ResolveUrlResult resolveUrl(const QUrl &url, QUrl *targetUrl, + QByteArray *data); + static QByteArray fileDataForLocalUrl(const QUrl &url); + + // Create an instance of QNetworkAccessManager for WebKit-type browsers. + static QNetworkAccessManager *createNetworkAccessManager(QObject *parent = nullptr); +}; + +QT_END_NAMESPACE + +#endif // HELPBROWSERSUPPORT_H diff --git a/src/assistant/assistant/helpdocsettings.cpp b/src/assistant/assistant/helpdocsettings.cpp new file mode 100644 index 0000000..2a027c7 --- /dev/null +++ b/src/assistant/assistant/helpdocsettings.cpp @@ -0,0 +1,192 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "helpdocsettings.h" + +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class HelpDocSettingsPrivate : public QSharedData +{ +public: + HelpDocSettingsPrivate() = default; + HelpDocSettingsPrivate(const HelpDocSettingsPrivate &other) = default; + ~HelpDocSettingsPrivate() = default; + + QMap m_namespaceToComponent; + QMap m_componentToNamespace; + + QMap m_namespaceToVersion; + QMap m_versionToNamespace; + + QMap m_namespaceToFileName; + QMap m_fileNameToNamespace; +}; + + +HelpDocSettings::HelpDocSettings() + : d(new HelpDocSettingsPrivate) +{ +} + +HelpDocSettings::HelpDocSettings(const HelpDocSettings &) = default; + +HelpDocSettings::HelpDocSettings(HelpDocSettings &&) = default; + +HelpDocSettings::~HelpDocSettings() = default; + +HelpDocSettings &HelpDocSettings::operator=(const HelpDocSettings &) = default; + +HelpDocSettings &HelpDocSettings::operator=(HelpDocSettings &&) = default; + +bool HelpDocSettings::addDocumentation(const QString &fileName) +{ + const QCompressedHelpInfo info = QCompressedHelpInfo::fromCompressedHelpFile(fileName); + + if (info.isNull()) + return false; + + const QString namespaceName = info.namespaceName(); + + if (d->m_namespaceToFileName.contains(namespaceName)) + return false; + + if (d->m_fileNameToNamespace.contains(fileName)) + return false; + + const QString component = info.component(); + const QVersionNumber version = info.version(); + + d->m_namespaceToFileName.insert(namespaceName, fileName); + d->m_fileNameToNamespace.insert(fileName, namespaceName); + + d->m_namespaceToComponent.insert(namespaceName, component); + d->m_componentToNamespace[component].append(namespaceName); + + d->m_namespaceToVersion.insert(namespaceName, version); + d->m_versionToNamespace[version].append(namespaceName); + + return true; +} + +bool HelpDocSettings::removeDocumentation(const QString &namespaceName) +{ + if (namespaceName.isEmpty()) + return false; + + const QString fileName = d->m_namespaceToFileName.value(namespaceName); + if (fileName.isEmpty()) + return false; + + const QString component = d->m_namespaceToComponent.value(namespaceName); + const QVersionNumber version = d->m_namespaceToVersion.value(namespaceName); + + d->m_namespaceToComponent.remove(namespaceName); + d->m_namespaceToVersion.remove(namespaceName); + d->m_namespaceToFileName.remove(namespaceName); + d->m_fileNameToNamespace.remove(fileName); + d->m_componentToNamespace[component].removeOne(namespaceName); + if (d->m_componentToNamespace[component].isEmpty()) + d->m_componentToNamespace.remove(component); + d->m_versionToNamespace[version].removeOne(namespaceName); + if (d->m_versionToNamespace[version].isEmpty()) + d->m_versionToNamespace.remove(version); + + return true; +} + +QString HelpDocSettings::namespaceName(const QString &fileName) const +{ + return d->m_fileNameToNamespace.value(fileName); +} + +QStringList HelpDocSettings::components() const +{ + return d->m_componentToNamespace.keys(); +} + +QList HelpDocSettings::versions() const +{ + return d->m_versionToNamespace.keys(); +} + +QStringList HelpDocSettings::namespaces() const +{ + return d->m_namespaceToFileName.keys(); +} + +QMap HelpDocSettings::namespaceToFileName() const +{ + return d->m_namespaceToFileName; +} + +HelpDocSettings HelpDocSettings::readSettings(QHelpEngineCore *helpEngine) +{ + QHelpFilterEngine *filterEngine = helpEngine->filterEngine(); + + HelpDocSettings docSettings; + docSettings.d->m_namespaceToComponent = filterEngine->namespaceToComponent(); + docSettings.d->m_namespaceToVersion = filterEngine->namespaceToVersion(); + for (auto it = docSettings.d->m_namespaceToComponent.constBegin(); + it != docSettings.d->m_namespaceToComponent.constEnd(); ++it) { + const QString namespaceName = it.key(); + const QString namespaceFileName = helpEngine->documentationFileName(namespaceName); + docSettings.d->m_namespaceToFileName.insert(namespaceName, namespaceFileName); + docSettings.d->m_fileNameToNamespace.insert(namespaceFileName, namespaceName); + docSettings.d->m_componentToNamespace[it.value()].append(namespaceName); + } + for (auto it = docSettings.d->m_namespaceToVersion.constBegin(); + it != docSettings.d->m_namespaceToVersion.constEnd(); ++it) { + docSettings.d->m_versionToNamespace[it.value()].append(it.key()); + } + + return docSettings; +} + +static QMap subtract(const QMap &minuend, + const QMap &subtrahend) +{ + auto result = minuend; + + for (auto itSubtrahend = subtrahend.cbegin(); itSubtrahend != subtrahend.cend(); ++itSubtrahend) { + auto itResult = result.find(itSubtrahend.key()); + if (itResult != result.end() && itSubtrahend.value() == itResult.value()) + result.erase(itResult); + } + + return result; +} + +bool HelpDocSettings::applySettings(QHelpEngineCore *helpEngine, + const HelpDocSettings &settings) +{ + const HelpDocSettings oldSettings = readSettings(helpEngine); + + const QMap docsToRemove = subtract( + oldSettings.namespaceToFileName(), + settings.namespaceToFileName()); + const QMap docsToAdd = subtract( + settings.namespaceToFileName(), + oldSettings.namespaceToFileName()); + + for (auto it = docsToRemove.cbegin(); it != docsToRemove.cend(); ++it) { + if (!helpEngine->unregisterDocumentation(it.key())) + qWarning() << "Cannot unregister documentation:" << it.key(); + } + + for (const QString &fileName : docsToAdd) { + if (!helpEngine->registerDocumentation(fileName)) + qWarning() << "Cannot register documentation file:" << fileName; + } + + return !docsToRemove.isEmpty() || !docsToAdd.isEmpty(); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/helpdocsettings.h b/src/assistant/assistant/helpdocsettings.h new file mode 100644 index 0000000..bcdedb3 --- /dev/null +++ b/src/assistant/assistant/helpdocsettings.h @@ -0,0 +1,46 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HELPDOCSETTINGS_H +#define HELPDOCSETTINGS_H + +#include + +QT_BEGIN_NAMESPACE + +class QVersionNumber; +class QHelpEngineCore; +class HelpDocSettingsPrivate; + +class HelpDocSettings final +{ +public: + HelpDocSettings(); + HelpDocSettings(const HelpDocSettings &other); + HelpDocSettings(HelpDocSettings &&other); + ~HelpDocSettings(); + + HelpDocSettings &operator=(const HelpDocSettings &other); + HelpDocSettings &operator=(HelpDocSettings &&other); + + void swap(HelpDocSettings &other) noexcept + { d.swap(other.d); } + + bool addDocumentation(const QString &fileName); + bool removeDocumentation(const QString &namespaceName); + QString namespaceName(const QString &fileName) const; + QStringList components() const; + QList versions() const; + QStringList namespaces() const; + QMap namespaceToFileName() const; + + static HelpDocSettings readSettings(QHelpEngineCore *helpEngine); + static bool applySettings(QHelpEngineCore *helpEngine, const HelpDocSettings &settings); + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +#endif // HELPDOCSETTINGS_H diff --git a/src/assistant/assistant/helpdocsettingswidget.cpp b/src/assistant/assistant/helpdocsettingswidget.cpp new file mode 100644 index 0000000..be1ca12 --- /dev/null +++ b/src/assistant/assistant/helpdocsettingswidget.cpp @@ -0,0 +1,162 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "helpdocsettings.h" +#include "helpdocsettingswidget.h" +#include "ui_helpdocsettingswidget.h" + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class HelpDocSettingsWidgetPrivate +{ + HelpDocSettingsWidget *q_ptr; + Q_DECLARE_PUBLIC(HelpDocSettingsWidget) +public: + HelpDocSettingsWidgetPrivate() = default; + + void addDocumentation(); + void removeDocumentation(); + void applyDocListFilter(QListWidgetItem *item); + + QMap m_namespaceToItem; + QHash m_itemToNamespace; + + Ui::HelpDocSettingsWidget m_ui; + HelpDocSettings m_settings; +}; + +void HelpDocSettingsWidgetPrivate::addDocumentation() +{ + Q_Q(HelpDocSettingsWidget); + + const QStringList &fileNames = + QFileDialog::getOpenFileNames(q, HelpDocSettingsWidget::tr("Add Documentation"), {}, + HelpDocSettingsWidget::tr("Qt Compressed Help Files (*.qch)")); + if (fileNames.isEmpty()) + return; + + bool added = false; + + for (const QString &fileName : fileNames) { + if (!m_settings.addDocumentation(fileName)) + continue; + + if (!added) { + added = true; + m_ui.registeredDocsListWidget->clearSelection(); + } + + const QString namespaceName = m_settings.namespaceName(fileName); + QListWidgetItem *item = new QListWidgetItem(namespaceName); + m_namespaceToItem.insert(namespaceName, item); + m_itemToNamespace.insert(item, namespaceName); + m_ui.registeredDocsListWidget->insertItem(m_namespaceToItem.keys().indexOf(namespaceName), item); + + item->setSelected(true); + applyDocListFilter(item); + } + + if (added) + emit q->docSettingsChanged(m_settings); +} + +void HelpDocSettingsWidgetPrivate::removeDocumentation() +{ + Q_Q(HelpDocSettingsWidget); + + const QList selectedItems = m_ui.registeredDocsListWidget->selectedItems(); + if (selectedItems.isEmpty()) + return; + + for (QListWidgetItem *item : selectedItems) { + const QString namespaceName = m_itemToNamespace.value(item); + m_itemToNamespace.remove(item); + m_namespaceToItem.remove(namespaceName); + delete item; + + m_settings.removeDocumentation(namespaceName); + } + + emit q->docSettingsChanged(m_settings); +} + +void HelpDocSettingsWidgetPrivate::applyDocListFilter(QListWidgetItem *item) +{ + const QString namespaceName = m_itemToNamespace.value(item); + const QString nameFilter = m_ui.registeredDocsFilterLineEdit->text(); + + const bool matches = nameFilter.isEmpty() || namespaceName.contains(nameFilter); + + if (!matches) + item->setSelected(false); + item->setHidden(!matches); +} + +HelpDocSettingsWidget::HelpDocSettingsWidget(QWidget *parent) + : QWidget(parent) + , d_ptr(new HelpDocSettingsWidgetPrivate()) +{ + Q_D(HelpDocSettingsWidget); + d->q_ptr = this; + d->m_ui.setupUi(this); + + connect(d->m_ui.docAddButton, &QAbstractButton::clicked, this, + [this]() { + Q_D(HelpDocSettingsWidget); + d->addDocumentation(); + }); + connect(d->m_ui.docRemoveButton, &QAbstractButton::clicked, this, + [this]() { + Q_D(HelpDocSettingsWidget); + d->removeDocumentation(); + }); + connect(d->m_ui.registeredDocsFilterLineEdit, &QLineEdit::textChanged, this, + [this](const QString &) { + Q_D(HelpDocSettingsWidget); + for (const auto item : d->m_namespaceToItem) + d->applyDocListFilter(item); + }); + connect(d->m_ui.registeredDocsListWidget, &QListWidget::itemSelectionChanged, this, + [this]() { + Q_D(HelpDocSettingsWidget); + d->m_ui.docRemoveButton->setEnabled( + !d->m_ui.registeredDocsListWidget->selectedItems().isEmpty()); + }); +} + +HelpDocSettingsWidget::~HelpDocSettingsWidget() = default; + +void HelpDocSettingsWidget::setDocSettings(const HelpDocSettings &settings) +{ + Q_D(HelpDocSettingsWidget); + d->m_settings = settings; + + d->m_ui.registeredDocsListWidget->clear(); + d->m_namespaceToItem.clear(); + d->m_itemToNamespace.clear(); + + for (const QString &namespaceName : d->m_settings.namespaces()) { + QListWidgetItem *item = new QListWidgetItem(namespaceName); + d->m_namespaceToItem.insert(namespaceName, item); + d->m_itemToNamespace.insert(item, namespaceName); + d->m_ui.registeredDocsListWidget->addItem(item); + d->applyDocListFilter(item); + } + + d->m_ui.docRemoveButton->setEnabled( + !d->m_ui.registeredDocsListWidget->selectedItems().isEmpty()); +} + +HelpDocSettings HelpDocSettingsWidget::docSettings() const +{ + Q_D(const HelpDocSettingsWidget); + return d->m_settings; +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/helpdocsettingswidget.h b/src/assistant/assistant/helpdocsettingswidget.h new file mode 100644 index 0000000..f638bb9 --- /dev/null +++ b/src/assistant/assistant/helpdocsettingswidget.h @@ -0,0 +1,37 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HELPDOCSETTINGSWIDGET_H +#define HELPDOCSETTINGSWIDGET_H + +#include + +QT_BEGIN_NAMESPACE + +class HelpDocSettings; +class HelpDocSettingsWidgetPrivate; + +class HelpDocSettingsWidget : public QWidget +{ + Q_OBJECT +public: + HelpDocSettingsWidget(QWidget *parent = nullptr); + + ~HelpDocSettingsWidget(); + + void setDocSettings(const HelpDocSettings &settings); + HelpDocSettings docSettings() const; + +Q_SIGNALS: + void docSettingsChanged(const HelpDocSettings &settings); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(HelpDocSettingsWidget) + Q_DISABLE_COPY_MOVE(HelpDocSettingsWidget) +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/assistant/assistant/helpdocsettingswidget.ui b/src/assistant/assistant/helpdocsettingswidget.ui new file mode 100644 index 0000000..4897dfe --- /dev/null +++ b/src/assistant/assistant/helpdocsettingswidget.ui @@ -0,0 +1,76 @@ + + + HelpDocSettingsWidget + + + + 0 + 0 + 268 + 128 + + + + Form + + + + + + Registered Documentation + + + + + + + <Filter> + + + true + + + + + + + + + Add... + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + QAbstractItemView::ExtendedSelection + + + + + + + + diff --git a/src/assistant/assistant/helpenginewrapper.cpp b/src/assistant/assistant/helpenginewrapper.cpp new file mode 100644 index 0000000..98a392e --- /dev/null +++ b/src/assistant/assistant/helpenginewrapper.cpp @@ -0,0 +1,791 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" + +#include "helpenginewrapper.h" +#include "../shared/collectionconfiguration.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + const QString AppFontKey("appFont"_L1); + const QString AppWritingSystemKey("appWritingSystem"_L1); + const QString BookmarksKey("Bookmarks"_L1); + const QString BrowserFontKey("browserFont"_L1); + const QString BrowserWritingSystemKey("browserWritingSystem"_L1); + const QString HomePageKey("homepage"_L1); + const QString MainWindowKey("MainWindow"_L1); + const QString MainWindowGeometryKey("MainWindowGeometry"_L1); + const QString SearchWasAttachedKey("SearchWasAttached"_L1); + const QString StartOptionKey("StartOption"_L1); + const QString UseAppFontKey("useAppFont"_L1); + const QString UseBrowserFontKey("useBrowserFont"_L1); + const QString VersionKey("qtVersion%1$$$%2"_L1.arg(QLatin1StringView(QT_VERSION_STR))); + const QString ShowTabsKey("showTabs"_L1); + const QString TopicChooserGeometryKey("TopicChooserGeometry"_L1); +} // anonymous namespace + +class TimeoutForwarder : public QObject +{ + Q_OBJECT +public: + TimeoutForwarder(const QString &fileName); +private slots: + void forward(); +private: + friend class HelpEngineWrapperPrivate; + + const QString m_fileName; +}; + +class HelpEngineWrapperPrivate : public QObject +{ + Q_OBJECT + friend class HelpEngineWrapper; + friend class TimeoutForwarder; +private slots: + void qchFileChanged(const QString &fileName); + +signals: + void documentationRemoved(const QString &namespaceName); + void documentationUpdated(const QString &namespaceName); + +private: + HelpEngineWrapperPrivate(const QString &collectionFile); + + void initFileSystemWatchers(); + void checkDocFilesWatched(); + void qchFileChanged(const QString &fileName, bool fromTimeout); + + static const int UpdateGracePeriod = 2000; + + QHelpEngine * const m_helpEngine; + QFileSystemWatcher * const m_qchWatcher; + struct RecentSignal { + QDateTime timestamp; + std::unique_ptr forwarder; + }; + std::map m_recentQchUpdates; +}; + +HelpEngineWrapper *HelpEngineWrapper::helpEngineWrapper = nullptr; + +HelpEngineWrapper &HelpEngineWrapper::instance() +{ + return instance({}); +} + +HelpEngineWrapper &HelpEngineWrapper::instance(const QString &collectionFile) +{ + TRACE_OBJ + /* + * Note that this Singleton cannot be static, because it has to be + * deleted before the QApplication. + */ + if (!helpEngineWrapper) + helpEngineWrapper = new HelpEngineWrapper(collectionFile); + return *helpEngineWrapper; +} + +void HelpEngineWrapper::removeInstance() +{ + TRACE_OBJ + delete helpEngineWrapper; + helpEngineWrapper = nullptr; +} + +HelpEngineWrapper::HelpEngineWrapper(const QString &collectionFile) + : d(new HelpEngineWrapperPrivate(collectionFile)) +{ + TRACE_OBJ + + /* + * Otherwise we will waste time if several new docs are found, + * because we will start to index them, only to be interrupted + * by the next request. Also, there is a nasty SQLITE bug that will + * cause the application to hang for minutes in that case. + * This call is reverted by initialDocSetupDone(), which must be + * called after the new docs have been installed. + */ +// TODO: probably remove it + disconnect(d->m_helpEngine, &QHelpEngineCore::setupFinished, + searchEngine(), &QHelpSearchEngine::scheduleIndexDocumentation); + + connect(d, &HelpEngineWrapperPrivate::documentationRemoved, + this, &HelpEngineWrapper::documentationRemoved); + connect(d, &HelpEngineWrapperPrivate::documentationUpdated, + this, &HelpEngineWrapper::documentationUpdated); + connect(d->m_helpEngine, &QHelpEngineCore::setupFinished, + this, &HelpEngineWrapper::setupFinished); +} + +HelpEngineWrapper::~HelpEngineWrapper() +{ + TRACE_OBJ + const QStringList &namespaces = d->m_helpEngine->registeredDocumentations(); + for (const QString &nameSpace : namespaces) { + const QString &docFile + = d->m_helpEngine->documentationFileName(nameSpace); + d->m_qchWatcher->removePath(docFile); + } + + delete d; +} + +void HelpEngineWrapper::initialDocSetupDone() +{ + TRACE_OBJ +// TODO: probably remove it + connect(d->m_helpEngine, &QHelpEngineCore::setupFinished, + searchEngine(), &QHelpSearchEngine::scheduleIndexDocumentation); + setupData(); +} + +QHelpSearchEngine *HelpEngineWrapper::searchEngine() const +{ + TRACE_OBJ + return d->m_helpEngine->searchEngine(); +} + +QHelpContentModel *HelpEngineWrapper::contentModel() const +{ + TRACE_OBJ + return d->m_helpEngine->contentModel(); +} + +QHelpIndexModel *HelpEngineWrapper::indexModel() const +{ + TRACE_OBJ + return d->m_helpEngine->indexModel(); +} + +QHelpContentWidget *HelpEngineWrapper::contentWidget() +{ + TRACE_OBJ + return d->m_helpEngine->contentWidget(); +} + +QHelpIndexWidget *HelpEngineWrapper::indexWidget() +{ + TRACE_OBJ + return d->m_helpEngine->indexWidget(); +} + +const QStringList HelpEngineWrapper::registeredDocumentations() const +{ + TRACE_OBJ + return d->m_helpEngine->registeredDocumentations(); +} + +QString HelpEngineWrapper::documentationFileName(const QString &namespaceName) const +{ + TRACE_OBJ + return d->m_helpEngine->documentationFileName(namespaceName); +} + +const QString HelpEngineWrapper::collectionFile() const +{ + TRACE_OBJ + return d->m_helpEngine->collectionFile(); +} + +bool HelpEngineWrapper::registerDocumentation(const QString &docFile) +{ + TRACE_OBJ + d->checkDocFilesWatched(); + if (!d->m_helpEngine->registerDocumentation(docFile)) + return false; + d->m_qchWatcher->addPath(docFile); + d->checkDocFilesWatched(); + return true; +} + +bool HelpEngineWrapper::unregisterDocumentation(const QString &namespaceName) +{ + TRACE_OBJ + d->checkDocFilesWatched(); + const QString &file = d->m_helpEngine->documentationFileName(namespaceName); + if (!d->m_helpEngine->unregisterDocumentation(namespaceName)) + return false; + d->m_qchWatcher->removePath(file); + d->checkDocFilesWatched(); + return true; +} + +bool HelpEngineWrapper::setupData() +{ + TRACE_OBJ + return d->m_helpEngine->setupData(); +} + +QUrl HelpEngineWrapper::findFile(const QUrl &url) const +{ + TRACE_OBJ + return d->m_helpEngine->findFile(url); +} + +QByteArray HelpEngineWrapper::fileData(const QUrl &url) const +{ + TRACE_OBJ + return d->m_helpEngine->fileData(url); +} + +QList HelpEngineWrapper::documentsForIdentifier(const QString &id) const +{ + TRACE_OBJ + return d->m_helpEngine->documentsForIdentifier(id); +} + +QString HelpEngineWrapper::error() const +{ + TRACE_OBJ + return d->m_helpEngine->error(); +} + +QHelpFilterEngine *HelpEngineWrapper::filterEngine() const +{ + return d->m_helpEngine->filterEngine(); +} + +const QStringList HelpEngineWrapper::qtDocInfo(const QString &component) const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(VersionKey.arg(component)).toString(). + split(CollectionConfiguration::ListSeparator); +} + +void HelpEngineWrapper::setQtDocInfo(const QString &component, + const QStringList &doc) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(VersionKey.arg(component), + doc.join(CollectionConfiguration::ListSeparator)); +} + +const QStringList HelpEngineWrapper::lastShownPages() const +{ + TRACE_OBJ + return CollectionConfiguration::lastShownPages(*d->m_helpEngine); +} + +void HelpEngineWrapper::setLastShownPages(const QStringList &lastShownPages) +{ + TRACE_OBJ + CollectionConfiguration::setLastShownPages(*d->m_helpEngine, lastShownPages); +} + +const QStringList HelpEngineWrapper::lastZoomFactors() const +{ + TRACE_OBJ + return CollectionConfiguration::lastZoomFactors(*d->m_helpEngine); +} + +void HelpEngineWrapper::setLastZoomFactors(const QStringList &lastZoomFactors) +{ + TRACE_OBJ + CollectionConfiguration::setLastZoomFactors(*d->m_helpEngine, lastZoomFactors); +} + +const QString HelpEngineWrapper::cacheDir() const +{ + TRACE_OBJ + return CollectionConfiguration::cacheDir(*d->m_helpEngine); +} + +bool HelpEngineWrapper::cacheDirIsRelativeToCollection() const +{ + TRACE_OBJ + return CollectionConfiguration::cacheDirIsRelativeToCollection(*d->m_helpEngine); +} + +void HelpEngineWrapper::setCacheDir(const QString &cacheDir, + bool relativeToCollection) +{ + TRACE_OBJ + CollectionConfiguration::setCacheDir(*d->m_helpEngine, cacheDir, + relativeToCollection); +} + +bool HelpEngineWrapper::filterFunctionalityEnabled() const +{ + TRACE_OBJ + return CollectionConfiguration::filterFunctionalityEnabled(*d->m_helpEngine); +} + +void HelpEngineWrapper::setFilterFunctionalityEnabled(bool enabled) +{ + TRACE_OBJ + CollectionConfiguration::setFilterFunctionalityEnabled(*d->m_helpEngine, + enabled); +} + +bool HelpEngineWrapper::filterToolbarVisible() const +{ + TRACE_OBJ + return CollectionConfiguration::filterToolbarVisible(*d->m_helpEngine); +} + +void HelpEngineWrapper::setFilterToolbarVisible(bool visible) +{ + TRACE_OBJ + CollectionConfiguration::setFilterToolbarVisible(*d->m_helpEngine, visible); +} + +bool HelpEngineWrapper::addressBarEnabled() const +{ + TRACE_OBJ + return CollectionConfiguration::addressBarEnabled(*d->m_helpEngine); +} + +void HelpEngineWrapper::setAddressBarEnabled(bool enabled) +{ + TRACE_OBJ + CollectionConfiguration::setAddressBarEnabled(*d->m_helpEngine, enabled); +} + +bool HelpEngineWrapper::addressBarVisible() const +{ + TRACE_OBJ + return CollectionConfiguration::addressBarVisible(*d->m_helpEngine); +} + +void HelpEngineWrapper::setAddressBarVisible(bool visible) +{ + TRACE_OBJ + CollectionConfiguration::setAddressBarVisible(*d->m_helpEngine, visible); +} + +bool HelpEngineWrapper::documentationManagerEnabled() const +{ + TRACE_OBJ + return CollectionConfiguration::documentationManagerEnabled(*d->m_helpEngine); +} + +void HelpEngineWrapper::setDocumentationManagerEnabled(bool enabled) +{ + TRACE_OBJ + CollectionConfiguration::setDocumentationManagerEnabled(*d->m_helpEngine, + enabled); +} + +const QByteArray HelpEngineWrapper::aboutMenuTexts() const +{ + TRACE_OBJ + return CollectionConfiguration::aboutMenuTexts(*d->m_helpEngine); +} + +void HelpEngineWrapper::setAboutMenuTexts(const QByteArray &texts) +{ + TRACE_OBJ + CollectionConfiguration::setAboutMenuTexts(*d->m_helpEngine, texts); +} + +const QByteArray HelpEngineWrapper::aboutIcon() const +{ + TRACE_OBJ + return CollectionConfiguration::aboutIcon(*d->m_helpEngine); +} + +void HelpEngineWrapper::setAboutIcon(const QByteArray &icon) +{ + TRACE_OBJ + CollectionConfiguration::setAboutIcon(*d->m_helpEngine, icon); +} + +const QByteArray HelpEngineWrapper::aboutImages() const +{ + TRACE_OBJ + return CollectionConfiguration::aboutImages(*d->m_helpEngine); +} + +void HelpEngineWrapper::setAboutImages(const QByteArray &images) +{ + TRACE_OBJ + CollectionConfiguration::setAboutImages(*d->m_helpEngine, images); +} + +const QByteArray HelpEngineWrapper::aboutTexts() const +{ + TRACE_OBJ + return CollectionConfiguration::aboutTexts(*d->m_helpEngine); +} + +void HelpEngineWrapper::setAboutTexts(const QByteArray &texts) +{ + TRACE_OBJ + CollectionConfiguration::setAboutTexts(*d->m_helpEngine, texts); +} + +const QString HelpEngineWrapper::windowTitle() const +{ + TRACE_OBJ + return CollectionConfiguration::windowTitle(*d->m_helpEngine); +} + +void HelpEngineWrapper::setWindowTitle(const QString &windowTitle) +{ + TRACE_OBJ + CollectionConfiguration::setWindowTitle(*d->m_helpEngine, windowTitle); +} + +const QByteArray HelpEngineWrapper::applicationIcon() const +{ + TRACE_OBJ + return CollectionConfiguration::applicationIcon(*d->m_helpEngine); +} + +void HelpEngineWrapper::setApplicationIcon(const QByteArray &icon) +{ + TRACE_OBJ + CollectionConfiguration::setApplicationIcon(*d->m_helpEngine, icon); +} + +const QByteArray HelpEngineWrapper::mainWindow() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(MainWindowKey).toByteArray(); +} + +void HelpEngineWrapper::setMainWindow(const QByteArray &mainWindow) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(MainWindowKey, mainWindow); +} + +const QByteArray HelpEngineWrapper::mainWindowGeometry() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(MainWindowGeometryKey).toByteArray(); +} + +void HelpEngineWrapper::setMainWindowGeometry(const QByteArray &geometry) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(MainWindowGeometryKey, geometry); +} + +const QByteArray HelpEngineWrapper::bookmarks() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(BookmarksKey).toByteArray(); +} + +void HelpEngineWrapper::setBookmarks(const QByteArray &bookmarks) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(BookmarksKey, bookmarks); +} + +int HelpEngineWrapper::lastTabPage() const +{ + TRACE_OBJ + return CollectionConfiguration::lastTabPage(*d->m_helpEngine); +} + +void HelpEngineWrapper::setLastTabPage(int lastPage) +{ + TRACE_OBJ + CollectionConfiguration::setLastTabPage(*d->m_helpEngine, lastPage); +} + +int HelpEngineWrapper::startOption() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(StartOptionKey, ShowLastPages).toInt(); +} + +void HelpEngineWrapper::setStartOption(int option) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(StartOptionKey, option); +} + +const QString HelpEngineWrapper::homePage() const +{ + TRACE_OBJ + const QString &homePage + = d->m_helpEngine->customValue(HomePageKey).toString(); + if (!homePage.isEmpty()) + return homePage; + return defaultHomePage(); +} + +void HelpEngineWrapper::setHomePage(const QString &page) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(HomePageKey, page); + +} + +const QString HelpEngineWrapper::defaultHomePage() const +{ + TRACE_OBJ + return CollectionConfiguration::defaultHomePage(*d->m_helpEngine); +} + +void HelpEngineWrapper::setDefaultHomePage(const QString &page) +{ + TRACE_OBJ + CollectionConfiguration::setDefaultHomePage(*d->m_helpEngine, page); +} + +bool HelpEngineWrapper::hasFontSettings() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(UseAppFontKey).isValid(); +} + +bool HelpEngineWrapper::usesAppFont() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(UseAppFontKey).toBool(); +} + +void HelpEngineWrapper::setUseAppFont(bool useAppFont) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(UseAppFontKey, useAppFont); +} + +bool HelpEngineWrapper::usesBrowserFont() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(UseBrowserFontKey, false).toBool(); +} + +void HelpEngineWrapper::setUseBrowserFont(bool useBrowserFont) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(UseBrowserFontKey, useBrowserFont); +} + +const QFont HelpEngineWrapper::appFont() const +{ + TRACE_OBJ + return qvariant_cast(d->m_helpEngine->customValue(AppFontKey)); +} + +void HelpEngineWrapper::setAppFont(const QFont &font) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(AppFontKey, font); +} + +QFontDatabase::WritingSystem HelpEngineWrapper::appWritingSystem() const +{ + TRACE_OBJ + return static_cast( + d->m_helpEngine->customValue(AppWritingSystemKey).toInt()); +} + +void HelpEngineWrapper::setAppWritingSystem(QFontDatabase::WritingSystem system) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(AppWritingSystemKey, system); +} + +const QFont HelpEngineWrapper::browserFont() const +{ + TRACE_OBJ + return qvariant_cast(d->m_helpEngine->customValue(BrowserFontKey)); +} + +void HelpEngineWrapper::setBrowserFont(const QFont &font) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(BrowserFontKey, font); +} + +QFontDatabase::WritingSystem HelpEngineWrapper::browserWritingSystem() const +{ + TRACE_OBJ + return static_cast( + d->m_helpEngine->customValue(BrowserWritingSystemKey).toInt()); +} + +void HelpEngineWrapper::setBrowserWritingSystem(QFontDatabase::WritingSystem system) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(BrowserWritingSystemKey, system); +} + +bool HelpEngineWrapper::showTabs() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(ShowTabsKey, false).toBool(); +} + +void HelpEngineWrapper::setShowTabs(bool show) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(ShowTabsKey, show); +} + +bool HelpEngineWrapper::fullTextSearchFallbackEnabled() const +{ + TRACE_OBJ + return CollectionConfiguration::fullTextSearchFallbackEnabled(*d->m_helpEngine); +} + +const QByteArray HelpEngineWrapper::topicChooserGeometry() const +{ + TRACE_OBJ + return d->m_helpEngine->customValue(TopicChooserGeometryKey).toByteArray(); +} + +void HelpEngineWrapper::setTopicChooserGeometry(const QByteArray &geometry) +{ + TRACE_OBJ + d->m_helpEngine->setCustomValue(TopicChooserGeometryKey, geometry); +} + +QHelpEngineCore *HelpEngineWrapper::helpEngine() const +{ + return d->m_helpEngine; +} + + +// -- TimeoutForwarder + +TimeoutForwarder::TimeoutForwarder(const QString &fileName) + : m_fileName(fileName) +{ + TRACE_OBJ +} + +void TimeoutForwarder::forward() +{ + TRACE_OBJ + HelpEngineWrapper::instance().d->qchFileChanged(m_fileName, true); +} + +// -- HelpEngineWrapperPrivate + +HelpEngineWrapperPrivate::HelpEngineWrapperPrivate(const QString &collectionFile) + : m_helpEngine(new QHelpEngine(collectionFile, this)), + m_qchWatcher(new QFileSystemWatcher(this)) +{ + TRACE_OBJ + m_helpEngine->setReadOnly(false); + m_helpEngine->setUsesFilterEngine(true); + initFileSystemWatchers(); +} + +void HelpEngineWrapperPrivate::initFileSystemWatchers() +{ + TRACE_OBJ + for (const QString &ns : m_helpEngine->registeredDocumentations()) + m_qchWatcher->addPath(m_helpEngine->documentationFileName(ns)); + + connect(m_qchWatcher, &QFileSystemWatcher::fileChanged, this, + QOverload::of(&HelpEngineWrapperPrivate::qchFileChanged)); + checkDocFilesWatched(); +} + +void HelpEngineWrapperPrivate::qchFileChanged(const QString &fileName) +{ + TRACE_OBJ + qchFileChanged(fileName, false); +} + +void HelpEngineWrapperPrivate::checkDocFilesWatched() +{ + TRACE_OBJ + const int watchedFilesCount = m_qchWatcher->files().size(); + const int docFilesCount = m_helpEngine->registeredDocumentations().size(); + if (watchedFilesCount != docFilesCount) { + qWarning("Strange: Have %d docs, but %d are being watched", + watchedFilesCount, docFilesCount); + } +} + +void HelpEngineWrapperPrivate::qchFileChanged(const QString &fileName, + bool fromTimeout) +{ + TRACE_OBJ + + /* + * We don't use QHelpEngineCore::namespaceName(fileName), because the file + * may not exist anymore or contain a different namespace. + */ + QString ns; + for (const QString &curNs : m_helpEngine->registeredDocumentations()) { + if (m_helpEngine->documentationFileName(curNs) == fileName) { + ns = curNs; + break; + } + } + + /* + * We can't do an assertion here, because QFileSystemWatcher may send the + * signal more than once. + */ + if (ns.isEmpty()) { + m_recentQchUpdates.erase(fileName); + return; + } + + /* + * Since the QFileSystemWatcher typically sends the signal more than once, + * we repeatedly delay our reaction a bit until we think the last signal + * was sent. + */ + + const auto &it = m_recentQchUpdates.find(fileName); + const QDateTime now = QDateTime::currentDateTimeUtc(); + + // Case 1: This is the first recent signal for the file. + if (it == m_recentQchUpdates.end()) { + auto forwarder = std::make_unique(fileName); + QTimer::singleShot(UpdateGracePeriod, forwarder.get(), + &TimeoutForwarder::forward); + m_recentQchUpdates.emplace(fileName, + RecentSignal{std::move(now), std::move(forwarder)}); + return; + } + + auto &[key, entry] = *it; + + // Case 2: The last signal for this file has not expired yet. + if (entry.timestamp > now.addMSecs(-UpdateGracePeriod)) { + if (!fromTimeout) + entry.timestamp = now; + else + QTimer::singleShot(UpdateGracePeriod, entry.forwarder.get(), + &TimeoutForwarder::forward); + return; + } + + // Case 3: The last signal for this file has expired. + if (m_helpEngine->unregisterDocumentation(ns)) { + if (!QFileInfo(fileName).exists() + || !m_helpEngine->registerDocumentation(fileName)) { + m_qchWatcher->removePath(fileName); + emit documentationRemoved(ns); + } else { + emit documentationUpdated(ns); + } + m_helpEngine->setupData(); + } + m_recentQchUpdates.erase(it); +} + +QT_END_NAMESPACE + +#include "helpenginewrapper.moc" diff --git a/src/assistant/assistant/helpenginewrapper.h b/src/assistant/assistant/helpenginewrapper.h new file mode 100644 index 0000000..f0e7b84 --- /dev/null +++ b/src/assistant/assistant/helpenginewrapper.h @@ -0,0 +1,179 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HELPENGINEWRAPPER_H +#define HELPENGINEWRAPPER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "qhelplink.h" + +QT_BEGIN_NAMESPACE + +class QFileSystemWatcher; +class QHelpContentModel; +class QHelpContentWidget; +class QHelpIndexModel; +class QHelpIndexWidget; +class QHelpSearchEngine; +class QHelpFilterEngine; +class QHelpEngineCore; + +enum { + ShowHomePage = 0, + ShowBlankPage = 1, + ShowLastPages = 2 +}; + +class HelpEngineWrapperPrivate; +class TimeoutForwarder; + +class HelpEngineWrapper : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(HelpEngineWrapper) + friend class TimeoutForwarder; +public: + static HelpEngineWrapper &instance(); + static HelpEngineWrapper &instance(const QString &collectionFile); + static void removeInstance(); + + // Forwarded help engine member functions, possibly enriched. + QHelpSearchEngine *searchEngine() const; + QHelpContentModel *contentModel() const; + QHelpIndexModel *indexModel() const; + QHelpContentWidget *contentWidget(); + QHelpIndexWidget *indexWidget(); + bool setupData(); + const QStringList registeredDocumentations() const; + QString documentationFileName(const QString &namespaceName) const; + const QString collectionFile() const; + bool registerDocumentation(const QString &docFile); + bool unregisterDocumentation(const QString &namespaceName); + QUrl findFile(const QUrl &url) const; + QByteArray fileData(const QUrl &url) const; + QList documentsForIdentifier(const QString &id) const; + QString error() const; + + QHelpFilterEngine *filterEngine() const; + + /* + * To be called after assistant has finished looking for new documentation. + * This will mainly cause the search index to be updated, if necessary. + */ + void initialDocSetupDone(); + + const QStringList qtDocInfo(const QString &component) const; + void setQtDocInfo(const QString &component, const QStringList &doc); + + const QString homePage() const; + void setHomePage(const QString &page); + const QString defaultHomePage() const; + void setDefaultHomePage(const QString &page); + + int lastTabPage() const; + void setLastTabPage(int lastPage); + + // TODO: Don't allow last pages and zoom factors to be set in isolation + // Perhaps also fill up missing elements automatically or assert. + const QStringList lastShownPages() const; + void setLastShownPages(const QStringList &lastShownPages); + const QStringList lastZoomFactors() const; + void setLastZoomFactors(const QStringList &lastZoomFactors); + + const QString cacheDir() const; + bool cacheDirIsRelativeToCollection() const; + void setCacheDir(const QString &cacheDir, bool relativeToCollection); + + bool filterFunctionalityEnabled() const; + void setFilterFunctionalityEnabled(bool enabled); + + bool filterToolbarVisible() const; + void setFilterToolbarVisible(bool visible); + + bool addressBarEnabled() const; + void setAddressBarEnabled(bool enabled); + + bool addressBarVisible() const; + void setAddressBarVisible(bool visible); + + bool documentationManagerEnabled() const; + void setDocumentationManagerEnabled(bool enabled); + + const QByteArray aboutMenuTexts() const; + void setAboutMenuTexts(const QByteArray &texts); + const QByteArray aboutTexts() const; + void setAboutTexts(const QByteArray &texts); + const QByteArray aboutIcon() const; + void setAboutIcon(const QByteArray &icon); + const QByteArray aboutImages() const; + void setAboutImages(const QByteArray &images); + + const QString windowTitle() const; + void setWindowTitle(const QString &windowTitle); + + const QByteArray applicationIcon() const; + void setApplicationIcon(const QByteArray &icon); + + const QByteArray mainWindow() const; + void setMainWindow(const QByteArray &mainWindow); + const QByteArray mainWindowGeometry() const; + void setMainWindowGeometry(const QByteArray &geometry); + + const QByteArray bookmarks() const; + void setBookmarks(const QByteArray &bookmarks); + + int startOption() const; + void setStartOption(int option); + + bool hasFontSettings() const; + bool usesAppFont() const; + void setUseAppFont(bool useAppFont); + bool usesBrowserFont() const; + void setUseBrowserFont(bool useBrowserFont); + const QFont appFont() const; + void setAppFont(const QFont &font); + QFontDatabase::WritingSystem appWritingSystem() const; + void setAppWritingSystem(QFontDatabase::WritingSystem system); + const QFont browserFont() const; + void setBrowserFont(const QFont &font); + QFontDatabase::WritingSystem browserWritingSystem() const; + void setBrowserWritingSystem(QFontDatabase::WritingSystem system); + + bool showTabs() const; + void setShowTabs(bool show); + + bool fullTextSearchFallbackEnabled() const; + + const QByteArray topicChooserGeometry() const; + void setTopicChooserGeometry(const QByteArray &geometry); + + QHelpEngineCore *helpEngine() const; + +signals: + + // For asynchronous doc updates triggered by external actions. + void documentationRemoved(const QString &namespaceName); + void documentationUpdated(const QString &namespaceName); + + // Forwarded from QHelpEngineCore. + void setupFinished(); + +private: + HelpEngineWrapper(const QString &collectionFile); + ~HelpEngineWrapper(); + + static HelpEngineWrapper *helpEngineWrapper; + + HelpEngineWrapperPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // HELPENGINEWRAPPER_H diff --git a/src/assistant/assistant/helpviewer.cpp b/src/assistant/assistant/helpviewer.cpp new file mode 100644 index 0000000..46d1457 --- /dev/null +++ b/src/assistant/assistant/helpviewer.cpp @@ -0,0 +1,477 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "helpviewer.h" +#include "helpviewerimpl.h" + +#include "helpenginewrapper.h" +#include "tracer.h" + +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include +#include + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +const int kMaxHistoryItems = 20; + +const struct ExtensionMap { + const char *extension; + const char *mimeType; +} extensionMap[] = { + { ".bmp", "image/bmp" }, + { ".css", "text/css" }, + { ".gif", "image/gif" }, + { ".html", "text/html" }, + { ".htm", "text/html" }, + { ".ico", "image/x-icon" }, + { ".jpeg", "image/jpeg" }, + { ".jpg", "image/jpeg" }, + { ".js", "application/x-javascript" }, + { ".mng", "video/x-mng" }, + { ".pbm", "image/x-portable-bitmap" }, + { ".pgm", "image/x-portable-graymap" }, + { ".pdf", nullptr }, + { ".png", "image/png" }, + { ".ppm", "image/x-portable-pixmap" }, + { ".rss", "application/rss+xml" }, + { ".svg", "image/svg+xml" }, + { ".svgz", "image/svg+xml" }, + { ".text", "text/plain" }, + { ".tif", "image/tiff" }, + { ".tiff", "image/tiff" }, + { ".txt", "text/plain" }, + { ".xbm", "image/x-xbitmap" }, + { ".xml", "text/xml" }, + { ".xpm", "image/x-xpm" }, + { ".xsl", "text/xsl" }, + { ".xhtml", "application/xhtml+xml" }, + { ".wml", "text/vnd.wap.wml" }, + { ".wmlc", "application/vnd.wap.wmlc" }, + { "about:blank", nullptr }, + { nullptr, nullptr } +}; + +static void setLight(QWidget *widget) +{ + // Make docs' contents visible in dark theme + QPalette p = widget->palette(); + p.setColor(QPalette::Inactive, QPalette::Highlight, + p.color(QPalette::Active, QPalette::Highlight)); + p.setColor(QPalette::Inactive, QPalette::HighlightedText, + p.color(QPalette::Active, QPalette::HighlightedText)); + p.setColor(QPalette::Base, Qt::white); + p.setColor(QPalette::Text, Qt::black); + widget->setPalette(p); +} + +static bool isDarkTheme() +{ + // Either Qt realizes that it is dark, or the palette exposes it, by having + // the window background darker than the text + return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark + || QGuiApplication::palette().color(QPalette::Base).lightnessF() + < QGuiApplication::palette().color(QPalette::Text).lightnessF(); +} + +static void setPaletteFromApp(QWidget *widget) +{ + QPalette appPalette = QGuiApplication::palette(); + // Detach the palette from the application palette, so it doesn't change directly when the + // application palette changes. + // That ensures that if the system is dark, and dark documentation is show, and the user + // switches the system to a light theme, that the documentation background stays dark for the + // visible page until either the we got informed by the change in application palette, or the + // user switched pages + appPalette.setColor(QPalette::Base, appPalette.color(QPalette::Base)); + widget->setPalette(appPalette); +} + +static QByteArray getData(const QUrl &url, QWidget *widget) +{ + // This is a hack for Qt documentation, + // which decides to use a simpler CSS if the viewer does not have JavaScript + // which was a hack to decide if we are viewing in QTextBrowser or QtWebEngine et al. + // Force it to use the "normal" offline CSS even without JavaScript, since litehtml can + // handle that, and inject a dark themed CSS into Qt documentation for dark Qt Creator themes + QUrl actualUrl = url; + QString path = url.path(QUrl::FullyEncoded); + static const char simpleCss[] = "/offline-simple.css"; + if (path.endsWith(simpleCss)) { + if (isDarkTheme()) { + // check if dark CSS is shipped with documentation + QString darkPath = path; + darkPath.replace(simpleCss, "/offline-dark.css"); + actualUrl.setPath(darkPath); + QByteArray data = HelpEngineWrapper::instance().fileData(actualUrl); + if (!data.isEmpty()) { + // we found the dark style + // set background dark (by using app palette) + setPaletteFromApp(widget); + return data; + } + } + path.replace(simpleCss, "/offline.css"); + actualUrl.setPath(path); + } + + if (actualUrl.isValid()) + return HelpEngineWrapper::instance().fileData(actualUrl); + + const bool isAbout = (actualUrl.toString() == "about:blank"_L1); + return isAbout ? HelpViewerImpl::AboutBlank.toUtf8() + : HelpViewerImpl::PageNotFoundMessage.arg(url.toString()).toUtf8(); +} + +class HelpViewerPrivate +{ +public: + struct HistoryItem + { + QUrl url; + QString title; + int vscroll; + }; + HistoryItem currentHistoryItem() const; + void setSourceInternal(const QUrl &url, int *vscroll = nullptr, bool reload = false); + void incrementZoom(int steps); + void applyZoom(int percentage); + + HelpViewer *q = nullptr; + QLiteHtmlWidget *m_viewer = nullptr; + std::vector m_backItems; + std::vector m_forwardItems; + int m_fontZoom = 100; // zoom percentage +}; + +HelpViewerPrivate::HistoryItem HelpViewerPrivate::currentHistoryItem() const +{ + return { m_viewer->url(), m_viewer->title(), m_viewer->verticalScrollBar()->value() }; +} + +void HelpViewerPrivate::setSourceInternal(const QUrl &url, int *vscroll, bool reload) +{ + QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + const bool isHelp = (url.toString() == "help"_L1); + const QUrl resolvedUrl = (isHelp ? HelpViewerImpl::LocalHelpFile + : HelpEngineWrapper::instance().findFile(url)); + + QUrl currentUrlWithoutFragment = m_viewer->url(); + currentUrlWithoutFragment.setFragment({}); + QUrl newUrlWithoutFragment = resolvedUrl; + newUrlWithoutFragment.setFragment({}); + + m_viewer->setUrl(resolvedUrl); + if (currentUrlWithoutFragment != newUrlWithoutFragment || reload) { + // Users can register arbitrary documentation, so we do not expect the documentation to + // support dark themes, and start with light palette. + // We override this if we find Qt's dark style + setLight(q); + m_viewer->setHtml(QString::fromUtf8(getData(resolvedUrl, q))); + } + if (vscroll) + m_viewer->verticalScrollBar()->setValue(*vscroll); + else + m_viewer->scrollToAnchor(resolvedUrl.fragment(QUrl::FullyEncoded)); + + QGuiApplication::restoreOverrideCursor(); + + emit q->sourceChanged(q->source()); + emit q->loadFinished(); + emit q->titleChanged(); +} + +void HelpViewerPrivate::incrementZoom(int steps) +{ + const int incrementPercentage = 10 * steps; // 10 percent increase by single step + const int previousZoom = m_fontZoom; + applyZoom(previousZoom + incrementPercentage); +} + +void HelpViewerPrivate::applyZoom(int percentage) +{ + const int newZoom = qBound(10, percentage, 300); + if (newZoom == m_fontZoom) + return; + m_fontZoom = newZoom; + m_viewer->setZoomFactor(newZoom / 100.0); +} + +HelpViewer::HelpViewer(qreal zoom, QWidget *parent) + : QWidget(parent) + , d(new HelpViewerPrivate) +{ + auto layout = new QVBoxLayout; + d->q = this; + d->m_viewer = new QLiteHtmlWidget(this); + d->m_viewer->setResourceHandler([this](const QUrl &url) { return getData(url, this); }); + d->m_viewer->viewport()->installEventFilter(this); + const int zoomPercentage = zoom == 0 ? 100 : zoom * 100; + d->applyZoom(zoomPercentage); + connect(d->m_viewer, &QLiteHtmlWidget::linkClicked, this, &HelpViewer::setSource); + connect(d->m_viewer, &QLiteHtmlWidget::linkHighlighted, this, &HelpViewer::highlighted); +#if QT_CONFIG(clipboard) + connect(d->m_viewer, &QLiteHtmlWidget::copyAvailable, this, &HelpViewer::copyAvailable); +#endif + setLayout(layout); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(d->m_viewer, 10); + + // If the platform supports it, changes of color scheme light/dark take effect during runtime: + connect( + QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, + [this] { + int vscroll = d->m_viewer->verticalScrollBar()->value(); + d->setSourceInternal(source(), &vscroll, /*reload*/ true); + }, + // Queue to make sure that the palette is actually applied on the application + Qt::QueuedConnection); +} + +HelpViewer::~HelpViewer() +{ + delete d; +} + +QFont HelpViewer::viewerFont() const +{ + return d->m_viewer->defaultFont(); +} + +void HelpViewer::setViewerFont(const QFont &font) +{ + d->m_viewer->setDefaultFont(font); +} + +void HelpViewer::scaleUp() +{ + d->incrementZoom(1); +} + +void HelpViewer::scaleDown() +{ + d->incrementZoom(-1); +} + +void HelpViewer::resetScale() +{ + d->applyZoom(100); +} + +qreal HelpViewer::scale() const +{ + return d->m_viewer->zoomFactor(); +} + +QString HelpViewer::title() const +{ + return d->m_viewer->title(); +} + +QUrl HelpViewer::source() const +{ + return d->m_viewer->url(); +} + +void HelpViewer::reload() +{ + doSetSource(source(), true); +} + +void HelpViewer::setSource(const QUrl &url) +{ + doSetSource(url, false); +} + +void HelpViewer::doSetSource(const QUrl &url, bool reload) +{ + if (launchWithExternalApp(url)) + return; + + d->m_forwardItems.clear(); + emit forwardAvailable(false); + if (d->m_viewer->url().isValid()) { + d->m_backItems.push_back(d->currentHistoryItem()); + while (d->m_backItems.size() > kMaxHistoryItems) // this should trigger only once anyhow + d->m_backItems.erase(d->m_backItems.begin()); + emit backwardAvailable(true); + } + + d->setSourceInternal(url, nullptr, reload); +} + +#if QT_CONFIG(printer) +void HelpViewer::print(QPrinter *printer) +{ + d->m_viewer->print(printer); +} +#endif + +QString HelpViewer::selectedText() const +{ + return d->m_viewer->selectedText(); +} + +bool HelpViewer::isForwardAvailable() const +{ + return !d->m_forwardItems.empty(); +} + +bool HelpViewer::isBackwardAvailable() const +{ + return !d->m_backItems.empty(); +} + +static QTextDocument::FindFlags textDocumentFlagsForFindFlags(HelpViewer::FindFlags flags) +{ + QTextDocument::FindFlags textDocFlags; + if (flags & HelpViewer::FindBackward) + textDocFlags |= QTextDocument::FindBackward; + if (flags & HelpViewer::FindCaseSensitively) + textDocFlags |= QTextDocument::FindCaseSensitively; + return textDocFlags; +} + +bool HelpViewer::findText(const QString &text, FindFlags flags, bool incremental, bool fromSearch) +{ + Q_UNUSED(fromSearch); + return d->m_viewer->findText(text, textDocumentFlagsForFindFlags(flags), incremental); +} + +#if QT_CONFIG(clipboard) +void HelpViewer::copy() +{ + QGuiApplication::clipboard()->setText(selectedText()); +} +#endif + +void HelpViewer::home() +{ + setSource(HelpEngineWrapper::instance().homePage()); +} + +void HelpViewer::forward() +{ + HelpViewerPrivate::HistoryItem nextItem = d->currentHistoryItem(); + if (d->m_forwardItems.empty()) + return; + d->m_backItems.push_back(nextItem); + nextItem = d->m_forwardItems.front(); + d->m_forwardItems.erase(d->m_forwardItems.begin()); + + emit backwardAvailable(isBackwardAvailable()); + emit forwardAvailable(isForwardAvailable()); + d->setSourceInternal(nextItem.url, &nextItem.vscroll); +} + +void HelpViewer::backward() +{ + HelpViewerPrivate::HistoryItem previousItem = d->currentHistoryItem(); + if (d->m_backItems.empty()) + return; + d->m_forwardItems.insert(d->m_forwardItems.begin(), previousItem); + previousItem = d->m_backItems.back(); + d->m_backItems.pop_back(); + + emit backwardAvailable(isBackwardAvailable()); + emit forwardAvailable(isForwardAvailable()); + d->setSourceInternal(previousItem.url, &previousItem.vscroll); +} + +bool HelpViewer::eventFilter(QObject *src, QEvent *event) +{ + if (event->type() == QEvent::Wheel) { + auto we = static_cast(event); + if (we->modifiers() == Qt::ControlModifier) { + we->accept(); + const int deltaY = we->angleDelta().y(); + if (deltaY != 0) + d->incrementZoom(deltaY / 120); + return true; + } + } + return QWidget::eventFilter(src, event); +} + +bool HelpViewer::isLocalUrl(const QUrl &url) +{ + TRACE_OBJ + const QString &scheme = url.scheme(); + return scheme.isEmpty() + || scheme == "file"_L1 + || scheme == "qrc"_L1 + || scheme == "data"_L1 + || scheme == "qthelp"_L1 + || scheme == "about"_L1; +} + +bool HelpViewer::canOpenPage(const QString &path) +{ + TRACE_OBJ + return !mimeFromUrl(QUrl::fromLocalFile(path)).isEmpty(); +} + +QString HelpViewer::mimeFromUrl(const QUrl &url) +{ + TRACE_OBJ + const QString &path = url.path(); + const int index = path.lastIndexOf(u'.'); + const QByteArray &ext = path.mid(index).toUtf8().toLower(); + + const ExtensionMap *e = extensionMap; + while (e->extension) { + if (ext == e->extension) + return QLatin1StringView(e->mimeType); + ++e; + } + return "application/octet-stream"_L1; +} + +bool HelpViewer::launchWithExternalApp(const QUrl &url) +{ + TRACE_OBJ + if (isLocalUrl(url)) { + const HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + const QUrl &resolvedUrl = helpEngine.findFile(url); + if (!resolvedUrl.isValid()) + return false; + + const QString& path = resolvedUrl.toLocalFile(); + if (!canOpenPage(path)) { + QTemporaryFile tmpTmpFile; + if (!tmpTmpFile.open()) + return false; + + const QString &extension = QFileInfo(path).completeSuffix(); + QFile actualTmpFile(tmpTmpFile.fileName() % "."_L1 % extension); + if (!actualTmpFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) + return false; + + actualTmpFile.write(helpEngine.fileData(resolvedUrl)); + actualTmpFile.close(); + return QDesktopServices::openUrl(QUrl::fromLocalFile(actualTmpFile.fileName())); + } + return false; + } + return QDesktopServices::openUrl(url); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/helpviewer.h b/src/assistant/assistant/helpviewer.h new file mode 100644 index 0000000..25c8047 --- /dev/null +++ b/src/assistant/assistant/helpviewer.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HELPVIEWER_H +#define HELPVIEWER_H + +#include +#include + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class HelpViewerPrivate; + +class HelpViewer : public QWidget +{ + Q_OBJECT +public: + enum FindFlag { + FindBackward = 0x01, + FindCaseSensitively = 0x02 + }; + Q_DECLARE_FLAGS(FindFlags, FindFlag) + + HelpViewer(qreal zoom, QWidget *parent = nullptr); + ~HelpViewer() override; + + QFont viewerFont() const; + void setViewerFont(const QFont &font); + + void scaleUp(); + void scaleDown(); + + void resetScale(); + qreal scale() const; + + QString title() const; + + QUrl source() const; + void reload(); + void setSource(const QUrl &url); + +#if QT_CONFIG(printer) + void print(QPrinter *printer); +#endif + + QString selectedText() const; + bool isForwardAvailable() const; + bool isBackwardAvailable() const; + + bool findText(const QString &text, FindFlags flags, bool incremental, + bool fromSearch); + + static bool isLocalUrl(const QUrl &url); + static bool canOpenPage(const QString &url); + static QString mimeFromUrl(const QUrl &url); + static bool launchWithExternalApp(const QUrl &url); + + // implementation detail, not a part of the interface + bool eventFilter(QObject *src, QEvent *event) override; + +public slots: +#if QT_CONFIG(clipboard) + void copy(); +#endif + void home(); + void forward(); + void backward(); + +signals: + void titleChanged(); + void copyAvailable(bool yes); + void sourceChanged(const QUrl &url); + void forwardAvailable(bool enabled); + void backwardAvailable(bool enabled); + void highlighted(const QUrl &link); + void printRequested(); + void loadFinished(); +private: + void doSetSource(const QUrl &url, bool reload); + + HelpViewerPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // HELPVIEWER_H diff --git a/src/assistant/assistant/helpviewerimpl.cpp b/src/assistant/assistant/helpviewerimpl.cpp new file mode 100644 index 0000000..70c5282 --- /dev/null +++ b/src/assistant/assistant/helpviewerimpl.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "helpviewerimpl.h" +#include "helpviewerimpl_p.h" + +#include "helpenginewrapper.h" +#include "tracer.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +const QString HelpViewerImpl::AboutBlank = + QCoreApplication::translate("HelpViewer", "about:blank"); + +const QString HelpViewerImpl::LocalHelpFile = + "qthelp://org.qt-project.qtassistant.%1%2%3/qtassistant/assistant-quick-guide.html"_L1.arg( + QString::number(QT_VERSION_MAJOR), QString::number(QT_VERSION_MINOR), + QString::number(QT_VERSION_PATCH)); + +const QString HelpViewerImpl::PageNotFoundMessage = + QCoreApplication::translate("HelpViewer", "Error 404...


The page could not be found.


'%1'" + "

"); + +HelpViewerImpl::~HelpViewerImpl() +{ + TRACE_OBJ + delete d; +} + +// -- public slots + +void HelpViewerImpl::home() +{ + TRACE_OBJ + setSource(HelpEngineWrapper::instance().homePage()); +} + +// -- private slots + +void HelpViewerImpl::setLoadFinished() +{ + emit sourceChanged(source()); +} + +// -- private + +bool HelpViewerImpl::handleForwardBackwardMouseButtons(QMouseEvent *event) +{ + TRACE_OBJ + if (event->button() == Qt::XButton1) { + backward(); + return true; + } + + if (event->button() == Qt::XButton2) { + forward(); + return true; + } + + return false; +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/helpviewerimpl.h b/src/assistant/assistant/helpviewerimpl.h new file mode 100644 index 0000000..5bf2f92 --- /dev/null +++ b/src/assistant/assistant/helpviewerimpl.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HELPVIEWERIMPL_H +#define HELPVIEWERIMPL_H + +#include +#include +#include + +#include + +#if defined(BROWSER_QTWEBKIT) +# include +#elif defined(BROWSER_QTEXTBROWSER) +# include +#endif + +#include "helpviewer.h" + +QT_BEGIN_NAMESPACE + +#if defined(BROWSER_QTWEBKIT) +#define TEXTBROWSER_OVERRIDE +class HelpViewerImpl : public QWebView +#elif defined(BROWSER_QTEXTBROWSER) +#define TEXTBROWSER_OVERRIDE override +class HelpViewerImpl : public QTextBrowser +#endif +{ + Q_OBJECT + class HelpViewerImplPrivate; + +public: + HelpViewerImpl(qreal zoom, QWidget *parent = nullptr); + ~HelpViewerImpl() override; + + QFont viewerFont() const; + void setViewerFont(const QFont &font); + + void scaleUp(); + void scaleDown(); + + void resetScale(); + qreal scale() const; + + QString title() const; + + QUrl source() const; + void doSetSource(const QUrl &url, QTextDocument::ResourceType type) TEXTBROWSER_OVERRIDE; + + QString selectedText() const; + bool isForwardAvailable() const; + bool isBackwardAvailable() const; + + bool findText(const QString &text, HelpViewer::FindFlags flags, bool incremental, + bool fromSearch); + + static const QString AboutBlank; + static const QString LocalHelpFile; + static const QString PageNotFoundMessage; + +public slots: +#if QT_CONFIG(clipboard) + void copy(); +#endif + void home() TEXTBROWSER_OVERRIDE; + + void forward() TEXTBROWSER_OVERRIDE; + void backward() TEXTBROWSER_OVERRIDE; + +signals: + void titleChanged(); +#if !defined(BROWSER_QTEXTBROWSER) + // Provide signals present in QTextBrowser, QTextEdit for browsers that do not inherit QTextBrowser + void copyAvailable(bool yes); + void sourceChanged(const QUrl &url); + void forwardAvailable(bool enabled); + void backwardAvailable(bool enabled); + void highlighted(const QUrl &link); + void printRequested(); +#elif !defined(BROWSER_QTWEBKIT) + // Provide signals present in QWebView for browsers that do not inherit QWebView + void loadFinished(bool finished); +#endif + +protected: + void keyPressEvent(QKeyEvent *e) override; + void wheelEvent(QWheelEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void resizeEvent(QResizeEvent *e) override; + +private slots: + void actionChanged(); + void setLoadFinished(); + +private: + bool eventFilter(QObject *obj, QEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + QVariant loadResource(int type, const QUrl &name) TEXTBROWSER_OVERRIDE; + bool handleForwardBackwardMouseButtons(QMouseEvent *e); + void scrollToTextPosition(int position); + +private: + HelpViewerImplPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // HELPVIEWERIMPL_H diff --git a/src/assistant/assistant/helpviewerimpl_p.h b/src/assistant/assistant/helpviewerimpl_p.h new file mode 100644 index 0000000..0760e10 --- /dev/null +++ b/src/assistant/assistant/helpviewerimpl_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HELPVIEWERIMPLPRIVATE_H +#define HELPVIEWERIMPLPRIVATE_H + +#include "centralwidget.h" +#include "helpviewer.h" +#include "openpagesmanager.h" + +#include +#if defined(BROWSER_QTEXTBROWSER) +# include +#elif defined(BROWSER_QTWEBKIT) +# include +# include +#endif // BROWSER_QTWEBKIT + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class HelpViewerImpl::HelpViewerImplPrivate : public QObject +{ + Q_OBJECT + +public: +#if defined(BROWSER_QTEXTBROWSER) + HelpViewerImplPrivate(int zoom) + : zoomCount(zoom) + { } +#elif defined(BROWSER_QTWEBKIT) + HelpViewerImplPrivate() + { + // The web uses 96dpi by default on the web to preserve the font size across platforms, but + // since we control the content for the documentation, we want the system DPI to be used. + // - OS X reports 72dpi but doesn't allow changing the DPI, ignore anything below a 1.0 ratio to handle this. + // - On Windows and Linux don't zoom the default web 96dpi below a 1.25 ratio to avoid + // filtered images in the doc unless the font readability difference is considerable. + webDpiRatio = QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.; + if (webDpiRatio < 1.25) + webDpiRatio = 1.0; + } +#endif // BROWSER_QTWEBKIT + +#if defined(BROWSER_QTEXTBROWSER) + bool hasAnchorAt(QTextBrowser *browser, const QPoint& pos) + { + lastAnchor = browser->anchorAt(pos); + if (lastAnchor.isEmpty()) + return false; + + lastAnchor = browser->source().resolved(lastAnchor).toString(); + if (lastAnchor.at(0) == u'#') { + QString src = browser->source().toString(); + int hsh = src.indexOf(u'#'); + lastAnchor = (hsh >= 0 ? src.left(hsh) : src) + lastAnchor; + } + return true; + } + +public slots: + void openLink() + { + doOpenLink(false); + } + + void openLinkInNewPage() + { + doOpenLink(true); + } + +public: + QString lastAnchor; + int zoomCount; + bool forceFont = false; + +private: + + void doOpenLink(bool newPage) + { + if (lastAnchor.isEmpty()) + return; + if (newPage) + OpenPagesManager::instance()->createPage(lastAnchor); + else + CentralWidget::instance()->setSource(lastAnchor); + lastAnchor.clear(); + } + +#elif defined(BROWSER_QTWEBKIT) + qreal webDpiRatio; +#endif // BROWSER_QTWEBKIT +}; + +QT_END_NAMESPACE + +#endif // HELPVIEWERIMPLPRIVATE_H diff --git a/src/assistant/assistant/helpviewerimpl_qtb.cpp b/src/assistant/assistant/helpviewerimpl_qtb.cpp new file mode 100644 index 0000000..c0c1fa0 --- /dev/null +++ b/src/assistant/assistant/helpviewerimpl_qtb.cpp @@ -0,0 +1,373 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "helpviewerimpl.h" +#include "helpviewerimpl_p.h" + +#include "globalactions.h" +#include "helpenginewrapper.h" +#include "openpagesmanager.h" +#include "tracer.h" + +#include + +#include +#include +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include + +QT_BEGIN_NAMESPACE + +HelpViewerImpl::HelpViewerImpl(qreal zoom, QWidget *parent) + : QTextBrowser(parent) + , d(new HelpViewerImplPrivate(zoom)) +{ + TRACE_OBJ + QPalette p = palette(); + p.setColor(QPalette::Inactive, QPalette::Highlight, + p.color(QPalette::Active, QPalette::Highlight)); + p.setColor(QPalette::Inactive, QPalette::HighlightedText, + p.color(QPalette::Active, QPalette::HighlightedText)); + setPalette(p); + + installEventFilter(this); + document()->setDocumentMargin(8); + + QFont font = viewerFont(); + font.setPointSize(int(font.pointSize() + zoom)); + setViewerFont(font); + + connect(this, &QTextBrowser::sourceChanged, this, &HelpViewerImpl::titleChanged); + connect(this, &HelpViewerImpl::loadFinished, this, &HelpViewerImpl::setLoadFinished); +} + +QFont HelpViewerImpl::viewerFont() const +{ + TRACE_OBJ + if (HelpEngineWrapper::instance().usesBrowserFont()) + return HelpEngineWrapper::instance().browserFont(); + return qApp->font(); +} + +void HelpViewerImpl::setViewerFont(const QFont &newFont) +{ + TRACE_OBJ + if (font() != newFont) { + d->forceFont = true; + setFont(newFont); + d->forceFont = false; + } +} + +void HelpViewerImpl::scaleUp() +{ + TRACE_OBJ + if (d->zoomCount < 10) { + d->zoomCount++; + d->forceFont = true; + zoomIn(); + d->forceFont = false; + } +} + +void HelpViewerImpl::scaleDown() +{ + TRACE_OBJ + if (d->zoomCount > -5) { + d->zoomCount--; + d->forceFont = true; + zoomOut(); + d->forceFont = false; + } +} + +void HelpViewerImpl::resetScale() +{ + TRACE_OBJ + if (d->zoomCount != 0) { + d->forceFont = true; + zoomOut(d->zoomCount); + d->forceFont = false; + } + d->zoomCount = 0; +} + +qreal HelpViewerImpl::scale() const +{ + TRACE_OBJ + return d->zoomCount; +} + +QString HelpViewerImpl::title() const +{ + TRACE_OBJ + return documentTitle(); +} + +QUrl HelpViewerImpl::source() const +{ + TRACE_OBJ + return QTextBrowser::source(); +} + +void HelpViewerImpl::doSetSource(const QUrl &url, QTextDocument::ResourceType type) +{ + TRACE_OBJ + Q_UNUSED(type); + if (HelpViewer::launchWithExternalApp(url)) + return; + + bool helpOrAbout = (url.toString() == "help"_L1); + const QUrl resolvedUrl = (helpOrAbout ? LocalHelpFile : HelpEngineWrapper::instance().findFile(url)); + + QTextBrowser::doSetSource(resolvedUrl, type); + + if (!resolvedUrl.isValid()) { + helpOrAbout = (url.toString() == "about:blank"_L1); + setHtml(helpOrAbout ? AboutBlank : PageNotFoundMessage.arg(url.toString())); + } + emit loadFinished(true); +} + +QString HelpViewerImpl::selectedText() const +{ + TRACE_OBJ + return textCursor().selectedText(); +} + +bool HelpViewerImpl::isForwardAvailable() const +{ + TRACE_OBJ + return QTextBrowser::isForwardAvailable(); +} + +bool HelpViewerImpl::isBackwardAvailable() const +{ + TRACE_OBJ + return QTextBrowser::isBackwardAvailable(); +} + +bool HelpViewerImpl::findText(const QString &text, HelpViewer::FindFlags flags, bool incremental, + bool fromSearch) +{ + TRACE_OBJ + QTextDocument *doc = document(); + QTextCursor cursor = textCursor(); + if (!doc || cursor.isNull()) + return false; + + const int position = cursor.selectionStart(); + if (incremental) + cursor.setPosition(position); + + QTextDocument::FindFlags textDocFlags; + if (flags & HelpViewer::FindBackward) + textDocFlags |= QTextDocument::FindBackward; + if (flags & HelpViewer::FindCaseSensitively) + textDocFlags |= QTextDocument::FindCaseSensitively; + + QTextCursor found = doc->find(text, cursor, textDocFlags); + if (found.isNull()) { + if ((flags & HelpViewer::FindBackward) == 0) + cursor.movePosition(QTextCursor::Start); + else + cursor.movePosition(QTextCursor::End); + found = doc->find(text, cursor, textDocFlags); + } + + if (fromSearch) { + cursor.beginEditBlock(); + viewport()->setUpdatesEnabled(false); + + QTextCharFormat marker; + marker.setForeground(Qt::red); + cursor.movePosition(QTextCursor::Start); + setTextCursor(cursor); + + while (find(text)) { + QTextCursor hit = textCursor(); + hit.mergeCharFormat(marker); + } + + viewport()->setUpdatesEnabled(true); + cursor.endEditBlock(); + } + + bool cursorIsNull = found.isNull(); + if (cursorIsNull) { + found = textCursor(); + found.setPosition(position); + } + setTextCursor(found); + return !cursorIsNull; +} + +// -- public slots + +#if QT_CONFIG(clipboard) +void HelpViewerImpl::copy() +{ + TRACE_OBJ + QTextBrowser::copy(); +} +#endif + +void HelpViewerImpl::forward() +{ + TRACE_OBJ + QTextBrowser::forward(); +} + +void HelpViewerImpl::backward() +{ + TRACE_OBJ + QTextBrowser::backward(); +} + +// -- protected + +void HelpViewerImpl::keyPressEvent(QKeyEvent *e) +{ + TRACE_OBJ + if ((e->key() == Qt::Key_Home && e->modifiers() != Qt::NoModifier) + || (e->key() == Qt::Key_End && e->modifiers() != Qt::NoModifier)) { + QKeyEvent* event = new QKeyEvent(e->type(), e->key(), Qt::NoModifier, + e->text(), e->isAutoRepeat(), e->count()); + e = event; + } + QTextBrowser::keyPressEvent(e); +} + +void HelpViewerImpl::wheelEvent(QWheelEvent *e) +{ + TRACE_OBJ + if (e->modifiers() == Qt::ControlModifier) { + e->accept(); + e->angleDelta().y() > 0 ? scaleUp() : scaleDown(); + } else { + QTextBrowser::wheelEvent(e); + } +} + +void HelpViewerImpl::mousePressEvent(QMouseEvent *e) +{ + TRACE_OBJ +#ifdef Q_OS_LINUX + if (handleForwardBackwardMouseButtons(e)) + return; +#endif + + QTextBrowser::mousePressEvent(e); +} + +void HelpViewerImpl::mouseReleaseEvent(QMouseEvent *e) +{ + TRACE_OBJ +#ifndef Q_OS_LINUX + if (handleForwardBackwardMouseButtons(e)) + return; +#endif + + bool controlPressed = e->modifiers() & Qt::ControlModifier; + if ((controlPressed && d->hasAnchorAt(this, e->pos())) || + (e->button() == Qt::MiddleButton && d->hasAnchorAt(this, e->pos()))) { + d->openLinkInNewPage(); + return; + } + + QTextBrowser::mouseReleaseEvent(e); +} + + +void HelpViewerImpl::resizeEvent(QResizeEvent *e) +{ + const int topTextPosition = cursorForPosition({width() / 2, 0}).position(); + QTextBrowser::resizeEvent(e); + scrollToTextPosition(topTextPosition); +} + +// -- private slots + +void HelpViewerImpl::actionChanged() +{ + // stub + TRACE_OBJ +} + +// -- private + +bool HelpViewerImpl::eventFilter(QObject *obj, QEvent *event) +{ + TRACE_OBJ + if (event->type() == QEvent::FontChange && !d->forceFont) + return true; + return QTextBrowser::eventFilter(obj, event); +} + +void HelpViewerImpl::contextMenuEvent(QContextMenuEvent *event) +{ + TRACE_OBJ + + QMenu menu(QString(), nullptr); + QUrl link; +#if QT_CONFIG(clipboard) + QAction *copyAnchorAction = nullptr; +#endif + if (d->hasAnchorAt(this, event->pos())) { + link = anchorAt(event->pos()); + if (link.isRelative()) + link = source().resolved(link); + menu.addAction(tr("Open Link"), d, &HelpViewerImplPrivate::openLink); + menu.addAction(tr("Open Link in New Tab\tCtrl+LMB"), d, &HelpViewerImplPrivate::openLinkInNewPage); + +#if QT_CONFIG(clipboard) + if (!link.isEmpty() && link.isValid()) + copyAnchorAction = menu.addAction(tr("Copy &Link Location")); +#endif + } else if (!selectedText().isEmpty()) { +#if QT_CONFIG(clipboard) + menu.addAction(tr("Copy"), this, &HelpViewerImpl::copy); +#endif + } else { + menu.addAction(tr("Reload"), this, &HelpViewerImpl::reload); + } + +#if QT_CONFIG(clipboard) + if (copyAnchorAction == menu.exec(event->globalPos())) + QApplication::clipboard()->setText(link.toString()); +#endif +} + +QVariant HelpViewerImpl::loadResource(int type, const QUrl &name) +{ + TRACE_OBJ + QByteArray ba; + if (type < 4) { + const QUrl url = HelpEngineWrapper::instance().findFile(name); + ba = HelpEngineWrapper::instance().fileData(url); + if (url.toString().endsWith(".svg"_L1, Qt::CaseInsensitive)) { + QImage image; + image.loadFromData(ba, "svg"); + if (!image.isNull()) + return image; + } + } + return ba; +} + + +void HelpViewerImpl::scrollToTextPosition(int position) +{ + QTextCursor tc(document()); + tc.setPosition(position); + const int dy = cursorRect(tc).top(); + if (verticalScrollBar()) { + verticalScrollBar()->setValue( + std::min(verticalScrollBar()->value() + dy, verticalScrollBar()->maximum())); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/helpviewerimpl_qwv.cpp b/src/assistant/assistant/helpviewerimpl_qwv.cpp new file mode 100644 index 0000000..7056610 --- /dev/null +++ b/src/assistant/assistant/helpviewerimpl_qwv.cpp @@ -0,0 +1,370 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "helpviewerimpl.h" +#include "helpviewerimpl_p.h" + +#include "centralwidget.h" +#include "helpenginewrapper.h" +#include "helpbrowsersupport.h" +#include "openpagesmanager.h" +#include "tracer.h" + +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +// -- HelpPage + +class HelpPage : public QWebPage +{ +public: + HelpPage(QObject *parent); + +protected: + virtual QWebPage *createWindow(QWebPage::WebWindowType); + virtual void triggerAction(WebAction action, bool checked = false); + + virtual bool acceptNavigationRequest(QWebFrame *frame, + const QNetworkRequest &request, NavigationType type); + +private: + bool closeNewTabIfNeeded; + + friend class HelpViewerImpl; + QUrl m_loadingUrl; + Qt::MouseButtons m_pressedButtons; + Qt::KeyboardModifiers m_keyboardModifiers; +}; + +HelpPage::HelpPage(QObject *parent) + : QWebPage(parent) + , closeNewTabIfNeeded(false) + , m_pressedButtons(Qt::NoButton) + , m_keyboardModifiers(Qt::NoModifier) +{ + TRACE_OBJ +} + +QWebPage *HelpPage::createWindow(QWebPage::WebWindowType) +{ + TRACE_OBJ + HelpPage *newPage = static_cast(OpenPagesManager::instance() + ->createBlankPage()->page()); + newPage->closeNewTabIfNeeded = closeNewTabIfNeeded; + closeNewTabIfNeeded = false; + return newPage; +} + +void HelpPage::triggerAction(WebAction action, bool checked) +{ + TRACE_OBJ + switch (action) { + case OpenLinkInNewWindow: + closeNewTabIfNeeded = true; + Q_FALLTHROUGH(); + default: + QWebPage::triggerAction(action, checked); + break; + } + +#if QT_CONFIG(clipboard) + if (action == CopyLinkToClipboard || action == CopyImageUrlToClipboard) { + const QString link = QApplication::clipboard()->text(); + QApplication::clipboard()->setText(HelpEngineWrapper::instance().findFile(link).toString()); + } +#endif +} + +bool HelpPage::acceptNavigationRequest(QWebFrame *, + const QNetworkRequest &request, QWebPage::NavigationType type) +{ + TRACE_OBJ + const bool closeNewTab = closeNewTabIfNeeded; + closeNewTabIfNeeded = false; + + const QUrl &url = request.url(); + if (HelpViewer::launchWithExternalApp(url)) { + if (closeNewTab) + QMetaObject::invokeMethod(OpenPagesManager::instance(), "closeCurrentPage"); + return false; + } + + if (type == QWebPage::NavigationTypeLinkClicked + && (m_keyboardModifiers & Qt::ControlModifier + || m_pressedButtons == Qt::MiddleButton)) { + m_pressedButtons = Qt::NoButton; + m_keyboardModifiers = Qt::NoModifier; + OpenPagesManager::instance()->createPage(url); + return false; + } + + m_loadingUrl = url; // because of async page loading, we will hit some kind + // of race condition while using a remote command, like a combination of + // SetSource; SyncContent. SetSource would be called and SyncContents shortly + // afterwards, but the page might not have finished loading and the old url + // would be returned. + return true; +} + +// -- HelpViewerImpl + +HelpViewerImpl::HelpViewerImpl(qreal zoom, QWidget *parent) + : QWebView(parent) + , d(new HelpViewerImplPrivate) +{ + TRACE_OBJ + setAcceptDrops(false); + settings()->setAttribute(QWebSettings::JavaEnabled, false); + settings()->setAttribute(QWebSettings::PluginsEnabled, false); + + setPage(new HelpPage(this)); + page()->setNetworkAccessManager(HelpBrowserSupport::createNetworkAccessManager(this)); + + QAction* action = pageAction(QWebPage::OpenLinkInNewWindow); + action->setText(tr("Open Link in New Page")); + + pageAction(QWebPage::DownloadLinkToDisk)->setVisible(false); + pageAction(QWebPage::DownloadImageToDisk)->setVisible(false); + pageAction(QWebPage::OpenImageInNewWindow)->setVisible(false); + + connect(pageAction(QWebPage::Copy), SIGNAL(changed()), this, + SLOT(actionChanged())); + connect(pageAction(QWebPage::Back), SIGNAL(changed()), this, + SLOT(actionChanged())); + connect(pageAction(QWebPage::Forward), SIGNAL(changed()), this, + SLOT(actionChanged())); + connect(page(), &QWebPage::linkHovered, this, + [this] (const QString &link, const QString &, const QString &) { + emit this->highlighted(QUrl(link)); + }); + connect(this, SIGNAL(urlChanged(QUrl)), this, SIGNAL(sourceChanged(QUrl))); + connect(this, SIGNAL(loadStarted()), this, SLOT(setLoadStarted())); + connect(this, SIGNAL(loadFinished(bool)), this, SLOT(setLoadFinished(bool))); + connect(this, SIGNAL(titleChanged(QString)), this, SIGNAL(titleChanged())); + connect(page(), SIGNAL(printRequested(QWebFrame*)), this, SIGNAL(printRequested())); + + setFont(viewerFont()); + setZoomFactor(d->webDpiRatio * (zoom == 0.0 ? 1.0 : zoom)); +} + +QFont HelpViewerImpl::viewerFont() const +{ + TRACE_OBJ + if (HelpEngineWrapper::instance().usesBrowserFont()) + return HelpEngineWrapper::instance().browserFont(); + + QWebSettings *webSettings = QWebSettings::globalSettings(); + return QFont(webSettings->fontFamily(QWebSettings::StandardFont), + webSettings->fontSize(QWebSettings::DefaultFontSize)); +} + +void HelpViewerImpl::setViewerFont(const QFont &font) +{ + TRACE_OBJ + QWebSettings *webSettings = settings(); + webSettings->setFontFamily(QWebSettings::StandardFont, font.family()); + webSettings->setFontSize(QWebSettings::DefaultFontSize, font.pointSize()); +} + +void HelpViewerImpl::scaleUp() +{ + TRACE_OBJ + setZoomFactor(zoomFactor() + 0.1); +} + +void HelpViewerImpl::scaleDown() +{ + TRACE_OBJ + setZoomFactor(qMax(0.0, zoomFactor() - 0.1)); +} + +void HelpViewerImpl::resetScale() +{ + TRACE_OBJ + setZoomFactor(d->webDpiRatio); +} + +qreal HelpViewerImpl::scale() const +{ + TRACE_OBJ + return zoomFactor() / d->webDpiRatio; +} + +QString HelpViewerImpl::title() const +{ + TRACE_OBJ + return QWebView::title(); +} + +void HelpViewerImpl::setTitle(const QString &title) +{ + TRACE_OBJ + Q_UNUSED(title); +} + +QUrl HelpViewerImpl::source() const +{ + TRACE_OBJ + HelpPage *currentPage = static_cast (page()); + if (currentPage && !d->m_loadFinished) { + // see HelpPage::acceptNavigationRequest(...) + return currentPage->m_loadingUrl; + } + return url(); +} + +void HelpViewerImpl::setSource(const QUrl &url) +{ + TRACE_OBJ + load(url.toString() == QLatin1String("help") ? LocalHelpFile : url); +} + +QString HelpViewerImpl::selectedText() const +{ + TRACE_OBJ + return QWebView::selectedText(); +} + +bool HelpViewerImpl::isForwardAvailable() const +{ + TRACE_OBJ + return pageAction(QWebPage::Forward)->isEnabled(); +} + +bool HelpViewerImpl::isBackwardAvailable() const +{ + TRACE_OBJ + return pageAction(QWebPage::Back)->isEnabled(); +} + +bool HelpViewerImpl::findText(const QString &text, HelpViewer::FindFlags flags, bool incremental, + bool fromSearch) +{ + TRACE_OBJ + Q_UNUSED(incremental); Q_UNUSED(fromSearch); + QWebPage::FindFlags options = QWebPage::FindWrapsAroundDocument; + if (flags & HelpViewer::FindBackward) + options |= QWebPage::FindBackward; + if (flags & HelpViewer::FindCaseSensitively) + options |= QWebPage::FindCaseSensitively; + + bool found = QWebView::findText(text, options); + options = QWebPage::HighlightAllOccurrences; + QWebView::findText(QString(), options); // clear first + QWebView::findText(text, options); // force highlighting of all other matches + return found; +} + +// -- public slots + +#if QT_CONFIG(clipboard) +void HelpViewerImpl::copy() +{ + TRACE_OBJ + triggerPageAction(QWebPage::Copy); +} +#endif + +void HelpViewerImpl::forward() +{ + TRACE_OBJ + QWebView::forward(); +} + +void HelpViewerImpl::backward() +{ + TRACE_OBJ + back(); +} + +// -- protected + +void HelpViewerImpl::keyPressEvent(QKeyEvent *e) +{ + TRACE_OBJ + // TODO: remove this once we support multiple keysequences per command +#if QT_CONFIG(clipboard) + if (e->key() == Qt::Key_Insert && e->modifiers() == Qt::CTRL) { + if (!selectedText().isEmpty()) + copy(); + } +#endif + QWebView::keyPressEvent(e); +} + +void HelpViewerImpl::wheelEvent(QWheelEvent *event) +{ + TRACE_OBJ + if (event->modifiers()& Qt::ControlModifier) { + event->accept(); + event->delta() > 0 ? scaleUp() : scaleDown(); + } else { + QWebView::wheelEvent(event); + } +} + +void HelpViewerImpl::mousePressEvent(QMouseEvent *event) +{ + TRACE_OBJ +#ifdef Q_OS_LINUX + if (handleForwardBackwardMouseButtons(event)) + return; +#endif + + if (HelpPage *currentPage = static_cast (page())) { + currentPage->m_pressedButtons = event->buttons(); + currentPage->m_keyboardModifiers = event->modifiers(); + } + + QWebView::mousePressEvent(event); +} + +void HelpViewerImpl::mouseReleaseEvent(QMouseEvent *event) +{ + TRACE_OBJ +#ifndef Q_OS_LINUX + if (handleForwardBackwardMouseButtons(event)) + return; +#endif + + QWebView::mouseReleaseEvent(event); +} + +// -- private slots + +void HelpViewerImpl::actionChanged() +{ + TRACE_OBJ + QAction *a = qobject_cast(sender()); + if (a == pageAction(QWebPage::Copy)) + emit copyAvailable(a->isEnabled()); + else if (a == pageAction(QWebPage::Back)) + emit backwardAvailable(a->isEnabled()); + else if (a == pageAction(QWebPage::Forward)) + emit forwardAvailable(a->isEnabled()); +} + +// -- private + +bool HelpViewerImpl::eventFilter(QObject *obj, QEvent *event) +{ + TRACE_OBJ + return QWebView::eventFilter(obj, event); +} + +void HelpViewerImpl::contextMenuEvent(QContextMenuEvent *event) +{ + TRACE_OBJ + QWebView::contextMenuEvent(event); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/images/assistant-128.png b/src/assistant/assistant/images/assistant-128.png new file mode 100644 index 0000000000000000000000000000000000000000..62c66ed8f5d0466b73d2c3ab84ac46b3554cb87b GIT binary patch literal 4118 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 literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/assistant.png b/src/assistant/assistant/images/assistant.png new file mode 100644 index 0000000000000000000000000000000000000000..6f124afb68b5a30ff47dccc5928e58808cc00aa1 GIT binary patch literal 586 zcmV-Q0=4~#P)nalm5tSwlG>Y`+@zf4q@mrZq~5Ej>9DEbu&U^>tl_e)>9nuo zwzcNFy6wTe?!&<8#m4N*#_-F??aj;a&&~4D(ec#M@zvAx*VXmd+4I}m_1)a{-`)1$ z;P&F-_vGXF<#tKW1ONa48gx=lQviT~gM{em`1tz!`}_O*{r&y@{{H^{{{H^8sglV6 z007QOL_t(I%VS^|Mu15JDIVnuI!9UIP#`5GY!d0AX&&W@Q-PFql#QTq zD3T-46ezfd8Au8EAvpp~fofoYnv}Fdl%*sN1$q%tU|`RwB7s$bb(EK_t*uLhuaXp( zxCEvGdAA5dAf@OXq79_@xz*7XfSn*ECF>Mr3g*kOv!Z(w9WWD6z{r4K0bHJeko+(J Y0F(7f`b+svTL1t607*qoM6N<$f-O-P<^TWy literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/bookmark.png b/src/assistant/assistant/images/bookmark.png new file mode 100644 index 0000000000000000000000000000000000000000..57e57e343ba5e5d710fe01b1720d06aacf97863d GIT binary patch literal 1266 zcmV9LGP;bIv_?=gzz9Hg=ornwyDknlIqOG7U@{NwlUSE{H~u{$pfNP+;XBL4Opa zMSp1Z&nk^dH;sB$B=92T7F%78$72U-7F9OjK{~fq-u%k2VfeHgdniaGA(?~`V}>gs5=G_0;CjVJeJH4!rdbjjz%j==~g5>+3f5+ z_mX8>1sRZr41`4vN$yZmf?pa*t z^z^vR|2{xP^c6~4o;RK4ti$d-h7dQ-$;7setbQ&USXiA}(NrUq<0z8 zX`FJKy8!MayZ7F8I_<0n7Okag@0(OS-9czY9n$_50Ac8quPjC@D`)V_@6_&#^LBiF z=Gdj&0Jv92cbmJ$t+t(A8{KPPK11rCcFc;!^nU&Uk(UL*RrO$}@jpBlB@LoqZRh7d zA52aO4&|Mx7t1I3Xw`EBpfufqaGKuUslWCUOnr=h{%o*;d56Ka;Q z>M8u#)RTIn{?cbVdUGcrLDxWe0gw`VBrw z#iJ2Y)!QhWyKwZQRw<+=FrV2(3=A-+X9e`lZ1faoTaFRUr@0A0Qmv`EuUkZaaKXuG_sN& zp5t+rcL2pWNg%u(fo0jGrQm+bvTScNpp;5~(%52K$`&3o@f4(?YnHNA+5-TkrFjEe c0F!ane_O^9CT!(x-T(jq07*qoM6N<$f&(L63;+NC literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/closebutton.png b/src/assistant/assistant/images/closebutton.png new file mode 100644 index 0000000000000000000000000000000000000000..c978cf51aaaf35cf4bf3939f3ba84eabce4837e8 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X4tcsbhE&{2`t$$4J+o>fLX@$qy77icq|D82#~o@UI%FGnFq~Btbdcdaa=qb)lAid10!D#+w<1rL zDfjt~$HiDfH1;xhQs(E8f(+8UPL^fe26Q#y3@m>q6Nw5LgI ja}!UN`EZ9rAc4W4K;+!fY<(x7Zx}pX{an^LB{Ts5`G;qN literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/darkclosebutton.png b/src/assistant/assistant/images/darkclosebutton.png new file mode 100644 index 0000000000000000000000000000000000000000..147a954bea1b8f787341a145861ac200bbb23f2b GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf63O!vMLp07OXCxfpmt3*TVOt}o zs}pBvW6t4QZAvPtg1pkwK7qa(88>*sg&0$|n{QaSn{gS7tJKwr3aXO>mtL$j6x7(e zq>*{LxnrBYa}oC}rWtoxS9o0j${b;0)%2jm&8H3-zI#tFG0dx&l_O^R R;|S1l22WQ%mvv4FO#q9hI5+?R literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/mac/book.png b/src/assistant/assistant/images/mac/book.png new file mode 100644 index 0000000000000000000000000000000000000000..7a3204c8704510001cda048891b43202e5608353 GIT binary patch literal 1477 zcmV;$1v>hPP)q2?EG7l|u?uv&+wJb`%<(@bC-nrT*^<=oB)^{1 z>DfHbod5agGR`?3r!+if8CXFv5@c8L-~>47Kbrw2#B}*WeqtN(4)H3{LFhC(Y1&>BSQ#PVA+&!!%6|q(~%E z3t%l_%2GJ8tsa0px&U;0`Ac*nmP)18_4oH5OeT|Ux~@YN{mx{--Ng4qg>bwAsK)KC z@%i)TkM#ETZgm_7+~Ket8*bmWZIntS6pKYnPfr^?Jv}}WmeTd>*N;4-F0Rfz$8jK6qo_8Mh>g=R=FIdw-XGpnPj2%)!Om3Mw`#l?p7&qPH)j zSeVCLE{B){!I zF}C`%xAyF)R2+mmwn5#pV6x#5b#q)CQ6f~8TxWsSZVJNi-s?NN$*ak$1mP#5W{YJr zXpt5u2d7sMQHJGSWHmK4aLg!54nwjn+6kc1D?lXdXK@QYL=zhxI4}4^J%;7WG))9z z36!}HHHH%h5cN6-RvCqU3PvzC??#9}eTUC2BhkBb1b5XE-XYZ_qKR=w+~257Q! z*_he54rkAv6*Z#}i^w}c^vjlog+2KaIp;8R1AfylrkxPmk6L$}psTA3 zfu{9zB+tYioPpKp(V?Ijs)6(O$6^72b8zJ(6Ozvi3Sv1N`Bj^dFBDOC`!}&VGzRFJ z1|M~pu7PVniCPOlk)DkWg~0Mzl!}F*>!}97_$)`|ejYl@V+jd^fB}RIhJXgJ*_#NF z%bb-*!3O3l0Of+t+_AE7vUMd3i=cE>1B|Xt?YcENoryt;bMaxOo(Y=9m`?}p-}%eb zi?i@ejKi{loCR2B9U^PimZTkL%6fsEJh$)Hfkac|U8H{)RRjF``L`GTC+ng~{6s*s zgdD3A?9j`*j*R?xrBP%5hTHtiv*TZUci}7L|GqL&k!?w0$-hk!ql@)bA!*V$Myw$= z5h-Fj(Mud4j=r!xb@|O#_KY7oxOaT_bE(S!J|uP%&l5X|t;Ewr7m*;sgpbfvKLd}h zIb!(APrr;Sw_1j{M>s3>&B&3{XUQqlHz@z>mK-KRBoWvLtZ=lRZS(>W>m{$M=TbV^m_8m@IP6;?+E3M^W>=l4} f29d2aSA~B7Fld^U-BB|&00000NkvXXu0mjfat*vm literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/mac/closetab.png b/src/assistant/assistant/images/mac/closetab.png new file mode 100644 index 0000000000000000000000000000000000000000..ab9d669eeebed9718363bd06ba84fdfd9e7cd6b5 GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMf)c~Im*Z=?jtE;Q)=;&BlTH4uz z0FbS#tLxz4;OOY6t*vcrY;0#|=i=((>goy*VC*;Rym}W@bPVXt_v{4bWI$Utd2zKcFE%#ZggFv9Yla9z1yT z=+WcHkDoq$`uzEGpmovF(ca$PK*OD#o#W!-l9G~KTwJoUva++Yi;Iif+uLW(oH={; z>^XDh+`D%V=rEugfYybCgoK8MIyyQW>XKdu^q_o6kY6x^2?(SEK?4XJ0D-GluU>l# z0v|tqT5?z%sC2)li(`m{WbVQ1Vh0^WST8Itzqt7R8J}4qF1|`02mjX#o&J=O|6qPh ztW^KfDn9$C2OYoKd^ON`yn53GBWDif%VMH6PulHvw?8kgNdITRzaVjwbgtfX-n%ny zGF%9px6S#suI0Djmkcuuyx+tsmFw%6F&c<|%X}`hbN6K3WemISa9w+1v4v0P9_xiG z^5I>frpsPC=QC;a^n{j}#2?Q{?cw^dLN8eNq0PlhsiN_wkryqmi|%ZjpS0V4_V*~& bKcCs&oRdi2E+elG^a_KgtDnm{r-UW|i^$5o literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/mac/editcopy.png b/src/assistant/assistant/images/mac/editcopy.png new file mode 100644 index 0000000000000000000000000000000000000000..f551364464a7f4500a2660fd3aa450e79010b0cb GIT binary patch literal 1468 zcmV;t1w;CYP)j91nuo^rMU``FpK}lmcH2BoZ zAH4JCyEWmjwMzgT#=uOCdmd7JuO$v)s8lK_84mKr3f?+?`0VR1?tg<)*HQ}J{^;_d zx+S0Y_4P-WQtN}){+tE#xOPNB3Z5T~fVOJdJdnQKmA@NUp8wZ z?$zp{)J%XNrjyRSus42A?Cl?jb<@afrs%r1WnkxaM7vZ3@+)At< zl`TV!sF2PwGTtu5AKvyjs4rA-qt%${9h(@D41&{(T zn8XDH>P|Q8whCx0La*w<^E@zO22eD33b<_!Q`>H{03sKo>$;F2@wvzODg1sK81Se7 zXg2VlTtfnRF4nRvL9>8>%_G3Q_jpaTF#^JSA{Ha<@i)NsIHARWyv9?(=O$}&1Ssr8 zEDDn5bPZisMnMBy1?bt#Jx4g$#%n0TPNX!_qa+0JbW~kLfJ9nCPGg4pF{+ML3+gaHLO^`>>3d5AaWOXXLDedCV3tU)P&1)oPTCjX*K5}0@QqH zO(G~76n^TCB#t^`Ik$*k#>bF(lmH~zwB+suqqLR=5F{VyIR3p>h-svrPjk~WiIfCT zL!y!I&5m8e$d!l4mh?p>q)stJ7Fjj8$jsFE%#H5~03raG4_Dc!$cT(gi@c~ zLseB0#6L51ows7ID$$1^8eBO$E%%%;5Jcm#~)CE0Jxx)4}dz+2FRW35VI&O1b5c ztKVJ_qymyagMj8jXkHb6&l~<30XPL<7Qj^iH-Gr@Oj@d$Ao7XPguGq^8idrf;(q`V WtAfH(WC6_p0000TU8fuOnJz9Z;7>&l9T#6`) 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 literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/mac/next.png b/src/assistant/assistant/images/mac/next.png new file mode 100644 index 0000000000000000000000000000000000000000..a585cab80c1435972d12fc8e4eabb631a116101f GIT binary patch literal 1310 zcma)4X;6~~5Dg%T)Qd9$R-hn=L5fhWihvMK6@dUzTMJf8y(!REKnFiFaw7t{B5HkXuoebvA_CZiws;@&-$dk8lW%nTu1kM|c^mq~ z5pR?4W^!v@4_Y1hUc!7nna3x&jii5kR%NrSu$HV9$vmSoSbMns?88JA$!{fvV$v_} zkaN(E0s)y?M*6ZceWFt9L<2&}pl?jNjceF(A?XpZOV~E(y)lJu_ep7kRwb)wB3A`} zStshR7fap4cN$qb>^;)3hE2HmeNo>g6^f*8%pD8NmhjHVjOljl-9^rRTjm*CRkg^B z=yvtuLG21RCvfc{k{i)G2LD_nRw1PkiKmdE%ZseTB$sTlHhtE;8DA05nZUmyaD87Kq798&c_h;0}&@&2=0{5oS zJ&INZu8c!9g+@7^zQD{2@CP8jjYA?#K7&ew+;+%rL;VyZ_c8GlqG6mFf~`AV-~Pb2 zF_A2qB3)N!mVuGJrSa^As}1KQQ7iox%-1vez!XZ7@!28erw=$I$=M8mK83z}()R-RKpl@PlFv}}GxoukWdtV)A zM&0#CYjwxgJB3M+N{PE6OaDRtuIG*lO@gcB-+O~i9j!BqS1hJmi_T~g;@E5!>)M4z zs+eKY#d$n7snIYr491Zi`_=_zWX|SsntfKfx*iytUNgPe;d52Hr1S5nw6xD|l?vs> z6RVtrjOG5{Wvc#5WAJz`p3Xh7MYdP^(y7U*f`*}Tm7gRdb)#;huf5=qaYf5kW%Tr= zhuJQ}{mwF}bl7_QJhh(O3?d*t$=uvDBYuDA+~}p`h}g<8IXjfO`}vIC4>m(*^HVQb zXm*EG)buVgv64IO>l=_ui%RI*AC$(6JIakCBJP%y@f?zqT?Q`wDmV1vRK^8HhFlIC zD)Dk>tahwmk4i&+DPSG7+CR%Z*Wkydnig?aNY>hIrDfb|{kDdS_H-+A{fK2RU*0tB zYaKB%Hy@R@>)sFZFf%jru$>qB(M6}G=H?YmQ908am;sWsloSc$S)O2ysqblJ0Kw;1 c={cR&`R7T_c+c>)8tpgH!h<83qQFGX|LB5QfdBvi literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/mac/previous.png b/src/assistant/assistant/images/mac/previous.png new file mode 100644 index 0000000000000000000000000000000000000000..612fb34dced693312ee764ee325acaea71c45766 GIT binary patch literal 1080 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 RY-0cb002ovPDHLkV1oVh`pp0U literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/mac/resetzoom.png b/src/assistant/assistant/images/mac/resetzoom.png new file mode 100644 index 0000000000000000000000000000000000000000..759b38296822035df02c13f7ffd9af933409833a GIT binary patch literal 1567 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 literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/mac/zoomin.png b/src/assistant/assistant/images/mac/zoomin.png new file mode 100644 index 0000000000000000000000000000000000000000..d46f5aff0d9810d21bc98be02776e843b2db7ee9 GIT binary patch literal 1696 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 IM6N<$f`v>W)&Kwi literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/win/book.png b/src/assistant/assistant/images/win/book.png new file mode 100644 index 0000000000000000000000000000000000000000..09ec4d33f7bb3ba78fe5d76832e230b570188eec GIT binary patch literal 1109 zcmV-b1giUqP)ZJNNHUCL|*bkSK|t+Pw-z^1@61g@yekOHXmP{IVCrZ3~xFKL|H+A zMtt!i1LCtzY03%^;M)_3=izMWrSIj}cD_0A)LVmV6Hih{ektE;XMr+phobIC8|Ca;!UL1ZCYk*l*=IX8JlzW(Nj%q`?)B@-7HKcW#ZVg^7-q* z5AV3hp2wDTJxiN4Gn9PDXBWxm=E!BPlUtZ!K6!;p7tWDOCs|2L$YL&;B+#6}u-E|% zHZ)L*XfZnl#86cb5frtE4=@rBO@amIPew8*%?~e`jCnc?`mE%*sV7JhPEB&(XiW$+rAwD+it>f972wZ zW4TziO>GW#t%h6|fbsEhcJKa}g9k^Lo}OmIzyOK$UBn(9!m_%Ecdm)_#DarBOTbXo zZp(_2@nWPFgiYKN}l6eSKk(tyLeN z+rSqjWMU3I(*1m@oQPsrL^}%1%qD*rJ)fPhn$Swn0KzR=qc5jen40AxaOU=j0BeB& zAT*M#>sdY2@4j!Ef!NqoZu00K)eFFHz$L)oP8!Gn+57(JE2T`JA8>)oA~MZ=c;J5( bng{G}W!lqy1LmiU00000NkvXXu0mjf$HfGj literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/win/closetab.png b/src/assistant/assistant/images/win/closetab.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9e02086c67814fd00c89620fd0d981b997e6cc GIT binary patch literal 375 zcmV--0f_#IP)fruJ3jc8<|8pe&b0zCL@6z2E5pzd+(iAlUs$!iqI4S^6p4AC9Nf@vzUPSy(P~ z1SuhEk+Uxp0002ovPDHLkV1ikVxH|v< literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/win/editcopy.png b/src/assistant/assistant/images/win/editcopy.png new file mode 100644 index 0000000000000000000000000000000000000000..1121b47d8b6b48f6cdf525851e7c0f502454ffe1 GIT binary patch literal 1325 zcmV+|1=9M7P)+{buk{F=nCJX#i^AQGtBfP^U9E=s!&EKn3+*9AyOC5y5_m4HQB2~pH` zRYX%2)m58>DgqW&sAx;EBU4=37{7w!SM2eOJs!`@eVlXtjqY3tTS5?(-{yaHb@l)B zec!pdSBVIB!=;~|e%P7xymRJ$5osgh_&4efy`JyCUMhx9Kk}ogMRI&{UihAR)H(VP z5`ixu00_b{5e5q-zVY>M|NPMBA38(Y-oZLchI61bL7|V$#mUE~-@NcRpbiK;|6O(N zfk%J%Y@t+fHjUDudrucpC_qYs_XMTNy?z?C^q*foM`|MO`|`smOlCq5Y$|AffYRZk z1m#2g^824X`-5laF9IMU{OT;%tWD%U5hqqQHn+rzNOfaXUit9}xpwhevNrpY)K=e+ z>e6d6H~pftTia5-{gJHQcvaqe>5RPb)EA!+5j21}j7vL!fJ7h$Fxhq7s2$=XbQ*apq!syTR7h7DJ;ak=_$L@YoY5qR0x4qnI>JyURTfv?kuG)9hFQ(Bzig%{x2T0Vw))rZsUuJTqc?}RB zayzmI$t>XKE_%9=#B-p^;FY>ji9xH7Anaju4o)p z9%uqB5oz!1paas%Pl^wu;12>?@7Wcg0d(Zx zhlqm$W(R^Xh!-Io1+@po6A*q0JdbvDCs~`i(%Gx6@WB#qEmxCq5xK#C!6ygR1K}{F z4Jh9Og<%N$=~Oqu`pG7hW&{7(U;2z<{$co^Ef>^0+f z`TE<9*>|q)G;S}O%acqlZ1T6YZ9d*=aWjvj1$6F8Kzppmtr|VSaptG4)aU>DhsN&h zS#$oPdv2-51h5X&^WrRbO)e0I{N~r^&-Nf3pIBgF>R(LU+~oac!W>Y~iu;rt_-#>2 jRe%!E1{&Go|C3Ju!Z7S4^>URZ00000NkvXXu0mjfFDrHZ literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/win/find.png b/src/assistant/assistant/images/win/find.png new file mode 100644 index 0000000000000000000000000000000000000000..6ea35e930d2f298790f19ce5e90cb006f925950f GIT binary patch literal 1944 zcmV;J2WR++P)`8uk zCYhUie)pU+=Vln^9PgfHyBDL7R1Rh_!lS4VIR^uUBp^o=kgIVBs)q37JU%#k6}JH_ zan3b-&0v2%gWtG!eE*cDaZ1XCjoZF!rOynUps+i?Az9Nm7zEM6lxUd$`%gzF+k0m& zT)c(90SJ(U`n3e^HNE36t^P$eI7gz0Mx&UYpGT(C1z*pn%6q>)*m-{#?>2YgDu6jo zk+eYP^|MoDhku@hgw^B-hr@XCWCYtwUWTH``100YW0(GT`oE(A{N-u~t^)`qDPWASK0y zEfvR1o{CEcs)k!$Z|T8B##lrj%47r-LQW_kFcpfth?^fbVm`P4L11t=Y>>Ih5Q-ol zKsGT=M3Zbm1mH0M@gKlwP?4LH3a_^xAsR-j)e4I#1*#l^s=*2@BP7=XzS4q(0;0f? zo1FngE`X1QadL7N9qsq<>0Q6%pTiaahX^xctQ)`-p>m4~!y(IZ zN_%^|2B2|$3B3ZG%XCCv5SR%B(b@G7xp^h9+p|!A`7-*ZCt=>P71?=ih>tq8k)EmG zNMJ#k`Q(e#nT4?Br?n>>$B!R>d0=2*CJ+dW0{D_77CFT_72wb`4Ok=E|7ZlUm%CI zMjx=$p^k?G5ej%bo*V!rgo`jHYyk(rH7#xh#>S>my6Y9HLJUh56S4|7!Rl~;F##(T zWsJjUioxaxAsXerus?^cx0aNYqypGTICX)=ge~BD8`icdFto)svqOVJ{%N{C&*^j;0HhHP!m!RF0=UMZagEN^6|k6$7#{MWw9JEirxUU$ ztw@sX3nC<;dWs88MxzluJw1BQVJ1W*2!R0S8iD0DaD5;Nq^H}c4L&f&kZ*mqdgYb* zh#RWHDhqNm=z4g)-Y9?wp%U6U0(@=ql_dH3S(q3f!ua?&HfK1I8D63s3OM3OsETkb z+gwmt=+2<)L3<#gzo0^dnoxiPD>qzQfuh0?9{({6ahdzNz6T~gam++ zSs+Rp7)c0{2?76z+TGSD_jlY-27RN!?okYTN6 zy`n$NW^D|j00;tD?M|tbTu%J6Tj2m2o=GeV7))uHog7oWt=Cp39j2sn7y{rYM)Y%m zfd2H7E~64rBBXyW2V`E?GNI*sTM%}V0G^|<)8$e*W55-!f@O){y@7X(BHo}x8a?G$ zoTTIW(*^ZX%LF3?9q4E)JP$kZ<4sm}>N^{S(kvT8R+<2pogpKQ@wacpS|0ed-!uJf zwVT+iZ?5q*NyCKvRsVSCgO*Oz6sEA6>U^QxX=BVq5}!|MGYt=v+W;;xy^m=`s$R-a zQb`j_IEb3fsqDn}+(P-IFb^~hXz67wDIU^qc0000ih35qsaQ;`--ztkXVg&b!}_EyONDzhzbxz zF-QmQ7*QMxdUjK)_TF5w#t}w+8}H5O;d;sCwBN;zxsTy_3FRuv)^PwByoiR;6!E>S|Y`qf#2eK^Miq_bAplK@?BR^9E<5T z_snd&KbJ}SLJ98SK-0G<=(W@)4C;J~z%y}7jZ^+|F0?+1@B7TVCq*`}h(M%WtjU0C zEVkdeseS%Kxj>qS$!7Lto}!>7Njb4Oad{T$go$P798WyVKN?rz`3aWIpGIq2SMb#? zRseQ&b)i+w*ZJvL-(R%olR3vW#C4J8Rpl$>sUd9}BwYi~(lIrKN*Li73Wf?d#-Wc6 zjEutrqpz{}zIJIGu6C_M6K|j5c?r2Fc6Cqhh?L|j4=($3qcj(T*T>kE+Q7A6ZxZk@9gM)`PvpG zUyNQ%rn6p<_Qc3|iPsOErL1`bu0exuke24HjTvUoXkePHFk^a#w$_HY%_4W7vS$1xX705_)OOv^~I-|Nq zO~S%=Wd06(D94U%5dt-yhN?im3QH%c?JM;IU z@t5|G{-pmr{lH+nn+hTHrZkN!7V*T)2JzU!9~4>zDk)R&r$4+NN8~vcUCY{e?Jq}e zzd`G~c5X^YBtqW$wlVPjh3MeHle~3ykTbwJ;IalFAted9{L??P?S&^;?VBp4vP38X zUDI%+c|uktf`lE9V}L3^R|T4?A_UQ|4)AK#cmTKtP! zV7v^frefJ9C?W=`LSQHmg%Q9Qa0$3l{Z>!~+=f)PuM?5%92F!4KDY8SXiD5|oO=kE zrh%rbR6-DdqEHoOpjb7h3YaWS#0??@ahFjD6+x`wn`6}0`lu3s0Hu2WB;5&MYH`Oz zfI@r(VkoCip5#Q|+muUrmUJy=!Quyytf>PgitO3_l(5ScjCg;q3IX{d$_FFO-gmbA z+Np17QFi`vOJU!ZrMH?h6wVLv9#8@*cLZ#FnXSMpKo-#XxD?a8vf6*~cQdzc{b9$d z$Gh2ZeCTxV?mu46=ko`Et3Z+4f=PcB)ep7}Sb)l8EqJK2^YPx^-Y>YWYXQ7_kz#d>UlDZ9;sw8|`XJ_X#n>TNMG%NM7XeTBL3l}cj0sN@COp5^j2FV@E UG#jKETmS$707*qoM6N<$f>h3*usiD1i|tuC~KVqEh_e@bU;BKg$RW)j5G>kCLlOgFxY`q z-FPzs8Z%^|+gKnx6xd)0tVIi5K}XmuNMWP_AN-{4uV=qH_s@2cdr$5;`F_vm-du4c z_c+CTr#V8168b@KG?5*Qi)2dd%G=U0gf`J5!(&2-lfJCux%a!T>?brNUDN91- z$>?f2`ueP6?WbN<=_u+DS{*N2d{D_|prQaoRCsyxw#ffoRD0g5 zGRF(+i^im7tFp`5xlUqeX(Fo0v?${v-9(OlB);~%_o`gHs%#-zx~Y;iWj$f2`K^*b zA{aE|rE3atbtdVn4D!-sZB-gtoBo2B)p+5cR^5Cx1&I^U+Fa9fMGLlYh2Z=w1{K91 z%^z*LWr=RFNI##cU6JH}XvB;2LlpwDB+Wu1AQKFY1uSg-j8;=|!T4^7Vuq~9=p8aT z=z*FFeX$h@rl}wx^P`Mi;!^*FKONn;=)3W90Ds+;_r^AGFS;c<`h2DJ;u{|(4K?Mx zPxyZlZdCZ~ek&vrj35|tf(Znw6CkBQk2|PZV8jH*C@@5Yr7#$dg@<9VasV{pFvWsr z70^S50auXmK*W)*`%WP5-ohHOTvSgf7FX1?ba|S1;4Tfjf z#*YSl1NLngrk_;4YSKGA#ih~u4ScFtrZX4}c*(XJl}a_cjN{_KsOb1YHI6GT+dXvJ zpR2|Vm&|gPbmi_D^J4|9ggy4=rB;nJgiLHhgOA0~C(=^fT=$iw?pTbo>XQHLE>rJW z@bq9v7=t%k(r^4FlwD!a+}>-xcd{+82z z510*o+6-rV&M*8*s0@-D?+SoEUHDelg2Fa&1>%)vx?c!9uIzOk!aH{@U8cU{Y^dEaG>aT_#(T zDQU;zU0kdk?VN3&9Hnu0HHxiAyUF6vQO51l9daLLx5G7d&2aUX_0^{$>4_zKEi1|e zpI?_VALV#*$^+V3+FI}YDHtl>cK>lte)y|^j>FV=YQlFMKMSAi)24{EpA&SG#QVnh Oxk5v@!FK{vM1KR8n#>9S literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/win/previous.png b/src/assistant/assistant/images/win/previous.png new file mode 100644 index 0000000000000000000000000000000000000000..0780bc23dd90aa90276b7fb9c7b3ac66dd47d701 GIT binary patch literal 898 zcmV-|1AY97P)c2lV=3YBRkqxxVMa%Q@+3*It9(8@r(B&pozCk>dB#bIx;~_Zc7}{7vA$ z6x>LJB>-SI;{sVc?71Efw{E9+s6@fwLz1~OCXgjSOi4UE(!DAfDa{DzjtGPkBv?u~ z*esy%sFFB%JltL}I@VRi$}jl$?DT_V3il#r+a#dyLlj;))F@%FDIFPwgq5fH>vs4; zDuu7MNnlxd6(Utf%mrITTD|Up))) zIaTt~AF(coo`oW|okD%bO=DgZv{3kV!fi?c&3Rd$QvMZtiV&52#0D1%qVRe!Q@fn- zG*h<VQ8iBU4;9T0`XB0UONejFCB0QT*)y|GM|cA)a*t4;DPwok)RfJY({(+g7|(nV&^_YZ7S-!%6>~= zPt4B?a*c*nZp-Q?JA}2IzPn!&%b4-RlsR_S4#DR|w478{ws{|H_1Gib3 zLJ*Hj2Ba(w&}#d)A)`IRmS*w$Xlgp8u}&Ik+&oQDEKd{72booyJVCM`LkI;l^D2gu zd`m`#D!P*$qzhOBS*|>Yc5?=y3lGBSw*sribTUc$LCC*y9MtUhD%0plHCui$h&##W z>Dd;-lUPP9vihJ+)~@UbkZ_XW{(FYIxuLy+4dVjtUF0~`-C*6VWT_-=KV(wx8_Lo* zYPWhrB7Ns=^V@V{C4^t_dJxek-_ja~8N1OeBkaVDBC(j;(CMzuKfA~&)FQL{=l?2x Y0Ke*zApqF`eEV2><{9 literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/win/print.png b/src/assistant/assistant/images/win/print.png new file mode 100644 index 0000000000000000000000000000000000000000..ba7c02dc18d3145ffc2565afd7761cf8d3bbea99 GIT binary patch literal 1456 zcmV;h1yA~kP)b-8&)Yht(3npj! zhVZz-1RFN&M_Jjs*iyCsr1Br})rl5`9!vdS1b~`S#iy$JW0wVxgL2xnlvejVftS)` zTKUc6;s?zyOrQ~b4PSpzcWVZ4(}_)5%DYZrOq#!QQ`PI0w4^^aL2|-VFePLo-nasN z*IK{-{nt6Te1OXfI6O;fodf8H!d2_Ev=sjnEGa1o(b3Tl5_n}VWUm)KAEByLKX9faxzp;q-{ z>lTYhucS2H_>FX@YA5Z$@Dy-s3>f$m7#f)`>;2jfo|YtOx5wjoB=}+neBJL?ffbTv zfBCMO^w=tAkqc_N@rAcvg)IH?>4}DuF+}ni$veda50xlz zt)=B6k$96%pJo?^z%^$>&$TW{BVO2LH-dO3l7yfjEpFY8fW?x5LqQUtv$GSD+YMuEJR-uvpwVhzFlgX$`>|^E zYT=ym@o{8iWMoD}L`)w#bf|zJuiZyLz(v{F*tvNb6ir(H{*zI;Y z9>eg^5T>j%h>M9sFx0ezpyPT40fGFLmzP(>a-2DHMgV4IWg#UcMF21vcf&dl5fBrz zG=bHeW$;rtmDJc;eGYPwgH7zbKMpsuC1_lPi z@L49UR*NulRBmoAmM>q9=*Va|Jr1axufdtDLX&dtewzjd2S>6L9I>&nsHv&J;NT#- zySvfQ&>$+0i;EL!GMT`|Fahh&UG(()f>RBr@KQ+y;*yQ9OE&*P2Z|sd879u3KaVwQ z)+{oor>6@bzSi8_LNaL(85t?&Fg-nqt5>fIx!k-*$N*KZR3b4kQMhl}GK-Ps6CzB* zn~Z%;>pIfBv97Kzk%t}FfNaML7cK~Cbh;26Ir0e&+$Dycl9Gyqghb@$=cA&cf|Y<@ z@gpxc&)(P9_dG$|6d*jrdnBtZDk@T1T3QxAlQa*dz5Sxn)^#{D?mf0*?u}E%yICkwE#w=FOWGvZK<`(V-kXctBaZ_E}}e zj#}mD(JPA0HV@p`*r<>h=zE7BDc_+q-}kl)5Cpb=AlNAs>*>{SgEkw#4y}~mQNCv~ zrMGY2{-#1Zm|q8tl+_CrKNyc9V3*tznV810(ObefUr=KNDeN(*ygw7St@o4KG#Yl*=aaWttW8qnEgBAePYusBkfjQLUK z*ESZNQaeg3M5%-3A{vmk(w1InXKhO(=rD7bfn}E%vnsF8lidAmi^l!go18TF?R}ne zpYxt`FNgDgUW))yg+j4gDwTfA<#NA->z#lyfDEt%zUF&C;N?) zX6v~GOhc?T+kKTvWr4b)xsWVES@6lR`Ifyk4?fuQb)?{sIkvuqdbGL2Ni-d&@CqtV2mz6!7y@NzPOPeGa~JW$#0 zx{;iNZ3C$YmQ%O8_x8}iP$)#STJ1E{HvsqmUNQnbSn&zEMN)f$e*0)=e=36a`zJ20 z>-udV5{V$e6R1B15CG;UBlyhc^G%@@6<--gmY(>vPjKqSpuqOmpzu877IreO?DNbu zk)3f$2mkIYa8BB5T(RMBI84-PHBN8lUcHQ6@9f(8|O-)Ti zEEe}e{nm76umt0e2}NhLboa(9TCaq2T86J?nZ~`D=1IS>W6GURO;Fgpiw-?>I-NwR zRK_zhGR{E#%5*M5|H?4_m{1%YeU4T{x8Q-I<|s=b98F;Jif!9w<#PE9)O7(cY?5?o z#w4!6_+vsbL^usv5#53Zio_@j+!L3{WK3aUAt_lQnE8DDJNKb>7n^8KswwcDQiSox zgkp%$=V(QA3mzzH4zir)@pzt9?`A!&S(`(C+)+SkH;Kp5jdLXtvGK2BLv)O_Vl{i9 zAHBrkxL0N_WGX*=i!^?mM@&^K$o{R%$L2~hkA1f+X5ArReJ)_HabE%?1FN&&V05bm zKFNW zMUr-v==HI+`EO$L?SR)_^fz3@%A)=+nkQXP0rWafTE>Nr&;S4c07*qoM6N<$f(f4} Ay8r+H literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/win/synctoc.png b/src/assistant/assistant/images/win/synctoc.png new file mode 100644 index 0000000000000000000000000000000000000000..da301bc59997c41db412bcc476afd63fdcf7af89 GIT binary patch literal 1235 zcmV;^1T6cBP)1vJX3njr^nz{ag-cLcFF+}grns{;X}m2osfrC6H73T5D{-SM-MC=I#)Z+9 zCZK47Bv#QNgepj*HBi!~LP!BA6k1MC&p9(^=D#?doHVEDX?c>#WWGthXWs8i<`>2o zD)I8Zffk_MvaPK(>(jfOnEjY#iIzk%mY$o*dikuk_nX5@cx~;*B(a~U73rSG`)`}$?y(ka_QsEYJ@}ul$D&jl>mR1IZW~Xu z4X`=e2^g--{uO=HDgsJ7&=i9LuAiVGv4tHCy-|vxvCp|U^BYdW#+hxOxn2OMI;nfdCBJ7>1{gaBA{vgt4iwZl-AR z_%a5>D+!_mk-(Q8F*`xRsiG)7Zp=>*PYYCFQI5u-9nE0pM|8IJa`5yZx7A!_f!50f z2E&)<4zQ4)4<&hY(ESJS=eIShl`%3k64 z_#uQ%v=<;+etZ31!ha&3EO0$Dj#PC3Ym7({EzF?Vux=||o1Tgioxd`KS9Gyrf>M8K zVomJZ{UHFuliv|27p2l`3zU)ss)#lUtrc1KE;XqJ-g)vM9TR&Pn?6O(&(IN$<>~Ej zlaALilbz!5=qCiYNFA&qh<0G1r9di))|%`0CiwpEUiv!rGw}HP^ggmLy2=^9d5+J{ zyhlOZi}s_mS}lPnB?zJvS_n=|et|J?^uni%{`V{08=s}4xtr=l9hdKna_Qz7hOd5$ zWgCPoK!Gu<2v{1CKx>H+0)Zexz~t;%Chwjl`Y9TL5C&lZ0t`{}2%!~LC4nR3o{&yT zYk@Ml)K{!L04xxg@=_!%fn`kP1hHaOPiv<6chAHf?akDQgfB{;hhaGw!Ab-GqL5tB zTF;6Q6GDl_2f~9t1@ued4k|4-hBccAG-a5T$D9EWLP#B$>7ipMKMo}YU?z09OM-HQ x5DriU!~p?VW%n3h1ETD*SdIc*AQMXR{{voRmD`$tY=8g&002ovPDHLkV1la-K^Oo4 literal 0 HcmV?d00001 diff --git a/src/assistant/assistant/images/win/zoomin.png b/src/assistant/assistant/images/win/zoomin.png new file mode 100644 index 0000000000000000000000000000000000000000..2e586fc7bfb6216aec1be411603315d1d0e129d7 GIT binary patch literal 1208 zcmV;p1V{UcP)3rU9b}h+zvU zvdt-Gx`Bk+iiy+!DMK?HAf-^(YFA1l#B5Pnlrg*b!*09YySzoz6%sSG$;nG!-@WJi z&OPUSWdOkbO|bus06#GZg-WF|C=`k#JRa|5{JtMij8G!t5WIgxfS-+0skE}LuI}Uh z{{BlIkLN}(7`!z;K7Q3^v-wOWlU*W_7&)T*mm<1b^YpT^vUeR0hd&SqfYa%Owzf7f zo6XSC(E$?^6X5lF!D6ul3kwT7@%bFY%19)CTRAl!86ExJ?RJCdmD=C(_ZzPjG#m?S z-?qZmx;I0+8_hp=T0aWoZoq1@UD0SX7OcyRMDi!iNfuf;H7`9}bM@&%uTP~kcLrHZ z8nD>Sz*c91tl2X2!ilcw_P(z{t!+d5{0IWUG7N)Vc8=NUD;Ok|5Ss zAz~3LA`$FGY36C5N<;lLi&4VHta5m+s2aBDUW2Tc8X>2=88UPYz~T#mt;vJK18%R+ z=YxuhifdT^210<~MpMTn@7L$2rlyGCCe|NE2oVoNBG~70x#r1=s#nd^G*FgM1Z=}jg#HgahLF;AC)A7t zFZlg_&}y}`gQJK9#DkFtiYY|o^NOa{xujz~L&EpI857!m8e<|{u>^@EdJx+~!-}zA zdY&4Z`_$_B_A)sM(@ry}UTz0UN+^Yhd|rIgdNKZl%eD6O>%H5ZaBdJugQ8ci9Q zw;RW1-JYpHxTU29WHQ+h*6&zu25}UBN+>y_y>DQ+`!qi;9YuByx z^z`dk*NdRA$s^`~BcT^p4#l4mN+F`%kQK=-G*BWYS@4+!rBWG6O-+UTG~xAw+csRm z+5?p6r3iT3J0+9iPYIFGrH|*`h~6#%v*&OOm1MP&zc}pS(@7?2*Y4tsv`>zbQoAmCxO$-^EDM zuM)2Mu39aE)>1j_(TII`UwhBaWR9d;BYt=`FL^aJFGoCdPY-==W~9CLPV@idU&Zf9 WSJt-!dAjKU0000&r*aqEiG-2SS)^r%jLd--*+JL5E8^P1os~i;AeGQTwI}6t8MM< z>>PJGoi}|x->l7MyJ0XG+$AL?#@N_c4NG+Id_;F@E>)>iADc`jkJsykfq?<2tE+>W zni|mQbTBwL2ricknwpw?IXO8EczrrzaVV0%-8nV4SgqeN3AMqfDEI8xtn*6(mQprWE;l*i*~v0g}W%|{?6%}Exzb80R+@X3Je zc!yJCa!gd7yEs$W>6lJA-95UpuIqe$^I5mqHW+MaX@QK4jBc#YM1=o!in#>YMx)V1 zn#&7z{~)M5Y0YaeUC{KnU2ikvo+{?s%TpJSMCHer#}BvlE?$udOA}SFB6T+iHWb71lwA;> zm=B!Tm*MH&i7&F+zv&wu9wvfYSbqe;N8A^R;5ED5K1Ehc+gCHP^4OO>oJZxr5pD*~ zD)k?D0uiT%B=e-P%;FpJcs!s`C}@HMh-k$9p$PIQMC9{=>O&JW0sq9se!k(ye!-c5 zRnQi&M4t(q7a9X*asN+kG=biE`5HM1(@r}mU1$OcC6q!$KF|9=e<|vy-5z=BnuGVL ze~8yS!yqmQJ`Y%vEK^2J{}|&O_6BQfYat;ap%?47F0_MX6n{!6IitR#+pli?+7nYh zbS_FaZi{S~bO>6ftnsD^6E$yV+&`PmW{}Bb{)mW((^$WDAs2CJ3W`4^ltw3?lNHG= zv{2HVWN~`DL29nvwry4_mCj&YJA%R{3Yi0zgdSY!6n{!6g@~phE0SAip+row;5B}U zL=s3&PKL}i{Fz;wR$a&1os{VL2)OJsC57To38fH`&&i797FsB2jE&{5+NJ^A~r-vSSyyY3te{)hhtqExg=2d>~g5ijRRfL z8rZ)zacnM0sP_Svl`EWG9FMhNG<)Q+=S4`;w?6uzfMz`(-c!ayW3d#f)ldCSvfwL| zC0az*BY#tf*kc%bo_-f1NoN{=$<c54ekZ>00006VoOIv0RI600RN!9r;`8x0f9+G zK~yNujgmb}f>9L5rH|0$5R%kb;F8*qSwko;8BtoMPjA?Zm0709(~F{yI;2sV^(lgu zww8vb8fx$Z1Vz&`dCqx)INe8xhBQ3zhdbQgJ@9tpq7hTPvYN1Qy z11edDlFXpQ7!SgXi6CQivW}bH=8kZhw&8KAhPkK?d{ScW9=9f#!fvMJG0V^C77=@6 zU6PYnjQcPfb)vjn!+1cYjqz>@yKZS=^jedgMjbGVJPMa*F|j@&tYRkehVqh+_JTHy zO?mXgNOJUun0~u7gbx`D@pnwGe_?8#DK@QAWZX*&BOQP5f0pTm@_r5SJB&wbjLC*D z)2fFBz$6WKHy6)sTk&w^NuLnuz={x-oU8=~Z?qYEXX^(|hOD&fm$b0EP9X +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +IndexWindow::IndexWindow(QWidget *parent) + : QWidget(parent) + , m_searchLineEdit(new QLineEdit) + , m_indexWidget(HelpEngineWrapper::instance().indexWidget()) +{ + TRACE_OBJ + QVBoxLayout *layout = new QVBoxLayout(this); + QLabel *l = new QLabel(tr("&Look for:")); + layout->addWidget(l); + + l->setBuddy(m_searchLineEdit); + m_searchLineEdit->setClearButtonEnabled(true); + connect(m_searchLineEdit, &QLineEdit::textChanged, + this, &IndexWindow::filterIndices); + m_searchLineEdit->installEventFilter(this); + layout->setContentsMargins(4, 4, 4, 4); + layout->addWidget(m_searchLineEdit); + + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + m_indexWidget->installEventFilter(this); + connect(helpEngine.indexModel(), &QHelpIndexModel::indexCreationStarted, + this, &IndexWindow::disableSearchLineEdit); + connect(helpEngine.indexModel(), &QHelpIndexModel::indexCreated, + this, &IndexWindow::enableSearchLineEdit); + connect(m_indexWidget, &QHelpIndexWidget::documentActivated, + this, [this](const QHelpLink &link) { + emit linkActivated(link.url); + }); + connect(m_indexWidget, &QHelpIndexWidget::documentsActivated, + this, &IndexWindow::documentsActivated); + connect(m_searchLineEdit, &QLineEdit::returnPressed, + m_indexWidget, &QHelpIndexWidget::activateCurrentItem); + layout->addWidget(m_indexWidget); + + m_indexWidget->viewport()->installEventFilter(this); +} + +IndexWindow::~IndexWindow() +{ + TRACE_OBJ +} + +void IndexWindow::filterIndices(const QString &filter) +{ + TRACE_OBJ + if (filter.contains(u'*')) + m_indexWidget->filterIndices(filter, filter); + else + m_indexWidget->filterIndices(filter, QString()); +} + +bool IndexWindow::eventFilter(QObject *obj, QEvent *e) +{ + TRACE_OBJ + if (obj == m_searchLineEdit && e->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(e); + QModelIndex idx = m_indexWidget->currentIndex(); + switch (ke->key()) { + case Qt::Key_Up: + idx = m_indexWidget->model()->index(idx.row()-1, + idx.column(), idx.parent()); + if (idx.isValid()) { + m_indexWidget->setCurrentIndex(idx); + return true; + } + break; + case Qt::Key_Down: + idx = m_indexWidget->model()->index(idx.row() + 1, + idx.column(), idx.parent()); + if (idx.isValid()) { + m_indexWidget->setCurrentIndex(idx); + return true; + } + break; + case Qt::Key_Escape: + emit escapePressed(); + return true; + default: ; // stop complaining + } + } else if (obj == m_indexWidget && e->type() == QEvent::ContextMenu) { + QContextMenuEvent *ctxtEvent = static_cast(e); + QModelIndex idx = m_indexWidget->indexAt(ctxtEvent->pos()); + if (idx.isValid()) { + QMenu menu; + QAction *curTab = menu.addAction(tr("Open Link")); + QAction *newTab = menu.addAction(tr("Open Link in New Tab")); + menu.move(m_indexWidget->mapToGlobal(ctxtEvent->pos())); + + QAction *action = menu.exec(); + if (curTab == action) + m_indexWidget->activateCurrentItem(); + else if (newTab == action) { + open(m_indexWidget, idx); + } + } + } else if (m_indexWidget && obj == m_indexWidget->viewport() + && e->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(e); + QModelIndex idx = m_indexWidget->indexAt(mouseEvent->pos()); + if (idx.isValid()) { + Qt::MouseButtons button = mouseEvent->button(); + if (((button == Qt::LeftButton) && (mouseEvent->modifiers() & Qt::ControlModifier)) + || (button == Qt::MiddleButton)) { + open(m_indexWidget, idx); + } + } + } +#ifdef Q_OS_MAC + else if (obj == m_indexWidget && e->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(e); + if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) + m_indexWidget->activateCurrentItem(); + } +#endif + return QWidget::eventFilter(obj, e); +} + +void IndexWindow::enableSearchLineEdit() +{ + TRACE_OBJ + m_searchLineEdit->setDisabled(false); + filterIndices(m_searchLineEdit->text()); +} + +void IndexWindow::disableSearchLineEdit() +{ + TRACE_OBJ + m_searchLineEdit->setDisabled(true); +} + +void IndexWindow::setSearchLineEditText(const QString &text) +{ + TRACE_OBJ + m_searchLineEdit->setText(text); +} + +void IndexWindow::focusInEvent(QFocusEvent *e) +{ + TRACE_OBJ + if (e->reason() != Qt::MouseFocusReason) { + m_searchLineEdit->selectAll(); + m_searchLineEdit->setFocus(); + } +} + +void IndexWindow::open(QHelpIndexWidget* indexWidget, const QModelIndex &index) +{ + TRACE_OBJ + QHelpIndexModel *model = qobject_cast(indexWidget->model()); + if (model) { + const QString keyword = model->data(index, Qt::DisplayRole).toString(); + const QList docs = model->helpEngine()->documentsForKeyword(keyword); + + QUrl url; + if (docs.size() > 1) { + TopicChooser tc(this, keyword, docs); + if (tc.exec() == QDialog::Accepted) + url = tc.link(); + } else if (!docs.isEmpty()) { + url = docs.first().url; + } else { + return; + } + + if (!HelpViewer::canOpenPage(url.path())) + CentralWidget::instance()->setSource(url); + else + OpenPagesManager::instance()->createPage(url); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/indexwindow.h b/src/assistant/assistant/indexwindow.h new file mode 100644 index 0000000..dab8e7a --- /dev/null +++ b/src/assistant/assistant/indexwindow.h @@ -0,0 +1,53 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef INDEXWINDOW_H +#define INDEXWINDOW_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpIndexWidget; +class QModelIndex; +struct QHelpLink; + +class IndexWindow : public QWidget +{ + Q_OBJECT + Q_MOC_INCLUDE() + +public: + IndexWindow(QWidget *parent = nullptr); + ~IndexWindow() override; + + void setSearchLineEditText(const QString &text); + QString searchLineEditText() const + { + return m_searchLineEdit->text(); + } + +signals: + void linkActivated(const QUrl &link); + void documentsActivated(const QList &documents, const QString &keyword); + void escapePressed(); + +private slots: + void filterIndices(const QString &filter); + void enableSearchLineEdit(); + void disableSearchLineEdit(); + +private: + bool eventFilter(QObject *obj, QEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + void open(QHelpIndexWidget *indexWidget, const QModelIndex &index); + + QLineEdit *m_searchLineEdit; + QHelpIndexWidget *m_indexWidget; +}; + +QT_END_NAMESPACE + +#endif // INDEXWINDOW_H diff --git a/src/assistant/assistant/main.cpp b/src/assistant/assistant/main.cpp new file mode 100644 index 0000000..b136b09 --- /dev/null +++ b/src/assistant/assistant/main.cpp @@ -0,0 +1,382 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#if defined(BROWSER_QTWEBKIT) +#include +#include +#endif + +#include "../shared/collectionconfiguration.h" +#include "helpenginewrapper.h" +#include "mainwindow.h" +#include "cmdlineparser.h" + +// #define TRACING_REQUESTED + +QT_USE_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + +void +updateLastPagesOnUnregister(QHelpEngineCore& helpEngine, const QString& nsName) +{ + TRACE_OBJ + int lastPage = CollectionConfiguration::lastTabPage(helpEngine); + QStringList currentPages = CollectionConfiguration::lastShownPages(helpEngine); + if (!currentPages.isEmpty()) { + QStringList zoomList = CollectionConfiguration::lastZoomFactors(helpEngine); + while (zoomList.size() < currentPages.size()) + zoomList.append(CollectionConfiguration::DefaultZoomFactor); + + for (int i = currentPages.size(); --i >= 0;) { + if (QUrl(currentPages.at(i)).host() == nsName) { + zoomList.removeAt(i); + currentPages.removeAt(i); + lastPage = (lastPage == (i + 1)) ? 1 : lastPage; + } + } + + CollectionConfiguration::setLastShownPages(helpEngine, currentPages); + CollectionConfiguration::setLastTabPage(helpEngine, lastPage); + CollectionConfiguration::setLastZoomFactors(helpEngine, zoomList); + } +} + +void stripNonexistingDocs(QHelpEngineCore& collection) +{ + TRACE_OBJ + const QStringList &namespaces = collection.registeredDocumentations(); + for (const QString &ns : namespaces) { + QFileInfo fi(collection.documentationFileName(ns)); + if (!fi.exists() || !fi.isFile()) + collection.unregisterDocumentation(ns); + } +} + +QString indexFilesFolder(const QString &collectionFile) +{ + TRACE_OBJ + QString indexFilesFolder = ".fulltextsearch"_L1; + if (!collectionFile.isEmpty()) { + QFileInfo fi(collectionFile); + indexFilesFolder = u'.' + fi.fileName().left(fi.fileName().lastIndexOf(".qhc"_L1)); + } + return indexFilesFolder; +} + +/* + * Returns the expected absolute file path of the cached collection file + * correspondinging to the given collection's file. + * It may or may not exist yet. + */ +QString constructCachedCollectionFilePath(const QHelpEngineCore &collection) +{ + TRACE_OBJ + const QString &filePath = collection.collectionFile(); + const QString &fileName = QFileInfo(filePath).fileName(); + const QString &cacheDir = CollectionConfiguration::cacheDir(collection); + const QString &dir = !cacheDir.isEmpty() + && CollectionConfiguration::cacheDirIsRelativeToCollection(collection) + ? QFileInfo(filePath).dir().absolutePath() + + QDir::separator() + cacheDir + : MainWindow::collectionFileDirectory(false, cacheDir); + return dir + QDir::separator() + fileName; +} + +bool synchronizeDocs(QHelpEngineCore &collection, + QHelpEngineCore &cachedCollection, + CmdLineParser &cmd) +{ + TRACE_OBJ + const QDateTime &lastCollectionRegisterTime = + CollectionConfiguration::lastRegisterTime(collection); + if (!lastCollectionRegisterTime.isValid() || lastCollectionRegisterTime + < CollectionConfiguration::lastRegisterTime(cachedCollection)) + return true; + + const QStringList &docs = collection.registeredDocumentations(); + const QStringList &cachedDocs = cachedCollection.registeredDocumentations(); + + /* + * Ensure that the cached collection contains all docs that + * the collection contains. + */ + for (const QString &doc : docs) { + if (!cachedDocs.contains(doc)) { + const QString &docFile = collection.documentationFileName(doc); + if (!cachedCollection.registerDocumentation(docFile)) { + cmd.showMessage(QCoreApplication::translate("Assistant", + "Error registering documentation file '%1': %2"). + arg(docFile).arg(cachedCollection.error()), true); + return false; + } + } + } + + CollectionConfiguration::updateLastRegisterTime(cachedCollection); + + return true; +} + +bool removeSearchIndex(const QString &collectionFile) +{ + TRACE_OBJ + const QString path = + QFileInfo(collectionFile).path() + u'/' + indexFilesFolder(collectionFile); + + QDir dir(path); + if (!dir.exists()) + return false; + + const QStringList &list = dir.entryList(QDir::Files | QDir::Hidden); + for (const QString &item : list) + dir.remove(item); + return true; +} + +QCoreApplication* createApplication(int &argc, char *argv[]) +{ + TRACE_OBJ +#ifndef Q_OS_WIN + // Look for arguments that imply command-line mode. + const char * cmdModeArgs[] = { + "-help", "-register", "-unregister", "-remove-search-index", + "-rebuild-search-index" + }; + for (int i = 1; i < argc; ++i) { + for (size_t j = 0; j < sizeof cmdModeArgs/sizeof *cmdModeArgs; ++j) { + if (strcmp(argv[i], cmdModeArgs[j]) == 0) + return new QCoreApplication(argc, argv); + } + } +#endif + QApplication *app = new QApplication(argc, argv); + app->connect(app, &QGuiApplication::lastWindowClosed, app, + &QCoreApplication::quit); + return app; +} + +bool registerDocumentation(QHelpEngineCore &collection, CmdLineParser &cmd, + bool printSuccess) +{ + TRACE_OBJ + if (!collection.registerDocumentation(cmd.helpFile())) { + cmd.showMessage(QCoreApplication::translate("Assistant", + "Could not register documentation file\n%1\n\nReason:\n%2") + .arg(cmd.helpFile()).arg(collection.error()), true); + return false; + } + if (printSuccess) + cmd.showMessage(QCoreApplication::translate("Assistant", + "Documentation successfully registered."), + false); + CollectionConfiguration::updateLastRegisterTime(collection); + return true; +} + +bool unregisterDocumentation(QHelpEngineCore &collection, + const QString &namespaceName, CmdLineParser &cmd, bool printSuccess) +{ + TRACE_OBJ + if (!collection.unregisterDocumentation(namespaceName)) { + cmd.showMessage(QCoreApplication::translate("Assistant", + "Could not unregister documentation" + " file\n%1\n\nReason:\n%2"). + arg(cmd.helpFile()).arg(collection.error()), true); + return false; + } + updateLastPagesOnUnregister(collection, namespaceName); + if (printSuccess) + cmd.showMessage(QCoreApplication::translate("Assistant", + "Documentation successfully unregistered."), + false); + return true; +} + +void setupTranslation(const QString &fileName, const QString &dir) +{ + QTranslator *translator = new QTranslator(QCoreApplication::instance()); + if (translator->load(QLocale(), fileName, "_"_L1, dir)) + QCoreApplication::installTranslator(translator); +} + +void setupTranslations() +{ + TRACE_OBJ + const QString &resourceDir + = QLibraryInfo::path(QLibraryInfo::TranslationsPath); + setupTranslation("assistant"_L1, resourceDir); + setupTranslation("qt"_L1, resourceDir); + setupTranslation("qt_help"_L1, resourceDir); +} + +} // Anonymous namespace. + +enum ExitStatus { + ExitSuccess = 0, + ExitFailure, + NoExit +}; + +static ExitStatus preliminarySetup(CmdLineParser *cmd) +{ + /* + * Create the collection objects that we need. We always have the + * cached collection file. Depending on whether the user specified + * one, we also may have an input collection file. + */ + const QString collectionFile = cmd->collectionFile(); + const bool collectionFileGiven = !collectionFile.isEmpty(); + QScopedPointer collection; + if (collectionFileGiven) { + collection.reset(new QHelpEngineCore(collectionFile)); + if (!collection->setupData()) { + cmd->showMessage(QCoreApplication::translate("Assistant", + "Error reading collection file '%1': %2.") + .arg(collectionFile).arg(collection->error()), true); + return ExitFailure; + } + } + const QString &cachedCollectionFile = collectionFileGiven + ? constructCachedCollectionFilePath(*collection) + : MainWindow::defaultHelpCollectionFileName(); + if (collectionFileGiven && !QFileInfo(cachedCollectionFile).exists() + && !collection->copyCollectionFile(cachedCollectionFile)) { + cmd->showMessage(QCoreApplication::translate("Assistant", + "Error creating collection file '%1': %2.") + .arg(cachedCollectionFile).arg(collection->error()), true); + return ExitFailure; + } + QHelpEngineCore cachedCollection(cachedCollectionFile); + if (!cachedCollection.setupData()) { + cmd->showMessage(QCoreApplication::translate("Assistant", + "Error reading collection file '%1': %2.") + .arg(cachedCollectionFile) + .arg(cachedCollection.error()), true); + return ExitFailure; + } + + stripNonexistingDocs(cachedCollection); + if (collectionFileGiven) { + if (CollectionConfiguration::isNewer(*collection, cachedCollection)) + CollectionConfiguration::copyConfiguration(*collection, + cachedCollection); + if (!synchronizeDocs(*collection, cachedCollection, *cmd)) + return ExitFailure; + } + + if (cmd->registerRequest() != CmdLineParser::None) { + const QStringList &cachedDocs = + cachedCollection.registeredDocumentations(); + const QString &namespaceName = + QHelpEngineCore::namespaceName(cmd->helpFile()); + if (cmd->registerRequest() == CmdLineParser::Register) { + if (collectionFileGiven + && !registerDocumentation(*collection, *cmd, true)) + return ExitFailure; + if (!cachedDocs.contains(namespaceName) + && !registerDocumentation(cachedCollection, *cmd, !collectionFileGiven)) + return ExitFailure; + return ExitSuccess; + } + if (cmd->registerRequest() == CmdLineParser::Unregister) { + if (collectionFileGiven + && !unregisterDocumentation(*collection, namespaceName, *cmd, true)) + return ExitFailure; + if (cachedDocs.contains(namespaceName) + && !unregisterDocumentation(cachedCollection, namespaceName, + *cmd, !collectionFileGiven)) + return ExitFailure; + return ExitSuccess; + } + } + + if (cmd->removeSearchIndex()) { + return removeSearchIndex(cachedCollectionFile) + ? ExitSuccess : ExitFailure; + } + + if (!QSqlDatabase::isDriverAvailable("QSQLITE"_L1)) { + cmd->showMessage(QCoreApplication::translate("Assistant", + "Cannot load sqlite database driver!"), + true); + return ExitFailure; + } + + if (!cmd->currentFilter().isEmpty()) { + if (collectionFileGiven) + collection->setCurrentFilter(cmd->currentFilter()); + cachedCollection.setCurrentFilter(cmd->currentFilter()); + } + + if (collectionFileGiven) + cmd->setCollectionFile(cachedCollectionFile); + + return NoExit; +} + +int main(int argc, char *argv[]) +{ + TRACE_OBJ + QScopedPointer a(createApplication(argc, argv)); +#if QT_CONFIG(library) + a->addLibraryPath(a->applicationDirPath() + "/plugins"_L1); +#endif + setupTranslations(); + +#if defined(BROWSER_QTWEBKIT) + if (qobject_cast(a.data())) { + QFont f; + f.setStyleHint(QFont::SansSerif); + QWebSettings::globalSettings()->setFontFamily(QWebSettings::StandardFont, f.defaultFamily()); + } +#endif // BROWSER_QTWEBKIT + + // Parse arguments. + CmdLineParser cmd(a->arguments()); + CmdLineParser::Result res = cmd.parse(); + if (res == CmdLineParser::Help) + return 0; + else if (res == CmdLineParser::Error) + return -1; + + const ExitStatus status = preliminarySetup(&cmd); + switch (status) { + case ExitFailure: return EXIT_FAILURE; + case ExitSuccess: return EXIT_SUCCESS; + default: break; + } + + MainWindow *w = new MainWindow(&cmd); + w->show(); + + /* + * We need to be careful here: The main window has to be deleted before + * the help engine wrapper, which has to be deleted before the + * QApplication. + */ + const int retval = a->exec(); + delete w; + HelpEngineWrapper::removeInstance(); + return retval; +} diff --git a/src/assistant/assistant/mainwindow.cpp b/src/assistant/assistant/mainwindow.cpp new file mode 100644 index 0000000..727d834 --- /dev/null +++ b/src/assistant/assistant/mainwindow.cpp @@ -0,0 +1,1154 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "mainwindow.h" + +#include "aboutdialog.h" +#include "bookmarkmanager.h" +#include "centralwidget.h" +#include "cmdlineparser.h" +#include "contentwindow.h" +#include "globalactions.h" +#include "helpenginewrapper.h" +#include "indexwindow.h" +#include "openpagesmanager.h" +#include "preferencesdialog.h" +#include "qtdocinstaller.h" +#include "remotecontrol.h" +#include "searchwidget.h" +#include "topicchooser.h" +#include "tracer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { warnAboutMissingModules = 0 }; + +MainWindow::MainWindow(CmdLineParser *cmdLine, QWidget *parent) + : QMainWindow(parent) + , m_cmdLine(cmdLine) +{ + TRACE_OBJ + + setToolButtonStyle(Qt::ToolButtonFollowStyle); + setDockOptions(dockOptions() | AllowNestedDocks); + + QString collectionFile; + if (usesDefaultCollection()) { + MainWindow::collectionFileDirectory(true); + collectionFile = MainWindow::defaultHelpCollectionFileName(); + } else { + collectionFile = cmdLine->collectionFile(); + } + HelpEngineWrapper &helpEngineWrapper = + HelpEngineWrapper::instance(collectionFile); + BookmarkManager *bookMarkManager = BookmarkManager::instance(); + + if (!initHelpDB(!cmdLine->collectionFileGiven())) { + qDebug("Fatal error: Help engine initialization failed. " + "Error message was: %s\nAssistant will now exit.", + qPrintable(HelpEngineWrapper::instance().error())); + std::exit(1); + } + + m_centralWidget = new CentralWidget(this); + setCentralWidget(m_centralWidget); + + m_indexWindow = new IndexWindow(this); + QDockWidget *indexDock = new QDockWidget(tr("Index"), this); + indexDock->setObjectName("IndexWindow"_L1); + indexDock->setWidget(m_indexWindow); + addDockWidget(Qt::LeftDockWidgetArea, indexDock); + + m_contentWindow = new ContentWindow; + QDockWidget *contentDock = new QDockWidget(tr("Contents"), this); + contentDock->setObjectName("ContentWindow"_L1); + contentDock->setWidget(m_contentWindow); + addDockWidget(Qt::LeftDockWidgetArea, contentDock); + + m_searchWindow = new SearchWidget(helpEngineWrapper.searchEngine()); + m_searchWindow->setFont(!helpEngineWrapper.usesBrowserFont() ? qApp->font() + : helpEngineWrapper.browserFont()); + QDockWidget *searchDock = new QDockWidget(tr("Search"), this); + searchDock->setObjectName("SearchWindow"_L1); + searchDock->setWidget(m_searchWindow); + addDockWidget(Qt::LeftDockWidgetArea, searchDock); + + QDockWidget *bookmarkDock = new QDockWidget(tr("Bookmarks"), this); + bookmarkDock->setObjectName("BookmarkWindow"_L1); + bookmarkDock->setWidget(m_bookmarkWidget + = bookMarkManager->bookmarkDockWidget()); + addDockWidget(Qt::LeftDockWidgetArea, bookmarkDock); + + QDockWidget *openPagesDock = new QDockWidget(tr("Open Pages"), this); + openPagesDock->setObjectName("Open Pages"_L1); + OpenPagesManager *openPagesManager + = OpenPagesManager::createInstance(this, usesDefaultCollection(), m_cmdLine->url()); + openPagesDock->setWidget(openPagesManager->openPagesWidget()); + addDockWidget(Qt::LeftDockWidgetArea, openPagesDock); + + connect(m_centralWidget, &CentralWidget::addBookmark, + bookMarkManager, &BookmarkManager::addBookmark); + connect(bookMarkManager, &BookmarkManager::escapePressed, + this, &MainWindow::activateCurrentCentralWidgetTab); + connect(bookMarkManager, &BookmarkManager::setSource, + m_centralWidget, &CentralWidget::setSource); + connect(bookMarkManager, &BookmarkManager::setSourceInNewTab, + openPagesManager, [openPagesManager](const QUrl &url){ openPagesManager->createPage(url); }); + + QHelpSearchEngine *searchEngine = helpEngineWrapper.searchEngine(); + connect(searchEngine, &QHelpSearchEngine::indexingStarted, + this, &MainWindow::indexingStarted); + connect(searchEngine, &QHelpSearchEngine::indexingFinished, + this, &MainWindow::indexingFinished); + + QString defWindowTitle = tr("Qt Assistant"); + setWindowTitle(defWindowTitle); + + setupActions(); + statusBar()->show(); + m_centralWidget->connectTabBar(); + + setupFilterToolbar(); + setupAddressToolbar(); + + const QString windowTitle = helpEngineWrapper.windowTitle(); + setWindowTitle(windowTitle.isEmpty() ? defWindowTitle : windowTitle); + QByteArray iconArray = helpEngineWrapper.applicationIcon(); + if (iconArray.size() > 0) { + QBuffer buffer(&iconArray); + QImageReader reader(&buffer); + QIcon appIcon; + do { + QPixmap pix; + pix.convertFromImage(reader.read()); + appIcon.addPixmap(pix); + } while (reader.jumpToNextImage()); + qApp->setWindowIcon(appIcon); + } +#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN) + else { + QIcon appIcon(":/qt-project.org/assistant/images/assistant-128.png"_L1); + qApp->setWindowIcon(appIcon); + } +#endif + + QToolBar *toolBar = addToolBar(tr("Bookmark Toolbar")); + toolBar->setObjectName("Bookmark Toolbar"_L1); + bookMarkManager->setBookmarksToolbar(toolBar); + + toolBar->hide(); + toolBarMenu()->addAction(toolBar->toggleViewAction()); + + QByteArray ba(helpEngineWrapper.mainWindow()); + if (!ba.isEmpty()) + restoreState(ba); + + ba = helpEngineWrapper.mainWindowGeometry(); + if (!ba.isEmpty()) { + restoreGeometry(ba); + } else { + tabifyDockWidget(contentDock, indexDock); + tabifyDockWidget(indexDock, bookmarkDock); + tabifyDockWidget(bookmarkDock, searchDock); + contentDock->raise(); + const QRect screen = QGuiApplication::primaryScreen()->geometry(); + adjustSize(); // make sure we won't start outside of the screen + resize(4 * screen.width() / 5, 4 * screen.height() / 5); + + move(screen.center() - rect().center()); + } + + if (!helpEngineWrapper.hasFontSettings()) { + helpEngineWrapper.setUseAppFont(false); + helpEngineWrapper.setUseBrowserFont(false); + helpEngineWrapper.setAppFont(qApp->font()); + helpEngineWrapper.setAppWritingSystem(QFontDatabase::Latin); + helpEngineWrapper.setBrowserFont(qApp->font()); + helpEngineWrapper.setBrowserWritingSystem(QFontDatabase::Latin); + } else { + updateApplicationFont(); + } + + updateAboutMenuText(); + + QTimer::singleShot(0, this, &MainWindow::insertLastPages); + if (m_cmdLine->enableRemoteControl()) + (void)new RemoteControl(this); + + if (m_cmdLine->contents() == CmdLineParser::Show) + showContents(); + else if (m_cmdLine->contents() == CmdLineParser::Hide) + hideContents(); + + if (m_cmdLine->index() == CmdLineParser::Show) + showIndex(); + else if (m_cmdLine->index() == CmdLineParser::Hide) + hideIndex(); + + if (m_cmdLine->bookmarks() == CmdLineParser::Show) + showBookmarksDockWidget(); + else if (m_cmdLine->bookmarks() == CmdLineParser::Hide) + hideBookmarksDockWidget(); + + if (m_cmdLine->search() == CmdLineParser::Show) + showSearch(); + else if (m_cmdLine->search() == CmdLineParser::Hide) + hideSearch(); + + if (m_cmdLine->contents() == CmdLineParser::Activate) + showContents(); + else if (m_cmdLine->index() == CmdLineParser::Activate) + showIndex(); + else if (m_cmdLine->bookmarks() == CmdLineParser::Activate) + showBookmarksDockWidget(); + + if (!m_cmdLine->currentFilter().isEmpty()) { + const QString &curFilter = m_cmdLine->currentFilter(); + if (helpEngineWrapper.filterEngine()->filters().contains(curFilter)) + helpEngineWrapper.filterEngine()->setActiveFilter(curFilter); + } + + if (usesDefaultCollection()) + QTimer::singleShot(0, this, &MainWindow::lookForNewQtDocumentation); + else + checkInitState(); + + connect(&helpEngineWrapper, &HelpEngineWrapper::documentationRemoved, + this, &MainWindow::documentationRemoved); + connect(&helpEngineWrapper, &HelpEngineWrapper::documentationUpdated, + this, &MainWindow::documentationUpdated); + + setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); + GlobalActions::instance()->updateActions(); + if (helpEngineWrapper.addressBarEnabled()) + showNewAddress(); +} + +MainWindow::~MainWindow() +{ + TRACE_OBJ + delete m_qtDocInstaller; +} + +bool MainWindow::usesDefaultCollection() const +{ + TRACE_OBJ + return m_cmdLine->collectionFile().isEmpty(); +} + +void MainWindow::closeEvent(QCloseEvent *e) +{ + TRACE_OBJ + BookmarkManager::destroy(); + HelpEngineWrapper::instance().setMainWindow(saveState()); + HelpEngineWrapper::instance().setMainWindowGeometry(saveGeometry()); + QMainWindow::closeEvent(e); +} + +bool MainWindow::initHelpDB(bool registerInternalDoc) +{ + TRACE_OBJ + HelpEngineWrapper &helpEngineWrapper = HelpEngineWrapper::instance(); + if (!helpEngineWrapper.setupData()) + return false; + + if (!registerInternalDoc && helpEngineWrapper.defaultHomePage() == "help"_L1) + helpEngineWrapper.setDefaultHomePage("about:blank"_L1); + + return true; +} + +static const char *docs[] = { + "assistant", "designer", "linguist", // Qt 4 + "qmake", + "qt", + "qtqmake", + "activeqt", + "qtandroidextras", + "qtassistant", + "qtbluetooth", + "qtconcurrent", + "qtconnectivity", + "qtcore", + "qtdbus", + "qtdesigner", + "qtdoc", + "qtenginio", + "qtgraphicaleffects", + "qtgui", + "qthelp", + "qtimageformats", + "qtlinguist", + "qtlocation", + "qtmacextras", + "qtmultimedia", + "qtmultimediawidgets", + "qtnfc", + "qtnetwork", + "qtopengl", + "qtpositioning", + "qtprintsupport", + "qtqml", + "qtquick", + "qtscript", + "qtscripttools", + "qtsensors", + "qtsql", + "qtsvg", + "qttestlib", + "qtuitools", + "qtwebkit", + "qtwebkitexamples", + "qtwidgets", + "qtxml", + "qtxmlpatterns", + "qdoc", + "qtx11extras", + "qtserialport", + "qtquickcontrols", + "qtquickcontrolsstyles", + "qtquickdialogs", + "qtquicklayouts", + "qtwebsockets", + "qtwinextras" +}; + +static QStringList newQtDocumentation() +{ + QStringList result; + const QDir docDirectory(QLibraryInfo::path(QLibraryInfo::DocumentationPath)); + const QFileInfoList entries = docDirectory.entryInfoList(QStringList(QStringLiteral("*.qch")), + QDir::Files, QDir::Name); + if (!entries.isEmpty()) { + result.reserve(entries.size()); + for (const QFileInfo &fi : entries) + result.append(fi.baseName()); + return result; + } + if (warnAboutMissingModules) + qWarning() << "No documentation found in " << QDir::toNativeSeparators(docDirectory.absolutePath()); + const int docCount = int(sizeof(docs) / sizeof(docs[0])); + result.reserve(docCount); + for (int d = 0; d < docCount; ++d) + result.append(QLatin1StringView(docs[d])); + return result; +} + +void MainWindow::lookForNewQtDocumentation() +{ + TRACE_OBJ + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + + const QStringList &docs = newQtDocumentation(); + const int docCount = docs.size(); + QList qtDocInfos; + qtDocInfos.reserve(docCount); + for (const QString &doc : docs) { + const QtDocInstaller::DocInfo docInfo(doc, helpEngine.qtDocInfo(doc)); + qtDocInfos.append(docInfo); + if (warnAboutMissingModules && (docInfo.second.isEmpty() || docInfo.second.first().isEmpty())) + qWarning() << "No documentation found for " << doc; + } + + m_qtDocInstaller = new QtDocInstaller(qtDocInfos); + connect(m_qtDocInstaller, &QtDocInstaller::docsInstalled, + this, &MainWindow::qtDocumentationInstalled); + connect(m_qtDocInstaller, &QtDocInstaller::qchFileNotFound, + this, &MainWindow::resetQtDocInfo); + connect(m_qtDocInstaller, &QtDocInstaller::registerDocumentation, + this, &MainWindow::registerDocumentation); + if (helpEngine.qtDocInfo("qt"_L1).size() != 2) + statusBar()->showMessage(tr("Looking for Qt Documentation...")); + m_qtDocInstaller->installDocs(); +} + +void MainWindow::qtDocumentationInstalled() +{ + TRACE_OBJ + OpenPagesManager::instance()->resetHelpPage(); + statusBar()->clearMessage(); + checkInitState(); +} + +void MainWindow::checkInitState() +{ + TRACE_OBJ + if (!m_cmdLine->enableRemoteControl()) { + HelpEngineWrapper::instance().initialDocSetupDone(); + return; + } + + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + if (helpEngine.contentModel()->isCreatingContents() + || helpEngine.indexModel()->isCreatingIndex()) { + if (!m_connectedInitSignals) { + connect(helpEngine.contentModel(), &QHelpContentModel::contentsCreated, + this, &MainWindow::checkInitState); + connect(helpEngine.indexModel(), &QHelpIndexModel::indexCreated, + this, &MainWindow::checkInitState); + m_connectedInitSignals = true; + } + } else { + if (m_connectedInitSignals) { + disconnect(helpEngine.contentModel(), nullptr, this, nullptr); + disconnect(helpEngine.indexModel(), nullptr, this, nullptr); + } + HelpEngineWrapper::instance().initialDocSetupDone(); + emit initDone(); + } +} + +void MainWindow::insertLastPages() +{ + TRACE_OBJ + if (m_cmdLine->search() == CmdLineParser::Activate) + showSearch(); +} + +void MainWindow::setupActions() +{ + TRACE_OBJ + QString resourcePath = ":/qt-project.org/assistant/images/"_L1; +#ifdef Q_OS_MAC + setUnifiedTitleAndToolBarOnMac(true); + resourcePath.append("mac"_L1); +#else + resourcePath.append("win"_L1); +#endif + + QMenu *menu = menuBar()->addMenu(tr("&File")); + OpenPagesManager *const openPages = OpenPagesManager::instance(); + m_newTabAction = menu->addAction(tr("New &Tab"), + openPages, &OpenPagesManager::createBlankPage); + m_newTabAction->setShortcut(QKeySequence::AddTab); + m_closeTabAction = menu->addAction(tr("&Close Tab"), + openPages, &OpenPagesManager::closeCurrentPage); + m_closeTabAction->setShortcuts(QKeySequence::Close); + m_closeTabAction->setEnabled(openPages->pageCount() > 1); + connect(openPages, &OpenPagesManager::pageClosed, + this, &MainWindow::handlePageCountChanged); + connect(openPages, &OpenPagesManager::pageAdded, + this, &MainWindow::handlePageCountChanged); + + menu->addSeparator(); + + m_pageSetupAction = menu->addAction(tr("Page Set&up..."), + m_centralWidget, &CentralWidget::pageSetup); + m_printPreviewAction = menu->addAction(tr("Print Preview..."), + m_centralWidget, &CentralWidget::printPreview); + + GlobalActions *globalActions = GlobalActions::instance(this); + menu->addAction(globalActions->printAction()); + menu->addSeparator(); + + QIcon appExitIcon = QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit); + QAction *tmp; +#ifdef Q_OS_WIN + tmp = menu->addAction(appExitIcon, tr("E&xit"), + this, &QWidget::close); + tmp->setShortcut(QKeySequence(tr("CTRL+Q"))); +#else + tmp = menu->addAction(appExitIcon, tr("&Quit"), + this, &QWidget::close); + tmp->setShortcut(QKeySequence::Quit); +#endif + tmp->setMenuRole(QAction::QuitRole); + + menu = menuBar()->addMenu(tr("&Edit")); +#if QT_CONFIG(clipboard) + menu->addAction(globalActions->copyAction()); +#endif + menu->addAction(globalActions->findAction()); + + QAction *findNextAction = menu->addAction(tr("Find &Next"), + m_centralWidget, &CentralWidget::findNext); + findNextAction->setShortcuts(QKeySequence::FindNext); + + QAction *findPreviousAction = menu->addAction(tr("Find &Previous"), + m_centralWidget, &CentralWidget::findPrevious); + findPreviousAction->setShortcuts(QKeySequence::FindPrevious); + + menu->addSeparator(); + tmp = menu->addAction(tr("Preferences..."), + this, &MainWindow::showPreferences); + tmp->setMenuRole(QAction::PreferencesRole); + + m_viewMenu = menuBar()->addMenu(tr("&View")); + m_viewMenu->addAction(globalActions->zoomInAction()); + m_viewMenu->addAction(globalActions->zoomOutAction()); + + m_resetZoomAction = m_viewMenu->addAction(tr("Normal &Size"), + m_centralWidget, &CentralWidget::resetZoom); + m_resetZoomAction->setPriority(QAction::LowPriority); + m_resetZoomAction->setIcon(QIcon(resourcePath + "/resetzoom.png"_L1)); + m_resetZoomAction->setShortcut(tr("Ctrl+0")); + + m_viewMenu->addSeparator(); + + m_viewMenu->addAction(tr("Contents"), QKeySequence(tr("ALT+C")), + this, &MainWindow::showContents); + m_viewMenu->addAction(tr("Index"), QKeySequence(tr("ALT+I")), + this, &MainWindow::showIndex); + m_viewMenu->addAction(tr("Bookmarks"), QKeySequence(tr("ALT+O")), + this, &MainWindow::showBookmarksDockWidget); + m_viewMenu->addAction(tr("Search"), QKeySequence(tr("ALT+S")), + this, &MainWindow::showSearch); + m_viewMenu->addAction(tr("Open Pages"), QKeySequence(tr("ALT+P")), + this, &MainWindow::showOpenPages); + + menu = menuBar()->addMenu(tr("&Go")); + menu->addAction(globalActions->homeAction()); + menu->addAction(globalActions->backAction()); + menu->addAction(globalActions->nextAction()); + + m_syncAction = menu->addAction(tr("Sync with Table of Contents"), + this, &MainWindow::syncContents); + m_syncAction->setIconText(tr("Sync")); + m_syncAction->setIcon(QIcon(resourcePath + "/synctoc.png"_L1)); + + menu->addSeparator(); + + tmp = menu->addAction(tr("Next Page"), + openPages, &OpenPagesManager::nextPage); + tmp->setShortcuts(QList() << QKeySequence(tr("Ctrl+Alt+Right")) + << QKeySequence(Qt::CTRL | Qt::Key_PageDown)); + + tmp = menu->addAction(tr("Previous Page"), + openPages, &OpenPagesManager::previousPage); + tmp->setShortcuts(QList() << QKeySequence(tr("Ctrl+Alt+Left")) + << QKeySequence(Qt::CTRL | Qt::Key_PageUp)); + + const Qt::Modifier modifier = +#ifdef Q_OS_MAC + Qt::ALT; +#else + Qt::CTRL; +#endif + + QShortcut *sct = new QShortcut(QKeySequence(modifier | Qt::Key_Tab), this); + connect(sct, &QShortcut::activated, + openPages, &OpenPagesManager::nextPageWithSwitcher); + sct = new QShortcut(QKeySequence(modifier | Qt::SHIFT | Qt::Key_Tab), this); + connect(sct, &QShortcut::activated, + openPages, &OpenPagesManager::previousPageWithSwitcher); + + BookmarkManager::instance()->setBookmarksMenu(menuBar()->addMenu(tr("&Bookmarks"))); + + menu = menuBar()->addMenu(tr("&Help")); + m_aboutAction = menu->addAction(tr("About..."), + this, &MainWindow::showAboutDialog); + m_aboutAction->setMenuRole(QAction::AboutRole); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + m_resetZoomAction->setIcon(QIcon::fromTheme("zoom-original"_L1, m_resetZoomAction->icon())); + m_syncAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ViewRefresh, + m_syncAction->icon())); +#endif + + QToolBar *navigationBar = addToolBar(tr("Navigation Toolbar")); + navigationBar->setObjectName("NavigationToolBar"_L1); + navigationBar->addAction(globalActions->backAction()); + navigationBar->addAction(globalActions->nextAction()); + navigationBar->addAction(globalActions->homeAction()); + navigationBar->addAction(m_syncAction); + navigationBar->addSeparator(); +#if QT_CONFIG(clipboard) + navigationBar->addAction(globalActions->copyAction()); +#endif + navigationBar->addAction(globalActions->printAction()); + navigationBar->addAction(globalActions->findAction()); + navigationBar->addSeparator(); + navigationBar->addAction(globalActions->zoomInAction()); + navigationBar->addAction(globalActions->zoomOutAction()); + navigationBar->addAction(m_resetZoomAction); + +#if defined(Q_OS_MAC) + QMenu *windowMenu = new QMenu(tr("&Window"), this); + menuBar()->insertMenu(menu->menuAction(), windowMenu); + windowMenu->addAction(tr("Zoom"), + this, &QWidget::showMaximized); + windowMenu->addAction(tr("Minimize"), QKeySequence(tr("Ctrl+M")), + this, &QWidget::showMinimized); +#endif + + // content viewer connections +#if QT_CONFIG(clipboard) + connect(m_centralWidget, &CentralWidget::copyAvailable, + globalActions, &GlobalActions::setCopyAvailable); +#endif + connect(m_centralWidget, &CentralWidget::currentViewerChanged, + globalActions, &GlobalActions::updateActions); + connect(m_centralWidget, &CentralWidget::forwardAvailable, + globalActions, &GlobalActions::updateActions); + connect(m_centralWidget, &CentralWidget::backwardAvailable, + globalActions, &GlobalActions::updateActions); + connect(m_centralWidget, &CentralWidget::highlighted, + this, [this](const QUrl &link) { statusBar()->showMessage(link.toString());} ); + + // index window + connect(m_indexWindow, &IndexWindow::linkActivated, + m_centralWidget, &CentralWidget::setSource); + connect(m_indexWindow, &IndexWindow::documentsActivated, + this, &MainWindow::showTopicChooser); + connect(m_indexWindow, &IndexWindow::escapePressed, + this, &MainWindow::activateCurrentCentralWidgetTab); + + // content window + connect(m_contentWindow, &ContentWindow::linkActivated, + m_centralWidget, &CentralWidget::setSource); + connect(m_contentWindow, &ContentWindow::escapePressed, + this, &MainWindow::activateCurrentCentralWidgetTab); + + // search window + connect(m_searchWindow, &SearchWidget::requestShowLink, + CentralWidget::instance(), &CentralWidget::setSourceFromSearch); + connect(m_searchWindow, &SearchWidget::requestShowLinkInNewTab, + OpenPagesManager::instance(), &OpenPagesManager::createNewPageFromSearch); + +#if defined(QT_NO_PRINTER) + m_pageSetupAction->setVisible(false); + m_printPreviewAction->setVisible(false); + globalActions->printAction()->setVisible(false); +#endif +} + +QMenu *MainWindow::toolBarMenu() +{ + TRACE_OBJ + if (!m_toolBarMenu) { + m_viewMenu->addSeparator(); + m_toolBarMenu = m_viewMenu->addMenu(tr("Toolbars")); + } + return m_toolBarMenu; +} + +void MainWindow::setupFilterToolbar() +{ + TRACE_OBJ + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + if (!helpEngine.filterFunctionalityEnabled()) + return; + + m_filterCombo = new QComboBox(this); + m_filterCombo->setMinimumWidth(QFontMetrics({}). + horizontalAdvance("MakeTheComboBoxWidthEnough"_L1)); + + QToolBar *filterToolBar = addToolBar(tr("Filter Toolbar")); + filterToolBar->setObjectName("FilterToolBar"_L1); + filterToolBar->addWidget(new QLabel(tr("Filtered by:").append(u' '), + this)); + filterToolBar->addWidget(m_filterCombo); + + if (!helpEngine.filterToolbarVisible()) + filterToolBar->hide(); + toolBarMenu()->addAction(filterToolBar->toggleViewAction()); + + connect(&helpEngine, &HelpEngineWrapper::setupFinished, + this, &MainWindow::setupFilterCombo, Qt::QueuedConnection); + connect(m_filterCombo, &QComboBox::activated, + this, &MainWindow::filterDocumentation); + connect(helpEngine.filterEngine(), &QHelpFilterEngine::filterActivated, + this, &MainWindow::currentFilterChanged); + + setupFilterCombo(); +} + +void MainWindow::setupAddressToolbar() +{ + TRACE_OBJ + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + if (!helpEngine.addressBarEnabled()) + return; + + m_addressLineEdit = new QLineEdit(this); + QToolBar *addressToolBar = addToolBar(tr("Address Toolbar")); + addressToolBar->setObjectName("AddressToolBar"_L1); + insertToolBarBreak(addressToolBar); + + addressToolBar->addWidget(new QLabel(tr("Address:").append(QChar::Space), + this)); + addressToolBar->addWidget(m_addressLineEdit); + + if (!helpEngine.addressBarVisible()) + addressToolBar->hide(); + toolBarMenu()->addAction(addressToolBar->toggleViewAction()); + + // address lineedit + connect(m_addressLineEdit, &QLineEdit::returnPressed, this, &MainWindow::gotoAddress); + connect(m_centralWidget, &CentralWidget::currentViewerChanged, + this, QOverload<>::of(&MainWindow::showNewAddress)); + connect(m_centralWidget, &CentralWidget::sourceChanged, + this, QOverload<>::of(&MainWindow::showNewAddress)); +} + +void MainWindow::updateAboutMenuText() +{ + TRACE_OBJ + QByteArray ba = HelpEngineWrapper::instance().aboutMenuTexts(); + if (ba.size() > 0) { + QString lang; + QString str; + QString trStr; + QString currentLang = QLocale::system().name(); + int i = currentLang.indexOf(u'_'); + if (i > -1) + currentLang = currentLang.left(i); + QDataStream s(&ba, QIODevice::ReadOnly); + while (!s.atEnd()) { + s >> lang; + s >> str; + if (lang == "default"_L1 && trStr.isEmpty()) { + trStr = str; + } else if (lang == currentLang) { + trStr = str; + break; + } + } + if (!trStr.isEmpty()) + m_aboutAction->setText(trStr); + } +} + +void MainWindow::showNewAddress() +{ + TRACE_OBJ + showNewAddress(m_centralWidget->currentSource()); +} + +void MainWindow::showNewAddress(const QUrl &url) +{ + TRACE_OBJ + m_addressLineEdit->setText(url.toString()); +} + +void MainWindow::gotoAddress() +{ + TRACE_OBJ + m_centralWidget->setSource(m_addressLineEdit->text()); +} + +void MainWindow::showTopicChooser(const QList &documents, + const QString &keyword) +{ + TRACE_OBJ + TopicChooser tc(this, keyword, documents); + if (tc.exec() == QDialog::Accepted) { + m_centralWidget->setSource(tc.link()); + } +} + +void MainWindow::showPreferences() +{ + TRACE_OBJ + PreferencesDialog dia(this); + connect(&dia, &PreferencesDialog::updateApplicationFont, + this, &MainWindow::updateApplicationFont); + connect(&dia, &PreferencesDialog::updateBrowserFont, + m_centralWidget, &CentralWidget::updateBrowserFont); + connect(&dia, &PreferencesDialog::updateUserInterface, + m_centralWidget, &CentralWidget::updateUserInterface); + dia.exec(); +} + +void MainWindow::syncContents() +{ + TRACE_OBJ + qApp->setOverrideCursor(QCursor(Qt::WaitCursor)); + const QUrl url = m_centralWidget->currentSource(); + showContents(); + if (!m_contentWindow->syncToContent(url)) + statusBar()->showMessage( + tr("Could not find the associated content item."), 3000); + qApp->restoreOverrideCursor(); +} + +void MainWindow::showAboutDialog() +{ + TRACE_OBJ + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + QByteArray contents; + QByteArray ba = helpEngine.aboutTexts(); + if (!ba.isEmpty()) { + QString lang; + QByteArray cba; + QString currentLang = QLocale::system().name(); + int i = currentLang.indexOf(u'_'); + if (i > -1) + currentLang = currentLang.left(i); + QDataStream s(&ba, QIODevice::ReadOnly); + while (!s.atEnd()) { + s >> lang; + s >> cba; + if (lang == "default"_L1 && contents.isEmpty()) { + contents = cba; + } else if (lang == currentLang) { + contents = cba; + break; + } + } + } + + AboutDialog aboutDia(this); + + QByteArray iconArray; + if (!contents.isEmpty()) { + iconArray = helpEngine.aboutIcon(); + QByteArray resources = helpEngine.aboutImages(); + QPixmap pix; + pix.loadFromData(iconArray); + aboutDia.setText(QString::fromUtf8(contents), resources); + if (!pix.isNull()) + aboutDia.setPixmap(pix); + aboutDia.setWindowTitle(aboutDia.documentTitle()); + } else { + QByteArray resources; +#if defined(BROWSER_QTWEBKIT) + QString browser = QStringLiteral("Qt WebKit"); +#else + QString browser = QStringLiteral("QTextBrowser"); +#endif + if (m_centralWidget->currentHelpViewer()) + browser = QStringLiteral("QLiteHtmlWidget"); + aboutDia.setText(tr("
" + "

%1

" + "

Version %2

" + "

Browser: %3

" + "

Copyright (C) The Qt Company Ltd. and other contributors.

") + .arg(tr("Qt Assistant"), QLatin1String(QT_VERSION_STR), browser), + resources); + aboutDia.setPixmap(QString(":/qt-project.org/assistant/images/assistant-128.png"_L1)); + } + if (aboutDia.windowTitle().isEmpty()) + aboutDia.setWindowTitle(tr("About %1").arg(windowTitle())); + aboutDia.exec(); +} + +void MainWindow::setContentsVisible(bool visible) +{ + TRACE_OBJ + if (visible) + showContents(); + else + hideContents(); +} + +void MainWindow::showContents() +{ + TRACE_OBJ + activateDockWidget(m_contentWindow); +} + +void MainWindow::hideContents() +{ + TRACE_OBJ + m_contentWindow->parentWidget()->hide(); +} + +void MainWindow::setIndexVisible(bool visible) +{ + TRACE_OBJ + if (visible) + showIndex(); + else + hideIndex(); +} + +void MainWindow::showIndex() +{ + TRACE_OBJ + activateDockWidget(m_indexWindow); +} + +void MainWindow::hideIndex() +{ + TRACE_OBJ + m_indexWindow->parentWidget()->hide(); +} + +void MainWindow::setBookmarksVisible(bool visible) +{ + TRACE_OBJ + if (visible) + showBookmarksDockWidget(); + else + hideBookmarksDockWidget(); +} + +void MainWindow::showBookmarksDockWidget() +{ + TRACE_OBJ + activateDockWidget(m_bookmarkWidget); +} + +void MainWindow::hideBookmarksDockWidget() +{ + TRACE_OBJ + m_bookmarkWidget->parentWidget()->hide(); +} + +void MainWindow::setSearchVisible(bool visible) +{ + TRACE_OBJ + if (visible) + showSearch(); + else + hideSearch(); +} + +void MainWindow::showSearch() +{ + TRACE_OBJ + activateDockWidget(m_searchWindow); +} + +void MainWindow::showOpenPages() +{ + TRACE_OBJ + activateDockWidget(OpenPagesManager::instance()->openPagesWidget()); +} + +void MainWindow::hideSearch() +{ + TRACE_OBJ + m_searchWindow->parentWidget()->hide(); +} + +void MainWindow::activateDockWidget(QWidget *w) +{ + TRACE_OBJ + w->parentWidget()->show(); + w->parentWidget()->raise(); + w->setFocus(); +} + +void MainWindow::setIndexString(const QString &str) +{ + TRACE_OBJ + m_indexWindow->setSearchLineEditText(str); +} + +void MainWindow::activateCurrentBrowser() +{ + TRACE_OBJ + CentralWidget::instance()->activateTab(); +} + +void MainWindow::activateCurrentCentralWidgetTab() +{ + TRACE_OBJ + m_centralWidget->activateTab(); +} + +void MainWindow::updateApplicationFont() +{ + TRACE_OBJ + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + QFont font = qApp->font(); + if (helpEngine.usesAppFont()) + font = helpEngine.appFont(); + + const QWidgetList &widgets = QApplication::allWidgets(); + for (QWidget *widget : widgets) + widget->setFont(font); +} + +void MainWindow::setupFilterCombo() +{ + TRACE_OBJ + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + const QString currentFilter = helpEngine.filterEngine()->activeFilter(); + m_filterCombo->clear(); + m_filterCombo->addItem(tr("Unfiltered")); + const QStringList allFilters = helpEngine.filterEngine()->filters(); + if (!allFilters.isEmpty()) + m_filterCombo->insertSeparator(1); + for (const QString &filter : allFilters) + m_filterCombo->addItem(filter, filter); + + int idx = m_filterCombo->findData(currentFilter); + if (idx < 0) + idx = 0; + m_filterCombo->setCurrentIndex(idx); +} + +void MainWindow::filterDocumentation(int filterIndex) +{ + TRACE_OBJ + + const QString filter = m_filterCombo->itemData(filterIndex).toString(); + HelpEngineWrapper::instance().filterEngine()->setActiveFilter(filter); +} + +void MainWindow::expandTOC(int depth) +{ + TRACE_OBJ + Q_ASSERT(depth >= -1); + m_contentWindow->expandToDepth(depth); +} + +void MainWindow::indexingStarted() +{ + TRACE_OBJ + if (!m_progressWidget) { + m_progressWidget = new QWidget(); + QLayout* hlayout = new QHBoxLayout(m_progressWidget); + + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + QLabel *label = new QLabel(tr("Updating search index")); + label->setSizePolicy(sizePolicy); + hlayout->addWidget(label); + + QProgressBar *progressBar = new QProgressBar(); + progressBar->setRange(0, 0); + progressBar->setTextVisible(false); + progressBar->setSizePolicy(sizePolicy); + + hlayout->setSpacing(6); + hlayout->setContentsMargins(QMargins()); + hlayout->addWidget(progressBar); + + statusBar()->addPermanentWidget(m_progressWidget); + } +} + +void MainWindow::indexingFinished() +{ + TRACE_OBJ + statusBar()->removeWidget(m_progressWidget); + delete m_progressWidget; + m_progressWidget = nullptr; +} + +QString MainWindow::collectionFileDirectory(bool createDir, const QString &cacheDir) +{ + TRACE_OBJ + QString collectionPath = + QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); + if (collectionPath.isEmpty()) { + if (cacheDir.isEmpty()) + collectionPath = QDir::homePath() + QDir::separator() + ".assistant"_L1; + else + collectionPath = QDir::homePath() + "/."_L1 + cacheDir; + } else { + if (cacheDir.isEmpty()) + collectionPath = collectionPath + "/QtProject/Assistant"_L1; + else + collectionPath = collectionPath + QDir::separator() + cacheDir; + } + if (createDir) { + QDir dir; + if (!dir.exists(collectionPath)) + dir.mkpath(collectionPath); + } + return collectionPath; +} + +QString MainWindow::defaultHelpCollectionFileName() +{ + TRACE_OBJ + // forces creation of the default collection file path + return collectionFileDirectory(true) + QDir::separator() + + QString("qthelpcollection_%1.qhc"_L1).arg(QLatin1StringView(QT_VERSION_STR)); +} + +void MainWindow::currentFilterChanged(const QString &filter) +{ + TRACE_OBJ + int index = m_filterCombo->findData(filter); + if (index < 0) + index = 0; + m_filterCombo->setCurrentIndex(index); +} + +void MainWindow::documentationRemoved(const QString &namespaceName) +{ + TRACE_OBJ + OpenPagesManager::instance()->closePages(namespaceName); +} + +void MainWindow::documentationUpdated(const QString &namespaceName) +{ + TRACE_OBJ + OpenPagesManager::instance()->reloadPages(namespaceName); +} + +void MainWindow::resetQtDocInfo(const QString &component) +{ + TRACE_OBJ + HelpEngineWrapper::instance().setQtDocInfo(component, + QStringList(QDateTime().toString(Qt::ISODate))); +} + +void MainWindow::registerDocumentation(const QString &component, + const QString &absFileName) +{ + TRACE_OBJ + QString ns = QHelpEngineCore::namespaceName(absFileName); + if (ns.isEmpty()) + return; + + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + if (helpEngine.registeredDocumentations().contains(ns)) + helpEngine.unregisterDocumentation(ns); + if (!helpEngine.registerDocumentation(absFileName)) { + QMessageBox::warning(this, tr("Qt Assistant"), + tr("Could not register file '%1': %2"). + arg(absFileName).arg(helpEngine.error())); + } else { + QStringList docInfo; + docInfo << QFileInfo(absFileName).lastModified().toString(Qt::ISODate) + << absFileName; + helpEngine.setQtDocInfo(component, docInfo); + } +} + +void MainWindow::handlePageCountChanged() +{ + m_closeTabAction->setEnabled(OpenPagesManager::instance()->pageCount() > 1); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/mainwindow.h b/src/assistant/assistant/mainwindow.h new file mode 100644 index 0000000..9629af5 --- /dev/null +++ b/src/assistant/assistant/mainwindow.h @@ -0,0 +1,131 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; +class QComboBox; +class QLineEdit; +class QMenu; + +class CentralWidget; +class CmdLineParser; +class ContentWindow; +class IndexWindow; +class QtDocInstaller; +class SearchWidget; +struct QHelpLink; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + Q_MOC_INCLUDE() + +public: + explicit MainWindow(CmdLineParser *cmdLine, QWidget *parent = nullptr); + ~MainWindow() override; + + static void activateCurrentBrowser(); + static QString collectionFileDirectory(bool createDir = false, + const QString &cacheDir = QString()); + static QString defaultHelpCollectionFileName(); + +public: + void setIndexString(const QString &str); + void expandTOC(int depth); + bool usesDefaultCollection() const; + +signals: + void initDone(); + +public slots: + void setContentsVisible(bool visible); + void setIndexVisible(bool visible); + void setBookmarksVisible(bool visible); + void setSearchVisible(bool visible); + void syncContents(); + void activateCurrentCentralWidgetTab(); + void currentFilterChanged(const QString &filter); + +private slots: + void showContents(); + void showIndex(); + void showSearch(); + void showOpenPages(); + void insertLastPages(); + void gotoAddress(); + void showPreferences(); + void showNewAddress(); + void showAboutDialog(); + void showNewAddress(const QUrl &url); + void showTopicChooser(const QList &documents, const QString &keyword); + void updateApplicationFont(); + void filterDocumentation(int filterIndex); + void setupFilterCombo(); + void lookForNewQtDocumentation(); + void indexingStarted(); + void indexingFinished(); + void qtDocumentationInstalled(); + void registerDocumentation(const QString &component, + const QString &absFileName); + void resetQtDocInfo(const QString &component); + void checkInitState(); + void documentationRemoved(const QString &namespaceName); + void documentationUpdated(const QString &namespaceName); + +private: + bool initHelpDB(bool registerInternalDoc); + void setupActions(); + void closeEvent(QCloseEvent *e) override; + void activateDockWidget(QWidget *w); + void updateAboutMenuText(); + void setupFilterToolbar(); + void setupAddressToolbar(); + QMenu *toolBarMenu(); + void hideContents(); + void hideIndex(); + void hideSearch(); + +private slots: + void showBookmarksDockWidget(); + void hideBookmarksDockWidget(); + void handlePageCountChanged(); + +private: + QWidget *m_bookmarkWidget = nullptr; + CentralWidget *m_centralWidget; + IndexWindow *m_indexWindow; + ContentWindow *m_contentWindow; + SearchWidget *m_searchWindow; + QLineEdit *m_addressLineEdit; + QComboBox *m_filterCombo = nullptr; + + QAction *m_syncAction; + QAction *m_printPreviewAction; + QAction *m_pageSetupAction; + QAction *m_resetZoomAction; + QAction *m_aboutAction; + QAction *m_closeTabAction; + QAction *m_newTabAction; + + QMenu *m_viewMenu; + QMenu *m_toolBarMenu = nullptr; + + CmdLineParser *m_cmdLine; + + QWidget *m_progressWidget = nullptr; + QtDocInstaller *m_qtDocInstaller = nullptr; + + bool m_connectedInitSignals = false; +}; + +QT_END_NAMESPACE + +#endif // MAINWINDOW_H diff --git a/src/assistant/assistant/openpagesmanager.cpp b/src/assistant/assistant/openpagesmanager.cpp new file mode 100644 index 0000000..2657c98 --- /dev/null +++ b/src/assistant/assistant/openpagesmanager.cpp @@ -0,0 +1,354 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "openpagesmanager.h" + +#include "centralwidget.h" +#include "helpenginewrapper.h" +#include "helpviewer.h" +#include "openpagesmodel.h" +#include "openpagesswitcher.h" +#include "openpageswidget.h" +#include "tracer.h" +#include "../shared/collectionconfiguration.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +OpenPagesManager *OpenPagesManager::m_instance = nullptr; + +OpenPagesManager *OpenPagesManager::createInstance(QObject *parent, + bool defaultCollection, const QUrl &cmdLineUrl) +{ + TRACE_OBJ + Q_ASSERT(!m_instance); + m_instance = new OpenPagesManager(parent, defaultCollection, cmdLineUrl); + return m_instance; +} + +OpenPagesManager *OpenPagesManager::instance() +{ + TRACE_OBJ + Q_ASSERT(m_instance); + return m_instance; +} + +OpenPagesManager::OpenPagesManager(QObject *parent, bool defaultCollection, + const QUrl &cmdLineUrl) + : QObject(parent) + , m_model(new OpenPagesModel(this)) +{ + TRACE_OBJ + m_openPagesWidget = new OpenPagesWidget(m_model); + m_openPagesWidget->setFrameStyle(QFrame::NoFrame); + connect(m_openPagesWidget, &OpenPagesWidget::setCurrentPage, + this, QOverload::of(&OpenPagesManager::setCurrentPage)); + connect(m_openPagesWidget, &OpenPagesWidget::closePage, + this, QOverload::of(&OpenPagesManager::closePage)); + connect(m_openPagesWidget, &OpenPagesWidget::closePagesExcept, + this, &OpenPagesManager::closePagesExcept); + + m_openPagesSwitcher = new OpenPagesSwitcher(m_model); + connect(m_openPagesSwitcher, &OpenPagesSwitcher::closePage, + this, QOverload::of(&OpenPagesManager::closePage)); + connect(m_openPagesSwitcher, &OpenPagesSwitcher::setCurrentPage, + this, QOverload::of(&OpenPagesManager::setCurrentPage)); + + setupInitialPages(defaultCollection, cmdLineUrl); +} + +OpenPagesManager ::~OpenPagesManager() +{ + TRACE_OBJ + m_instance = nullptr; + delete m_openPagesSwitcher; +} + +int OpenPagesManager::pageCount() const +{ + TRACE_OBJ + return m_model->rowCount(); +} + +void OpenPagesManager::setupInitialPages(bool defaultCollection, + const QUrl &cmdLineUrl) +{ + TRACE_OBJ + if (cmdLineUrl.isValid()) { + createPage(cmdLineUrl); + return; + } + + HelpEngineWrapper &helpEngine = HelpEngineWrapper::instance(); + int initialPage = 0; + switch (helpEngine.startOption()) { + case ShowHomePage: + m_model->addPage(helpEngine.homePage()); + break; + case ShowBlankPage: + m_model->addPage(QUrl("about:blank"_L1)); + break; + case ShowLastPages: { + const QStringList &lastShownPageList = helpEngine.lastShownPages(); + const int pageCount = lastShownPageList.size(); + if (pageCount == 0) { + if (defaultCollection) + m_helpPageViewer = m_model->addPage(QUrl("help"_L1)); + else + m_model->addPage(QUrl("about:blank"_L1)); + } else { + QStringList zoomFactors = helpEngine.lastZoomFactors(); + while (zoomFactors.size() < pageCount) + zoomFactors.append(CollectionConfiguration::DefaultZoomFactor); + initialPage = helpEngine.lastTabPage(); + if (initialPage >= pageCount) { + qWarning("Initial page set to %d, maximum possible value is %d", + initialPage, pageCount - 1); + initialPage = 0; + } + for (int curPage = 0; curPage < pageCount; ++curPage) { + const QString &curFile = lastShownPageList.at(curPage); + if (helpEngine.findFile(curFile).isValid() + || curFile == "about:blank"_L1) { + m_model->addPage(curFile, zoomFactors.at(curPage).toFloat()); + } else if (curPage <= initialPage && initialPage > 0) + --initialPage; + } + } + break; + } + default: + Q_ASSERT(0); + } + + if (m_model->rowCount() == 0) + m_model->addPage(helpEngine.homePage()); + for (int i = 0; i < m_model->rowCount(); ++i) + CentralWidget::instance()->addPage(m_model->pageAt(i)); + setCurrentPage((initialPage >= m_model->rowCount()) + ? m_model->rowCount() - 1 : initialPage); + m_openPagesSwitcher->selectCurrentPage(); +} + +HelpViewer *OpenPagesManager::createBlankPage() +{ + TRACE_OBJ + return createPage(QUrl("about:blank"_L1)); +} + +void OpenPagesManager::closeCurrentPage() +{ + TRACE_OBJ + Q_ASSERT(m_model->rowCount() > 1); + const QModelIndexList selectedIndexes + = m_openPagesWidget->selectionModel()->selectedRows(); + if (selectedIndexes.isEmpty()) + return; + Q_ASSERT(selectedIndexes.size() == 1); + removePage(selectedIndexes.first().row()); +} + +HelpViewer *OpenPagesManager::createPage(const QUrl &url, bool fromSearch) +{ + TRACE_OBJ + if (HelpViewer::launchWithExternalApp(url)) + return nullptr; + + emit aboutToAddPage(); + + m_model->addPage(url); + const int index = m_model->rowCount() - 1; + HelpViewer * const page = m_model->pageAt(index); + CentralWidget::instance()->addPage(page, fromSearch); + setCurrentPage(index); + + emit pageAdded(index); + return page; +} + +HelpViewer *OpenPagesManager::createNewPageFromSearch(const QUrl &url) +{ + TRACE_OBJ + return createPage(url, true); +} + +void OpenPagesManager::closePage(HelpViewer *viewer) +{ + TRACE_OBJ + for (int i = 0; i < m_model->rowCount(); ++i) { + if (m_model->pageAt(i) == viewer) { + removePage(i); + break; + } + } +} + +void OpenPagesManager::closePage(const QModelIndex &index) +{ + TRACE_OBJ + if (index.isValid()) + removePage(index.row()); +} + +void OpenPagesManager::closePages(const QString &nameSpace) +{ + TRACE_OBJ + closeOrReloadPages(nameSpace, false); +} + +void OpenPagesManager::reloadPages(const QString &nameSpace) +{ + TRACE_OBJ + closeOrReloadPages(nameSpace, true); + m_openPagesWidget->selectCurrentPage(); +} + +void OpenPagesManager::closeOrReloadPages(const QString &nameSpace, bool tryReload) +{ + TRACE_OBJ + for (int i = m_model->rowCount() - 1; i >= 0; --i) { + HelpViewer *page = m_model->pageAt(i); + if (page->source().host() != nameSpace) + continue; + if (tryReload && HelpEngineWrapper::instance().findFile(page->source()).isValid()) + page->reload(); + else if (m_model->rowCount() == 1) + page->setSource(QUrl("about:blank"_L1)); + else + removePage(i); + } +} + +bool OpenPagesManager::pagesOpenForNamespace(const QString &nameSpace) const +{ + TRACE_OBJ + for (int i = 0; i < m_model->rowCount(); ++i) + if (m_model->pageAt(i)->source().host() == nameSpace) + return true; + return false; +} + +void OpenPagesManager::setCurrentPage(const QModelIndex &index) +{ + TRACE_OBJ + if (index.isValid()) + setCurrentPage(index.row()); +} + +void OpenPagesManager::setCurrentPage(int index) +{ + TRACE_OBJ + setCurrentPage(m_model->pageAt(index)); +} + +void OpenPagesManager::resetHelpPage() +{ + if (m_helpPageViewer) + m_helpPageViewer->reload(); +} + +void OpenPagesManager::setCurrentPage(HelpViewer *page) +{ + TRACE_OBJ + CentralWidget::instance()->setCurrentPage(page); + m_openPagesWidget->selectCurrentPage(); +} + +void OpenPagesManager::removePage(int index) +{ + TRACE_OBJ + emit aboutToClosePage(index); + + CentralWidget::instance()->removePage(index); + m_model->removePage(index); + m_openPagesWidget->selectCurrentPage(); + + emit pageClosed(); +} + + +void OpenPagesManager::closePagesExcept(const QModelIndex &index) +{ + TRACE_OBJ + if (!index.isValid()) + return; + + int i = 0; + HelpViewer *viewer = m_model->pageAt(index.row()); + while (m_model->rowCount() > 1) { + if (m_model->pageAt(i) != viewer) + removePage(i); + else + ++i; + } +} + +QAbstractItemView *OpenPagesManager::openPagesWidget() const +{ + TRACE_OBJ + return m_openPagesWidget; +} + +void OpenPagesManager::nextPage() +{ + TRACE_OBJ + nextOrPreviousPage(1); +} + +void OpenPagesManager::nextPageWithSwitcher() +{ + TRACE_OBJ + if (!m_openPagesSwitcher->isVisible()) { + m_openPagesSwitcher->selectCurrentPage(); + m_openPagesSwitcher->gotoNextPage(); + showSwitcherOrSelectPage(); + } else { + m_openPagesSwitcher->gotoNextPage(); + } +} + +void OpenPagesManager::previousPage() +{ + TRACE_OBJ + nextOrPreviousPage(-1); +} + +void OpenPagesManager::previousPageWithSwitcher() +{ + TRACE_OBJ + if (!m_openPagesSwitcher->isVisible()) { + m_openPagesSwitcher->selectCurrentPage(); + m_openPagesSwitcher->gotoPreviousPage(); + showSwitcherOrSelectPage(); + } else { + m_openPagesSwitcher->gotoPreviousPage(); + } +} + +void OpenPagesManager::nextOrPreviousPage(int offset) +{ + TRACE_OBJ + setCurrentPage((CentralWidget::instance()->currentIndex() + offset + + m_model->rowCount()) % m_model->rowCount()); +} + +void OpenPagesManager::showSwitcherOrSelectPage() const +{ + TRACE_OBJ + if (QApplication::keyboardModifiers() != Qt::NoModifier) { + const int width = CentralWidget::instance()->width(); + const int height = CentralWidget::instance()->height(); + const QPoint p(CentralWidget::instance()->mapToGlobal(QPoint(0, 0))); + m_openPagesSwitcher->move((width - m_openPagesSwitcher->width()) / 2 + p.x(), + (height - m_openPagesSwitcher->height()) / 2 + p.y()); + m_openPagesSwitcher->setVisible(true); + } else { + m_openPagesSwitcher->selectAndHide(); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/openpagesmanager.h b/src/assistant/assistant/openpagesmanager.h new file mode 100644 index 0000000..f61bf0c --- /dev/null +++ b/src/assistant/assistant/openpagesmanager.h @@ -0,0 +1,89 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OPENPAGESMANAGER_H +#define OPENPAGESMANAGER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QAbstractItemView; +class QModelIndex; +class QUrl; + +class HelpViewer; +class OpenPagesModel; +class OpenPagesSwitcher; +class OpenPagesWidget; + +class OpenPagesManager : public QObject +{ + Q_OBJECT +public: + static OpenPagesManager *createInstance(QObject *parent, + bool defaultCollection, const QUrl &cmdLineUrl); + static OpenPagesManager *instance(); + + bool pagesOpenForNamespace(const QString &nameSpace) const; + void closePages(const QString &nameSpace); + void reloadPages(const QString &nameSpace); + + QAbstractItemView* openPagesWidget() const; + + int pageCount() const; + void setCurrentPage(int index); + + void resetHelpPage(); + +public slots: + HelpViewer *createPage(const QUrl &url, bool fromSearch = false); + HelpViewer *createNewPageFromSearch(const QUrl &url); + HelpViewer *createBlankPage(); + void closeCurrentPage(); + + void nextPage(); + void nextPageWithSwitcher(); + void previousPage(); + void previousPageWithSwitcher(); + + void closePage(HelpViewer *page); + void setCurrentPage(HelpViewer *page); + +signals: + void aboutToAddPage(); + void pageAdded(int index); + + void pageClosed(); + void aboutToClosePage(int index); + +private slots: + void setCurrentPage(const QModelIndex &index); + void closePage(const QModelIndex &index); + void closePagesExcept(const QModelIndex &index); + +private: + OpenPagesManager(QObject *parent, bool defaultCollection, + const QUrl &cmdLineUrl); + ~OpenPagesManager(); + + void setupInitialPages(bool defaultCollection, const QUrl &cmdLineUrl); + void closeOrReloadPages(const QString &nameSpace, bool tryReload); + void removePage(int index); + + void nextOrPreviousPage(int offset); + void showSwitcherOrSelectPage() const; + + OpenPagesModel *m_model; + OpenPagesWidget *m_openPagesWidget = nullptr; + OpenPagesSwitcher *m_openPagesSwitcher = nullptr; + + QPointer m_helpPageViewer; + + static OpenPagesManager *m_instance; +}; + +QT_END_NAMESPACE + +#endif // OPENPAGESMANAGER_H diff --git a/src/assistant/assistant/openpagesmodel.cpp b/src/assistant/assistant/openpagesmodel.cpp new file mode 100644 index 0000000..12620f6 --- /dev/null +++ b/src/assistant/assistant/openpagesmodel.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "openpagesmodel.h" + +#include "helpenginewrapper.h" +#include "helpviewer.h" +#include "tracer.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +OpenPagesModel::OpenPagesModel(QObject *parent) : QAbstractTableModel(parent) +{ + TRACE_OBJ +} + +int OpenPagesModel::rowCount(const QModelIndex &parent) const +{ + TRACE_OBJ + return parent.isValid() ? 0 : m_pages.size(); +} + +int OpenPagesModel::columnCount(const QModelIndex &/*parent*/) const +{ + TRACE_OBJ + return 2; +} + +QVariant OpenPagesModel::data(const QModelIndex &index, int role) const +{ + TRACE_OBJ + if (!index.isValid() || index.row() >= rowCount() || index.column() > 0 + || role != Qt::DisplayRole) + return QVariant(); + QString title = m_pages.at(index.row())->title(); + title.replace(u'&', "&&"_L1); + return title.isEmpty() ? "(Untitled)"_L1 : title; +} + +HelpViewer *OpenPagesModel::addPage(const QUrl &url, qreal zoom) +{ + TRACE_OBJ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + HelpViewer *page = new HelpViewer(zoom); + connect(page, &HelpViewer::titleChanged, this, &OpenPagesModel::handleTitleChanged); + m_pages << page; + endInsertRows(); + page->setSource(url); + return page; +} + +void OpenPagesModel::removePage(int index) +{ + TRACE_OBJ + Q_ASSERT(index >= 0 && index < rowCount()); + beginRemoveRows(QModelIndex(), index, index); + HelpViewer *page = m_pages.at(index); + m_pages.removeAt(index); + endRemoveRows(); + page->deleteLater(); +} + +HelpViewer *OpenPagesModel::pageAt(int index) const +{ + TRACE_OBJ + Q_ASSERT(index >= 0 && index < rowCount()); + return m_pages.at(index); +} + +void OpenPagesModel::handleTitleChanged() +{ + TRACE_OBJ + HelpViewer *page = static_cast(sender()); + const int row = m_pages.indexOf(page); + Q_ASSERT(row != -1 ); + const QModelIndex &item = index(row, 0); + emit dataChanged(item, item); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/openpagesmodel.h b/src/assistant/assistant/openpagesmodel.h new file mode 100644 index 0000000..3e184bf --- /dev/null +++ b/src/assistant/assistant/openpagesmodel.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OPENPAGESMODEL_H +#define OPENPAGESMODEL_H + +#include "openpagesmanager.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class HelpViewer; +class QUrl; + +class OpenPagesModel : public QAbstractTableModel +{ + Q_OBJECT + friend class OpenPagesManager; +public: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + HelpViewer *addPage(const QUrl &url, qreal zoom = 0); + void removePage(int index); + HelpViewer *pageAt(int index) const; + +private slots: + void handleTitleChanged(); + +private: + OpenPagesModel(QObject *parent); + +private: + QList m_pages; +}; + +QT_END_NAMESPACE + +#endif // OPENPAGESMODEL_H diff --git a/src/assistant/assistant/openpagesswitcher.cpp b/src/assistant/assistant/openpagesswitcher.cpp new file mode 100644 index 0000000..c5d97bd --- /dev/null +++ b/src/assistant/assistant/openpagesswitcher.cpp @@ -0,0 +1,156 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "openpagesswitcher.h" + +#include "centralwidget.h" +#include "openpagesmodel.h" +#include "openpageswidget.h" +#include "tracer.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +const int gWidth = 300; +const int gHeight = 200; + +OpenPagesSwitcher::OpenPagesSwitcher(OpenPagesModel *model) + : QFrame(nullptr, Qt::Popup) + , m_openPagesModel(model) +{ + TRACE_OBJ + resize(gWidth, gHeight); + + m_openPagesWidget = new OpenPagesWidget(m_openPagesModel); + + // We disable the frame on this list view and use a QFrame around it instead. + // This improves the look with QGTKStyle. +#ifndef Q_OS_MAC + setFrameStyle(m_openPagesWidget->frameStyle()); +#endif + m_openPagesWidget->setFrameStyle(QFrame::NoFrame); + + m_openPagesWidget->allowContextMenu(false); + m_openPagesWidget->installEventFilter(this); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->addWidget(m_openPagesWidget); + + connect(m_openPagesWidget, &OpenPagesWidget::closePage, + this, &OpenPagesSwitcher::closePage); + connect(m_openPagesWidget, &OpenPagesWidget::setCurrentPage, + this, &OpenPagesSwitcher::setCurrentPage); +} + +OpenPagesSwitcher::~OpenPagesSwitcher() +{ + TRACE_OBJ +} + +void OpenPagesSwitcher::gotoNextPage() +{ + TRACE_OBJ + selectPageUpDown(1); +} + +void OpenPagesSwitcher::gotoPreviousPage() +{ + TRACE_OBJ + selectPageUpDown(-1); +} + +void OpenPagesSwitcher::selectAndHide() +{ + TRACE_OBJ + setVisible(false); + emit setCurrentPage(m_openPagesWidget->currentIndex()); +} + +void OpenPagesSwitcher::selectCurrentPage() +{ + TRACE_OBJ + m_openPagesWidget->selectCurrentPage(); +} + +void OpenPagesSwitcher::setVisible(bool visible) +{ + TRACE_OBJ + QWidget::setVisible(visible); + if (visible) + setFocus(); +} + +void OpenPagesSwitcher::focusInEvent(QFocusEvent *event) +{ + TRACE_OBJ + Q_UNUSED(event); + m_openPagesWidget->setFocus(); +} + +bool OpenPagesSwitcher::eventFilter(QObject *object, QEvent *event) +{ + TRACE_OBJ + if (object == m_openPagesWidget) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + if (ke->key() == Qt::Key_Escape) { + setVisible(false); + return true; + } + + const int key = ke->key(); + if (key == Qt::Key_Return || key == Qt::Key_Enter || key == Qt::Key_Space) { + emit setCurrentPage(m_openPagesWidget->currentIndex()); + return true; + } + + Qt::KeyboardModifier modifier = Qt::ControlModifier; +#ifdef Q_OS_MAC + modifier = Qt::AltModifier; +#endif + if (key == Qt::Key_Backtab + && (ke->modifiers() == (modifier | Qt::ShiftModifier))) + gotoPreviousPage(); + else if (key == Qt::Key_Tab && (ke->modifiers() == modifier)) + gotoNextPage(); + } else if (event->type() == QEvent::KeyRelease) { + QKeyEvent *ke = static_cast(event); + if (ke->modifiers() == 0 + /*HACK this is to overcome some event inconsistencies between platforms*/ + || (ke->modifiers() == Qt::AltModifier + && (ke->key() == Qt::Key_Alt || ke->key() == -1))) { + selectAndHide(); + } + } + } + return QWidget::eventFilter(object, event); +} + +void OpenPagesSwitcher::selectPageUpDown(int summand) +{ + TRACE_OBJ + const int pageCount = m_openPagesModel->rowCount(); + if (pageCount < 2) + return; + + const QModelIndexList &list = m_openPagesWidget->selectionModel()->selectedIndexes(); + if (list.isEmpty()) + return; + + QModelIndex index = list.first(); + if (!index.isValid()) + return; + + index = m_openPagesModel->index((index.row() + summand + pageCount) % pageCount, 0); + if (index.isValid()) { + m_openPagesWidget->setCurrentIndex(index); + m_openPagesWidget->scrollTo(index, QAbstractItemView::PositionAtCenter); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/openpagesswitcher.h b/src/assistant/assistant/openpagesswitcher.h new file mode 100644 index 0000000..b904bcc --- /dev/null +++ b/src/assistant/assistant/openpagesswitcher.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OPENPAGESSWITCHER_H +#define OPENPAGESSWITCHER_H + +#include + +QT_BEGIN_NAMESPACE + +class OpenPagesModel; +class OpenPagesWidget; +class QModelIndex; + +class OpenPagesSwitcher : public QFrame +{ + Q_OBJECT + +public: + OpenPagesSwitcher(OpenPagesModel *model); + ~OpenPagesSwitcher() override; + + void gotoNextPage(); + void gotoPreviousPage(); + + void selectAndHide(); + void selectCurrentPage(); + + void setVisible(bool visible) override; + void focusInEvent(QFocusEvent *event) override; + bool eventFilter(QObject *object, QEvent *event) override; + +signals: + void closePage(const QModelIndex &index); + void setCurrentPage(const QModelIndex &index); + +private: + void selectPageUpDown(int summand); + +private: + OpenPagesModel *m_openPagesModel; + OpenPagesWidget *m_openPagesWidget; +}; + +QT_END_NAMESPACE + +#endif // OPENPAGESSWITCHER_H diff --git a/src/assistant/assistant/openpageswidget.cpp b/src/assistant/assistant/openpageswidget.cpp new file mode 100644 index 0000000..937e55b --- /dev/null +++ b/src/assistant/assistant/openpageswidget.cpp @@ -0,0 +1,198 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "openpageswidget.h" + +#include "centralwidget.h" +#include "openpagesmodel.h" +#include "tracer.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +OpenPagesDelegate::OpenPagesDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ + TRACE_OBJ +} + +void OpenPagesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + TRACE_OBJ + if (option.state & QStyle::State_MouseOver) { +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + if ((QApplication::mouseButtons() & Qt::LeftButton) == 0) + pressedIndex = QModelIndex(); +QT_WARNING_POP + QBrush brush = option.palette.alternateBase(); + if (index == pressedIndex) + brush = option.palette.dark(); + painter->fillRect(option.rect, brush); + } + + QStyledItemDelegate::paint(painter, option, index); + + if (index.column() == 1 && index.model()->rowCount() > 1 + && option.state & QStyle::State_MouseOver) { + QIcon icon((option.state & QStyle::State_Selected) + ? ":/qt-project.org/assistant/images/closebutton.png" + : ":/qt-project.org/assistant/images/darkclosebutton.png"); + + const QRect iconRect(option.rect.right() - option.rect.height(), + option.rect.top(), option.rect.height(), option.rect.height()); + icon.paint(painter, iconRect, Qt::AlignRight | Qt::AlignVCenter); + } +} + +// -- OpenPagesWidget + +OpenPagesWidget::OpenPagesWidget(OpenPagesModel *model) + : m_allowContextMenu(true) +{ + TRACE_OBJ + setModel(model); + setIndentation(0); + setItemDelegate((m_delegate = new OpenPagesDelegate(this))); + + setTextElideMode(Qt::ElideMiddle); + setAttribute(Qt::WA_MacShowFocusRect, false); + + viewport()->setAttribute(Qt::WA_Hover); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + + header()->hide(); + header()->setStretchLastSection(false); + header()->setSectionResizeMode(0, QHeaderView::Stretch); + header()->setSectionResizeMode(1, QHeaderView::Fixed); + header()->resizeSection(1, 18); + + installEventFilter(this); + setUniformRowHeights(true); + setContextMenuPolicy(Qt::CustomContextMenu); + + connect(this, &QAbstractItemView::clicked, + this, &OpenPagesWidget::handleClicked); + connect(this, &QAbstractItemView::pressed, + this, &OpenPagesWidget::handlePressed); + connect(this, &QWidget::customContextMenuRequested, + this, &OpenPagesWidget::contextMenuRequested); +} + +OpenPagesWidget::~OpenPagesWidget() +{ + TRACE_OBJ +} + +void OpenPagesWidget::selectCurrentPage() +{ + TRACE_OBJ + const QModelIndex ¤t = + model()->index(CentralWidget::instance()->currentIndex(), 0); + + QItemSelectionModel * const selModel = selectionModel(); + selModel->select(current, + QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + selModel->clearSelection(); + + setCurrentIndex(current); + scrollTo(currentIndex()); +} + +void OpenPagesWidget::allowContextMenu(bool ok) +{ + TRACE_OBJ + m_allowContextMenu = ok; +} + +void OpenPagesWidget::contextMenuRequested(QPoint pos) +{ + TRACE_OBJ + QModelIndex index = indexAt(pos); + if (!index.isValid() || !m_allowContextMenu) + return; + + if (index.column() == 1) + index = index.sibling(index.row(), 0); + QMenu contextMenu; + QAction *closeEditor = contextMenu.addAction(tr("Close %1").arg(index.data() + .toString())); + QAction *closeOtherEditors = contextMenu.addAction(tr("Close All Except %1") + .arg(index.data().toString())); + + if (model()->rowCount() == 1) { + closeEditor->setEnabled(false); + closeOtherEditors->setEnabled(false); + } + + QAction *action = contextMenu.exec(mapToGlobal(pos)); + if (action == closeEditor) + emit closePage(index); + else if (action == closeOtherEditors) + emit closePagesExcept(index); +} + +void OpenPagesWidget::handlePressed(const QModelIndex &index) +{ + TRACE_OBJ + if (index.column() == 0) + emit setCurrentPage(index); + + if (index.column() == 1) + m_delegate->pressedIndex = index; +} + +void OpenPagesWidget::handleClicked(const QModelIndex &index) +{ + TRACE_OBJ + // implemented here to handle the funky close button and to work around a + // bug in item views where the delegate wouldn't get the QStyle::State_MouseOver + if (index.column() == 1) { + if (model()->rowCount() > 1) + emit closePage(index); + + QWidget *vp = viewport(); + const QPoint &cursorPos = QCursor::pos(); + QMouseEvent e(QEvent::MouseMove, vp->mapFromGlobal(cursorPos), cursorPos, + Qt::NoButton, {}, {}); + QCoreApplication::sendEvent(vp, &e); + } +} + +bool OpenPagesWidget::eventFilter(QObject *obj, QEvent *event) +{ + TRACE_OBJ + if (obj != this) + return QWidget::eventFilter(obj, event); + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(event); + if (currentIndex().isValid() && ke->modifiers() == 0) { + const int key = ke->key(); + if (key == Qt::Key_Return || key == Qt::Key_Enter + || key == Qt::Key_Space) { + emit setCurrentPage(currentIndex()); + } else if ((key == Qt::Key_Delete || key == Qt::Key_Backspace) + && model()->rowCount() > 1) { + emit closePage(currentIndex()); + } + } + } else if (event->type() == QEvent::KeyRelease) { + QKeyEvent *ke = static_cast(event); + if (ke->modifiers() == 0 + && (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down)) { + emit setCurrentPage(currentIndex()); + } + } + return QWidget::eventFilter(obj, event); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/openpageswidget.h b/src/assistant/assistant/openpageswidget.h new file mode 100644 index 0000000..6cb27b6 --- /dev/null +++ b/src/assistant/assistant/openpageswidget.h @@ -0,0 +1,54 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OPENPAGESWIDGET_H +#define OPENPAGESWIDGET_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class OpenPagesModel; + +class OpenPagesDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit OpenPagesDelegate(QObject *parent = nullptr); + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + mutable QModelIndex pressedIndex; +}; + +class OpenPagesWidget : public QTreeView +{ + Q_OBJECT +public: + OpenPagesWidget(OpenPagesModel *model); + ~OpenPagesWidget() override; + + void selectCurrentPage(); + void allowContextMenu(bool ok); + +signals: + void setCurrentPage(const QModelIndex &index); + void closePage(const QModelIndex &index); + void closePagesExcept(const QModelIndex &index); + +private slots: + void contextMenuRequested(QPoint pos); + void handlePressed(const QModelIndex &index); + void handleClicked(const QModelIndex &index); + +private: + bool eventFilter(QObject *obj, QEvent *event) override; + + bool m_allowContextMenu; + OpenPagesDelegate *m_delegate; +}; + +QT_END_NAMESPACE + +#endif // OPENPAGESWIDGET_H diff --git a/src/assistant/assistant/preferencesdialog.cpp b/src/assistant/assistant/preferencesdialog.cpp new file mode 100644 index 0000000..f4cb91e --- /dev/null +++ b/src/assistant/assistant/preferencesdialog.cpp @@ -0,0 +1,252 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "preferencesdialog.h" + +#include "centralwidget.h" +#include "fontpanel_p.h" +#include "helpenginewrapper.h" +#include "openpagesmanager.h" +#include "helpdocsettingswidget.h" + +#include + +#include + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +PreferencesDialog::PreferencesDialog(QWidget *parent) + : QDialog(parent) + , m_appFontChanged(false) + , m_browserFontChanged(false) + , helpEngine(HelpEngineWrapper::instance()) + , m_hideFiltersTab(!helpEngine.filterFunctionalityEnabled()) + , m_hideDocsTab(!helpEngine.documentationManagerEnabled()) +{ + m_ui.setupUi(this); + + connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, + this, &PreferencesDialog::okClicked); + connect(m_ui.buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, + this, &PreferencesDialog::applyClicked); + connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, + this, &QDialog::reject); + + m_docSettings = HelpDocSettings::readSettings(helpEngine.helpEngine()); + + if (m_hideDocsTab) { + m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.docsTab)); + } else { + connect(m_ui.docSettingsWidget, &HelpDocSettingsWidget::docSettingsChanged, this, + [this](const HelpDocSettings &settings) { + m_docSettings = settings; + if (m_hideFiltersTab) + return; + + m_ui.filterSettingsWidget->setAvailableComponents(m_docSettings.components()); + m_ui.filterSettingsWidget->setAvailableVersions(m_docSettings.versions()); + }); + + m_ui.docSettingsWidget->setDocSettings(m_docSettings); + } + + if (m_hideFiltersTab) { + m_ui.tabWidget->removeTab(m_ui.tabWidget->indexOf(m_ui.filtersTab)); + } else { + m_ui.filterSettingsWidget->setAvailableComponents(m_docSettings.components()); + m_ui.filterSettingsWidget->setAvailableVersions(m_docSettings.versions()); + m_ui.filterSettingsWidget->readSettings(helpEngine.filterEngine()); + } + + updateFontSettingsPage(); + updateOptionsPage(); + + if (helpEngine.usesAppFont()) + setFont(helpEngine.appFont()); +} + +void PreferencesDialog::okClicked() +{ + applyChanges(); + accept(); +} + +void PreferencesDialog::applyClicked() +{ + applyChanges(); + + m_docSettings = HelpDocSettings::readSettings(helpEngine.helpEngine()); + + if (!m_hideDocsTab) + m_ui.docSettingsWidget->setDocSettings(m_docSettings); + if (!m_hideFiltersTab) { + m_ui.filterSettingsWidget->setAvailableComponents(m_docSettings.components()); + m_ui.filterSettingsWidget->setAvailableVersions(m_docSettings.versions()); + m_ui.filterSettingsWidget->readSettings(helpEngine.filterEngine()); + } +} + +void PreferencesDialog::applyChanges() +{ + bool changed = false; + if (!m_hideDocsTab) + changed = HelpDocSettings::applySettings(helpEngine.helpEngine(), m_docSettings); + if (!m_hideFiltersTab) + changed = changed || m_ui.filterSettingsWidget->applySettings(helpEngine.filterEngine()); + + if (changed) { + // In order to update the filtercombobox and indexwidget + // according to the new filter configuration. + helpEngine.setupData(); + } + + helpEngine.setShowTabs(m_ui.showTabs->isChecked()); + if (m_showTabs != m_ui.showTabs->isChecked()) + emit updateUserInterface(); + + if (m_appFontChanged) { + helpEngine.setAppFont(m_appFontPanel->selectedFont()); + helpEngine.setUseAppFont(m_appFontPanel->isChecked()); + helpEngine.setAppWritingSystem(m_appFontPanel->writingSystem()); + emit updateApplicationFont(); + m_appFontChanged = false; + } + + if (m_browserFontChanged) { + helpEngine.setBrowserFont(m_browserFontPanel->selectedFont()); + helpEngine.setUseBrowserFont(m_browserFontPanel->isChecked()); + helpEngine.setBrowserWritingSystem(m_browserFontPanel->writingSystem()); + emit updateBrowserFont(); + m_browserFontChanged = false; + } + + QString homePage = m_ui.homePageLineEdit->text(); + if (homePage.isEmpty()) + homePage = "help"_L1; + helpEngine.setHomePage(homePage); + + const int option = m_ui.helpStartComboBox->currentIndex(); + helpEngine.setStartOption(option); +} + +void PreferencesDialog::updateFontSettingsPage() +{ + m_browserFontPanel = new FontPanel(this); + m_browserFontPanel->setCheckable(true); + m_ui.stackedWidget_2->insertWidget(0, m_browserFontPanel); + + m_appFontPanel = new FontPanel(this); + m_appFontPanel->setCheckable(true); + m_ui.stackedWidget_2->insertWidget(1, m_appFontPanel); + + m_ui.stackedWidget_2->setCurrentIndex(0); + + const QString customSettings(tr("Use custom settings")); + m_appFontPanel->setTitle(customSettings); + + QFont font = helpEngine.appFont(); + m_appFontPanel->setSelectedFont(font); + + QFontDatabase::WritingSystem system = helpEngine.appWritingSystem(); + m_appFontPanel->setWritingSystem(system); + + m_appFontPanel->setChecked(helpEngine.usesAppFont()); + + m_browserFontPanel->setTitle(customSettings); + + font = helpEngine.browserFont(); + m_browserFontPanel->setSelectedFont(font); + + system = helpEngine.browserWritingSystem(); + m_browserFontPanel->setWritingSystem(system); + + m_browserFontPanel->setChecked(helpEngine.usesBrowserFont()); + + connect(m_appFontPanel, &QGroupBox::toggled, + this, &PreferencesDialog::appFontSettingToggled); + connect(m_browserFontPanel, &QGroupBox::toggled, + this, &PreferencesDialog::browserFontSettingToggled); + + const QList &appCombos = m_appFontPanel->findChildren(); + for (QComboBox* box : appCombos) { + connect(box, &QComboBox::currentIndexChanged, + this, &PreferencesDialog::appFontSettingChanged); + } + + const QList &browserCombos = m_browserFontPanel->findChildren(); + for (QComboBox* box : browserCombos) { + connect(box, &QComboBox::currentIndexChanged, + this, &PreferencesDialog::browserFontSettingChanged); + } +} + +void PreferencesDialog::appFontSettingToggled(bool on) +{ + Q_UNUSED(on); + m_appFontChanged = true; +} + +void PreferencesDialog::appFontSettingChanged(int index) +{ + Q_UNUSED(index); + m_appFontChanged = true; +} + +void PreferencesDialog::browserFontSettingToggled(bool on) +{ + Q_UNUSED(on); + m_browserFontChanged = true; +} + +void PreferencesDialog::browserFontSettingChanged(int index) +{ + Q_UNUSED(index); + m_browserFontChanged = true; +} + +void PreferencesDialog::updateOptionsPage() +{ + m_ui.homePageLineEdit->setText(helpEngine.homePage()); + + int option = helpEngine.startOption(); + m_ui.helpStartComboBox->setCurrentIndex(option); + + m_showTabs = helpEngine.showTabs(); + m_ui.showTabs->setChecked(m_showTabs); + + connect(m_ui.blankPageButton, &QAbstractButton::clicked, + this, &PreferencesDialog::setBlankPage); + connect(m_ui.currentPageButton, &QAbstractButton::clicked, + this, &PreferencesDialog::setCurrentPage); + connect(m_ui.defaultPageButton, &QAbstractButton::clicked, + this, &PreferencesDialog::setDefaultPage); +} + +void PreferencesDialog::setBlankPage() +{ + m_ui.homePageLineEdit->setText("about:blank"_L1); +} + +void PreferencesDialog::setCurrentPage() +{ + QString homepage = CentralWidget::instance()->currentSource().toString(); + if (homepage.isEmpty()) + homepage = "help"_L1; + + m_ui.homePageLineEdit->setText(homepage); +} + +void PreferencesDialog::setDefaultPage() +{ + m_ui.homePageLineEdit->setText(helpEngine.defaultHomePage()); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/preferencesdialog.h b/src/assistant/assistant/preferencesdialog.h new file mode 100644 index 0000000..9a1d2d3 --- /dev/null +++ b/src/assistant/assistant/preferencesdialog.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PREFERENCESDIALOG_H +#define PREFERENCESDIALOG_H + +#include +#include +#include +#include "ui_preferencesdialog.h" +#include "helpdocsettings.h" + +QT_BEGIN_NAMESPACE + +class FontPanel; +class HelpEngineWrapper; +class QFileSystemWatcher; + +class PreferencesDialog : public QDialog +{ + Q_OBJECT + +public: + PreferencesDialog(QWidget *parent = nullptr); + +private slots: + void okClicked(); + void applyClicked(); + void applyChanges(); + void appFontSettingToggled(bool on); + void appFontSettingChanged(int index); + void browserFontSettingToggled(bool on); + void browserFontSettingChanged(int index); + + void setBlankPage(); + void setCurrentPage(); + void setDefaultPage(); + +signals: + void updateBrowserFont(); + void updateApplicationFont(); + void updateUserInterface(); + +private: + void updateFontSettingsPage(); + void updateOptionsPage(); + + Ui::PreferencesDialogClass m_ui; + + HelpDocSettings m_docSettings; + + FontPanel *m_appFontPanel; + FontPanel *m_browserFontPanel; + bool m_appFontChanged; + bool m_browserFontChanged; + HelpEngineWrapper &helpEngine; + const bool m_hideFiltersTab; + const bool m_hideDocsTab; + bool m_showTabs; +}; + +QT_END_NAMESPACE + +#endif // SETTINGSDIALOG_H diff --git a/src/assistant/assistant/preferencesdialog.ui b/src/assistant/assistant/preferencesdialog.ui new file mode 100644 index 0000000..2d1c480 --- /dev/null +++ b/src/assistant/assistant/preferencesdialog.ui @@ -0,0 +1,314 @@ + + + PreferencesDialogClass + + + + 0 + 0 + 395 + 376 + + + + Preferences + + + + + + 2 + + + + Fonts + + + + + + + + + 0 + 0 + + + + Font settings: + + + + + + + + Browser + + + + + Application + + + + + + + + + + 0 + + + + + + + + + Filters + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + Documentation + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + Options + + + + + + + + + + + + + 0 + 0 + + + + On help start: + + + + + + + + 0 + 0 + + + + + Show my home page + + + + + Show a blank page + + + + + Show my tabs from last session + + + + + + + + Qt::Horizontal + + + + 54 + 20 + + + + + + + + + + + + + + + + + + + Homepage + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Current Page + + + + + + + Blank Page + + + + + + + Restore to default + + + + + + + + + + + + Appearance + + + + + + Show tabs for each individual page + + + + + + + + + + Qt::Vertical + + + + 20 + 72 + + + + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + HelpDocSettingsWidget + QWidget +
helpdocsettingswidget.h
+ 1 +
+ + QHelpFilterSettingsWidget + QWidget +
qhelpfiltersettingswidget.h
+ 1 +
+
+ + + + comboBox + currentIndexChanged(int) + stackedWidget_2 + setCurrentIndex(int) + + + 375 + 32 + + + 347 + 125 + + + + +
diff --git a/src/assistant/assistant/qtdocinstaller.cpp b/src/assistant/assistant/qtdocinstaller.cpp new file mode 100644 index 0000000..ba72fa1 --- /dev/null +++ b/src/assistant/assistant/qtdocinstaller.cpp @@ -0,0 +1,91 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" + +#include +#include +#include +#include +#include +#include "helpenginewrapper.h" +#include "qtdocinstaller.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QtDocInstaller::QtDocInstaller(const QList &docInfos) + : m_abort(false), m_docInfos(docInfos) +{ + TRACE_OBJ +} + +QtDocInstaller::~QtDocInstaller() +{ + TRACE_OBJ + if (!isRunning()) + return; + m_mutex.lock(); + m_abort = true; + m_mutex.unlock(); + wait(); +} + +void QtDocInstaller::installDocs() +{ + TRACE_OBJ + start(LowPriority); +} + +void QtDocInstaller::run() +{ + TRACE_OBJ + m_qchDir.setPath(QLibraryInfo::path(QLibraryInfo::DocumentationPath)); + m_qchFiles = m_qchDir.entryList(QStringList() << "*.qch"_L1); + + bool changes = false; + for (const DocInfo &docInfo : std::as_const(m_docInfos)) { + changes |= installDoc(docInfo); + m_mutex.lock(); + if (m_abort) { + m_mutex.unlock(); + return; + } + m_mutex.unlock(); + } + emit docsInstalled(changes); +} + +bool QtDocInstaller::installDoc(const DocInfo &docInfo) +{ + TRACE_OBJ + const QString &component = docInfo.first; + const QStringList &info = docInfo.second; + QDateTime dt; + if (!info.isEmpty() && !info.first().isEmpty()) + dt = QDateTime::fromString(info.first(), Qt::ISODate); + + QString qchFile; + if (info.size() == 2) + qchFile = info.last(); + + if (m_qchFiles.isEmpty()) { + emit qchFileNotFound(component); + return false; + } + for (const QString &f : std::as_const(m_qchFiles)) { + if (f.startsWith(component)) { + QFileInfo fi(m_qchDir.absolutePath() + QDir::separator() + f); + if (dt.isValid() && fi.lastModified().toSecsSinceEpoch() == dt.toSecsSinceEpoch() + && qchFile == fi.absoluteFilePath()) + return false; + emit registerDocumentation(component, fi.absoluteFilePath()); + return true; + } + } + + emit qchFileNotFound(component); + return false; +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/qtdocinstaller.h b/src/assistant/assistant/qtdocinstaller.h new file mode 100644 index 0000000..932c077 --- /dev/null +++ b/src/assistant/assistant/qtdocinstaller.h @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QTDOCINSTALLER +#define QTDOCINSTALLER + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class HelpEngineWrapper; + +class QtDocInstaller : public QThread +{ + Q_OBJECT + +public: + typedef QPair DocInfo; + QtDocInstaller(const QList &docInfos); + ~QtDocInstaller() override; + void installDocs(); + +signals: + void qchFileNotFound(const QString &component); + void registerDocumentation(const QString &component, + const QString &absFileName); + void docsInstalled(bool newDocsInstalled); + +private: + void run() override; + bool installDoc(const DocInfo &docInfo); + + bool m_abort; + QMutex m_mutex; + QStringList m_qchFiles; + QDir m_qchDir; + QList m_docInfos; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/assistant/assistant/remotecontrol.cpp b/src/assistant/assistant/remotecontrol.cpp new file mode 100644 index 0000000..dbb0efb --- /dev/null +++ b/src/assistant/assistant/remotecontrol.cpp @@ -0,0 +1,282 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "remotecontrol.h" + +#include "centralwidget.h" +#include "helpenginewrapper.h" +#include "mainwindow.h" +#include "openpagesmanager.h" +#include "tracer.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +# include "stdinlistener_win.h" +#else +# include "stdinlistener.h" +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +RemoteControl::RemoteControl(MainWindow *mainWindow) + : QObject(mainWindow) + , m_mainWindow(mainWindow) + , helpEngine(HelpEngineWrapper::instance()) +{ + TRACE_OBJ + connect(m_mainWindow, &MainWindow::initDone, + this, &RemoteControl::applyCache); + + StdInListener *l = new StdInListener(this); + connect(l, &StdInListener::receivedCommand, + this, &RemoteControl::handleCommandString); + l->start(); +} + +void RemoteControl::handleCommandString(const QString &cmdString) +{ + TRACE_OBJ + const QStringList &commands = cmdString.split(u';'); + for (const QString &command : commands) { + QString cmd, arg; + splitInputString(command, cmd, arg); + + if (m_debug) + QMessageBox::information(nullptr, tr("Debugging Remote Control"), + tr("Received Command: %1 %2").arg(cmd).arg(arg)); + + if (cmd == "debug"_L1) + handleDebugCommand(arg); + else if (cmd == "show"_L1) + handleShowOrHideCommand(arg, true); + else if (cmd == "hide"_L1) + handleShowOrHideCommand(arg, false); + else if (cmd == "quit"_L1) + handleQuitCommand(); + else if (cmd == "setsource"_L1) + handleSetSourceCommand(arg); + else if (cmd == "synccontents"_L1) + handleSyncContentsCommand(); + else if (cmd == "activatekeyword"_L1) + handleActivateKeywordCommand(arg); + else if (cmd == "activateidentifier"_L1) + handleActivateIdentifierCommand(arg); + else if (cmd == "expandtoc"_L1) + handleExpandTocCommand(arg); + else if (cmd == "setcurrentfilter"_L1) + handleSetCurrentFilterCommand(arg); + else if (cmd == "register"_L1) + handleRegisterCommand(arg); + else if (cmd == "unregister"_L1) + handleUnregisterCommand(arg); + else + break; + } + m_mainWindow->raise(); + m_mainWindow->activateWindow(); +} + +void RemoteControl::splitInputString(const QString &input, QString &cmd, + QString &arg) +{ + TRACE_OBJ + QString cmdLine = input.trimmed(); + int i = cmdLine.indexOf(u' '); + cmd = cmdLine.left(i); + arg = cmdLine.mid(i + 1); + cmd = cmd.toLower(); +} + +void RemoteControl::handleDebugCommand(const QString &arg) +{ + TRACE_OBJ + m_debug = arg == "on"_L1; +} + +void RemoteControl::handleShowOrHideCommand(const QString &arg, bool show) +{ + TRACE_OBJ + if (arg.toLower() == "contents"_L1) + m_mainWindow->setContentsVisible(show); + else if (arg.toLower() == "index"_L1) + m_mainWindow->setIndexVisible(show); + else if (arg.toLower() == "bookmarks"_L1) + m_mainWindow->setBookmarksVisible(show); + else if (arg.toLower() == "search"_L1) + m_mainWindow->setSearchVisible(show); +} + +void RemoteControl::handleQuitCommand() +{ + QCoreApplication::quit(); +} + +void RemoteControl::handleSetSourceCommand(const QString &arg) +{ + TRACE_OBJ + QUrl url(arg); + if (url.isValid()) { + if (url.isRelative()) + url = CentralWidget::instance()->currentSource().resolved(url); + if (m_caching) { + clearCache(); + m_setSource = url; + } else { + CentralWidget::instance()->setSource(url); + } + } +} + +void RemoteControl::handleSyncContentsCommand() +{ + TRACE_OBJ + if (m_caching) + m_syncContents = true; + else + m_mainWindow->syncContents(); +} + +void RemoteControl::handleActivateKeywordCommand(const QString &arg) +{ + TRACE_OBJ + if (m_caching) { + clearCache(); + m_activateKeyword = arg; + } else { + m_mainWindow->setIndexString(arg); + if (!arg.isEmpty()) { + if (!helpEngine.indexWidget()->currentIndex().isValid() + && helpEngine.fullTextSearchFallbackEnabled()) { + if (QHelpSearchEngine *se = helpEngine.searchEngine()) { + m_mainWindow->setSearchVisible(true); + if (QHelpSearchQueryWidget *w = se->queryWidget()) { + w->collapseExtendedSearch(); + w->setSearchInput(arg); + se->search(arg); + } + } + } else { + m_mainWindow->setIndexVisible(true); + helpEngine.indexWidget()->activateCurrentItem(); + } + } + } +} + +void RemoteControl::handleActivateIdentifierCommand(const QString &arg) +{ + TRACE_OBJ + if (m_caching) { + clearCache(); + m_activateIdentifier = arg; + } else { + const auto docs = helpEngine.documentsForIdentifier(arg); + if (!docs.isEmpty()) + CentralWidget::instance()->setSource(docs.first().url); + } +} + +void RemoteControl::handleExpandTocCommand(const QString &arg) +{ + TRACE_OBJ + bool ok = false; + int depth = -2; + if (!arg.isEmpty()) + depth = arg.toInt(&ok); + if (!ok || depth < -2) + depth = -2; + + if (m_caching) + m_expandTOC = depth; + else if (depth != -2) + m_mainWindow->expandTOC(depth); +} + +void RemoteControl::handleSetCurrentFilterCommand(const QString &arg) +{ + TRACE_OBJ + if (helpEngine.filterEngine()->filters().contains(arg)) { + if (m_caching) { + clearCache(); + m_currentFilter = arg; + } else { + helpEngine.filterEngine()->setActiveFilter(arg); + } + } +} + +void RemoteControl::handleRegisterCommand(const QString &arg) +{ + TRACE_OBJ + const QString &absFileName = QFileInfo(arg).absoluteFilePath(); + if (helpEngine.registeredDocumentations(). + contains(QHelpEngineCore::namespaceName(absFileName))) + return; + if (helpEngine.registerDocumentation(absFileName)) + helpEngine.setupData(); +} + +void RemoteControl::handleUnregisterCommand(const QString &arg) +{ + TRACE_OBJ + const QString &absFileName = QFileInfo(arg).absoluteFilePath(); + const QString &ns = QHelpEngineCore::namespaceName(absFileName); + if (helpEngine.registeredDocumentations().contains(ns)) { + OpenPagesManager::instance()->closePages(ns); + if (helpEngine.unregisterDocumentation(ns)) + helpEngine.setupData(); + } +} + +void RemoteControl::applyCache() +{ + TRACE_OBJ + if (m_setSource.isValid()) { + CentralWidget::instance()->setSource(m_setSource); + } else if (!m_activateKeyword.isEmpty()) { + m_mainWindow->setIndexString(m_activateKeyword); + helpEngine.indexWidget()->activateCurrentItem(); + } else if (!m_activateIdentifier.isEmpty()) { + const auto docs = + helpEngine.documentsForIdentifier(m_activateIdentifier); + if (!docs.isEmpty()) + CentralWidget::instance()->setSource(docs.first().url); + } else if (!m_currentFilter.isEmpty()) { + helpEngine.filterEngine()->setActiveFilter(m_currentFilter); + } + + if (m_syncContents) + m_mainWindow->syncContents(); + + Q_ASSERT(m_expandTOC >= -2); + if (m_expandTOC != -2) + m_mainWindow->expandTOC(m_expandTOC); + + m_caching = false; +} + +void RemoteControl::clearCache() +{ + TRACE_OBJ + m_currentFilter.clear(); + m_setSource.clear(); + m_syncContents = false; + m_activateKeyword.clear(); + m_activateIdentifier.clear(); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/remotecontrol.h b/src/assistant/assistant/remotecontrol.h new file mode 100644 index 0000000..4db2545 --- /dev/null +++ b/src/assistant/assistant/remotecontrol.h @@ -0,0 +1,58 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef REMOTECONTROL_H +#define REMOTECONTROL_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class HelpEngineWrapper; +class MainWindow; + +class RemoteControl : public QObject +{ + Q_OBJECT + +public: + RemoteControl(MainWindow *mainWindow); + +private slots: + void handleCommandString(const QString &cmdString); + void applyCache(); + +private: + void clearCache(); + void splitInputString(const QString &input, QString &cmd, QString &arg); + void handleDebugCommand(const QString &arg); + void handleShowOrHideCommand(const QString &arg, bool show); + static void handleQuitCommand(); + void handleSetSourceCommand(const QString &arg); + void handleSyncContentsCommand(); + void handleActivateKeywordCommand(const QString &arg); + void handleActivateIdentifierCommand(const QString &arg); + void handleExpandTocCommand(const QString &arg); + void handleSetCurrentFilterCommand(const QString &arg); + void handleRegisterCommand(const QString &arg); + void handleUnregisterCommand(const QString &arg); + +private: + MainWindow *m_mainWindow; + QUrl m_setSource; + QString m_activateKeyword; + QString m_activateIdentifier; + QString m_currentFilter; + HelpEngineWrapper &helpEngine; + int m_expandTOC = -2; + bool m_debug = false; + + bool m_caching = true; + bool m_syncContents = false; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/assistant/assistant/searchwidget.cpp b/src/assistant/assistant/searchwidget.cpp new file mode 100644 index 0000000..01d2721 --- /dev/null +++ b/src/assistant/assistant/searchwidget.cpp @@ -0,0 +1,209 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" + +#include "mainwindow.h" +#include "searchwidget.h" + +#include +#include +#include +#include + +#include +#include +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +SearchWidget::SearchWidget(QHelpSearchEngine *engine, QWidget *parent) + : QWidget(parent) + , zoomCount(0) + , searchEngine(engine) +{ + TRACE_OBJ + QVBoxLayout *vLayout = new QVBoxLayout(this); + + resultWidget = searchEngine->resultWidget(); + QHelpSearchQueryWidget *queryWidget = searchEngine->queryWidget(); + + vLayout->addWidget(queryWidget); + vLayout->addWidget(resultWidget); + + setFocusProxy(queryWidget); + + connect(queryWidget, &QHelpSearchQueryWidget::search, + this, &SearchWidget::search); + connect(resultWidget, &QHelpSearchResultWidget::requestShowLink, + this, &SearchWidget::requestShowLink); + + connect(searchEngine, &QHelpSearchEngine::searchingStarted, + this, &SearchWidget::searchingStarted); + connect(searchEngine, &QHelpSearchEngine::searchingFinished, + this, &SearchWidget::searchingFinished); + + QTextBrowser* browser = resultWidget->findChild(); + if (browser) + browser->viewport()->installEventFilter(this); +} + +SearchWidget::~SearchWidget() +{ + TRACE_OBJ + // nothing todo +} + +void SearchWidget::zoomIn() +{ + TRACE_OBJ + QTextBrowser* browser = resultWidget->findChild(); + if (browser && zoomCount != 10) { + zoomCount++; + browser->zoomIn(); + } +} + +void SearchWidget::zoomOut() +{ + TRACE_OBJ + QTextBrowser* browser = resultWidget->findChild(); + if (browser && zoomCount != -5) { + zoomCount--; + browser->zoomOut(); + } +} + +void SearchWidget::resetZoom() +{ + TRACE_OBJ + if (zoomCount == 0) + return; + + QTextBrowser* browser = resultWidget->findChild(); + if (browser) { + browser->zoomOut(zoomCount); + zoomCount = 0; + } +} + +void SearchWidget::search() const +{ + TRACE_OBJ + searchEngine->search(searchEngine->queryWidget()->searchInput()); +} + +void SearchWidget::searchingStarted() +{ + TRACE_OBJ + qApp->setOverrideCursor(QCursor(Qt::WaitCursor)); +} + +void SearchWidget::searchingFinished(int searchResultCount) +{ + TRACE_OBJ + Q_UNUSED(searchResultCount); + qApp->restoreOverrideCursor(); +} + +bool SearchWidget::eventFilter(QObject* o, QEvent *e) +{ + TRACE_OBJ + QTextBrowser* browser = resultWidget->findChild(); + if (browser && o == browser->viewport() + && e->type() == QEvent::MouseButtonRelease){ + QMouseEvent *me = static_cast(e); + QUrl link = resultWidget->linkAt(me->pos()); + if (!link.isEmpty() || link.isValid()) { + bool controlPressed = me->modifiers() & Qt::ControlModifier; + if ((me->button() == Qt::LeftButton && controlPressed) + || (me->button() == Qt::MiddleButton)) { + emit requestShowLinkInNewTab(link); + } + } + } + return QWidget::eventFilter(o,e); +} + +void SearchWidget::keyPressEvent(QKeyEvent *keyEvent) +{ + TRACE_OBJ + if (keyEvent->key() == Qt::Key_Escape) + MainWindow::activateCurrentBrowser(); + else + keyEvent->ignore(); +} + +void SearchWidget::contextMenuEvent(QContextMenuEvent *contextMenuEvent) +{ + TRACE_OBJ + QMenu menu; + QPoint point = contextMenuEvent->globalPos(); + + QTextBrowser* browser = resultWidget->findChild(); + if (!browser) + return; + + point = browser->mapFromGlobal(point); + if (!browser->rect().contains(point, true)) + return; + + QUrl link = browser->anchorAt(point); + + QKeySequence keySeq; +#if QT_CONFIG(clipboard) + keySeq = QKeySequence::Copy; + QAction *copyAction = menu.addAction(tr("&Copy") + u'\t' + + keySeq.toString(QKeySequence::NativeText)); + copyAction->setEnabled(QTextCursor(browser->textCursor()).hasSelection()); + + QAction *copyAnchorAction = menu.addAction(tr("Copy &Link Location")); + copyAnchorAction->setEnabled(!link.isEmpty() && link.isValid()); +#endif + + keySeq = QKeySequence(Qt::CTRL); + QAction *newTabAction = menu.addAction(tr("Open Link in New Tab") + u'\t' + + keySeq.toString(QKeySequence::NativeText) + "LMB"_L1); + newTabAction->setEnabled(!link.isEmpty() && link.isValid()); + + menu.addSeparator(); + + keySeq = QKeySequence::SelectAll; + QAction *selectAllAction = + menu.addAction(tr("Select All") + u'\t' + keySeq.toString(QKeySequence::NativeText)); + + QAction *usedAction = menu.exec(mapToGlobal(contextMenuEvent->pos())); +#if QT_CONFIG(clipboard) + if (usedAction == copyAction) { + QTextCursor cursor = browser->textCursor(); + if (!cursor.isNull() && cursor.hasSelection()) { + QString selectedText = cursor.selectedText(); + QMimeData *data = new QMimeData(); + data->setText(selectedText); + QApplication::clipboard()->setMimeData(data); + } + } + else if (usedAction == copyAnchorAction) { + QApplication::clipboard()->setText(link.toString()); + } + else +#endif + if (usedAction == newTabAction) { + emit requestShowLinkInNewTab(link); + } + else if (usedAction == selectAllAction) { + browser->selectAll(); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/searchwidget.h b/src/assistant/assistant/searchwidget.h new file mode 100644 index 0000000..11df479 --- /dev/null +++ b/src/assistant/assistant/searchwidget.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SEARCHWIDGET_H +#define SEARCHWIDGET_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QMouseEvent; +class QHelpSearchEngine; +class QHelpSearchResultWidget; + +class SearchWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SearchWidget(QHelpSearchEngine *engine, QWidget *parent = nullptr); + ~SearchWidget() override; + + void zoomIn(); + void zoomOut(); + void resetZoom(); + +signals: + void requestShowLink(const QUrl &url); + void requestShowLinkInNewTab(const QUrl &url); + +private slots: + void search() const; + void searchingStarted(); + void searchingFinished(int searchResultCount); + +private: + bool eventFilter(QObject* o, QEvent *e) override; + void keyPressEvent(QKeyEvent *keyEvent) override; + void contextMenuEvent(QContextMenuEvent *contextMenuEvent) override; + +private: + int zoomCount; + QHelpSearchEngine *searchEngine; + QHelpSearchResultWidget *resultWidget; +}; + +QT_END_NAMESPACE + +#endif // SEARCHWIDGET_H diff --git a/src/assistant/assistant/stdinlistener.cpp b/src/assistant/assistant/stdinlistener.cpp new file mode 100644 index 0000000..40cc555 --- /dev/null +++ b/src/assistant/assistant/stdinlistener.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "stdinlistener.h" + +#include "tracer.h" + +#include + +QT_BEGIN_NAMESPACE + +StdInListener::StdInListener(QObject *parent) + : QSocketNotifier(fileno(stdin), QSocketNotifier::Read, parent) +{ + TRACE_OBJ + connect(this, &QSocketNotifier::activated, + this, &StdInListener::receivedData); +} + +StdInListener::~StdInListener() +{ + TRACE_OBJ +} + +void StdInListener::start() +{ + setEnabled(true); +} + +void StdInListener::receivedData() +{ + TRACE_OBJ + QByteArray ba; + while (true) { + const int c = getc(stdin); + if (c == EOF) { + setEnabled(false); + break; + } + if (c == '\0') + break; + if (c) + ba.append(char(c)); + if (c == '\n') + break; + } + emit receivedCommand(QString::fromLocal8Bit(ba)); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/stdinlistener.h b/src/assistant/assistant/stdinlistener.h new file mode 100644 index 0000000..b175a59 --- /dev/null +++ b/src/assistant/assistant/stdinlistener.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef STDINLISTENER_H +#define STDINLISTENER_H + +#include + +QT_BEGIN_NAMESPACE + +class StdInListener : public QSocketNotifier +{ + Q_OBJECT + +public: + StdInListener(QObject *parent); + ~StdInListener(); + +public slots: + void start(); + +signals: + void receivedCommand(const QString &cmd); + +private slots: + void receivedData(); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/assistant/assistant/stdinlistener_win.cpp b/src/assistant/assistant/stdinlistener_win.cpp new file mode 100644 index 0000000..f7bb746 --- /dev/null +++ b/src/assistant/assistant/stdinlistener_win.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "stdinlistener_win.h" + +#include "tracer.h" + +QT_BEGIN_NAMESPACE + +StdInListener::StdInListener(QObject *parent) + : QThread(parent) +{ + TRACE_OBJ +} + +StdInListener::~StdInListener() +{ + TRACE_OBJ + terminate(); + wait(); +} + +void StdInListener::run() +{ + TRACE_OBJ + bool ok = true; + char chBuf[4096]; + DWORD dwRead; + HANDLE hStdinDup; + + const HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + if (hStdin == INVALID_HANDLE_VALUE) + return; + + DuplicateHandle(GetCurrentProcess(), hStdin, + GetCurrentProcess(), &hStdinDup, + 0, false, DUPLICATE_SAME_ACCESS); + + CloseHandle(hStdin); + + while (ok) { + ok = ReadFile(hStdinDup, chBuf, sizeof(chBuf), &dwRead, NULL); + if (ok && dwRead != 0) + emit receivedCommand(QString::fromLocal8Bit(chBuf, dwRead)); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/stdinlistener_win.h b/src/assistant/assistant/stdinlistener_win.h new file mode 100644 index 0000000..0ddf572 --- /dev/null +++ b/src/assistant/assistant/stdinlistener_win.h @@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef STDINLISTENER_WIN_H +#define STDINLISTENER_WIN_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class StdInListener : public QThread +{ + Q_OBJECT + +public: + StdInListener(QObject *parent); + ~StdInListener(); + +signals: + void receivedCommand(const QString &cmd); + +private: + void run() override; + bool ok; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/assistant/assistant/topicchooser.cpp b/src/assistant/assistant/topicchooser.cpp new file mode 100644 index 0000000..8553823 --- /dev/null +++ b/src/assistant/assistant/topicchooser.cpp @@ -0,0 +1,117 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" + +#include "topicchooser.h" +#include "helpenginewrapper.h" + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +TopicChooser::TopicChooser(QWidget *parent, const QString &keyword, const QList &docs) + : QDialog(parent) + , m_filterModel(new QSortFilterProxyModel(this)) +{ + TRACE_OBJ + ui.setupUi(this); + + setFocusProxy(ui.lineEdit); + ui.lineEdit->installEventFilter(this); + ui.lineEdit->setPlaceholderText(tr("Filter")); + ui.label->setText(tr("Choose a topic for %1:").arg(keyword)); + + QStandardItemModel *model = new QStandardItemModel(this); + m_filterModel->setSourceModel(model); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + for (const auto &doc : docs) { + m_links.append(doc.url); + QStandardItem *item = new QStandardItem(doc.title); + item->setToolTip(doc.url.toString()); + model->appendRow(item); + } + + ui.listWidget->setModel(m_filterModel); + ui.listWidget->setUniformItemSizes(true); + ui.listWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + + if (m_filterModel->rowCount() != 0) + ui.listWidget->setCurrentIndex(m_filterModel->index(0, 0)); + + connect(ui.buttonCancel, &QAbstractButton::clicked, + this, &QDialog::reject); + connect(ui.buttonDisplay, &QAbstractButton::clicked, + this, &TopicChooser::acceptDialog); + connect(ui.lineEdit, &QLineEdit::textChanged, + this, &TopicChooser::setFilter); + connect(ui.listWidget, &QAbstractItemView::activated, + this, &TopicChooser::activated); + + const QByteArray ba = HelpEngineWrapper::instance().topicChooserGeometry(); + if (!ba.isEmpty()) + restoreGeometry(ba); +} + +TopicChooser::~TopicChooser() +{ + HelpEngineWrapper::instance().setTopicChooserGeometry(saveGeometry()); +} + +QUrl TopicChooser::link() const +{ + TRACE_OBJ + if (m_activedIndex.isValid()) + return m_links.at(m_filterModel->mapToSource(m_activedIndex).row()); + return QUrl(); +} + +void TopicChooser::acceptDialog() +{ + TRACE_OBJ + m_activedIndex = ui.listWidget->currentIndex(); + accept(); +} + +void TopicChooser::setFilter(const QString &pattern) +{ + TRACE_OBJ + m_filterModel->setFilterFixedString(pattern); + if (m_filterModel->rowCount() != 0 && !ui.listWidget->currentIndex().isValid()) + ui.listWidget->setCurrentIndex(m_filterModel->index(0, 0)); +} + +void TopicChooser::activated(const QModelIndex &index) +{ + TRACE_OBJ + m_activedIndex = index; + accept(); +} + +bool TopicChooser::eventFilter(QObject *object, QEvent *event) +{ + TRACE_OBJ + if (object == ui.lineEdit && event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + switch (keyEvent->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + QCoreApplication::sendEvent(ui.listWidget, event); + break; + } + } else if (ui.lineEdit && event->type() == QEvent::FocusIn + && static_cast(event)->reason() != Qt::MouseFocusReason) { + ui.lineEdit->selectAll(); + ui.lineEdit->setFocus(); + } + return QDialog::eventFilter(object, event); +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/topicchooser.h b/src/assistant/assistant/topicchooser.h new file mode 100644 index 0000000..ea8b08e --- /dev/null +++ b/src/assistant/assistant/topicchooser.h @@ -0,0 +1,44 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TOPICCHOOSER_H +#define TOPICCHOOSER_H + +#include "ui_topicchooser.h" + +#include + +QT_BEGIN_NAMESPACE + +class QSortFilterProxyModel; +struct QHelpLink; + +class TopicChooser : public QDialog +{ + Q_OBJECT + +public: + TopicChooser(QWidget *parent, const QString &keyword, const QList &docs); + ~TopicChooser() override; + + QUrl link() const; + +private slots: + void acceptDialog(); + void setFilter(const QString &pattern); + void activated(const QModelIndex &index); + +private: + bool eventFilter(QObject *object, QEvent *event) override; + +private: + Ui::TopicChooser ui; + QList m_links; + + QModelIndex m_activedIndex; + QSortFilterProxyModel *m_filterModel; +}; + +QT_END_NAMESPACE + +#endif // TOPICCHOOSER_H diff --git a/src/assistant/assistant/topicchooser.ui b/src/assistant/assistant/topicchooser.ui new file mode 100644 index 0000000..b28a5e7 --- /dev/null +++ b/src/assistant/assistant/topicchooser.ui @@ -0,0 +1,111 @@ + + + TopicChooser + + + TopicChooser + + + + 0 + 0 + 391 + 223 + + + + Choose Topic + + + true + + + + unnamed + + + 11 + + + 6 + + + + + label + + + &Topics + + + listWidget + + + + + + + + + + + + + unnamed + + + 0 + + + 6 + + + + + + 20 + 20 + + + + Expanding + + + Horizontal + + + + + + + buttonDisplay + + + &Display + + + true + + + true + + + + + + + buttonCancel + + + &Close + + + true + + + + + + + + diff --git a/src/assistant/assistant/tracer.h b/src/assistant/assistant/tracer.h new file mode 100644 index 0000000..03d8725 --- /dev/null +++ b/src/assistant/assistant/tracer.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TRACER_H +#define TRACER_H + +#include + +QT_BEGIN_NAMESPACE + +class Tracer +{ +public: + Tracer(const char *func) : m_func(func) + { + qDebug("Entering function %s.", m_func); + } + + ~Tracer() + { + qDebug("Leaving function %s.", m_func); + } + +private: + const char * const m_func; +}; + +QT_END_NAMESPACE + +// #define TRACING_REQUESTED +#ifdef TRACING_REQUESTED +#define TRACE_OBJ Tracer traceObj__(Q_FUNC_INFO); +#else +#define TRACE_OBJ +#endif + +#endif // TRACER_H diff --git a/src/assistant/assistant/xbelsupport.cpp b/src/assistant/assistant/xbelsupport.cpp new file mode 100644 index 0000000..ffe71c7 --- /dev/null +++ b/src/assistant/assistant/xbelsupport.cpp @@ -0,0 +1,189 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "tracer.h" + +#include "xbelsupport.h" + +#include "bookmarkitem.h" +#include "bookmarkmodel.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +struct Bookmark { + QString title; + QString url; + bool folded; +}; + +XbelWriter::XbelWriter(BookmarkModel *model) + : QXmlStreamWriter() + , bookmarkModel(model) +{ + TRACE_OBJ + setAutoFormatting(true); +} + +void XbelWriter::writeToFile(QIODevice *device) +{ + TRACE_OBJ + setDevice(device); + + writeStartDocument(); + writeDTD(""_L1); + writeStartElement("xbel"_L1); + writeAttribute("version"_L1, "1.0"_L1); + + const QModelIndex root; + for (int i = 0; i < bookmarkModel->rowCount(root); ++i) + writeData(bookmarkModel->index(i, 0, root)); + writeEndDocument(); +} + +void XbelWriter::writeData(const QModelIndex &index) +{ + TRACE_OBJ + if (index.isValid()) { + Bookmark entry; + entry.title = index.data().toString(); + entry.url = index.data(UserRoleUrl).toString(); + + if (index.data(UserRoleFolder).toBool()) { + writeStartElement("folder"_L1); + entry.folded = !index.data(UserRoleExpanded).toBool(); + writeAttribute("folded"_L1, entry.folded ? "yes"_L1 : "no"_L1); + writeTextElement("title"_L1, entry.title); + for (int i = 0; i < bookmarkModel->rowCount(index); ++i) + writeData(bookmarkModel->index(i, 0 , index)); + writeEndElement(); + } else { + writeStartElement("bookmark"_L1); + writeAttribute("href"_L1, entry.url); + writeTextElement("title"_L1, entry.title); + writeEndElement(); + } + } +} + +// -- XbelReader + +XbelReader::XbelReader(BookmarkModel *model) + : QXmlStreamReader() + , bookmarkModel(model) +{ + TRACE_OBJ +} + +bool XbelReader::readFromFile(QIODevice *device) +{ + TRACE_OBJ + setDevice(device); + + while (!atEnd()) { + readNext(); + + if (isStartElement()) { + if (name() == "xbel"_L1 && attributes().value("version"_L1) == "1.0"_L1) { + const QModelIndex root; + parents.append(bookmarkModel->addItem(root, true)); + readXBEL(); + bookmarkModel->setData(parents.first(), + QDate::currentDate().toString(Qt::ISODate), Qt::EditRole); + } else { + raiseError("The file is not an XBEL version 1.0 file."_L1); + } + } + } + + return !error(); +} + +void XbelReader::readXBEL() +{ + TRACE_OBJ + while (!atEnd()) { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) { + if (name() == "folder"_L1) + readFolder(); + else if (name() == "bookmark"_L1) + readBookmark(); + else + readUnknownElement(); + } + } +} + +void XbelReader::readFolder() +{ + TRACE_OBJ + parents.append(bookmarkModel->addItem(parents.last(), true)); + bookmarkModel->setData(parents.last(), attributes().value("folded"_L1) == "no"_L1, + UserRoleExpanded); + + while (!atEnd()) { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) { + if (name() == "title"_L1) + bookmarkModel->setData(parents.last(), readElementText(), Qt::EditRole); + else if (name() == "folder"_L1) + readFolder(); + else if (name() == "bookmark"_L1) + readBookmark(); + else + readUnknownElement(); + } + } + + parents.removeLast(); +} + +void XbelReader::readBookmark() +{ + TRACE_OBJ + const QModelIndex &index = bookmarkModel->addItem(parents.last(), false); + if (BookmarkItem* item = bookmarkModel->itemFromIndex(index)) + item->setData(UserRoleUrl, attributes().value("href"_L1).toString()); + + while (!atEnd()) { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) { + if (name() == "title"_L1) + bookmarkModel->setData(index, readElementText(), Qt::EditRole); + else + readUnknownElement(); + } + } +} + +void XbelReader::readUnknownElement() +{ + TRACE_OBJ + while (!atEnd()) { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) + readUnknownElement(); + } +} + +QT_END_NAMESPACE diff --git a/src/assistant/assistant/xbelsupport.h b/src/assistant/assistant/xbelsupport.h new file mode 100644 index 0000000..2917922 --- /dev/null +++ b/src/assistant/assistant/xbelsupport.h @@ -0,0 +1,49 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef XBELSUPPORT_H +#define XBELSUPPORT_H + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QIODevice) +QT_FORWARD_DECLARE_CLASS(QModelIndex) + +QT_BEGIN_NAMESPACE + +class BookmarkModel; + +class XbelWriter : public QXmlStreamWriter +{ +public: + XbelWriter(BookmarkModel *model); + void writeToFile(QIODevice *device); + +private: + void writeData(const QModelIndex &index); + +private: + BookmarkModel *bookmarkModel; +}; + +class XbelReader : public QXmlStreamReader +{ +public: + XbelReader(BookmarkModel *model); + bool readFromFile(QIODevice *device); + +private: + void readXBEL(); + void readFolder(); + void readBookmark(); + void readUnknownElement(); + +private: + BookmarkModel *bookmarkModel; + QList parents; +}; + +QT_END_NAMESPACE + +#endif // XBELSUPPORT_H diff --git a/src/assistant/help/CMakeLists.txt b/src/assistant/help/CMakeLists.txt new file mode 100644 index 0000000..76dca7c --- /dev/null +++ b/src/assistant/help/CMakeLists.txt @@ -0,0 +1,83 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## Help Module: +##################################################################### + +qt_internal_add_module(Help + PLUGIN_TYPES help + SOURCES + # QtHelpCore + qcompressedhelpinfo.cpp qcompressedhelpinfo.h + qhelp_global.cpp qhelp_global.h + qhelpcollectionhandler.cpp qhelpcollectionhandler_p.h + qhelpcontentitem.cpp qhelpcontentitem.h + qhelpdbreader.cpp qhelpdbreader_p.h + qhelpenginecore.cpp qhelpenginecore.h + qhelpfilterdata.cpp qhelpfilterdata.h + qhelpfilterengine.cpp qhelpfilterengine.h + qhelplink.cpp qhelplink.h + qhelpsearchenginecore.cpp qhelpsearchenginecore.h + qhelpsearchindexreader.cpp qhelpsearchindexreader_p.h + qhelpsearchindexwriter.cpp qhelpsearchindexwriter_p.h + qhelpsearchresult.cpp qhelpsearchresult.h + DEFINES + # -QT_ASCII_CAST_WARNINGS # special case remove + QHELP_LIB + LIBRARIES + Qt::CorePrivate + Qt::Tools + PUBLIC_LIBRARIES + Qt::Core + Qt::Sql + NO_GENERATE_CPP_EXPORTS +) + +if(QT_FEATURE_fullqthelp) + qt_internal_extend_target(Help + SOURCES + qfilternamedialog.cpp qfilternamedialog_p.h + qhelpcontentwidget.cpp qhelpcontentwidget.h + qhelpengine.cpp qhelpengine.h + qhelpfiltersettingswidget.cpp qhelpfiltersettingswidget.h + qhelpindexwidget.cpp qhelpindexwidget.h + qhelpsearchengine.cpp qhelpsearchengine.h + qhelpsearchquerywidget.cpp qhelpsearchquerywidget.h + qhelpsearchresultwidget.cpp qhelpsearchresultwidget.h + qoptionswidget.cpp qoptionswidget_p.h + PUBLIC_LIBRARIES + Qt::Gui + Qt::Widgets + ) + + qt_add_ui(Help + SOURCES + qfilternamedialog.ui + qhelpfiltersettingswidget.ui + ) + + # Resources: + set(helpsystem_resource_files + "images/1leftarrow.png" + "images/1rightarrow.png" + "images/3leftarrow.png" + "images/3rightarrow.png" + "images/mac/minus.png" + "images/mac/plus.png" + "images/win/minus.png" + "images/win/plus.png" + ) + + qt_internal_add_resource(Help "helpsystem" + PREFIX + "/qt-project.org/assistant" + FILES + ${helpsystem_resource_files} + ) +endif() + +qt_internal_add_docs(Help + doc/qthelp.qdocconf +) + diff --git a/src/assistant/help/doc/qthelp.qdocconf b/src/assistant/help/doc/qthelp.qdocconf new file mode 100644 index 0000000..7ab1650 --- /dev/null +++ b/src/assistant/help/doc/qthelp.qdocconf @@ -0,0 +1,50 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qttools.qdocconf) + +project = QtHelp +description = Qt Help Reference Documentation +version = $QT_VERSION + +examplesinstallpath = help + +# Path to the root of qttools (for automatic linking to source code) +url.sources.rootdir = ../../../.. + +qhp.projects = QtHelp + +qhp.QtHelp.file = qthelp.qhp +qhp.QtHelp.namespace = org.qt-project.qthelp.$QT_VERSION_TAG +qhp.QtHelp.virtualFolder = qthelp +qhp.QtHelp.indexTitle = Qt Help +qhp.QtHelp.indexRoot = + +qhp.QtHelp.subprojects = manual classes +qhp.QtHelp.subprojects.manual.title = Qt Help +qhp.QtHelp.subprojects.manual.indexTitle = Qt Help module topics +qhp.QtHelp.subprojects.manual.type = manual +qhp.QtHelp.subprojects.classes.title = C++ Classes +qhp.QtHelp.subprojects.classes.indexTitle = Qt Help C++ Classes +qhp.QtHelp.subprojects.classes.selectors = class fake:headerfile +qhp.QtHelp.subprojects.classes.sortPages = true + +language = Cpp + +depends += qtdoc qtcore qtwidgets qmake qtgui + +headerdirs += .. + +sourcedirs += .. + +exampledirs = ../../../../examples/help \ + snippets + +imagedirs = images + +navigation.landingpage = "Qt Help" +navigation.cppclassespage = "Qt Help C++ Classes" +# Auto-generate navigation linking based on "Qt Help module topics": +navigation.toctitles = "Qt Help module topics" +navigation.toctitles.inclusive = false + +# Enforce zero documentation warnings +warninglimit = 0 diff --git a/src/assistant/help/doc/snippets/doc_src_qthelp.cpp b/src/assistant/help/doc/snippets/doc_src_qthelp.cpp new file mode 100644 index 0000000..7498c22 --- /dev/null +++ b/src/assistant/help/doc/snippets/doc_src_qthelp.cpp @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [6] +QHelpEngineCore helpEngine("mycollection.qhc"); +... + +// get all file references for the identifier +QList links = + helpEngine.documentsForIdentifier(QLatin1String("MyDialog::ChangeButton")); + +// If help is available for this keyword, get the help data +// of the first file reference. +if (links.count()) { + QByteArray helpData = helpEngine->fileData(links.constBegin()->url); + // show the documentation to the user + if (!helpData.isEmpty()) + displayHelp(helpData); +} +//! [6] + + diff --git a/src/assistant/help/doc/snippets/doc_src_qthelp.qdoc b/src/assistant/help/doc/snippets/doc_src_qthelp.qdoc new file mode 100644 index 0000000..1d027a8 --- /dev/null +++ b/src/assistant/help/doc/snippets/doc_src_qthelp.qdoc @@ -0,0 +1,138 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +//! [1] +QT += help +//! [1] + + +//! [2] +qhelpgenerator doc.qhp -o doc.qch +//! [2] + + +//! [3] + + + + + doc.qch + + + +//! [3] + + +//! [4] +qhelpgenerator mycollection.qhcp -o mycollection.qhc +//! [4] + + +//! [5] +... + + + + doc.qhp + doc.qch + + + + doc.qch + + +... +//! [5] + + +//! [7] + + + mycompany.com.myapplication.1.0 + doc + + myapp + 1.0 + + + myapp + 1.0 + +
+
+
+
+
+ + + + + + + + classic.css + *.html + + + +//! [7] + + +//! [8] +... +doc +... +//! [8] + + +//! [9] +... + + myapp + 1.0 + +... +//! [9] + + +//! [10] +... + + myapp + 1.0 +... +//! [10] + + +//! [11] +... + +
+
+
+
+
+ +... +//! [11] + + +//! [12] +... + + + + + +... +//! [12] + + +//! [13] +... + + classic.css + *.html + +... +//! [13] diff --git a/src/assistant/help/doc/src/qthelp-examples.qdoc b/src/assistant/help/doc/src/qthelp-examples.qdoc new file mode 100644 index 0000000..e2191bb --- /dev/null +++ b/src/assistant/help/doc/src/qthelp-examples.qdoc @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \group examples-qthelp + \ingroup all-examples + \title Qt Help Examples + \brief Adding context-sensitive help to Qt applications. + + The Qt help system includes tools for generating and viewing Qt help files. + In addition, it provides classes for accessing help contents programatically + to be able to integrate online help into Qt applications. + + The following example illustrates how to add context-sensitive help to + applications. + +*/ + +/* + \list + \li \l{contextsensitivehelp}{Context-Sensitive Help} + \endlist +*/ diff --git a/src/assistant/help/doc/src/qthelp-index.qdoc b/src/assistant/help/doc/src/qthelp-index.qdoc new file mode 100644 index 0000000..9177707 --- /dev/null +++ b/src/assistant/help/doc/src/qthelp-index.qdoc @@ -0,0 +1,44 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qthelp-index.html + \title Qt Help + \brief The QtHelp module provides classes for integrating + online documentation in applications. + + The Qt help system includes tools for generating and viewing Qt help files. + In addition, it provides classes for accessing help contents + programmatically to be able to integrate online help into Qt applications. + + \section1 Getting Started + + To link against the Qt Help module, add this line to the project file: + + \snippet doc_src_qthelp.qdoc 1 + + \section1 Articles and Guides + + The classes and tools supplied with Qt to enable developers to include + online help and documentation in their applications are described in + more detail in \l{The Qt Help Framework}. + + \section1 API Reference + + These are links to the API reference material: + + \list + \li \l{Qt Help C++ Classes}{C++ Classes} + \endlist + + \section1 License Information + + Qt Help is available under commercial licenses from \l{The Qt Company}. + In addition, it is available under free software licenses. Since Qt 5.4, + these free software licenses are + \l{GNU Lesser General Public License, version 3}, or + the \l{GNU General Public License, version 2}. + See \l{Qt Licensing} for further details. + + \annotatedlist attributions-qthelp +*/ diff --git a/src/assistant/help/doc/src/qthelp-module.qdoc b/src/assistant/help/doc/src/qthelp-module.qdoc new file mode 100644 index 0000000..3308b95 --- /dev/null +++ b/src/assistant/help/doc/src/qthelp-module.qdoc @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \module QtHelp + \title Qt Help C++ Classes + \ingroup modules + \qtcmakepackage Help + \qtvariable help + + \brief Provides classes for integrating online documentation in + applications. + + The classes and tools supplied with Qt to enable developers to include + online help and documentation in their applications are described in + more detail in \l{The Qt Help Framework} overview document. + + To link against the module, add this line to your \l qmake \c + .pro file: + + \snippet doc_src_qthelp.qdoc 1 + +*/ diff --git a/src/assistant/help/doc/src/qthelp-toc.qdoc b/src/assistant/help/doc/src/qthelp-toc.qdoc new file mode 100644 index 0000000..dd5079b --- /dev/null +++ b/src/assistant/help/doc/src/qthelp-toc.qdoc @@ -0,0 +1,18 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qthelp-toc.html + \title Qt Help module topics + + The following list has links to all the individual topics (HTML files) + in the Qt Help module. + + \list + \li \l {Help System} + \li \l {The Qt Help Framework}{Integrating documentation into applications} + \li \l {Qt Help Project} + \li \l {Qt Help Examples}{Examples} + \endlist + +*/ diff --git a/src/assistant/help/doc/src/qthelp.qdoc b/src/assistant/help/doc/src/qthelp.qdoc new file mode 100644 index 0000000..2c010b8 --- /dev/null +++ b/src/assistant/help/doc/src/qthelp.qdoc @@ -0,0 +1,284 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \group helpsystem + \title Help System + \ingroup groups + + \brief Classes used to provide online-help for applications. + + \keyword help system + + These classes provide online-help for your application, + with three levels of detail: + + \list 1 + \li Tool Tips and Status Bar message - flyweight help, extremely brief, + entirely integrated in the user interface, requiring little + or no user interaction to invoke. + \li What's This? - lightweight, but can be + a three-paragraph explanation. + \li Online Help - can encompass any amount of information, + but is typically slower to call up, somewhat separated + from the user's work, and often users feel that using online + help is a digression from their real task. + \endlist + +*/ + +/*! + \page qthelp-framework.html + \title The Qt Help Framework + \brief Integrating Documentation in Applications + \ingroup frameworks-technologies + + \section1 Overview + The Qt help system includes tools for generating and viewing + Qt help files. In addition, it provides classes for accessing + help contents programmatically to be able to integrate online + help into Qt applications. + + The actual help data, meaning the table of contents, index + keywords, or HTML documents, is contained in Qt compressed help + files. So, one such a help file represents usually one manual + or documentation set. Since most products are more comprehensive + and consist of a number of tools, one manual is rarely enough. + Instead, more manuals, which should be accessible at the same + time, exist. Ideally, it should also be possible to reference + certain points of interest of one manual to another. + Therefore, the Qt help system operates on help collection files, + which include any number of compressed help files. + + However, having collection files to merge many documentation + sets may lead to some problems. For example, one index keyword + may be defined in different documentation sets. So, when only seeing + a keyword in the index and activating it, you cannot be sure that + the expected documentation will be shown. Therefore, the Qt + help system offers the possibility to filter the help contents + after certain attributes. This requires, however, that the + attributes have been assigned to the help contents before the + generation of the compressed help file. + + As already mentioned, the Qt compressed help file contains all + data, so there is no need any longer to ship all the single HTML + files. Instead, only the compressed help file and, optionally, the + collection file have to be distributed. The collection file is + optional since any existing collection file, for example from an older + release, could be used. + + So, in general, there are four files interacting with the help + system, two used for generating Qt help and two meant for + distribution: + + \table + \header + \li Name + \li Extension + \li Brief Description + \row + \li \l {Qt Help Project} + \li .qhp + \li Contains the table of contents, indices, and references to the + actual documentation files (*.html). It also defines a unique + namespace for the documentation. This file is passed to the help + generator for creating a compressed help file. + + \row + \li Qt Compressed Help + \li .qch + \li Contains all the information specified in the help project file + along with all the compressed documentation files. + + \row + \li \l {Qt Help Collection Project} + \li .qhcp + \li An XML file that contains references to the compressed help + files that should be included in the help collection. This file + can be passed to the help generator for creating a help collection + file. + + \row + \li Qt Help Collection + \li .qhc + \li The help collection file that QHelpEngine operates on. It can + contain references to any number of compressed help files as + well as additional information. + \endtable + + \section1 Generating Qt Help + + Building help files for the Qt help system assumes that the HTML + documentation files already exist. + + Once the HTML documents are in place, a \l {Qt Help Project} file, with + an extension of \c .qhp, has to be created. After specifying all the relevant + information in this file, it needs to be compiled by calling: + + \snippet doc_src_qthelp.qdoc 2 + + The file \e doc.qch contains all the HTML files in compressed + form along with the table of contents and index keywords. To + test if the generated file is correct, open Qt Assistant and + install the file in \uicontrol Settings > \uicontrol Documentation. + + For the standard Qt source build, the .qhp file is generated and placed + in the same directory as the HTML pages. + + \target Qt Help Collection Project + \section2 Creating a Qt Help Collection + + The first step is to create a Qt Help Collection Project file. + Since a Qt help collection stores primarily references to + compressed help files, the project \e mycollection.qhcp file + looks unsurprisingly simple: + + \snippet doc_src_qthelp.qdoc 3 + + For actually creating the collection file call: + + \snippet doc_src_qthelp.qdoc 4 + + To generate both the compressed help and the collection file in one go, + modify the help collection project file so that it instructs the help + generator to create the compressed help first: + + \snippet doc_src_qthelp.qdoc 5 + + Of course, it is possible to specify more than one file in the + \c generate or \c register section, so any number of compressed + help files can be generated and registered in one go. + + \section1 Using QHelpEngine API + + QHelpEngine allows embedding the help contents directly in an + application. + + Instead of showing the help in an external application such as a + web browser, it is also possible to embed the online help in + the application. The contents can then be retrieved via the + QHelpEngine class and can be displayed in nearly any form. + Showing the help in a QTextBrowser is probably the most common way, but + embedding it in What's This help is also perfectly possible. + + Retrieving help data from the file engine does not involve a + lot of code. The first step is to create an instance of the + help engine. Then we ask the engine for the links assigned to + the identifier, in this case \c MyDialog::ChangeButton. If a link + was found, meaning at least one help document exists on this topic, + we get the actual help contents by calling QHelpEngineCore::fileData() and + display the document to the user. + + \snippet doc_src_qthelp.cpp 6 + + For further information on how to use the API, have a look at + the QHelpEngine class reference. +*/ + +/*! + \page qthelpproject.html + \title Qt Help Project + + A Qt help project collects all data necessary to generate a + compressed help file. Along with the actual help data, like + the table of contents, index keywords and help documents, it + contains some extra information like a namespace to identify + the help file. One help project stands for one documentation set, + for example the \l{qmake Manual}. + + \section1 Qt Help Project File Format + + The file format is XML-based. For a better understanding of + the format we will discuss the following example: + + \snippet doc_src_qthelp.qdoc 7 + + \section2 Namespace + + To enable the QHelpEngine to retrieve the proper documentation to + a given link, every documentation set has to have a unique + identifier. A unique identifier also makes it possible for the + help collection to keep track of a documentation set without relying + on its file name. The Qt help system uses a namespace as identifier + which is defined by the mandatory namespace tags. In the example + above, the namespace is "mycompany.com.myapplication.1.0". + + \target Virtual Folders + \section2 Virtual Folders + + Having a namespace for every documentation set naturally means that + the documentation sets are quite separated. From the help engine's + point of view, this is beneficial. However, from the writer's view + it is often desirable to cross reference certain topics from one + manual to another without having to specify absolute links. To + solve this problem, the help system introduced the concept of + virtual folders. + + A virtual folder will become the root directory of all files + referenced in a compressed help file. When two documentation sets + share the same virtual folder, they can use relative paths when + defining hyperlinks pointing to each other. If a file is contained + in both documentation sets, the one from the current set has + precedence over the other. + + \snippet doc_src_qthelp.qdoc 8 + + The above example specifies \e doc as virtual folder. If another + manual specifies the same folder, for example for a small helper + tool \e {My Application}, it is sufficient to write + \e {doc.html#section1} to reference the first section in the + \e {My Application} manual. + + The virtual folder tag is mandatory and the folder name must not + contain any slashes (/). + + \target Filter Section + \section2 Filter Section + + A filter section contains the actual documentation. A Qt help project + file may contain more than one filter section. Every filter section + consists of the table of contents, the keywords, and the files list. + In theory all parts are optional but not specifying anything there will + result in an empty documentation set. + + \section3 Table of Contents + + \snippet doc_src_qthelp.qdoc 11 + + One section tag represents one item in the table of contents. The + sections can be nested to any degree, but from a user's perspective + it should not be more than four or five levels. A section is defined + by its title and reference. The reference, like all file references in a Qt + help project, are relative to the help project file itself. + \note The referenced files must be in the same directory as the help + project file (or in a subdirectory). An absolute file path is not supported + either. + + \section3 Keywords + + \snippet doc_src_qthelp.qdoc 12 + + The keyword section lists all keywords of this filter section. A + keyword consists basically of a name and a file reference. If the + attribute \e name is used, the keyword specified there will appear in the + visible index. That is, it will be accessible through the QHelpIndexModel + class. If \e id is used, the keyword does not appear in the index and is + only accessible via \l QHelpEngineCore::documentsForIdentifier(). \e name + and \e id can be specified at the same time. + + \section3 Files + + \snippet doc_src_qthelp.qdoc 13 + + Finally, the actual documentation files have to be listed. Make sure + that all files necessary to display the help are mentioned. That is, + stylesheets or similar files need to be listed as well. The files, like all + file references in a Qt help project, are relative to the help project file + itself. As the example shows, files (but not directories) can also be + specified as patterns using wildcards. All listed files will be compressed + and written to the Qt compressed help file. So, in the end, one single Qt + help file contains all documentation files along with the contents and + indices. \note The referenced files must be inside the same directory + as the help project file (or in a subdirectory). An absolute file path + is not supported either. +*/ diff --git a/src/assistant/help/images/1leftarrow.png b/src/assistant/help/images/1leftarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..bd1a5a2499af38761bd8db135b4c0ea537789bb5 GIT binary patch literal 669 zcmeAS@N?(olHy`uVBq!ia0vp^0w65F1|^Y9 z4OYz%b8PKlAcrl<+uh|q7;r{>zr(=5_{-D9F~s9|a>4?k58u9h;^J^vesi7E3kFM$ zy+6cS%r>{IT)gS1&l-VS6ZoCZ-{o0of2aAv|NqV}g2Xn-HarWi$FbGJSt_*tP;dx)$nsaKWpZQ~+Khi}F$ z_x3QoN?ze?cii!@nYhzxx%R9a#j}hXZ(or4AYse3U<-qKi{E|zh3hMwP4_ilC|}4X z;`r3{oq#y2A%CE3!2W&*rIw3a7p~k9WbGA7<@l@5<|N;IEZ4v!x1cD&T)j>HDkJY_ z&S}gUsxg*ZIDA_#Xd5YfDbLlo%W_sUp!8YaieHQWEw);)ao(Y(4(3_D%~uvaWxA61 z-)EuXRyn8bpPaar)I-=!js~sPXuG7eP+(Vqk;&Q{jXKdWqb_sJ0ICNGToB{rVFL2kcg7t z{Irtt#G+INhKgI-`uf*TpE<4P=cnUw^|ZbhJA>H`wg`dXQ!zl*44$rjF6*2UngG*7 B3pfA( literal 0 HcmV?d00001 diff --git a/src/assistant/help/images/1rightarrow.png b/src/assistant/help/images/1rightarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..0c0c44ae6ffdf8c7cf75664b16388bf18fc8f699 GIT binary patch literal 706 zcmV;z0zLhSP)8wDn%-V5Q7wjD2g_vcv)zpEfSh3Fi-uuuk#)M6HdKO1IRJutVe~sron3TC{ zsr1E|n(&#nct0L!4V|!ZpwvoiQK_b$s&_i^%}Fd#z_`KX|24PD`%wX!(BTNTg=P@@ z4$P=lN&B>;e{e1vyXr9hiS-Ansdu9SXzLxnBMV@ebPO9u(LZROE}v85eAYxwxV;Y- z(=h+xzm*`q7Ee{-uoe!+;^Yl%y^d+V&=TnQ^%N_>87wGYdBNC3Jgn0)(5!iFI<9KY zp52(PCbcK5)`ZfdCt2)QfC=;WtQ4IV^^9L#zR-X#%J7hC91|DzqA~|`-&BBZ9kWY# zSqsc4KtRWANcYF_fNsXoh;K%7Lf2WdS-$C zUX8yH<(HA=5!KGCdmuj<*BAV+*YRK7E!{y~a)){V001I-R9JLVZ)S9NVRB^v0C?If oFE7{2%*!rLPAo{(%P&d?05;eLSP)anTmS$707*qoM6N<$g03ApKmY&$ literal 0 HcmV?d00001 diff --git a/src/assistant/help/images/3leftarrow.png b/src/assistant/help/images/3leftarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..8d38b0f5782eb432de9890e0c4a7c806290697ca GIT binary patch literal 832 zcmV-G1Hb%)+51es$zUMqI zM`Da~AtWi;LM_Z|Q~#gok8{QpCGHV&TkN`OVCgCBsD<`urUA(VYP4|U8Q7W&t>@AN z$g7~|1#CC`EkN~zEWSlo^5 z5??M_`d)H{?Ne-5n{Y$u3H;IzZ&t!Bw5m0Z)KjFR0Vf_qmyZID#{&)o%n!=n%X-$! zJ_xtd%MGkv*%bA2GczgHxiZ`^Os{(PNto@8ujW54PThG5SOfcdD4;75FlD}1J}v9n z9X2VPP9HB}4a$aS9R<*h_m{w(HkegC&j8wv{ub!IKmq>56c~AhFv)t%z1z;3dA_*f z`btv}zztIt_?h;zBM8@a(ty>er(nf(_+}D%D(C_7)9(T-!ZBsQ7_<#aR9@Nu2WsHV zU0PfR;i*?J@0}{3kOJrd`qxo_vcPS)s0~(ahTa+Y(+!`6p!i6;q5zH?D1Z*8toHwO z5b5ZZ+BuzkbP`7B;2ENGA>^Wf4hrz816EVO2nDRq@F{oBVVdk&U8qP1g(jo~yOqnIVFuxNOppX1b1l}m3z+a0_93kvr>Fx650 zumAu6B6?I2D?NY%?PN(TTo*alb-Q_5Tb0000< KMNUMnLSTY|=Wf0L literal 0 HcmV?d00001 diff --git a/src/assistant/help/images/3rightarrow.png b/src/assistant/help/images/3rightarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..c2faf501ccf7bd265ef8d99367a6eba02839701e GIT binary patch literal 820 zcmV-41Izr0P)HqSe~k;sVXVg)9sH$yEN zO$sl}iy)>K%^-s6YD$!*7EVO_Fhkizk+2XlANC+t-&Hf2DD53F)iIZ-?O99yS1?FGnx?_JED12h|9CTV> zeQlfo>F?pI8W;@1w*#3N8qDTn7b9)9>Nb==(oZ+;8fu|dx zH%pkPT(K-}7aD3;^h=p^MWo1U_UGg!w|p1%M`?WiC)UCztx&It*X`lYYk9>=xHmkj z5Fi*7dDNif2ZPi_3({q87HQO^WJCvO=dQ1Y163S=V!yGv85Z7z&KCH!63$yh8C58z zj%Kn6gSvLT&_$HOD=?}Gzlwbh*s>39uK&-g_*t9)+UtDd-Gd!F;qD%8 zx~zZu7-@{Kdj{GHe+P(toHRV4rg3FI$2&ekLm)K%3p`CV)wnDoDgXcgB6?I2D?NY%?PN(TTo*alb-Q_5Tb00008XoPuMz?Y|Rw{0vjgv+B!ch zy;dm_ut20@$;9K+a_T>ye{*<+*6l4ly3tG?44ev#Dh)O)`svI2yTab}@8^|GHkfho zNeOTfAYE#xV zTYdQ2V(!{EY<(cpv1OlC|Mk$nFJwxr5_&M{ssYb!Y!6iMT-Lnu z^%LX6O%qa2gs^lmow~IC?DKoCclAn#7p^FN%^<|Ei6_mNUm#sTTOc~pY5nu>t=(d~ zq~A~M3_fyFjkTusWO04OyzP?5OwSte2Tr^2{`Z+?r;S>P_p|Ifa>5t{+wz6q>78?A zjm)yTU*U1P=G*Jdx;OcCUKB)#cx$;a{ezuM^;+MJ)Bq-|n aKk(|Y38wz&&UFFC3WKMspUXO@geCxP=D{TZ literal 0 HcmV?d00001 diff --git a/src/assistant/help/images/mac/plus.png b/src/assistant/help/images/mac/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..1ee45423e3958221bd545ee6f122d989302cab3f GIT binary patch literal 810 zcmV+_1J(SAP)B<|3F)+>u<}zfuZe7)NT=Ybf%p@9RIu@KocNNrs{{OFYs@fvL zX^u9hJWdQiy;9GCE&}Y4r^W#*azk#i!X z0tQeD-}3nENIJp}&;hlob6(uM5O*#)UjX|Y5+K#c2tsS zZMN$5yBRrqOa_ap_a;nIVsn9|z+v%N9CfB%If8gRp1^qXR>?32 z*3w6Ivpw-?m;j)qg+kXMEtJw|G)lG5(gLLcMIhjghO{k_bu#)CdcjL^)(HV4rIl#l9 z`bJLq*V>D~Rp82RirDxVP5}7Rn~Yjj!U$|-YlH0pxKM8{H$~096rTrE8Kjh7nlCq5 ziUE@h66K&=O}1m34oT2-J2r6&15LJLTiV(KGC9`_1Eo1-3Ch!yU=dz^csbc-8xNq17Gynhq07*qoM6N<$f^&^p>Hq)$ literal 0 HcmV?d00001 diff --git a/src/assistant/help/images/win/minus.png b/src/assistant/help/images/win/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..c0dc274bb4fe04dab9220c9ce39fd3be89d08712 GIT binary patch literal 429 zcmV;e0aE^nP)<@ELK+5mf)&HQGufU<-T zBC6sBQo0Xen_HG(wsrovyXEZt)A|7JAZq=LG0fYAshlbGju?6^H$ z#q(M~k{(Y_p6_Q@bMy9cg{q}Civ;D|6cXpd;j(ejj!kV}hyJi>_p9gx{<*+6 XZ_4%z@%F;;00000NkvXXu0mjf6#2YG literal 0 HcmV?d00001 diff --git a/src/assistant/help/images/win/plus.png b/src/assistant/help/images/win/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..ecf0589415464d3a432931d311c32550a53dae8d GIT binary patch literal 709 zcmV;$0y_PPP)g)r>qUh;|~v(@^U5(^ERwC$Wgg^wRYFo4c>EYN#})l#)s+lz`Uq(7pmE4yTs= z$A8!jT{g?FZ1_#K{1zSCp<_C9vJUOdc2h*;aEV;V_>!-aX@ZdyqxEuF$*_^dM)lR< zaO$~lW_jno^TX{!&cE(qMrKZWP-sCK4ltzHkYOalh{0hn7##h{V~?pkZ_97&>H!>g zPB`)`9v-Q*qS8Pmfqc}bBDSLd-Hfb=R)cuIN$5N!K8n4F7w^#3-ZgN*? z{A}?2(K8e|fmF(=;jifD5*f0llI(UO r(pvkt);u!M&-s-o&^cYW>~G6o+k}(Vi#=;y00000NkvXXu0mjfTZTZ= literal 0 HcmV?d00001 diff --git a/src/assistant/help/qcompressedhelpinfo.cpp b/src/assistant/help/qcompressedhelpinfo.cpp new file mode 100644 index 0000000..8051fdd --- /dev/null +++ b/src/assistant/help/qcompressedhelpinfo.cpp @@ -0,0 +1,147 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qcompressedhelpinfo.h" + +#include "qhelpdbreader_p.h" + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QCompressedHelpInfoPrivate : public QSharedData +{ +public: + QCompressedHelpInfoPrivate() = default; + QCompressedHelpInfoPrivate(const QCompressedHelpInfoPrivate &other) + : QSharedData(other) + , m_namespaceName(other.m_namespaceName) + , m_component(other.m_component) + , m_version(other.m_version) + , m_isNull(other.m_isNull) + { } + + QString m_namespaceName; + QString m_component; + QVersionNumber m_version; + bool m_isNull = true; +}; + +/*! + \class QCompressedHelpInfo + \since 5.13 + \inmodule QtHelp + \brief The QCompressedHelpInfo class provides access to + the details about a compressed help file. + + The detailed information about the compressed + help file can be fetched by calling the fromCompressedHelpFile() + static method, providing the path to the compressed + help file. + + The class provides access to various information about a compressed help file. + The namespace associated with the given compressed help file is + namespaceName(), the associated component name is component() + and version() provides version information. + + \sa QHelpFilterEngine +*/ + +/*! + Constructs empty information about a compressed help file. +*/ +QCompressedHelpInfo::QCompressedHelpInfo() + : d(new QCompressedHelpInfoPrivate) +{} + +/*! + Constructs a copy of \a other. +*/ +QCompressedHelpInfo::QCompressedHelpInfo(const QCompressedHelpInfo &) = default; + +/*! + Move-constructs a QCompressedHelpInfo instance, + making it point to the same object that \a other was pointing to, + so that it contains the information the \a other used to contain. +*/ +QCompressedHelpInfo::QCompressedHelpInfo(QCompressedHelpInfo &&) = default; + +/*! + Destroys the QCompressedHelpInfo. +*/ +QCompressedHelpInfo::~QCompressedHelpInfo() = default; + +/*! + Makes this QHelpCollectionDetails into a copy of \a other, so the two + are identical, and returns a reference to this QHelpCollectionDetails. +*/ +QCompressedHelpInfo &QCompressedHelpInfo::operator=(const QCompressedHelpInfo &) = default; + +/*! + Move-assigns \a other to this QCompressedHelpInfo instance. +*/ +QCompressedHelpInfo &QCompressedHelpInfo::operator=(QCompressedHelpInfo &&) = default; + +/*! + \fn void QCompressedHelpInfo::swap(QCompressedHelpInfo &other) + + Swaps the compressed help file \a other with this compressed help file. This + operation is very fast and never fails. +*/ + +/*! + Returns the namespace name of the compressed help file. +*/ +QString QCompressedHelpInfo::namespaceName() const +{ + return d->m_namespaceName; +} + +/*! + Returns the component of the compressed help file. +*/ +QString QCompressedHelpInfo::component() const +{ + return d->m_component; +} + +/*! + Returns the version of the compressed help file. +*/ +QVersionNumber QCompressedHelpInfo::version() const +{ + return d->m_version; +} + +/*! + Returns \c true if the info is invalid, otherwise returns + \c false. +*/ +bool QCompressedHelpInfo::isNull() const +{ + return d->m_isNull; +} + +/*! + Returns the QCompressedHelpInfo instance for the + \a documentationFileName of the existing qch file. +*/ +QCompressedHelpInfo QCompressedHelpInfo::fromCompressedHelpFile(const QString &documentationFileName) +{ + void *pointer = const_cast(&documentationFileName); + QHelpDBReader reader(documentationFileName, QHelpGlobal::uniquifyConnectionName( + "GetCompressedHelpInfo"_L1, pointer), nullptr); + if (reader.init()) { + QCompressedHelpInfo info; + info.d->m_namespaceName = reader.namespaceName(); + info.d->m_component = reader.virtualFolder(); + info.d->m_version = QVersionNumber::fromString(reader.version()); + info.d->m_isNull = false; + return info; + } + return {}; +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qcompressedhelpinfo.h b/src/assistant/help/qcompressedhelpinfo.h new file mode 100644 index 0000000..a68c03c --- /dev/null +++ b/src/assistant/help/qcompressedhelpinfo.h @@ -0,0 +1,43 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCOMPRESSEDHELPINFO_H +#define QCOMPRESSEDHELPINFO_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QCompressedHelpInfoPrivate; +class QVersionNumber; + +class QHELP_EXPORT QCompressedHelpInfo final +{ +public: + QCompressedHelpInfo(); + QCompressedHelpInfo(const QCompressedHelpInfo &other); + QCompressedHelpInfo(QCompressedHelpInfo &&other); + ~QCompressedHelpInfo(); + + QCompressedHelpInfo &operator=(const QCompressedHelpInfo &other); + QCompressedHelpInfo &operator=(QCompressedHelpInfo &&other); + + void swap(QCompressedHelpInfo &other) Q_DECL_NOTHROW + { d.swap(other.d); } + + QString namespaceName() const; + QString component() const; + QVersionNumber version() const; + bool isNull() const; + + static QCompressedHelpInfo fromCompressedHelpFile(const QString &documentationFileName); + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +#endif // QCOMPRESSEDHELPINFO_H diff --git a/src/assistant/help/qfilternamedialog.cpp b/src/assistant/help/qfilternamedialog.cpp new file mode 100644 index 0000000..11e71bc --- /dev/null +++ b/src/assistant/help/qfilternamedialog.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qfilternamedialog_p.h" + +#include + +QT_BEGIN_NAMESPACE + +QFilterNameDialog::QFilterNameDialog(QWidget *parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, + this, &QDialog::accept); + connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, + this, &QDialog::reject); + connect(m_ui.lineEdit, &QLineEdit::textChanged, this, &QFilterNameDialog::updateOkButton); + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true); +} + +void QFilterNameDialog::setFilterName(const QString &filter) +{ + m_ui.lineEdit->setText(filter); + m_ui.lineEdit->selectAll(); +} + +void QFilterNameDialog::updateOkButton() +{ + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(m_ui.lineEdit->text().isEmpty()); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qfilternamedialog.ui b/src/assistant/help/qfilternamedialog.ui new file mode 100644 index 0000000..1da584a --- /dev/null +++ b/src/assistant/help/qfilternamedialog.ui @@ -0,0 +1,55 @@ + + + FilterNameDialogClass + + + + 0 + 0 + 312 + 77 + + + + Add Filter + + + + + + Filter Name: + + + + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + diff --git a/src/assistant/help/qfilternamedialog_p.h b/src/assistant/help/qfilternamedialog_p.h new file mode 100644 index 0000000..76110ab --- /dev/null +++ b/src/assistant/help/qfilternamedialog_p.h @@ -0,0 +1,43 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QFILTERNAMEDIALOG_H +#define QFILTERNAMEDIALOG_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the help generator tools. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include "ui_qfilternamedialog.h" + +#include + +QT_BEGIN_NAMESPACE + +class QFilterNameDialog : public QDialog +{ + Q_OBJECT + +public: + QFilterNameDialog(QWidget *parent = nullptr); + + void setFilterName(const QString &filter); + QString filterName() const { return m_ui.lineEdit->text(); } + +private slots: + void updateOkButton(); + +private: + Ui::FilterNameDialogClass m_ui; +}; + +QT_END_NAMESPACE + +#endif // QFILTERNAMEDIALOG_H diff --git a/src/assistant/help/qhelp_global.cpp b/src/assistant/help/qhelp_global.cpp new file mode 100644 index 0000000..5e9cb07 --- /dev/null +++ b/src/assistant/help/qhelp_global.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelp_global.h" +#include + +#include +#include +#include +#if QT_CONFIG(fullqthelp) +# include +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QString QHelpGlobal::uniquifyConnectionName(const QString &name, void *pointer) +{ + static QMutex mutex; + QMutexLocker locker(&mutex); + static QHash idHash; + return QString::asprintf("%ls-%p-%d", qUtf16Printable(name), pointer, ++idHash[name]); +} + +QString QHelpGlobal::documentTitle(const QString &content) +{ +#if QT_CONFIG(fullqthelp) + QString title = QCoreApplication::translate("QHelp", "Untitled"); + if (!content.isEmpty()) { + const int start = content.indexOf(""_L1, 0, Qt::CaseInsensitive) + 7; + const int end = content.indexOf(""_L1, 0, Qt::CaseInsensitive); + if ((end - start) > 0) { + title = content.mid(start, end - start); + if (Qt::mightBeRichText(title) || title.contains(u'&')) { + QTextDocument doc; + doc.setHtml(title); + title = doc.toPlainText(); + } + } + } + return title; +#else + Q_UNUSED(content); + return {}; +#endif +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelp_global.h b/src/assistant/help/qhelp_global.h new file mode 100644 index 0000000..b7367ee --- /dev/null +++ b/src/assistant/help/qhelp_global.h @@ -0,0 +1,32 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELP_GLOBAL_H +#define QHELP_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +class QString; + +#ifdef QT_STATIC +# define QHELP_EXPORT +#elif defined(QHELP_LIB) +# define QHELP_EXPORT Q_DECL_EXPORT +#else +# define QHELP_EXPORT Q_DECL_IMPORT +#endif + +// TODO Qt 7.0: Remove the class and make it a namespace with a collection of functions. +// Review, if they are still need to be public. +class QHELP_EXPORT QHelpGlobal +{ +public: + static QString uniquifyConnectionName(const QString &name, void *pointer); + static QString documentTitle(const QString &content); +}; + +QT_END_NAMESPACE + +#endif // QHELP_GLOBAL_H diff --git a/src/assistant/help/qhelpcollectionhandler.cpp b/src/assistant/help/qhelpcollectionhandler.cpp new file mode 100644 index 0000000..5625130 --- /dev/null +++ b/src/assistant/help/qhelpcollectionhandler.cpp @@ -0,0 +1,2377 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpcollectionhandler_p.h" +#include "qhelp_global.h" +#include "qhelpdbreader_p.h" +#include "qhelpfilterdata.h" +#include "qhelplink.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class Transaction +{ +public: + Q_DISABLE_COPY_MOVE(Transaction); + + Transaction(const QString &connectionName) + : m_db(QSqlDatabase::database(connectionName)), + m_inTransaction(m_db.driver()->hasFeature(QSqlDriver::Transactions)) + { + if (m_inTransaction) + m_inTransaction = m_db.transaction(); + } + + ~Transaction() + { + if (m_inTransaction) + m_db.rollback(); + } + + void commit() + { + if (!m_inTransaction) + return; + + m_db.commit(); + m_inTransaction = false; + } + +private: + QSqlDatabase m_db; + bool m_inTransaction; +}; + +QHelpCollectionHandler::QHelpCollectionHandler(const QString &collectionFile, QObject *parent) + : QObject(parent) + , m_collectionFile(collectionFile) +{ + const QFileInfo fi(m_collectionFile); + if (!fi.isAbsolute()) + m_collectionFile = fi.absoluteFilePath(); +} + +QHelpCollectionHandler::~QHelpCollectionHandler() +{ + closeDB(); +} + +bool QHelpCollectionHandler::isDBOpened() const +{ + if (m_query) + return true; + auto *that = const_cast(this); + emit that->error(tr("The collection file \"%1\" is not set up yet.").arg(m_collectionFile)); + return false; +} + +void QHelpCollectionHandler::closeDB() +{ + if (!m_query) + return; + + m_query.reset(); + QSqlDatabase::removeDatabase(m_connectionName); + m_connectionName.clear(); +} + +bool QHelpCollectionHandler::openCollectionFile() +{ + if (m_query) + return true; + + m_connectionName = QHelpGlobal::uniquifyConnectionName("QHelpCollectionHandler"_L1, this); + { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, m_connectionName); + if (db.driver() + && db.driver()->lastError().type() == QSqlError::ConnectionError) { + emit error(tr("Cannot load sqlite database driver.")); + return false; + } + + db.setDatabaseName(collectionFile()); + if (db.open()) + m_query.reset(new QSqlQuery(db)); + + if (!m_query) { + QSqlDatabase::removeDatabase(m_connectionName); + emit error(tr("Cannot open collection file: %1").arg(collectionFile())); + return false; + } + } + + if (m_readOnly) + return true; + + m_query->exec("PRAGMA synchronous=OFF"_L1); + m_query->exec("PRAGMA cache_size=3000"_L1); + + m_query->exec("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\' " + "AND Name=\'NamespaceTable\'"_L1); + m_query->next(); + + const bool tablesExist = m_query->value(0).toInt() > 0; + if (!tablesExist) { + if (!createTables(m_query.get())) { + closeDB(); + emit error(tr("Cannot create tables in file %1.").arg(collectionFile())); + return false; + } + } + + bool indexAndNamespaceFilterTablesMissing = false; + + const QStringList newTables = { + "IndexTable"_L1, + "FileNameTable"_L1, + "ContentsTable"_L1, + "FileFilterTable"_L1, + "IndexFilterTable"_L1, + "ContentsFilterTable"_L1, + "FileAttributeSetTable"_L1, + "OptimizedFilterTable"_L1, + "TimeStampTable"_L1, + "VersionTable"_L1, + "Filter"_L1, + "ComponentTable"_L1, + "ComponentMapping"_L1, + "ComponentFilter"_L1, + "VersionFilter"_L1 + }; + + QString queryString = "SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\'"_L1; + queryString.append(" AND (Name=\'"_L1); + queryString.append(newTables.join("\' OR Name=\'"_L1)); + queryString.append("\')"_L1); + + m_query->exec(queryString); + m_query->next(); + if (m_query->value(0).toInt() != newTables.size()) { + if (!recreateIndexAndNamespaceFilterTables(m_query.get())) { + emit error(tr("Cannot create index tables in file %1.").arg(collectionFile())); + return false; + } + + // Old tables exist, index tables didn't, recreate index tables only in this case + indexAndNamespaceFilterTablesMissing = tablesExist; + } + + const FileInfoList &docList = registeredDocumentations(); + if (indexAndNamespaceFilterTablesMissing) { + for (const QHelpCollectionHandler::FileInfo &info : docList) { + if (!registerIndexAndNamespaceFilterTables(info.namespaceName, true)) { + emit error(tr("Cannot register index tables in file %1.").arg(collectionFile())); + return false; + } + } + return true; + } + + QList timeStamps; + m_query->exec("SELECT NamespaceId, FolderId, FilePath, Size, TimeStamp FROM TimeStampTable"_L1); + while (m_query->next()) { + TimeStamp timeStamp; + timeStamp.namespaceId = m_query->value(0).toInt(); + timeStamp.folderId = m_query->value(1).toInt(); + timeStamp.fileName = m_query->value(2).toString(); + timeStamp.size = m_query->value(3).toInt(); + timeStamp.timeStamp = m_query->value(4).toDateTime(); + timeStamps.append(timeStamp); + } + + QList toRemove; + for (const TimeStamp &timeStamp : timeStamps) { + if (!isTimeStampCorrect(timeStamp)) + toRemove.append(timeStamp); + } + + // TODO: we may optimize when toRemove.size() == timeStamps.size(). + // In this case we remove all records from tables. + Transaction transaction(m_connectionName); + for (const TimeStamp &timeStamp : toRemove) { + if (!unregisterIndexTable(timeStamp.namespaceId, timeStamp.folderId)) { + emit error(tr("Cannot unregister index tables in file %1.").arg(collectionFile())); + return false; + } + } + transaction.commit(); + + for (const QHelpCollectionHandler::FileInfo &info : docList) { + if (!hasTimeStampInfo(info.namespaceName) + && !registerIndexAndNamespaceFilterTables(info.namespaceName)) { + // we may have a doc registered without a timestamp + // and the doc may be missing currently + unregisterDocumentation(info.namespaceName); + } + } + return true; +} + +QString QHelpCollectionHandler::absoluteDocPath(const QString &fileName) const +{ + const QFileInfo fi(collectionFile()); + return QDir::isAbsolutePath(fileName) + ? fileName + : QFileInfo(fi.absolutePath() + u'/' + fileName).absoluteFilePath(); +} + +bool QHelpCollectionHandler::isTimeStampCorrect(const TimeStamp &timeStamp) const +{ + const QFileInfo fi(absoluteDocPath(timeStamp.fileName)); + + if (!fi.exists()) + return false; + + if (fi.size() != timeStamp.size) + return false; + + if (fi.lastModified(QTimeZone::UTC) != timeStamp.timeStamp) + return false; + + m_query->prepare("SELECT FilePath FROM NamespaceTable WHERE Id = ?"_L1); + m_query->bindValue(0, timeStamp.namespaceId); + if (!m_query->exec() || !m_query->next()) + return false; + + const QString oldFileName = m_query->value(0).toString(); + m_query->clear(); + if (oldFileName != timeStamp.fileName) + return false; + + return true; +} + +bool QHelpCollectionHandler::hasTimeStampInfo(const QString &nameSpace) const +{ + m_query->prepare( + "SELECT " + "TimeStampTable.NamespaceId " + "FROM " + "NamespaceTable, " + "TimeStampTable " + "WHERE NamespaceTable.Id = TimeStampTable.NamespaceId " + "AND NamespaceTable.Name = ? LIMIT 1"_L1); + m_query->bindValue(0, nameSpace); + if (!m_query->exec()) + return false; + + if (!m_query->next()) + return false; + + m_query->clear(); + return true; +} + +void QHelpCollectionHandler::scheduleVacuum() +{ + if (m_vacuumScheduled) + return; + + m_vacuumScheduled = true; + QTimer::singleShot(0, this, &QHelpCollectionHandler::execVacuum); +} + +void QHelpCollectionHandler::execVacuum() +{ + if (!m_query) + return; + + m_query->exec("VACUUM"_L1); + m_vacuumScheduled = false; +} + +bool QHelpCollectionHandler::copyCollectionFile(const QString &fileName) +{ + if (!m_query) + return false; + + const QFileInfo fi(fileName); + if (fi.exists()) { + emit error(tr("The collection file \"%1\" already exists.").arg(fileName)); + return false; + } + + if (!fi.absoluteDir().exists() && !QDir().mkpath(fi.absolutePath())) { + emit error(tr("Cannot create directory: %1").arg(fi.absolutePath())); + return false; + } + + const QString &colFile = fi.absoluteFilePath(); + const QString &connectionName = + QHelpGlobal::uniquifyConnectionName("QHelpCollectionHandlerCopy"_L1, this); + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, connectionName); + db.setDatabaseName(colFile); + if (!db.open()) { + emit error(tr("Cannot open collection file: %1").arg(colFile)); + return false; + } + + QSqlQuery copyQuery(db); + copyQuery.exec("PRAGMA synchronous=OFF"_L1); + copyQuery.exec("PRAGMA cache_size=3000"_L1); + + if (!createTables(©Query) || !recreateIndexAndNamespaceFilterTables(©Query)) { + emit error(tr("Cannot copy collection file: %1").arg(colFile)); + return false; + } + + const QString &oldBaseDir = QFileInfo(collectionFile()).absolutePath(); + const QFileInfo newColFi(colFile); + m_query->exec("SELECT Name, FilePath FROM NamespaceTable"_L1); + while (m_query->next()) { + copyQuery.prepare("INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1); + copyQuery.bindValue(0, m_query->value(0).toString()); + QString oldFilePath = m_query->value(1).toString(); + if (!QDir::isAbsolutePath(oldFilePath)) + oldFilePath = oldBaseDir + u'/' + oldFilePath; + copyQuery.bindValue(1, newColFi.absoluteDir().relativeFilePath(oldFilePath)); + copyQuery.exec(); + } + + m_query->exec("SELECT NamespaceId, Name FROM FolderTable"_L1); + while (m_query->next()) { + copyQuery.prepare("INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1); + copyQuery.bindValue(0, m_query->value(0).toString()); + copyQuery.bindValue(1, m_query->value(1).toString()); + copyQuery.exec(); + } + + m_query->exec("SELECT Name FROM FilterAttributeTable"_L1); + while (m_query->next()) { + copyQuery.prepare("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1); + copyQuery.bindValue(0, m_query->value(0).toString()); + copyQuery.exec(); + } + + m_query->exec("SELECT Name FROM FilterNameTable"_L1); + while (m_query->next()) { + copyQuery.prepare("INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1); + copyQuery.bindValue(0, m_query->value(0).toString()); + copyQuery.exec(); + } + + m_query->exec("SELECT NameId, FilterAttributeId FROM FilterTable"_L1); + while (m_query->next()) { + copyQuery.prepare("INSERT INTO FilterTable VALUES(?, ?)"_L1); + copyQuery.bindValue(0, m_query->value(0).toInt()); + copyQuery.bindValue(1, m_query->value(1).toInt()); + copyQuery.exec(); + } + + m_query->exec("SELECT Key, Value FROM SettingsTable"_L1); + while (m_query->next()) { + if (m_query->value(0).toString() == "FTS5IndexedNamespaces"_L1) + continue; + copyQuery.prepare("INSERT INTO SettingsTable VALUES(?, ?)"_L1); + copyQuery.bindValue(0, m_query->value(0).toString()); + copyQuery.bindValue(1, m_query->value(1)); + copyQuery.exec(); + } + + copyQuery.clear(); + QSqlDatabase::removeDatabase(connectionName); + return true; +} + +bool QHelpCollectionHandler::createTables(QSqlQuery *query) +{ + const QStringList tables = { + "CREATE TABLE NamespaceTable (" + "Id INTEGER PRIMARY KEY, " + "Name TEXT, " + "FilePath TEXT )"_L1, + "CREATE TABLE FolderTable (" + "Id INTEGER PRIMARY KEY, " + "NamespaceId INTEGER, " + "Name TEXT )"_L1, + "CREATE TABLE FilterAttributeTable (" + "Id INTEGER PRIMARY KEY, " + "Name TEXT )"_L1, + "CREATE TABLE FilterNameTable (" + "Id INTEGER PRIMARY KEY, " + "Name TEXT )"_L1, + "CREATE TABLE FilterTable (" + "NameId INTEGER, " + "FilterAttributeId INTEGER )"_L1, + "CREATE TABLE SettingsTable (" + "Key TEXT PRIMARY KEY, " + "Value BLOB )"_L1 + }; + + for (const QString &q : tables) { + if (!query->exec(q)) + return false; + } + return true; +} + +bool QHelpCollectionHandler::recreateIndexAndNamespaceFilterTables(QSqlQuery *query) +{ + const QStringList tables = { + "DROP TABLE IF EXISTS FileNameTable"_L1, + "DROP TABLE IF EXISTS IndexTable"_L1, + "DROP TABLE IF EXISTS ContentsTable"_L1, + "DROP TABLE IF EXISTS FileFilterTable"_L1, // legacy + "DROP TABLE IF EXISTS IndexFilterTable"_L1, // legacy + "DROP TABLE IF EXISTS ContentsFilterTable"_L1, // legacy + "DROP TABLE IF EXISTS FileAttributeSetTable"_L1, // legacy + "DROP TABLE IF EXISTS OptimizedFilterTable"_L1, // legacy + "DROP TABLE IF EXISTS TimeStampTable"_L1, + "DROP TABLE IF EXISTS VersionTable"_L1, + "DROP TABLE IF EXISTS Filter"_L1, + "DROP TABLE IF EXISTS ComponentTable"_L1, + "DROP TABLE IF EXISTS ComponentMapping"_L1, + "DROP TABLE IF EXISTS ComponentFilter"_L1, + "DROP TABLE IF EXISTS VersionFilter"_L1, + "CREATE TABLE FileNameTable (" + "FolderId INTEGER, " + "Name TEXT, " + "FileId INTEGER PRIMARY KEY, " + "Title TEXT)"_L1, + "CREATE TABLE IndexTable (" + "Id INTEGER PRIMARY KEY, " + "Name TEXT, " + "Identifier TEXT, " + "NamespaceId INTEGER, " + "FileId INTEGER, " + "Anchor TEXT)"_L1, + "CREATE TABLE ContentsTable (" + "Id INTEGER PRIMARY KEY, " + "NamespaceId INTEGER, " + "Data BLOB)"_L1, + "CREATE TABLE FileFilterTable (" + "FilterAttributeId INTEGER, " + "FileId INTEGER)"_L1, + "CREATE TABLE IndexFilterTable (" + "FilterAttributeId INTEGER, " + "IndexId INTEGER)"_L1, + "CREATE TABLE ContentsFilterTable (" + "FilterAttributeId INTEGER, " + "ContentsId INTEGER )"_L1, + "CREATE TABLE FileAttributeSetTable (" + "NamespaceId INTEGER, " + "FilterAttributeSetId INTEGER, " + "FilterAttributeId INTEGER)"_L1, + "CREATE TABLE OptimizedFilterTable (" + "NamespaceId INTEGER, " + "FilterAttributeId INTEGER)"_L1, + "CREATE TABLE TimeStampTable (" + "NamespaceId INTEGER, " + "FolderId INTEGER, " + "FilePath TEXT, " + "Size INTEGER, " + "TimeStamp TEXT)"_L1, + "CREATE TABLE VersionTable (" + "NamespaceId INTEGER, " + "Version TEXT)"_L1, + "CREATE TABLE Filter (" + "FilterId INTEGER PRIMARY KEY, " + "Name TEXT)"_L1, + "CREATE TABLE ComponentTable (" + "ComponentId INTEGER PRIMARY KEY, " + "Name TEXT)"_L1, + "CREATE TABLE ComponentMapping (" + "ComponentId INTEGER, " + "NamespaceId INTEGER)"_L1, + "CREATE TABLE ComponentFilter (" + "ComponentName TEXT, " + "FilterId INTEGER)"_L1, + "CREATE TABLE VersionFilter (" + "Version TEXT, " + "FilterId INTEGER)"_L1 + }; + + for (const QString &q : tables) { + if (!query->exec(q)) + return false; + } + return true; +} + +QStringList QHelpCollectionHandler::customFilters() const +{ + QStringList list; + if (m_query) { + m_query->exec("SELECT Name FROM FilterNameTable"_L1); + while (m_query->next()) + list.append(m_query->value(0).toString()); + } + return list; +} + +QStringList QHelpCollectionHandler::filters() const +{ + QStringList list; + if (m_query) { + m_query->exec("SELECT Name FROM Filter ORDER BY Name"_L1); + while (m_query->next()) + list.append(m_query->value(0).toString()); + } + return list; +} + +QStringList QHelpCollectionHandler::availableComponents() const +{ + QStringList list; + if (m_query) { + m_query->exec("SELECT DISTINCT Name FROM ComponentTable ORDER BY Name"_L1); + while (m_query->next()) + list.append(m_query->value(0).toString()); + } + return list; +} + +QList QHelpCollectionHandler::availableVersions() const +{ + QList list; + if (m_query) { + m_query->exec("SELECT DISTINCT Version FROM VersionTable ORDER BY Version"_L1); + while (m_query->next()) + list.append(QVersionNumber::fromString(m_query->value(0).toString())); + } + return list; +} + +QMap QHelpCollectionHandler::namespaceToComponent() const +{ + QMap result; + if (m_query) { + m_query->exec( + "SELECT " + "NamespaceTable.Name, " + "ComponentTable.Name " + "FROM NamespaceTable, " + "ComponentTable, " + "ComponentMapping " + "WHERE NamespaceTable.Id = ComponentMapping.NamespaceId " + "AND ComponentMapping.ComponentId = ComponentTable.ComponentId"_L1); + while (m_query->next()) + result.insert(m_query->value(0).toString(), m_query->value(1).toString()); + } + return result; +} + +QMap QHelpCollectionHandler::namespaceToVersion() const +{ + QMap result; + if (m_query) { + m_query->exec( + "SELECT " + "NamespaceTable.Name, " + "VersionTable.Version " + "FROM NamespaceTable, " + "VersionTable " + "WHERE NamespaceTable.Id = VersionTable.NamespaceId"_L1); + while (m_query->next()) { + result.insert(m_query->value(0).toString(), + QVersionNumber::fromString(m_query->value(1).toString())); + } + } + return result; +} + +QHelpFilterData QHelpCollectionHandler::filterData(const QString &filterName) const +{ + QStringList components; + QList versions; + if (m_query) { + m_query->prepare( + "SELECT ComponentFilter.ComponentName " + "FROM ComponentFilter, Filter " + "WHERE ComponentFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ? " + "ORDER BY ComponentFilter.ComponentName"_L1); + m_query->bindValue(0, filterName); + m_query->exec(); + while (m_query->next()) + components.append(m_query->value(0).toString()); + + m_query->prepare( + "SELECT VersionFilter.Version " + "FROM VersionFilter, Filter " + "WHERE VersionFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ? " + "ORDER BY VersionFilter.Version"_L1); + m_query->bindValue(0, filterName); + m_query->exec(); + while (m_query->next()) + versions.append(QVersionNumber::fromString(m_query->value(0).toString())); + + } + QHelpFilterData data; + data.setComponents(components); + data.setVersions(versions); + return data; +} + +bool QHelpCollectionHandler::setFilterData(const QString &filterName, + const QHelpFilterData &filterData) +{ + if (!removeFilter(filterName)) + return false; + + m_query->prepare("INSERT INTO Filter VALUES (NULL, ?)"_L1); + m_query->bindValue(0, filterName); + if (!m_query->exec()) + return false; + + const int filterId = m_query->lastInsertId().toInt(); + + QVariantList componentList; + QVariantList versionList; + QVariantList filterIdList; + + for (const QString &component : filterData.components()) { + componentList.append(component); + filterIdList.append(filterId); + } + + m_query->prepare("INSERT INTO ComponentFilter VALUES (?, ?)"_L1); + m_query->addBindValue(componentList); + m_query->addBindValue(filterIdList); + if (!m_query->execBatch()) + return false; + + filterIdList.clear(); + for (const QVersionNumber &version : filterData.versions()) { + versionList.append(version.isNull() ? QString() : version.toString()); + filterIdList.append(filterId); + } + + m_query->prepare("INSERT INTO VersionFilter VALUES (?, ?)"_L1); + m_query->addBindValue(versionList); + m_query->addBindValue(filterIdList); + if (!m_query->execBatch()) + return false; + + return true; +} + +bool QHelpCollectionHandler::removeFilter(const QString &filterName) +{ + m_query->prepare("SELECT FilterId FROM Filter WHERE Name = ?"_L1); + m_query->bindValue(0, filterName); + if (!m_query->exec()) + return false; + + if (!m_query->next()) + return true; // no filter in DB + + const int filterId = m_query->value(0).toInt(); + + m_query->prepare("DELETE FROM Filter WHERE Filter.Name = ?"_L1); + m_query->bindValue(0, filterName); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM ComponentFilter WHERE ComponentFilter.FilterId = ?"_L1); + m_query->bindValue(0, filterId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM VersionFilter WHERE VersionFilter.FilterId = ?"_L1); + m_query->bindValue(0, filterId); + if (!m_query->exec()) + return false; + + return true; +} + +bool QHelpCollectionHandler::removeCustomFilter(const QString &filterName) +{ + if (!isDBOpened() || filterName.isEmpty()) + return false; + + int filterNameId = -1; + m_query->prepare("SELECT Id FROM FilterNameTable WHERE Name=?"_L1); + m_query->bindValue(0, filterName); + m_query->exec(); + if (m_query->next()) + filterNameId = m_query->value(0).toInt(); + + if (filterNameId < 0) { + emit error(tr("Unknown filter \"%1\".").arg(filterName)); + return false; + } + + m_query->prepare("DELETE FROM FilterTable WHERE NameId=?"_L1); + m_query->bindValue(0, filterNameId); + m_query->exec(); + + m_query->prepare("DELETE FROM FilterNameTable WHERE Id=?"_L1); + m_query->bindValue(0, filterNameId); + m_query->exec(); + + return true; +} + +bool QHelpCollectionHandler::addCustomFilter(const QString &filterName, + const QStringList &attributes) +{ + if (!isDBOpened() || filterName.isEmpty()) + return false; + + int nameId = -1; + m_query->prepare("SELECT Id FROM FilterNameTable WHERE Name=?"_L1); + m_query->bindValue(0, filterName); + m_query->exec(); + if (m_query->next()) + nameId = m_query->value(0).toInt(); + + m_query->exec("SELECT Id, Name FROM FilterAttributeTable"_L1); + QStringList idsToInsert = attributes; + QMap attributeMap; + while (m_query->next()) { + // all old attributes + const QString attributeName = m_query->value(1).toString(); + attributeMap.insert(attributeName, m_query->value(0).toInt()); + idsToInsert.removeAll(attributeName); + } + + for (const QString &id : std::as_const(idsToInsert)) { + m_query->prepare("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1); + m_query->bindValue(0, id); + m_query->exec(); + attributeMap.insert(id, m_query->lastInsertId().toInt()); + } + + if (nameId < 0) { + m_query->prepare("INSERT INTO FilterNameTable VALUES(NULL, ?)"_L1); + m_query->bindValue(0, filterName); + if (m_query->exec()) + nameId = m_query->lastInsertId().toInt(); + } + + if (nameId < 0) { + emit error(tr("Cannot register filter %1.").arg(filterName)); + return false; + } + + m_query->prepare("DELETE FROM FilterTable WHERE NameId=?"_L1); + m_query->bindValue(0, nameId); + m_query->exec(); + + for (const QString &att : attributes) { + m_query->prepare("INSERT INTO FilterTable VALUES(?, ?)"_L1); + m_query->bindValue(0, nameId); + m_query->bindValue(1, attributeMap[att]); + if (!m_query->exec()) + return false; + } + return true; +} + +QHelpCollectionHandler::FileInfo QHelpCollectionHandler::registeredDocumentation( + const QString &namespaceName) const +{ + FileInfo fileInfo; + + if (!m_query) + return fileInfo; + + m_query->prepare( + "SELECT " + "NamespaceTable.Name, " + "NamespaceTable.FilePath, " + "FolderTable.Name " + "FROM " + "NamespaceTable, " + "FolderTable " + "WHERE NamespaceTable.Id = FolderTable.NamespaceId " + "AND NamespaceTable.Name = ? LIMIT 1"_L1); + m_query->bindValue(0, namespaceName); + if (!m_query->exec() || !m_query->next()) + return fileInfo; + + fileInfo.namespaceName = m_query->value(0).toString(); + fileInfo.fileName = m_query->value(1).toString(); + fileInfo.folderName = m_query->value(2).toString(); + + m_query->clear(); + + return fileInfo; +} + +QHelpCollectionHandler::FileInfoList QHelpCollectionHandler::registeredDocumentations() const +{ + FileInfoList list; + if (!m_query) + return list; + + m_query->exec( + "SELECT " + "NamespaceTable.Name, " + "NamespaceTable.FilePath, " + "FolderTable.Name " + "FROM " + "NamespaceTable, " + "FolderTable " + "WHERE NamespaceTable.Id = FolderTable.NamespaceId"_L1); + + while (m_query->next()) { + FileInfo fileInfo; + fileInfo.namespaceName = m_query->value(0).toString(); + fileInfo.fileName = m_query->value(1).toString(); + fileInfo.folderName = m_query->value(2).toString(); + list.append(fileInfo); + } + + return list; +} + +bool QHelpCollectionHandler::registerDocumentation(const QString &fileName) +{ + if (!isDBOpened()) + return false; + + QHelpDBReader reader(fileName, QHelpGlobal::uniquifyConnectionName( + "QHelpCollectionHandler"_L1, this), nullptr); + if (!reader.init()) { + emit error(tr("Cannot open documentation file %1.").arg(fileName)); + return false; + } + + const QString &ns = reader.namespaceName(); + if (ns.isEmpty()) { + emit error(tr("Invalid documentation file \"%1\".").arg(fileName)); + return false; + } + + const int nsId = registerNamespace(ns, fileName); + if (nsId < 1) + return false; + + const int vfId = registerVirtualFolder(reader.virtualFolder(), nsId); + if (vfId < 1) + return false; + + registerVersion(reader.version(), nsId); + registerFilterAttributes(reader.filterAttributeSets(), nsId); // qset, what happens when removing documentation? + for (const QString &filterName : reader.customFilters()) + addCustomFilter(filterName, reader.filterAttributes(filterName)); + + if (!registerIndexTable(reader.indexTable(), nsId, vfId, registeredDocumentation(ns).fileName)) + return false; + + return true; +} + +bool QHelpCollectionHandler::unregisterDocumentation(const QString &namespaceName) +{ + if (!isDBOpened()) + return false; + + m_query->prepare("SELECT Id FROM NamespaceTable WHERE Name = ?"_L1); + m_query->bindValue(0, namespaceName); + m_query->exec(); + + if (!m_query->next()) { + emit error(tr("The namespace %1 was not registered.").arg(namespaceName)); + return false; + } + + const int nsId = m_query->value(0).toInt(); + + m_query->prepare("DELETE FROM NamespaceTable WHERE Id = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("SELECT Id FROM FolderTable WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + m_query->exec(); + + if (!m_query->next()) { + emit error(tr("The namespace %1 was not registered.").arg(namespaceName)); + return false; + } + + const int vfId = m_query->value(0).toInt(); + + m_query->prepare("DELETE FROM NamespaceTable WHERE Id = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM FolderTable WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + if (!unregisterIndexTable(nsId, vfId)) + return false; + + scheduleVacuum(); + + return true; +} + +static QHelpCollectionHandler::FileInfo extractFileInfo(const QUrl &url) +{ + QHelpCollectionHandler::FileInfo fileInfo; + + if (!url.isValid() || url.toString().count(u'/') < 4 + || url.scheme() != "qthelp"_L1) { + return fileInfo; + } + + fileInfo.namespaceName = url.authority(); + fileInfo.fileName = url.path(); + if (fileInfo.fileName.startsWith(u'/')) + fileInfo.fileName = fileInfo.fileName.mid(1); + fileInfo.folderName = fileInfo.fileName.mid(0, fileInfo.fileName.indexOf(u'/', 1)); + fileInfo.fileName.remove(0, fileInfo.folderName.size() + 1); + + return fileInfo; +} + +bool QHelpCollectionHandler::fileExists(const QUrl &url) const +{ + if (!isDBOpened()) + return false; + + const FileInfo fileInfo = extractFileInfo(url); + if (fileInfo.namespaceName.isEmpty()) + return false; + + m_query->prepare( + "SELECT COUNT (DISTINCT NamespaceTable.Id) " + "FROM " + "FileNameTable, " + "NamespaceTable, " + "FolderTable " + "WHERE FolderTable.Name = ? " + "AND FileNameTable.Name = ? " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1); + m_query->bindValue(0, fileInfo.folderName); + m_query->bindValue(1, fileInfo.fileName); + if (!m_query->exec() || !m_query->next()) + return false; + + const int count = m_query->value(0).toInt(); + m_query->clear(); + + return count; +} + +static QString prepareFilterQuery(const QString &filterName) +{ + if (filterName.isEmpty()) + return {}; + + return " AND EXISTS(SELECT * FROM Filter WHERE Filter.Name = ?) " + "AND (" + "(NOT EXISTS(" // 1. filter by component + "SELECT * FROM " + "ComponentFilter, " + "Filter " + "WHERE ComponentFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ?) " + "OR NamespaceTable.Id IN (" + "SELECT " + "NamespaceTable.Id " + "FROM " + "NamespaceTable, " + "ComponentTable, " + "ComponentMapping, " + "ComponentFilter, " + "Filter " + "WHERE ComponentMapping.NamespaceId = NamespaceTable.Id " + "AND ComponentTable.ComponentId = ComponentMapping.ComponentId " + "AND ((ComponentTable.Name = ComponentFilter.ComponentName) " + "OR (ComponentTable.Name IS NULL AND ComponentFilter.ComponentName IS NULL)) " + "AND ComponentFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ?))" + " AND " + "(NOT EXISTS(" // 2. filter by version + "SELECT * FROM " + "VersionFilter, " + "Filter " + "WHERE VersionFilter.FilterId = Filter.FilterId " + "AND Filter.Name = ?) " + "OR NamespaceTable.Id IN (" + "SELECT " + "NamespaceTable.Id " + "FROM " + "NamespaceTable, " + "VersionFilter, " + "VersionTable, " + "Filter " + "WHERE VersionFilter.FilterId = Filter.FilterId " + "AND ((VersionFilter.Version = VersionTable.Version) " + "OR (VersionFilter.Version IS NULL AND VersionTable.Version IS NULL)) " + "AND VersionTable.NamespaceId = NamespaceTable.Id " + "AND Filter.Name = ?))" + ")"_L1; +} + +static void bindFilterQuery(QSqlQuery *query, int bindStart, const QString &filterName) +{ + if (filterName.isEmpty()) + return; + + query->bindValue(bindStart, filterName); + query->bindValue(bindStart + 1, filterName); + query->bindValue(bindStart + 2, filterName); + query->bindValue(bindStart + 3, filterName); + query->bindValue(bindStart + 4, filterName); +} + +static QString prepareFilterQuery(int attributesCount, + const QString &idTableName, + const QString &idColumnName, + const QString &filterTableName, + const QString &filterColumnName) +{ + if (!attributesCount) + return {}; + + QString filterQuery = " AND (%1.%2 IN ("_L1.arg(idTableName, idColumnName); + + const QString filterQueryTemplate = + "SELECT %1.%2 " + "FROM %1, FilterAttributeTable " + "WHERE %1.FilterAttributeId = FilterAttributeTable.Id " + "AND FilterAttributeTable.Name = ?"_L1.arg(filterTableName, filterColumnName); + + for (int i = 0; i < attributesCount; ++i) { + if (i > 0) + filterQuery.append(" INTERSECT "_L1); + filterQuery.append(filterQueryTemplate); + } + + filterQuery.append(") OR NamespaceTable.Id IN ("_L1); + + const QString optimizedFilterQueryTemplate = + "SELECT OptimizedFilterTable.NamespaceId " + "FROM OptimizedFilterTable, FilterAttributeTable " + "WHERE OptimizedFilterTable.FilterAttributeId = FilterAttributeTable.Id " + "AND FilterAttributeTable.Name = ?"_L1; + + for (int i = 0; i < attributesCount; ++i) { + if (i > 0) + filterQuery.append(" INTERSECT "_L1); + filterQuery.append(optimizedFilterQueryTemplate); + } + + filterQuery.append("))"_L1); + + return filterQuery; +} + +static void bindFilterQuery(QSqlQuery *query, int startingBindPos, const QStringList &filterAttributes) +{ + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < filterAttributes.size(); j++) { + query->bindValue(i * filterAttributes.size() + j + startingBindPos, + filterAttributes.at(j)); + } + } +} + +QString QHelpCollectionHandler::namespaceForFile(const QUrl &url, + const QStringList &filterAttributes) const +{ + if (!isDBOpened()) + return {}; + + const FileInfo fileInfo = extractFileInfo(url); + if (fileInfo.namespaceName.isEmpty()) + return {}; + + const QString filterlessQuery = + "SELECT DISTINCT " + "NamespaceTable.Name " + "FROM " + "FileNameTable, " + "NamespaceTable, " + "FolderTable " + "WHERE FolderTable.Name = ? " + "AND FileNameTable.Name = ? " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.size(), "FileNameTable"_L1, "FileId"_L1, + "FileFilterTable"_L1, "FileId"_L1); + + m_query->prepare(filterQuery); + m_query->bindValue(0, fileInfo.folderName); + m_query->bindValue(1, fileInfo.fileName); + bindFilterQuery(m_query.get(), 2, filterAttributes); + + if (!m_query->exec()) + return {}; + + QStringList namespaceList; + while (m_query->next()) + namespaceList.append(m_query->value(0).toString()); + + if (namespaceList.isEmpty()) + return {}; + + if (namespaceList.contains(fileInfo.namespaceName)) + return fileInfo.namespaceName; + + const QString originalVersion = namespaceVersion(fileInfo.namespaceName); + + for (const QString &ns : namespaceList) { + const QString nsVersion = namespaceVersion(ns); + if (originalVersion == nsVersion) + return ns; + } + + // TODO: still, we may like to return the ns for the highest available version + return namespaceList.first(); +} + +QString QHelpCollectionHandler::namespaceForFile(const QUrl &url, + const QString &filterName) const +{ + if (!isDBOpened()) + return {}; + + const FileInfo fileInfo = extractFileInfo(url); + if (fileInfo.namespaceName.isEmpty()) + return {}; + + const QString filterlessQuery = + "SELECT DISTINCT " + "NamespaceTable.Name " + "FROM " + "FileNameTable, " + "NamespaceTable, " + "FolderTable " + "WHERE FolderTable.Name = ? " + "AND FileNameTable.Name = ? " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id"_L1; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + m_query->bindValue(0, fileInfo.folderName); + m_query->bindValue(1, fileInfo.fileName); + bindFilterQuery(m_query.get(), 2, filterName); + + if (!m_query->exec()) + return {}; + + QStringList namespaceList; + while (m_query->next()) + namespaceList.append(m_query->value(0).toString()); + + if (namespaceList.isEmpty()) + return {}; + + if (namespaceList.contains(fileInfo.namespaceName)) + return fileInfo.namespaceName; + + const QString originalVersion = namespaceVersion(fileInfo.namespaceName); + + for (const QString &ns : namespaceList) { + const QString nsVersion = namespaceVersion(ns); + if (originalVersion == nsVersion) + return ns; + } + + // TODO: still, we may like to return the ns for the highest available version + return namespaceList.first(); +} + +QStringList QHelpCollectionHandler::files(const QString &namespaceName, + const QStringList &filterAttributes, + const QString &extensionFilter) const +{ + if (!isDBOpened()) + return {}; + + const QString extensionQuery = extensionFilter.isEmpty() + ? QString() : " AND FileNameTable.Name LIKE ?"_L1; + const QString filterlessQuery = + "SELECT " + "FolderTable.Name, " + "FileNameTable.Name " + "FROM " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Name = ?"_L1 + extensionQuery; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.size(), "FileNameTable"_L1, "FileId"_L1, + "FileFilterTable"_L1, "FileId"_L1); + + m_query->prepare(filterQuery); + m_query->bindValue(0, namespaceName); + int bindCount = 1; + if (!extensionFilter.isEmpty()) { + m_query->bindValue(bindCount, "%.%1"_L1.arg(extensionFilter)); + ++bindCount; + } + bindFilterQuery(m_query.get(), bindCount, filterAttributes); + + if (!m_query->exec()) + return {}; + + QStringList fileNames; + while (m_query->next()) + fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString()); + return fileNames; +} + +QStringList QHelpCollectionHandler::files(const QString &namespaceName, + const QString &filterName, + const QString &extensionFilter) const +{ + if (!isDBOpened()) + return {}; + + const QString extensionQuery = extensionFilter.isEmpty() + ? QString() : " AND FileNameTable.Name LIKE ?"_L1; + const QString filterlessQuery = + "SELECT " + "FolderTable.Name, " + "FileNameTable.Name " + "FROM " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Name = ?"_L1 + extensionQuery; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + m_query->bindValue(0, namespaceName); + int bindCount = 1; + if (!extensionFilter.isEmpty()) { + m_query->bindValue(bindCount, "%.%1"_L1.arg(extensionFilter)); + ++bindCount; + } + + bindFilterQuery(m_query.get(), bindCount, filterName); + + if (!m_query->exec()) + return{}; + + QStringList fileNames; + while (m_query->next()) + fileNames.append(m_query->value(0).toString() + u'/' + m_query->value(1).toString()); + return fileNames; +} + +QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QStringList &filterAttributes) const +{ + if (!isDBOpened()) + return {}; + + const QString namespaceName = namespaceForFile(url, filterAttributes); + if (namespaceName.isEmpty()) + return {}; + + QUrl result = url; + result.setAuthority(namespaceName); + return result; +} + +QUrl QHelpCollectionHandler::findFile(const QUrl &url, const QString &filterName) const +{ + if (!isDBOpened()) + return {}; + + const QString namespaceName = namespaceForFile(url, filterName); + if (namespaceName.isEmpty()) + return {}; + + QUrl result = url; + result.setAuthority(namespaceName); + return result; +} + +QByteArray QHelpCollectionHandler::fileData(const QUrl &url) const +{ + if (!isDBOpened()) + return {}; + + const QString namespaceName = namespaceForFile(url, QString()); + if (namespaceName.isEmpty()) + return {}; + + const FileInfo fileInfo = extractFileInfo(url); + + const FileInfo docInfo = registeredDocumentation(namespaceName); + const QString absFileName = absoluteDocPath(docInfo.fileName); + + QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName( + docInfo.fileName, const_cast(this)), nullptr); + if (!reader.init()) + return {}; + + return reader.fileData(fileInfo.folderName, fileInfo.fileName); +} + +QStringList QHelpCollectionHandler::indicesForFilter(const QStringList &filterAttributes) const +{ + QStringList indices; + + if (!isDBOpened()) + return indices; + + const QString filterlessQuery = + "SELECT DISTINCT " + "IndexTable.Name " + "FROM " + "IndexTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE IndexTable.FileId = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.size(), "IndexTable"_L1, "Id"_L1, + "IndexFilterTable"_L1, "IndexId"_L1) + + " ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1; + // this doesn't work: ASC COLLATE NOCASE + + m_query->prepare(filterQuery); + bindFilterQuery(m_query.get(), 0, filterAttributes); + + m_query->exec(); + + while (m_query->next()) + indices.append(m_query->value(0).toString()); + + return indices; +} + +QStringList QHelpCollectionHandler::indicesForFilter(const QString &filterName) const +{ + QStringList indices; + + if (!isDBOpened()) + return indices; + + const QString filterlessQuery = + "SELECT DISTINCT " + "IndexTable.Name " + "FROM " + "IndexTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE IndexTable.FileId = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND IndexTable.NamespaceId = NamespaceTable.Id"_L1; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName) + + " ORDER BY LOWER(IndexTable.Name), IndexTable.Name"_L1; + + m_query->prepare(filterQuery); + bindFilterQuery(m_query.get(), 0, filterName); + + m_query->exec(); + + while (m_query->next()) + indices.append(m_query->value(0).toString()); + + return indices; +} + +static QString getTitle(const QByteArray &contents) +{ + if (!contents.size()) + return {}; + + int depth = 0; + QString link; + QString title; + + QDataStream s(contents); + s >> depth; + s >> link; + s >> title; + + return title; +} + +QList QHelpCollectionHandler::contentsForFilter( + const QStringList &filterAttributes) const +{ + if (!isDBOpened()) + return {}; + + const QString filterlessQuery = + "SELECT DISTINCT " + "NamespaceTable.Name, " + "FolderTable.Name, " + "ContentsTable.Data, " + "VersionTable.Version " + "FROM " + "FolderTable, " + "NamespaceTable, " + "ContentsTable, " + "VersionTable " + "WHERE ContentsTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Id = FolderTable.NamespaceId " + "AND ContentsTable.NamespaceId = NamespaceTable.Id " + "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.size(), "ContentsTable"_L1, "Id"_L1, + "ContentsFilterTable"_L1, "ContentsId"_L1); + + m_query->prepare(filterQuery); + bindFilterQuery(m_query.get(), 0, filterAttributes); + + m_query->exec(); + + QMap> contentsMap; + + while (m_query->next()) { + const QString namespaceName = m_query->value(0).toString(); + const QByteArray contents = m_query->value(2).toByteArray(); + const QString versionString = m_query->value(3).toString(); + + const QString title = getTitle(contents); + const QVersionNumber version = QVersionNumber::fromString(versionString); + // get existing or insert a new one otherwise + ContentsData &contentsData = contentsMap[title][version]; + contentsData.namespaceName = namespaceName; + contentsData.folderName = m_query->value(1).toString(); + contentsData.contentsList.append(contents); + } + + QList result; + for (const auto &versionContents : std::as_const(contentsMap)) { + // insert items in the reverse order of version number + const auto itBegin = versionContents.constBegin(); + auto it = versionContents.constEnd(); + while (it != itBegin) { + --it; + result.append(it.value()); + } + } + return result; +} + +QList QHelpCollectionHandler::contentsForFilter(const QString &filterName) const +{ + if (!isDBOpened()) + return {}; + + const QString filterlessQuery = + "SELECT DISTINCT " + "NamespaceTable.Name, " + "FolderTable.Name, " + "ContentsTable.Data, " + "VersionTable.Version " + "FROM " + "FolderTable, " + "NamespaceTable, " + "ContentsTable, " + "VersionTable " + "WHERE ContentsTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Id = FolderTable.NamespaceId " + "AND ContentsTable.NamespaceId = NamespaceTable.Id " + "AND VersionTable.NamespaceId = NamespaceTable.Id"_L1; + + const QString filterQuery = filterlessQuery + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + bindFilterQuery(m_query.get(), 0, filterName); + + m_query->exec(); + + QMap> contentsMap; + + while (m_query->next()) { + const QString namespaceName = m_query->value(0).toString(); + const QByteArray contents = m_query->value(2).toByteArray(); + const QString versionString = m_query->value(3).toString(); + + const QString title = getTitle(contents); + const QVersionNumber version = QVersionNumber::fromString(versionString); + // get existing or insert a new one otherwise + ContentsData &contentsData = contentsMap[title][version]; + contentsData.namespaceName = namespaceName; + contentsData.folderName = m_query->value(1).toString(); + contentsData.contentsList.append(contents); + } + + QList result; + for (const auto &versionContents : std::as_const(contentsMap)) { + // insert items in the reverse order of version number + const auto itBegin = versionContents.constBegin(); + auto it = versionContents.constEnd(); + while (it != itBegin) { + --it; + result.append(it.value()); + } + } + return result; +} + +bool QHelpCollectionHandler::removeCustomValue(const QString &key) +{ + if (!isDBOpened()) + return false; + + m_query->prepare("DELETE FROM SettingsTable WHERE Key=?"_L1); + m_query->bindValue(0, key); + return m_query->exec(); +} + +QVariant QHelpCollectionHandler::customValue(const QString &key, + const QVariant &defaultValue) const +{ + if (!m_query) + return defaultValue; + + m_query->prepare("SELECT COUNT(Key) FROM SettingsTable WHERE Key=?"_L1); + m_query->bindValue(0, key); + if (!m_query->exec() || !m_query->next() || !m_query->value(0).toInt()) { + m_query->clear(); + return defaultValue; + } + + m_query->clear(); + m_query->prepare("SELECT Value FROM SettingsTable WHERE Key=?"_L1); + m_query->bindValue(0, key); + if (m_query->exec() && m_query->next()) { + const QVariant &value = m_query->value(0); + m_query->clear(); + return value; + } + return defaultValue; +} + +bool QHelpCollectionHandler::setCustomValue(const QString &key, + const QVariant &value) +{ + if (!isDBOpened()) + return false; + + m_query->prepare("SELECT Value FROM SettingsTable WHERE Key=?"_L1); + m_query->bindValue(0, key); + m_query->exec(); + if (m_query->next()) { + m_query->prepare("UPDATE SettingsTable SET Value=? where Key=?"_L1); + m_query->bindValue(0, value); + m_query->bindValue(1, key); + } else { + m_query->prepare("INSERT INTO SettingsTable VALUES(?, ?)"_L1); + m_query->bindValue(0, key); + m_query->bindValue(1, value); + } + return m_query->exec(); +} + +bool QHelpCollectionHandler::registerFilterAttributes(const QList &attributeSets, + int nsId) +{ + if (!isDBOpened()) + return false; + + m_query->exec("SELECT Name FROM FilterAttributeTable"_L1); + QSet atts; + while (m_query->next()) + atts.insert(m_query->value(0).toString()); + + for (const QStringList &attributeSet : attributeSets) { + for (const QString &attribute : attributeSet) { + if (!atts.contains(attribute)) { + m_query->prepare("INSERT INTO FilterAttributeTable VALUES(NULL, ?)"_L1); + m_query->bindValue(0, attribute); + m_query->exec(); + } + } + } + return registerFileAttributeSets(attributeSets, nsId); +} + +bool QHelpCollectionHandler::registerFileAttributeSets(const QList &attributeSets, + int nsId) +{ + if (!isDBOpened()) + return false; + + if (attributeSets.isEmpty()) + return true; + + QVariantList nsIds; + QVariantList attributeSetIds; + QVariantList filterAttributeIds; + + if (!m_query->exec("SELECT MAX(FilterAttributeSetId) FROM FileAttributeSetTable"_L1) + || !m_query->next()) { + return false; + } + + int attributeSetId = m_query->value(0).toInt(); + + for (const QStringList &attributeSet : attributeSets) { + ++attributeSetId; + + for (const QString &attribute : attributeSet) { + m_query->prepare("SELECT Id FROM FilterAttributeTable WHERE Name=?"_L1); + m_query->bindValue(0, attribute); + + if (!m_query->exec() || !m_query->next()) + return false; + + nsIds.append(nsId); + attributeSetIds.append(attributeSetId); + filterAttributeIds.append(m_query->value(0).toInt()); + } + } + + m_query->prepare("INSERT INTO FileAttributeSetTable " + "(NamespaceId, FilterAttributeSetId, FilterAttributeId) " + "VALUES(?, ?, ?)"_L1); + m_query->addBindValue(nsIds); + m_query->addBindValue(attributeSetIds); + m_query->addBindValue(filterAttributeIds); + return m_query->execBatch(); +} + +QStringList QHelpCollectionHandler::filterAttributes() const +{ + QStringList list; + if (m_query) { + m_query->exec("SELECT Name FROM FilterAttributeTable"_L1); + while (m_query->next()) + list.append(m_query->value(0).toString()); + } + return list; +} + +QStringList QHelpCollectionHandler::filterAttributes(const QString &filterName) const +{ + QStringList list; + if (m_query) { + m_query->prepare( + "SELECT " + "FilterAttributeTable.Name " + "FROM " + "FilterAttributeTable, " + "FilterTable, " + "FilterNameTable " + "WHERE FilterAttributeTable.Id = FilterTable.FilterAttributeId " + "AND FilterTable.NameId = FilterNameTable.Id " + "AND FilterNameTable.Name=?"_L1); + m_query->bindValue(0, filterName); + m_query->exec(); + while (m_query->next()) + list.append(m_query->value(0).toString()); + } + return list; +} + +QList QHelpCollectionHandler::filterAttributeSets(const QString &namespaceName) const +{ + if (!isDBOpened()) + return {}; + + m_query->prepare( + "SELECT " + "FileAttributeSetTable.FilterAttributeSetId, " + "FilterAttributeTable.Name " + "FROM " + "FileAttributeSetTable, " + "FilterAttributeTable, " + "NamespaceTable " + "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id " + "AND FileAttributeSetTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Name = ? " + "ORDER BY FileAttributeSetTable.FilterAttributeSetId"_L1); + m_query->bindValue(0, namespaceName); + m_query->exec(); + int oldId = -1; + QList result; + while (m_query->next()) { + const int id = m_query->value(0).toInt(); + if (id != oldId) { + result.append(QStringList()); + oldId = id; + } + result.last().append(m_query->value(1).toString()); + } + + if (result.isEmpty()) + result.append(QStringList()); + return result; +} + +QString QHelpCollectionHandler::namespaceVersion(const QString &namespaceName) const +{ + if (!m_query) + return {}; + + m_query->prepare( + "SELECT " + "VersionTable.Version " + "FROM " + "NamespaceTable, " + "VersionTable " + "WHERE NamespaceTable.Name = ? " + "AND NamespaceTable.Id = VersionTable.NamespaceId"_L1); + m_query->bindValue(0, namespaceName); + if (!m_query->exec() || !m_query->next()) + return {}; + + const QString ret = m_query->value(0).toString(); + m_query->clear(); + return ret; +} + +int QHelpCollectionHandler::registerNamespace(const QString &nspace, const QString &fileName) +{ + const int errorValue = -1; + if (!m_query) + return errorValue; + + m_query->prepare("SELECT COUNT(Id) FROM NamespaceTable WHERE Name=?"_L1); + m_query->bindValue(0, nspace); + m_query->exec(); + while (m_query->next()) { + if (m_query->value(0).toInt() > 0) { + emit error(tr("Namespace %1 already exists.").arg(nspace)); + return errorValue; + } + } + + QFileInfo fi(m_collectionFile); + m_query->prepare("INSERT INTO NamespaceTable VALUES(NULL, ?, ?)"_L1); + m_query->bindValue(0, nspace); + m_query->bindValue(1, fi.absoluteDir().relativeFilePath(fileName)); + int namespaceId = errorValue; + if (m_query->exec()) { + namespaceId = m_query->lastInsertId().toInt(); + m_query->clear(); + } + if (namespaceId < 1) { + emit error(tr("Cannot register namespace \"%1\".").arg(nspace)); + return errorValue; + } + return namespaceId; +} + +int QHelpCollectionHandler::registerVirtualFolder(const QString &folderName, int namespaceId) +{ + if (!m_query) + return false; + + m_query->prepare("INSERT INTO FolderTable VALUES(NULL, ?, ?)"_L1); + m_query->bindValue(0, namespaceId); + m_query->bindValue(1, folderName); + + int virtualId = -1; + if (m_query->exec()) { + virtualId = m_query->lastInsertId().toInt(); + m_query->clear(); + } + if (virtualId < 1) { + emit error(tr("Cannot register virtual folder '%1'.").arg(folderName)); + return -1; + } + if (registerComponent(folderName, namespaceId) < 0) + return -1; + return virtualId; +} + +int QHelpCollectionHandler::registerComponent(const QString &componentName, int namespaceId) +{ + m_query->prepare("SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1); + m_query->bindValue(0, componentName); + if (!m_query->exec()) + return -1; + + if (!m_query->next()) { + m_query->prepare("INSERT INTO ComponentTable VALUES(NULL, ?)"_L1); + m_query->bindValue(0, componentName); + if (!m_query->exec()) + return -1; + + m_query->prepare("SELECT ComponentId FROM ComponentTable WHERE Name = ?"_L1); + m_query->bindValue(0, componentName); + if (!m_query->exec() || !m_query->next()) + return -1; + } + + const int componentId = m_query->value(0).toInt(); + + m_query->prepare("INSERT INTO ComponentMapping VALUES(?, ?)"_L1); + m_query->bindValue(0, componentId); + m_query->bindValue(1, namespaceId); + if (!m_query->exec()) + return -1; + + return componentId; +} + +bool QHelpCollectionHandler::registerVersion(const QString &version, int namespaceId) +{ + if (!m_query) + return false; + + m_query->prepare("INSERT INTO VersionTable (NamespaceId, Version) VALUES(?, ?)"_L1); + m_query->addBindValue(namespaceId); + m_query->addBindValue(version); + return m_query->exec(); +} + +bool QHelpCollectionHandler::registerIndexAndNamespaceFilterTables( + const QString &nameSpace, bool createDefaultVersionFilter) +{ + if (!isDBOpened()) + return false; + + m_query->prepare("SELECT Id, FilePath FROM NamespaceTable WHERE Name=?"_L1); + m_query->bindValue(0, nameSpace); + m_query->exec(); + if (!m_query->next()) + return false; + + const int nsId = m_query->value(0).toInt(); + const QString fileName = m_query->value(1).toString(); + + m_query->prepare("SELECT Id, Name FROM FolderTable WHERE NamespaceId=?"_L1); + m_query->bindValue(0, nsId); + m_query->exec(); + if (!m_query->next()) + return false; + + const int vfId = m_query->value(0).toInt(); + const QString vfName = m_query->value(1).toString(); + + const QString absFileName = absoluteDocPath(fileName); + QHelpDBReader reader(absFileName, QHelpGlobal::uniquifyConnectionName( + fileName, this), this); + if (!reader.init()) + return false; + + registerComponent(vfName, nsId); + registerVersion(reader.version(), nsId); + if (!registerFileAttributeSets(reader.filterAttributeSets(), nsId)) + return false; + + if (!registerIndexTable(reader.indexTable(), nsId, vfId, fileName)) + return false; + + if (createDefaultVersionFilter) + createVersionFilter(reader.version()); + return true; +} + +void QHelpCollectionHandler::createVersionFilter(const QString &version) +{ + if (version.isEmpty()) + return; + + const QVersionNumber versionNumber = QVersionNumber::fromString(version); + if (versionNumber.isNull()) + return; + + const QString filterName = tr("Version %1").arg(version); + if (filters().contains(filterName)) + return; + + QHelpFilterData filterData; + filterData.setVersions({versionNumber}); + setFilterData(filterName, filterData); +} + +bool QHelpCollectionHandler::registerIndexTable(const QHelpDBReader::IndexTable &indexTable, + int nsId, int vfId, const QString &fileName) +{ + Transaction transaction(m_connectionName); + + QMap filterAttributeToNewFileId; + + QVariantList fileFolderIds; + QVariantList fileNames; + QVariantList fileTitles; + const int fileSize = indexTable.fileItems.size(); + fileFolderIds.reserve(fileSize); + fileNames.reserve(fileSize); + fileTitles.reserve(fileSize); + + if (!m_query->exec("SELECT MAX(FileId) FROM FileNameTable"_L1) || !m_query->next()) + return false; + + const int maxFileId = m_query->value(0).toInt(); + + int newFileId = 0; + for (const QHelpDBReader::FileItem &item : indexTable.fileItems) { + fileFolderIds.append(vfId); + fileNames.append(item.name); + fileTitles.append(item.title); + + for (const QString &filterAttribute : item.filterAttributes) + filterAttributeToNewFileId[filterAttribute].append(maxFileId + newFileId + 1); + ++newFileId; + } + + m_query->prepare("INSERT INTO FileNameTable VALUES(?, ?, NULL, ?)"_L1); + m_query->addBindValue(fileFolderIds); + m_query->addBindValue(fileNames); + m_query->addBindValue(fileTitles); + if (!m_query->execBatch()) + return false; + + for (auto it = filterAttributeToNewFileId.cbegin(), + end = filterAttributeToNewFileId.cend(); it != end; ++it) { + const QString filterAttribute = it.key(); + m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1); + m_query->bindValue(0, filterAttribute); + if (!m_query->exec() || !m_query->next()) + return false; + + const int attributeId = m_query->value(0).toInt(); + + QVariantList attributeIds; + for (int i = 0; i < it.value().size(); i++) + attributeIds.append(attributeId); + + m_query->prepare("INSERT INTO FileFilterTable VALUES(?, ?)"_L1); + m_query->addBindValue(attributeIds); + m_query->addBindValue(it.value()); + if (!m_query->execBatch()) + return false; + } + + QMap filterAttributeToNewIndexId; + + if (!m_query->exec("SELECT MAX(Id) FROM IndexTable"_L1) || !m_query->next()) + return false; + + const int maxIndexId = m_query->value(0).toInt(); + int newIndexId = 0; + + QVariantList indexNames; + QVariantList indexIdentifiers; + QVariantList indexNamespaceIds; + QVariantList indexFileIds; + QVariantList indexAnchors; + const int indexSize = indexTable.indexItems.size(); + indexNames.reserve(indexSize); + indexIdentifiers.reserve(indexSize); + indexNamespaceIds.reserve(indexSize); + indexFileIds.reserve(indexSize); + indexAnchors.reserve(indexSize); + + for (const QHelpDBReader::IndexItem &item : indexTable.indexItems) { + indexNames.append(item.name); + indexIdentifiers.append(item.identifier); + indexNamespaceIds.append(nsId); + indexFileIds.append(maxFileId + item.fileId + 1); + indexAnchors.append(item.anchor); + + for (const QString &filterAttribute : item.filterAttributes) + filterAttributeToNewIndexId[filterAttribute].append(maxIndexId + newIndexId + 1); + ++newIndexId; + } + + m_query->prepare("INSERT INTO IndexTable VALUES(NULL, ?, ?, ?, ?, ?)"_L1); + m_query->addBindValue(indexNames); + m_query->addBindValue(indexIdentifiers); + m_query->addBindValue(indexNamespaceIds); + m_query->addBindValue(indexFileIds); + m_query->addBindValue(indexAnchors); + if (!m_query->execBatch()) + return false; + + for (auto it = filterAttributeToNewIndexId.cbegin(), + end = filterAttributeToNewIndexId.cend(); it != end; ++it) { + const QString filterAttribute = it.key(); + m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1); + m_query->bindValue(0, filterAttribute); + if (!m_query->exec() || !m_query->next()) + return false; + + const int attributeId = m_query->value(0).toInt(); + + QVariantList attributeIds; + for (int i = 0; i < it.value().size(); i++) + attributeIds.append(attributeId); + + m_query->prepare("INSERT INTO IndexFilterTable VALUES(?, ?)"_L1); + m_query->addBindValue(attributeIds); + m_query->addBindValue(it.value()); + if (!m_query->execBatch()) + return false; + } + + QMap filterAttributeToNewContentsId; + + QVariantList contentsNsIds; + QVariantList contentsData; + const int contentsSize = indexTable.contentsItems.size(); + contentsNsIds.reserve(contentsSize); + contentsData.reserve(contentsSize); + + if (!m_query->exec("SELECT MAX(Id) FROM ContentsTable"_L1) || !m_query->next()) + return false; + + const int maxContentsId = m_query->value(0).toInt(); + + int newContentsId = 0; + for (const QHelpDBReader::ContentsItem &item : indexTable.contentsItems) { + contentsNsIds.append(nsId); + contentsData.append(item.data); + + for (const QString &filterAttribute : item.filterAttributes) { + filterAttributeToNewContentsId[filterAttribute] + .append(maxContentsId + newContentsId + 1); + } + ++newContentsId; + } + + m_query->prepare("INSERT INTO ContentsTable VALUES(NULL, ?, ?)"_L1); + m_query->addBindValue(contentsNsIds); + m_query->addBindValue(contentsData); + if (!m_query->execBatch()) + return false; + + for (auto it = filterAttributeToNewContentsId.cbegin(), + end = filterAttributeToNewContentsId.cend(); it != end; ++it) { + const QString filterAttribute = it.key(); + m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1); + m_query->bindValue(0, filterAttribute); + if (!m_query->exec() || !m_query->next()) + return false; + + const int attributeId = m_query->value(0).toInt(); + + QVariantList attributeIds; + for (int i = 0; i < it.value().size(); i++) + attributeIds.append(attributeId); + + m_query->prepare("INSERT INTO ContentsFilterTable VALUES(?, ?)"_L1); + m_query->addBindValue(attributeIds); + m_query->addBindValue(it.value()); + if (!m_query->execBatch()) + return false; + } + + QVariantList filterNsIds; + QVariantList filterAttributeIds; + for (const QString &filterAttribute : indexTable.usedFilterAttributes) { + filterNsIds.append(nsId); + + m_query->prepare("SELECT Id From FilterAttributeTable WHERE Name = ?"_L1); + m_query->bindValue(0, filterAttribute); + if (!m_query->exec() || !m_query->next()) + return false; + + filterAttributeIds.append(m_query->value(0).toInt()); + } + + m_query->prepare("INSERT INTO OptimizedFilterTable " + "(NamespaceId, FilterAttributeId) VALUES(?, ?)"_L1); + m_query->addBindValue(filterNsIds); + m_query->addBindValue(filterAttributeIds); + if (!m_query->execBatch()) + return false; + + m_query->prepare("INSERT INTO TimeStampTable " + "(NamespaceId, FolderId, FilePath, Size, TimeStamp) " + "VALUES(?, ?, ?, ?, ?)"_L1); + m_query->addBindValue(nsId); + m_query->addBindValue(vfId); + m_query->addBindValue(fileName); + const QFileInfo fi(absoluteDocPath(fileName)); + m_query->addBindValue(fi.size()); + QDateTime lastModified = fi.lastModified(QTimeZone::UTC); + if (qEnvironmentVariableIsSet("SOURCE_DATE_EPOCH")) { + const QString sourceDateEpochStr = qEnvironmentVariable("SOURCE_DATE_EPOCH"); + bool ok; + const qlonglong sourceDateEpoch = sourceDateEpochStr.toLongLong(&ok); + if (ok && sourceDateEpoch < lastModified.toSecsSinceEpoch()) + lastModified.setSecsSinceEpoch(sourceDateEpoch); + } + m_query->addBindValue(lastModified); + if (!m_query->exec()) + return false; + + transaction.commit(); + return true; +} + +bool QHelpCollectionHandler::unregisterIndexTable(int nsId, int vfId) +{ + m_query->prepare("DELETE FROM IndexFilterTable WHERE IndexId IN " + "(SELECT Id FROM IndexTable WHERE NamespaceId = ?)"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM IndexTable WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM FileFilterTable WHERE FileId IN " + "(SELECT FileId FROM FileNameTable WHERE FolderId = ?)"_L1); + m_query->bindValue(0, vfId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM FileNameTable WHERE FolderId = ?"_L1); + m_query->bindValue(0, vfId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM ContentsFilterTable WHERE ContentsId IN " + "(SELECT Id FROM ContentsTable WHERE NamespaceId = ?)"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM ContentsTable WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM FileAttributeSetTable WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM OptimizedFilterTable WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM TimeStampTable WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("DELETE FROM VersionTable WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("SELECT ComponentId FROM ComponentMapping WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + if (!m_query->next()) + return false; + + const int componentId = m_query->value(0).toInt(); + + m_query->prepare("DELETE FROM ComponentMapping WHERE NamespaceId = ?"_L1); + m_query->bindValue(0, nsId); + if (!m_query->exec()) + return false; + + m_query->prepare("SELECT ComponentId FROM ComponentMapping WHERE ComponentId = ?"_L1); + m_query->bindValue(0, componentId); + if (!m_query->exec()) + return false; + + if (!m_query->next()) { // no more namespaces refer to the componentId + m_query->prepare("DELETE FROM ComponentTable WHERE ComponentId = ?"_L1); + m_query->bindValue(0, componentId); + if (!m_query->exec()) + return false; + } + + return true; +} + +QUrl QHelpCollectionHandler::buildQUrl(const QString &ns, const QString &folder, + const QString &relFileName, const QString &anchor) +{ + QUrl url; + url.setScheme("qthelp"_L1); + url.setAuthority(ns); + url.setPath(u'/' + folder + u'/' + relFileName); + url.setFragment(anchor); + return url; +} + +QList QHelpCollectionHandler::documentsForIdentifier( + const QString &id, const QStringList &filterAttributes) const +{ + return documentsForField("Identifier"_L1, id, filterAttributes); +} + +QList QHelpCollectionHandler::documentsForKeyword( + const QString &keyword, const QStringList &filterAttributes) const +{ + return documentsForField("Name"_L1, keyword, filterAttributes); +} + +QList QHelpCollectionHandler::documentsForField(const QString &fieldName, + const QString &fieldValue, const QStringList &filterAttributes) const +{ + if (!isDBOpened()) + return {}; + + const QString filterlessQuery = + "SELECT " + "FileNameTable.Title, " + "NamespaceTable.Name, " + "FolderTable.Name, " + "FileNameTable.Name, " + "IndexTable.Anchor " + "FROM " + "IndexTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE IndexTable.FileId = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND IndexTable.NamespaceId = NamespaceTable.Id " + "AND IndexTable.%1 = ?"_L1.arg(fieldName); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterAttributes.size(), "IndexTable"_L1, "Id"_L1, + "IndexFilterTable"_L1, "IndexId"_L1); + + m_query->prepare(filterQuery); + m_query->bindValue(0, fieldValue); + bindFilterQuery(m_query.get(), 1, filterAttributes); + + m_query->exec(); + + QList docList; + while (m_query->next()) { + QString title = m_query->value(0).toString(); + if (title.isEmpty()) // generate a title + corresponding path + title = fieldValue + " : "_L1 + m_query->value(3).toString(); + + const QUrl url = buildQUrl(m_query->value(1).toString(), + m_query->value(2).toString(), + m_query->value(3).toString(), + m_query->value(4).toString()); + docList.append(QHelpLink {url, title}); + } + return docList; +} + +QList QHelpCollectionHandler::documentsForIdentifier( + const QString &id, const QString &filterName) const +{ + return documentsForField("Identifier"_L1, id, filterName); +} + +QList QHelpCollectionHandler::documentsForKeyword( + const QString &keyword, const QString &filterName) const +{ + return documentsForField("Name"_L1, keyword, filterName); +} + +QMultiMap QHelpCollectionHandler::linksForField(const QString &fieldName, + const QString &fieldValue, const QString &filterName) const +{ + QMultiMap linkMap; + const auto documents = documentsForField(fieldName, fieldValue, filterName); + for (const auto &document : documents) + linkMap.insert(document.title, document.url); + return linkMap; +} + +QList QHelpCollectionHandler::documentsForField(const QString &fieldName, + const QString &fieldValue, const QString &filterName) const +{ + if (!isDBOpened()) + return {}; + + const QString filterlessQuery = + "SELECT " + "FileNameTable.Title, " + "NamespaceTable.Name, " + "FolderTable.Name, " + "FileNameTable.Name, " + "IndexTable.Anchor " + "FROM " + "IndexTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE IndexTable.FileId = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND IndexTable.NamespaceId = NamespaceTable.Id " + "AND IndexTable.%1 = ?"_L1.arg(fieldName); + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName) + + " ORDER BY LOWER(FileNameTable.Title), FileNameTable.Title"_L1; + + m_query->prepare(filterQuery); + m_query->bindValue(0, fieldValue); + bindFilterQuery(m_query.get(), 1, filterName); + + m_query->exec(); + + QList docList; + while (m_query->next()) { + QString title = m_query->value(0).toString(); + if (title.isEmpty()) // generate a title + corresponding path + title = fieldValue + " : "_L1 + m_query->value(3).toString(); + + const QUrl url = buildQUrl(m_query->value(1).toString(), + m_query->value(2).toString(), + m_query->value(3).toString(), + m_query->value(4).toString()); + docList.append(QHelpLink {url, title}); + } + return docList; +} + +QStringList QHelpCollectionHandler::namespacesForFilter(const QString &filterName) const +{ + QStringList namespaceList; + + if (!isDBOpened()) + return namespaceList; + + const QString filterlessQuery = + "SELECT " + "NamespaceTable.Name " + "FROM " + "NamespaceTable " + "WHERE TRUE"_L1; + + const QString filterQuery = filterlessQuery + + prepareFilterQuery(filterName); + + m_query->prepare(filterQuery); + bindFilterQuery(m_query.get(), 0, filterName); + + m_query->exec(); + + while (m_query->next()) + namespaceList.append(m_query->value(0).toString()); + return namespaceList; +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpcollectionhandler_p.h b/src/assistant/help/qhelpcollectionhandler_p.h new file mode 100644 index 0000000..28f13e5 --- /dev/null +++ b/src/assistant/help/qhelpcollectionhandler_p.h @@ -0,0 +1,201 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPCOLLECTIONHANDLER_H +#define QHELPCOLLECTIONHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the help generator tools. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include "qhelpdbreader_p.h" +#include "qhelplink.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpFilterData; +class QSqlQuery; +class QVariant; +class QVersionNumber; + +class QHelpCollectionHandler : public QObject +{ + Q_OBJECT + +public: + struct FileInfo + { + QString fileName; + QString folderName; + QString namespaceName; + }; + typedef QList FileInfoList; + + struct TimeStamp + { + int namespaceId = -1; + int folderId = -1; + QString fileName; + int size = 0; + QDateTime timeStamp; + }; + + struct ContentsData + { + QString namespaceName; + QString folderName; + QList contentsList; + }; + + explicit QHelpCollectionHandler(const QString &collectionFile, QObject *parent = nullptr); + ~QHelpCollectionHandler(); + + QString collectionFile() const { return m_collectionFile; } + + bool openCollectionFile(); + bool copyCollectionFile(const QString &fileName); + + // *** Legacy block start *** + // legacy API since Qt 5.13 + + // use filters() instead + QStringList customFilters() const; + + // use QHelpFilterEngine::removeFilter() instead + bool removeCustomFilter(const QString &filterName); + + // use QHelpFilterEngine::setFilterData() instead + bool addCustomFilter(const QString &filterName, const QStringList &attributes); + + // use files(const QString &, const QString &, const QString &) instead + QStringList files(const QString &namespaceName, + const QStringList &filterAttributes, + const QString &extensionFilter) const; + + // use namespaceForFile(const QUrl &, const QString &) instead + QString namespaceForFile(const QUrl &url, const QStringList &filterAttributes) const; + + // use findFile(const QUrl &, const QString &) instead + QUrl findFile(const QUrl &url, const QStringList &filterAttributes) const; + + // use indicesForFilter(const QString &) instead + QStringList indicesForFilter(const QStringList &filterAttributes) const; + + // use contentsForFilter(const QString &) instead + QList contentsForFilter(const QStringList &filterAttributes) const; + + // use QHelpFilterEngine::activeFilter() and filterData(const QString &) instead; + QStringList filterAttributes() const; + + // use filterData(const QString &) instead + QStringList filterAttributes(const QString &filterName) const; + + // use filterData(const QString &) instead + QList filterAttributeSets(const QString &namespaceName) const; + + // *** Legacy block end *** + + QStringList filters() const; + + QStringList availableComponents() const; + QList availableVersions() const; + QMap namespaceToComponent() const; + QMap namespaceToVersion() const; + QHelpFilterData filterData(const QString &filterName) const; + bool setFilterData(const QString &filterName, const QHelpFilterData &filterData); + bool removeFilter(const QString &filterName); + + FileInfo registeredDocumentation(const QString &namespaceName) const; + FileInfoList registeredDocumentations() const; + bool registerDocumentation(const QString &fileName); + bool unregisterDocumentation(const QString &namespaceName); + + bool fileExists(const QUrl &url) const; + QStringList files(const QString &namespaceName, + const QString &filterName, + const QString &extensionFilter) const; + QString namespaceForFile(const QUrl &url, const QString &filterName) const; + QUrl findFile(const QUrl &url, const QString &filterName) const; + QByteArray fileData(const QUrl &url) const; + + QStringList indicesForFilter(const QString &filterName) const; + QList contentsForFilter(const QString &filterName) const; + + bool removeCustomValue(const QString &key); + QVariant customValue(const QString &key, const QVariant &defaultValue) const; + bool setCustomValue(const QString &key, const QVariant &value); + + int registerNamespace(const QString &nspace, const QString &fileName); + int registerVirtualFolder(const QString &folderName, int namespaceId); + int registerComponent(const QString &componentName, int namespaceId); + bool registerVersion(const QString &version, int namespaceId); + + QList documentsForIdentifier(const QString &id, const QString &filterName) const; + QList documentsForKeyword(const QString &keyword, const QString &filterName) const; + QList documentsForIdentifier(const QString &id, + const QStringList &filterAttributes) const; + QList documentsForKeyword(const QString &keyword, + const QStringList &filterAttributes) const; + + QStringList namespacesForFilter(const QString &filterName) const; + + void setReadOnly(bool readOnly) { m_readOnly = readOnly; } + + static QUrl buildQUrl(const QString &ns, const QString &folder, + const QString &relFileName, const QString &anchor); + +signals: + void error(const QString &msg); + +private: + // legacy stuff + QList documentsForField(const QString &fieldName, + const QString &fieldValue, + const QStringList &filterAttributes) const; + + QString namespaceVersion(const QString &namespaceName) const; + QMultiMap linksForField(const QString &fieldName, const QString &fieldValue, + const QString &filterName) const; + QList documentsForField(const QString &fieldName, + const QString &fieldValue, + const QString &filterName) const; + + bool isDBOpened() const; + bool createTables(QSqlQuery *query); + void closeDB(); + bool recreateIndexAndNamespaceFilterTables(QSqlQuery *query); + bool registerIndexAndNamespaceFilterTables(const QString &nameSpace, + bool createDefaultVersionFilter = false); + void createVersionFilter(const QString &version); + bool registerFilterAttributes(const QList &attributeSets, int nsId); + bool registerFileAttributeSets(const QList &attributeSets, int nsId); + bool registerIndexTable(const QHelpDBReader::IndexTable &indexTable, + int nsId, int vfId, const QString &fileName); + bool unregisterIndexTable(int nsId, int vfId); + QString absoluteDocPath(const QString &fileName) const; + bool isTimeStampCorrect(const TimeStamp &timeStamp) const; + bool hasTimeStampInfo(const QString &nameSpace) const; + void scheduleVacuum(); + void execVacuum(); + + QString m_collectionFile; + QString m_connectionName; + std::unique_ptr m_query; + bool m_vacuumScheduled = false; + bool m_readOnly = true; +}; + +QT_END_NAMESPACE + +#endif // QHELPCOLLECTIONHANDLER_H diff --git a/src/assistant/help/qhelpcontentitem.cpp b/src/assistant/help/qhelpcontentitem.cpp new file mode 100644 index 0000000..3c437ef --- /dev/null +++ b/src/assistant/help/qhelpcontentitem.cpp @@ -0,0 +1,102 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpcontentitem.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpContentItemPrivate +{ +public: + QString title; + QUrl link; + QHelpContentItem *parent; + QList childItems = {}; +}; + +/*! + \class QHelpContentItem + \inmodule QtHelp + \brief The QHelpContentItem class provides an item for use with QHelpContentModel. + \since 4.4 +*/ + +QHelpContentItem::QHelpContentItem(const QString &name, const QUrl &link, QHelpContentItem *parent) + : d(new QHelpContentItemPrivate{name, link, parent}) +{ + if (parent) + parent->d->childItems.append(this); +} + +/*! + Destroys the help content item. +*/ +QHelpContentItem::~QHelpContentItem() +{ + qDeleteAll(d->childItems); + delete d; +} + +/*! + Returns the child of the content item in the give \a row. + + \sa parent() +*/ +QHelpContentItem *QHelpContentItem::child(int row) const +{ + return d->childItems.value(row); +} + +/*! + Returns the number of child items. +*/ +int QHelpContentItem::childCount() const +{ + return d->childItems.size(); +} + +/*! + Returns the row of this item from its parents view. +*/ +int QHelpContentItem::row() const +{ + // TODO: Optimize by keeping the index internally. + return d->parent ? d->parent->d->childItems.indexOf(const_cast(this)) : 0; +} + +/*! + Returns the title of the content item. +*/ +QString QHelpContentItem::title() const +{ + return d->title; +} + +/*! + Returns the URL of this content item. +*/ +QUrl QHelpContentItem::url() const +{ + return d->link; +} + +/*! + Returns the parent content item. +*/ +QHelpContentItem *QHelpContentItem::parent() const +{ + return d->parent; +} + +/*! + Returns the position of a given \a child. +*/ +int QHelpContentItem::childPosition(QHelpContentItem *child) const +{ + return d->childItems.indexOf(child); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpcontentitem.h b/src/assistant/help/qhelpcontentitem.h new file mode 100644 index 0000000..de65a84 --- /dev/null +++ b/src/assistant/help/qhelpcontentitem.h @@ -0,0 +1,38 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPCONTENTITEM_H +#define QHELPCONTENTITEM_H + +#include + +QT_BEGIN_NAMESPACE + +class QHelpContentItemPrivate; +class QString; +class QUrl; + +class QHELP_EXPORT QHelpContentItem final +{ + Q_DISABLE_COPY_MOVE(QHelpContentItem) +public: + ~QHelpContentItem(); + + QHelpContentItem *child(int row) const; + int childCount() const; + QString title() const; + QUrl url() const; + int row() const; + QHelpContentItem *parent() const; + int childPosition(QHelpContentItem *child) const; + +private: + QHelpContentItem(const QString &name, const QUrl &link, QHelpContentItem *parent = nullptr); + + QHelpContentItemPrivate *d; + friend QHelpContentItem *createContentItem(const QString &, const QUrl &, QHelpContentItem *); +}; + +QT_END_NAMESPACE + +#endif // QHELPCONTENTITEM_H diff --git a/src/assistant/help/qhelpcontentwidget.cpp b/src/assistant/help/qhelpcontentwidget.cpp new file mode 100644 index 0000000..2483761 --- /dev/null +++ b/src/assistant/help/qhelpcontentwidget.cpp @@ -0,0 +1,311 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpcontentwidget.h" +#include "qhelpenginecore.h" + +#if QT_CONFIG(future) +#include +#endif + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QHelpContentModelPrivate +{ +#if QT_CONFIG(future) + using ItemFutureProvider = std::function>()>; + + struct WatcherDeleter + { + void operator()(QFutureWatcherBase *watcher) { + watcher->disconnect(); + watcher->cancel(); + watcher->waitForFinished(); + delete watcher; + } + }; +#endif + +public: +#if QT_CONFIG(future) + void createContents(const ItemFutureProvider &futureProvider); +#endif + + QHelpContentModel *q = nullptr; + QHelpEngineCore *helpEngine = nullptr; + std::shared_ptr rootItem = {}; +#if QT_CONFIG(future) + std::unique_ptr>, WatcherDeleter> watcher = {}; +#endif +}; + +#if QT_CONFIG(future) +void QHelpContentModelPrivate::createContents(const ItemFutureProvider &futureProvider) +{ + const bool wasRunning = bool(watcher); + watcher.reset(new QFutureWatcher>); + QObject::connect(watcher.get(), &QFutureWatcherBase::finished, q, [this] { + if (!watcher->isCanceled()) { + const std::shared_ptr result = watcher->result(); + if (result && result.get()) { + q->beginResetModel(); + rootItem = result; + q->endResetModel(); + } + } + watcher.release()->deleteLater(); + emit q->contentsCreated(); + }); + watcher->setFuture(futureProvider()); + + if (wasRunning) + return; + + if (rootItem) { + q->beginResetModel(); + rootItem.reset(); + q->endResetModel(); + } + emit q->contentsCreationStarted(); +} +#endif + +/*! + \class QHelpContentModel + \inmodule QtHelp + \brief The QHelpContentModel class provides a model that supplies content to views. + \since 4.4 +*/ + +/*! + \fn void QHelpContentModel::contentsCreationStarted() + + This signal is emitted when the creation of the contents has + started. The current contents are invalid from this point on + until the signal contentsCreated() is emitted. + + \sa isCreatingContents() +*/ + +/*! + \fn void QHelpContentModel::contentsCreated() + + This signal is emitted when the contents have been created. +*/ + +QHelpContentModel::QHelpContentModel(QHelpEngineCore *helpEngine) + : QAbstractItemModel(helpEngine) + , d(new QHelpContentModelPrivate{this, helpEngine}) +{} + +/*! + Destroys the help content model. +*/ +QHelpContentModel::~QHelpContentModel() +{ + delete d; +} + +/*! + \since 6.8 + + Creates new contents by querying the help system for contents specified for the current filter. +*/ +void QHelpContentModel::createContentsForCurrentFilter() +{ +#if QT_CONFIG(future) + d->createContents([this] { return d->helpEngine->requestContentForCurrentFilter(); }); +#endif +} + +/*! + Creates new contents by querying the help system + for contents specified for the custom \a filter name. +*/ +void QHelpContentModel::createContents(const QString &filter) +{ +#if QT_CONFIG(future) + d->createContents([this, filter] { return d->helpEngine->requestContent(filter); }); +#endif +} + +// TODO: Remove me +void QHelpContentModel::insertContents() +{} + +/*! + Returns true if the contents are currently rebuilt, otherwise + false. +*/ +bool QHelpContentModel::isCreatingContents() const +{ +#if QT_CONFIG(future) + return bool(d->watcher); +#else + return false; +#endif +} + +/*! + Returns the help content item at the model index position + \a index. +*/ +QHelpContentItem *QHelpContentModel::contentItemAt(const QModelIndex &index) const +{ + return index.isValid() ? static_cast(index.internalPointer()) + : d->rootItem.get(); +} + +/*! + Returns the index of the item in the model specified by + the given \a row, \a column and \a parent index. +*/ +QModelIndex QHelpContentModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!d->rootItem) + return {}; + + QHelpContentItem *parentItem = contentItemAt(parent); + QHelpContentItem *item = parentItem->child(row); + if (!item) + return {}; + return createIndex(row, column, item); +} + +/*! + Returns the parent of the model item with the given + \a index, or QModelIndex() if it has no parent. +*/ +QModelIndex QHelpContentModel::parent(const QModelIndex &index) const +{ + QHelpContentItem *item = contentItemAt(index); + if (!item) + return {}; + + QHelpContentItem *parentItem = static_cast(item->parent()); + if (!parentItem) + return {}; + + QHelpContentItem *grandparentItem = static_cast(parentItem->parent()); + if (!grandparentItem) + return {}; + + const int row = grandparentItem->childPosition(parentItem); + return createIndex(row, index.column(), parentItem); +} + +/*! + Returns the number of rows under the given \a parent. +*/ +int QHelpContentModel::rowCount(const QModelIndex &parent) const +{ + QHelpContentItem *parentItem = contentItemAt(parent); + if (parentItem) + return parentItem->childCount(); + return 0; +} + +/*! + Returns the number of columns under the given \a parent. Currently returns always 1. +*/ +int QHelpContentModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +/*! + Returns the data stored under the given \a role for + the item referred to by the \a index. +*/ +QVariant QHelpContentModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) { + QHelpContentItem *item = contentItemAt(index); + if (item) + return item->title(); + } + return {}; +} + +/*! + \class QHelpContentWidget + \inmodule QtHelp + \brief The QHelpContentWidget class provides a tree view for displaying help content model items. + \since 4.4 +*/ + +/*! + \fn void QHelpContentWidget::linkActivated(const QUrl &link) + + This signal is emitted when a content item is activated and + its associated \a link should be shown. +*/ + +QHelpContentWidget::QHelpContentWidget() +{ + header()->hide(); + setUniformRowHeights(true); + connect(this, &QAbstractItemView::activated, this, &QHelpContentWidget::showLink); +} + +/*! + Returns the index of the content item with the \a link. + An invalid index is returned if no such an item exists. +*/ +QModelIndex QHelpContentWidget::indexOf(const QUrl &link) +{ + QHelpContentModel *contentModel = qobject_cast(model()); + if (!contentModel || link.scheme() != "qthelp"_L1) + return {}; + + m_syncIndex = {}; + for (int i = 0; i < contentModel->rowCount(); ++i) { + QHelpContentItem *itm = contentModel->contentItemAt(contentModel->index(i, 0)); + if (itm && itm->url().host() == link.host()) { + if (searchContentItem(contentModel, contentModel->index(i, 0), QDir::cleanPath(link.path()))) + return m_syncIndex; + } + } + return {}; +} + +bool QHelpContentWidget::searchContentItem(QHelpContentModel *model, const QModelIndex &parent, + const QString &cleanPath) +{ + QHelpContentItem *parentItem = model->contentItemAt(parent); + if (!parentItem) + return false; + + if (QDir::cleanPath(parentItem->url().path()) == cleanPath) { + m_syncIndex = parent; + return true; + } + + for (int i = 0; i < parentItem->childCount(); ++i) { + if (searchContentItem(model, model->index(i, 0, parent), cleanPath)) + return true; + } + return false; +} + +void QHelpContentWidget::showLink(const QModelIndex &index) +{ + QHelpContentModel *contentModel = qobject_cast(model()); + if (!contentModel) + return; + + QHelpContentItem *item = contentModel->contentItemAt(index); + if (!item) + return; + QUrl url = item->url(); + if (url.isValid()) + emit linkActivated(url); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpcontentwidget.h b/src/assistant/help/qhelpcontentwidget.h new file mode 100644 index 0000000..cf96688 --- /dev/null +++ b/src/assistant/help/qhelpcontentwidget.h @@ -0,0 +1,75 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPCONTENTWIDGET_H +#define QHELPCONTENTWIDGET_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpContentModelPrivate; +class QHelpEngine; +class QHelpEngineCore; +class QUrl; + +class QHELP_EXPORT QHelpContentModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + ~QHelpContentModel() override; + + void createContentsForCurrentFilter(); + void createContents(const QString &customFilterName); + QHelpContentItem *contentItemAt(const QModelIndex &index) const; + + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = {}) const override; + int columnCount(const QModelIndex &parent = {}) const override; + bool isCreatingContents() const; + +Q_SIGNALS: + void contentsCreationStarted(); + void contentsCreated(); + +private Q_SLOTS: + void insertContents(); + +private: + QHelpContentModel(QHelpEngineCore *helpEngine); + QHelpContentModelPrivate *d; + friend class QHelpEnginePrivate; + friend class QHelpContentModelPrivate; +}; + +class QHELP_EXPORT QHelpContentWidget : public QTreeView +{ + Q_OBJECT + +public: + QModelIndex indexOf(const QUrl &link); + +Q_SIGNALS: + void linkActivated(const QUrl &link); + +private Q_SLOTS: + void showLink(const QModelIndex &index); + +private: + bool searchContentItem(QHelpContentModel *model, const QModelIndex &parent, + const QString &path); + QModelIndex m_syncIndex; + +private: + QHelpContentWidget(); + friend class QHelpEngine; +}; + +QT_END_NAMESPACE + +#endif // QHELPCONTENTWIDGET_H diff --git a/src/assistant/help/qhelpdbreader.cpp b/src/assistant/help/qhelpdbreader.cpp new file mode 100644 index 0000000..cfe1b68 --- /dev/null +++ b/src/assistant/help/qhelpdbreader.cpp @@ -0,0 +1,503 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpdbreader_p.h" +#include "qhelp_global.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QHelpDBReader::QHelpDBReader(const QString &dbName) + : m_dbName(dbName) + , m_uniqueId(QHelpGlobal::uniquifyConnectionName("QHelpDBReader"_L1, this)) +{} + +QHelpDBReader::QHelpDBReader(const QString &dbName, const QString &uniqueId, QObject *parent) + : QObject(parent) + , m_dbName(dbName) + , m_uniqueId(uniqueId) +{} + +QHelpDBReader::~QHelpDBReader() +{ + if (m_initDone) + QSqlDatabase::removeDatabase(m_uniqueId); +} + +bool QHelpDBReader::init() +{ + if (m_initDone) + return true; + + if (!QFile::exists(m_dbName)) + return false; + + if (!initDB()) { + QSqlDatabase::removeDatabase(m_uniqueId); + return false; + } + + m_initDone = true; + m_query.reset(new QSqlQuery(QSqlDatabase::database(m_uniqueId))); + return true; +} + +bool QHelpDBReader::initDB() +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, m_uniqueId); + db.setConnectOptions("QSQLITE_OPEN_READONLY"_L1); + db.setDatabaseName(m_dbName); + if (!db.open()) { + /*: The placeholders are: %1 - The name of the database which cannot be opened + %2 - The unique id for the connection + %3 - The actual error string */ + m_error = tr("Cannot open database \"%1\" \"%2\": %3").arg(m_dbName, m_uniqueId, db.lastError().text()); + return false; + } + return true; +} + +QString QHelpDBReader::namespaceName() const +{ + if (!m_namespace.isEmpty()) + return m_namespace; + if (m_query) { + m_query->exec("SELECT Name FROM NamespaceTable"_L1); + if (m_query->next()) + m_namespace = m_query->value(0).toString(); + } + return m_namespace; +} + +QString QHelpDBReader::virtualFolder() const +{ + if (m_query) { + m_query->exec("SELECT Name FROM FolderTable WHERE Id=1"_L1); + if (m_query->next()) + return m_query->value(0).toString(); + } + return {}; +} + +QString QHelpDBReader::version() const +{ + const QString versionString = metaData("version"_L1).toString(); + if (versionString.isEmpty()) + return qtVersionHeuristic(); + return versionString; +} + +QString QHelpDBReader::qtVersionHeuristic() const +{ + const QString nameSpace = namespaceName(); + if (!nameSpace.startsWith("org.qt-project."_L1)) + return {}; + + // We take the namespace tail, starting from the last letter in namespace name. + // We drop any non digit characters. + const QChar dot(u'.'); + QString tail; + for (int i = nameSpace.size(); i > 0; --i) { + const QChar c = nameSpace.at(i - 1); + if (c.isDigit() || c == dot) + tail.prepend(c); + + if (c.isLetter()) + break; + } + + if (!tail.startsWith(dot) && tail.count(dot) == 1) { + // The org.qt-project.qtquickcontrols2.5120 case, + // tail = 2.5120 here. We need to cut "2." here. + const int dotIndex = tail.indexOf(dot); + if (dotIndex > 0) + tail = tail.mid(dotIndex); + } + + // Drop beginning dots + while (tail.startsWith(dot)) + tail = tail.mid(1); + + // Drop ending dots + while (tail.endsWith(dot)) + tail.chop(1); + + if (tail.count(dot) == 0) { + if (tail.size() > 5) + return tail; + + // When we have 3 digits, we split it like: ABC -> A.B.C + // When we have 4 digits, we split it like: ABCD -> A.BC.D + // When we have 5 digits, we split it like: ABCDE -> A.BC.DE + const int major = tail.left(1).toInt(); + const int minor = tail.size() == 3 + ? tail.mid(1, 1).toInt() : tail.mid(1, 2).toInt(); + const int patch = tail.size() == 5 + ? tail.right(2).toInt() : tail.right(1).toInt(); + + return QString::fromUtf8("%1.%2.%3").arg(major).arg(minor).arg(patch); + } + return tail; +} + +static bool isAttributeUsed(QSqlQuery *query, const QString &tableName, int attributeId) +{ + query->prepare(QString::fromLatin1("SELECT FilterAttributeId " + "FROM %1 " + "WHERE FilterAttributeId = ? " + "LIMIT 1").arg(tableName)); + query->bindValue(0, attributeId); + query->exec(); + return query->next(); // if we got a result it means it was used +} + +static int filterDataCount(QSqlQuery *query, const QString &tableName) +{ + query->exec(QString::fromLatin1("SELECT COUNT(*) FROM" + "(SELECT DISTINCT * FROM %1)").arg(tableName)); + query->next(); + return query->value(0).toInt(); +} + +QHelpDBReader::IndexTable QHelpDBReader::indexTable() const +{ + IndexTable table; + if (!m_query) + return table; + + QMap attributeIds; + m_query->exec("SELECT DISTINCT Id, Name FROM FilterAttributeTable ORDER BY Id"_L1); + while (m_query->next()) + attributeIds.insert(m_query->value(0).toInt(), m_query->value(1).toString()); + + // Maybe some are unused and specified erroneously in the named filter only, + // like it was in case of qtlocation.qch <= qt 5.9 + QList usedAttributeIds; + for (auto it = attributeIds.cbegin(), end = attributeIds.cend(); it != end; ++it) { + const int attributeId = it.key(); + if (isAttributeUsed(m_query.get(), "IndexFilterTable"_L1, attributeId) + || isAttributeUsed(m_query.get(), "ContentsFilterTable"_L1, attributeId) + || isAttributeUsed(m_query.get(), "FileFilterTable"_L1, attributeId)) { + usedAttributeIds.append(attributeId); + } + } + + bool legacy = false; + m_query->exec("SELECT * FROM pragma_table_info('IndexTable') WHERE name='ContextName'"_L1); + if (m_query->next()) + legacy = true; + + const QString identifierColumnName = legacy ? "ContextName"_L1 : "Identifier"_L1; + const int usedAttributeCount = usedAttributeIds.size(); + + QMap idToIndexItem; + m_query->exec(QString::fromLatin1("SELECT Name, %1, FileId, Anchor, Id " + "FROM IndexTable " + "ORDER BY Id").arg(identifierColumnName)); + while (m_query->next()) { + IndexItem indexItem; + indexItem.name = m_query->value(0).toString(); + indexItem.identifier = m_query->value(1).toString(); + indexItem.fileId = m_query->value(2).toInt(); + indexItem.anchor = m_query->value(3).toString(); + const int indexId = m_query->value(4).toInt(); + + idToIndexItem.insert(indexId, indexItem); + } + + QMap idToFileItem; + QMap originalFileIdToNewFileId; + + int filesCount = 0; + m_query->exec( + "SELECT " + "FileNameTable.FileId, " + "FileNameTable.Name, " + "FileNameTable.Title " + "FROM FileNameTable, FolderTable " + "WHERE FileNameTable.FolderId = FolderTable.Id " + "ORDER BY FileId"_L1); + while (m_query->next()) { + const int fileId = m_query->value(0).toInt(); + FileItem fileItem; + fileItem.name = m_query->value(1).toString(); + fileItem.title = m_query->value(2).toString(); + + idToFileItem.insert(fileId, fileItem); + originalFileIdToNewFileId.insert(fileId, filesCount); + ++filesCount; + } + + QMap idToContentsItem; + + m_query->exec("SELECT Data, Id FROM ContentsTable ORDER BY Id"_L1); + while (m_query->next()) { + ContentsItem contentsItem; + contentsItem.data = m_query->value(0).toByteArray(); + const int contentsId = m_query->value(1).toInt(); + + idToContentsItem.insert(contentsId, contentsItem); + } + + bool optimized = true; + + if (usedAttributeCount) { + // May optimize only when all usedAttributes are attached to every + // index and file. It means the number of rows in the + // IndexTable multiplied by number of used attributes + // must equal the number of rows inside IndexFilterTable + // (yes, we have a combinatorial explosion of data in IndexFilterTable, + // which we want to optimize). The same with FileNameTable and + // FileFilterTable. + + const bool mayOptimizeIndexTable = filterDataCount(m_query.get(), "IndexFilterTable"_L1) + == idToIndexItem.size() * usedAttributeCount; + const bool mayOptimizeFileTable = filterDataCount(m_query.get(), "FileFilterTable"_L1) + == idToFileItem.size() * usedAttributeCount; + const bool mayOptimizeContentsTable = + filterDataCount(m_query.get(), "ContentsFilterTable"_L1) + == idToContentsItem.size() * usedAttributeCount; + optimized = mayOptimizeIndexTable && mayOptimizeFileTable && mayOptimizeContentsTable; + + if (!optimized) { + m_query->exec( + "SELECT " + "IndexFilterTable.IndexId, " + "FilterAttributeTable.Name " + "FROM " + "IndexFilterTable, " + "FilterAttributeTable " + "WHERE " + "IndexFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1); + while (m_query->next()) { + const int indexId = m_query->value(0).toInt(); + auto it = idToIndexItem.find(indexId); + if (it != idToIndexItem.end()) + it.value().filterAttributes.append(m_query->value(1).toString()); + } + + m_query->exec( + "SELECT " + "FileFilterTable.FileId, " + "FilterAttributeTable.Name " + "FROM " + "FileFilterTable, " + "FilterAttributeTable " + "WHERE " + "FileFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1); + while (m_query->next()) { + const int fileId = m_query->value(0).toInt(); + auto it = idToFileItem.find(fileId); + if (it != idToFileItem.end()) + it.value().filterAttributes.append(m_query->value(1).toString()); + } + + m_query->exec( + "SELECT " + "ContentsFilterTable.ContentsId, " + "FilterAttributeTable.Name " + "FROM " + "ContentsFilterTable, " + "FilterAttributeTable " + "WHERE " + "ContentsFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1); + while (m_query->next()) { + const int contentsId = m_query->value(0).toInt(); + auto it = idToContentsItem.find(contentsId); + if (it != idToContentsItem.end()) + it.value().filterAttributes.append(m_query->value(1).toString()); + } + } + } + + // reindex fileId references + for (auto it = idToIndexItem.cbegin(), end = idToIndexItem.cend(); it != end; ++it) { + IndexItem item = it.value(); + item.fileId = originalFileIdToNewFileId.value(item.fileId); + table.indexItems.append(item); + } + + table.fileItems = idToFileItem.values(); + table.contentsItems = idToContentsItem.values(); + + if (optimized) { + for (int attributeId : usedAttributeIds) + table.usedFilterAttributes.append(attributeIds.value(attributeId)); + } + return table; +} + +QList QHelpDBReader::filterAttributeSets() const +{ + QList result; + if (m_query) { + m_query->exec( + "SELECT " + "FileAttributeSetTable.Id, " + "FilterAttributeTable.Name " + "FROM " + "FileAttributeSetTable, " + "FilterAttributeTable " + "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id " + "ORDER BY FileAttributeSetTable.Id"_L1); + int oldId = -1; + while (m_query->next()) { + const int id = m_query->value(0).toInt(); + if (id != oldId) { + result.append(QStringList()); + oldId = id; + } + result.last().append(m_query->value(1).toString()); + } + } + return result; +} + +QByteArray QHelpDBReader::fileData(const QString &virtualFolder, + const QString &filePath) const +{ + QByteArray ba; + if (virtualFolder.isEmpty() || filePath.isEmpty() || !m_query) + return ba; + + namespaceName(); + m_query->prepare( + "SELECT " + "FileDataTable.Data " + "FROM " + "FileDataTable, " + "FileNameTable, " + "FolderTable, " + "NamespaceTable " + "WHERE FileDataTable.Id = FileNameTable.FileId " + "AND (FileNameTable.Name = ? OR FileNameTable.Name = ?) " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FolderTable.Name = ? " + "AND FolderTable.NamespaceId = NamespaceTable.Id " + "AND NamespaceTable.Name = ?"_L1); + m_query->bindValue(0, filePath); + m_query->bindValue(1, QString("./"_L1 + filePath)); + m_query->bindValue(2, virtualFolder); + m_query->bindValue(3, m_namespace); + m_query->exec(); + if (m_query->next() && m_query->isValid()) + ba = qUncompress(m_query->value(0).toByteArray()); + return ba; +} + +QStringList QHelpDBReader::customFilters() const +{ + QStringList lst; + if (m_query) { + m_query->exec("SELECT Name FROM FilterNameTable"_L1); + while (m_query->next()) + lst.append(m_query->value(0).toString()); + } + return lst; +} + +QStringList QHelpDBReader::filterAttributes(const QString &filterName) const +{ + QStringList lst; + if (m_query) { + if (filterName.isEmpty()) { + m_query->prepare("SELECT Name FROM FilterAttributeTable"_L1); + } else { + m_query->prepare( + "SELECT " + "FilterAttributeTable.Name " + "FROM " + "FilterAttributeTable, " + "FilterTable, " + "FilterNameTable " + "WHERE FilterNameTable.Name = ? " + "AND FilterNameTable.Id = FilterTable.NameId " + "AND FilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1); + m_query->bindValue(0, filterName); + } + m_query->exec(); + while (m_query->next()) + lst.append(m_query->value(0).toString()); + } + return lst; +} + +QMultiMap QHelpDBReader::filesData(const QStringList &filterAttributes, + const QString &extensionFilter) const +{ + if (!m_query) + return {}; + + QString query; + QString extension; + if (!extensionFilter.isEmpty()) + extension = "AND FileNameTable.Name LIKE \'%.%1\'"_L1.arg(extensionFilter); + + if (filterAttributes.isEmpty()) { + query = + "SELECT " + "FileNameTable.Name, " + "FileDataTable.Data " + "FROM " + "FolderTable, " + "FileNameTable, " + "FileDataTable " + "WHERE FileDataTable.Id = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id %1"_L1.arg(extension); + } else { + for (int i = 0; i < filterAttributes.size(); ++i) { + if (i > 0) + query.append(" INTERSECT "_L1); + query.append( + "SELECT " + "FileNameTable.Name, " + "FileDataTable.Data " + "FROM " + "FolderTable, " + "FileNameTable, " + "FileDataTable, " + "FileFilterTable, " + "FilterAttributeTable " + "WHERE FileDataTable.Id = FileNameTable.FileId " + "AND FileNameTable.FolderId = FolderTable.Id " + "AND FileNameTable.FileId = FileFilterTable.FileId " + "AND FileFilterTable.FilterAttributeId = FilterAttributeTable.Id " + "AND FilterAttributeTable.Name = \'%1\' %2"_L1 + .arg(quote(filterAttributes.at(i)), extension)); + } + } + m_query->exec(query); + QMultiMap result; + while (m_query->next()) + result.insert(m_query->value(0).toString(), qUncompress(m_query->value(1).toByteArray())); + return result; +} + +QVariant QHelpDBReader::metaData(const QString &name) const +{ + if (!m_query) + return {}; + + m_query->prepare("SELECT COUNT(Value), Value FROM MetaDataTable WHERE Name=?"_L1); + m_query->bindValue(0, name); + if (m_query->exec() && m_query->next() && m_query->value(0).toInt() == 1) + return m_query->value(1); + return {}; +} + +QString QHelpDBReader::quote(const QString &string) const +{ + QString s = string; + s.replace(u'\'', "\'\'"_L1); + return s; +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpdbreader_p.h b/src/assistant/help/qhelpdbreader_p.h new file mode 100644 index 0000000..1bc4fc7 --- /dev/null +++ b/src/assistant/help/qhelpdbreader_p.h @@ -0,0 +1,100 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPDBREADER_H +#define QHELPDBREADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the help generator tools. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QSqlQuery; + +class QHelpDBReader : public QObject +{ + Q_OBJECT + +public: + class IndexItem + { + public: + QString name; + QString identifier; + int fileId = 0; + QString anchor; + QStringList filterAttributes; + }; + + class FileItem + { + public: + QString name; + QString title; + QStringList filterAttributes; + }; + + class ContentsItem + { + public: + QByteArray data; + QStringList filterAttributes; + }; + + class IndexTable + { + public: + QList indexItems; + QList fileItems; + QList contentsItems; + QStringList usedFilterAttributes; + }; + + QHelpDBReader(const QString &dbName); + QHelpDBReader(const QString &dbName, const QString &uniqueId, QObject *parent); + ~QHelpDBReader(); + + bool init(); + + QString namespaceName() const; + QString virtualFolder() const; + QString version() const; + IndexTable indexTable() const; + QList filterAttributeSets() const; + QMultiMap filesData(const QStringList &filterAttributes, + const QString &extensionFilter = {}) const; + QByteArray fileData(const QString &virtualFolder, const QString &filePath) const; + + QStringList customFilters() const; + QStringList filterAttributes(const QString &filterName = {}) const; + + QVariant metaData(const QString &name) const; + +private: + QString quote(const QString &string) const; + bool initDB(); + QString qtVersionHeuristic() const; + + bool m_initDone = false; + QString m_dbName; + QString m_uniqueId; + QString m_error; + std::unique_ptr m_query; + mutable QString m_namespace; +}; + +QT_END_NAMESPACE + +#endif // QHELPDBREADER_H diff --git a/src/assistant/help/qhelpengine.cpp b/src/assistant/help/qhelpengine.cpp new file mode 100644 index 0000000..e773018 --- /dev/null +++ b/src/assistant/help/qhelpengine.cpp @@ -0,0 +1,157 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpengine.h" +#include "qhelpcontentwidget.h" +#include "qhelpfilterengine.h" +#include "qhelpindexwidget.h" +#include "qhelpsearchengine.h" + +#include + +QT_BEGIN_NAMESPACE + +class QHelpEnginePrivate +{ +public: + QHelpEnginePrivate(QHelpEngineCore *helpEngineCore); + + QHelpContentModel *contentModel = nullptr; + QHelpContentWidget *contentWidget = nullptr; + + QHelpIndexModel *indexModel = nullptr; + QHelpIndexWidget *indexWidget = nullptr; + + QHelpSearchEngine *searchEngine = nullptr; + + bool m_isApplyCurrentFilterScheduled = false; + QHelpEngineCore *m_helpEngineCore = nullptr; +}; + +QHelpEnginePrivate::QHelpEnginePrivate(QHelpEngineCore *helpEngineCore) + : m_helpEngineCore(helpEngineCore) +{ + if (!contentModel) + contentModel = new QHelpContentModel(helpEngineCore); + if (!indexModel) + indexModel = new QHelpIndexModel(m_helpEngineCore); + + const auto applyCurrentFilter = [this] { + m_isApplyCurrentFilterScheduled = false; + contentModel->createContentsForCurrentFilter(); + indexModel->createIndexForCurrentFilter(); + }; + + const auto scheduleApplyCurrentFilter = [this, applyCurrentFilter] { + if (!m_helpEngineCore->error().isEmpty()) + return; + + if (m_isApplyCurrentFilterScheduled) + return; + + m_isApplyCurrentFilterScheduled = true; + QTimer::singleShot(0, m_helpEngineCore, applyCurrentFilter); + }; + + QObject::connect(m_helpEngineCore, &QHelpEngineCore::setupFinished, + m_helpEngineCore, scheduleApplyCurrentFilter); + QObject::connect(m_helpEngineCore, &QHelpEngineCore::currentFilterChanged, + m_helpEngineCore, scheduleApplyCurrentFilter); + QObject::connect(m_helpEngineCore->filterEngine(), &QHelpFilterEngine::filterActivated, + m_helpEngineCore, scheduleApplyCurrentFilter); +} + +/*! + \class QHelpEngine + \since 4.4 + \inmodule QtHelp + \brief The QHelpEngine class provides access to contents and + indices of the help engine. +*/ + +/*! + Constructs a new help engine with the given \a parent. The help + engine uses the information stored in the \a collectionFile for + providing help. If the collection file does not already exist, + it will be created. +*/ +QHelpEngine::QHelpEngine(const QString &collectionFile, QObject *parent) + : QHelpEngineCore(collectionFile, parent) + , d(new QHelpEnginePrivate(this)) +{} + +/*! + Destroys the help engine object. +*/ +QHelpEngine::~QHelpEngine() +{ + delete d; +} + +/*! + Returns the content model. +*/ +QHelpContentModel *QHelpEngine::contentModel() const +{ + return d->contentModel; +} + +/*! + Returns the index model. +*/ +QHelpIndexModel *QHelpEngine::indexModel() const +{ + return d->indexModel; +} + +/*! + Returns the content widget. +*/ +QHelpContentWidget *QHelpEngine::contentWidget() +{ + if (!d->contentWidget) { + d->contentWidget = new QHelpContentWidget; + d->contentWidget->setModel(d->contentModel); +#if QT_CONFIG(cursor) + connect(d->contentModel, &QHelpContentModel::contentsCreationStarted, this, [this] { + d->contentWidget->setCursor(Qt::WaitCursor); + }); + connect(d->contentModel, &QHelpContentModel::contentsCreated, this, [this] { + d->contentWidget->unsetCursor(); + }); +#endif + } + return d->contentWidget; +} + +/*! + Returns the index widget. +*/ +QHelpIndexWidget *QHelpEngine::indexWidget() +{ + if (!d->indexWidget) { + d->indexWidget = new QHelpIndexWidget; + d->indexWidget->setModel(d->indexModel); +#if QT_CONFIG(cursor) + connect(d->indexModel, &QHelpIndexModel::indexCreationStarted, this, [this] { + d->indexWidget->setCursor(Qt::WaitCursor); + }); + connect(d->indexModel, &QHelpIndexModel::indexCreated, this, [this] { + d->indexWidget->unsetCursor(); + }); +#endif + } + return d->indexWidget; +} + +/*! + Returns the default search engine. +*/ +QHelpSearchEngine* QHelpEngine::searchEngine() +{ + if (!d->searchEngine) + d->searchEngine = new QHelpSearchEngine(this, this); + return d->searchEngine; +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpengine.h b/src/assistant/help/qhelpengine.h new file mode 100644 index 0000000..31b8fb3 --- /dev/null +++ b/src/assistant/help/qhelpengine.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPENGINE_H +#define QHELPENGINE_H + +#include + +QT_BEGIN_NAMESPACE + +class QHelpContentModel; +class QHelpContentWidget; +class QHelpIndexModel; +class QHelpIndexWidget; +class QHelpEnginePrivate; +class QHelpSearchEngine; + +class QHELP_EXPORT QHelpEngine : public QHelpEngineCore +{ + Q_OBJECT + +public: + explicit QHelpEngine(const QString &collectionFile, QObject *parent = nullptr); + ~QHelpEngine(); + + QHelpContentModel *contentModel() const; + QHelpIndexModel *indexModel() const; + + QHelpContentWidget *contentWidget(); + QHelpIndexWidget *indexWidget(); + + QHelpSearchEngine *searchEngine(); + +private: + QHelpEnginePrivate *d; + + friend class HelpEngineWrapper; +}; + +QT_END_NAMESPACE + +#endif // QHELPENGINE_H diff --git a/src/assistant/help/qhelpenginecore.cpp b/src/assistant/help/qhelpenginecore.cpp new file mode 100644 index 0000000..84a3481 --- /dev/null +++ b/src/assistant/help/qhelpenginecore.cpp @@ -0,0 +1,942 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpenginecore.h" +#include "qhelpcollectionhandler_p.h" +#include "qhelpdbreader_p.h" +#include "qhelpfilterengine.h" +#include "qhelplink.h" + +#if QT_CONFIG(future) +#include +#include +#endif + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QHelpEngineCorePrivate +{ +public: + QHelpEngineCorePrivate(const QString &collectionFile, QHelpEngineCore *helpEngineCore); + + void init(const QString &collectionFile); + bool setup(); + + std::unique_ptr collectionHandler; + QHelpFilterEngine *filterEngine = nullptr; + QString currentFilter; + QString error; + bool needsSetup = true; + bool autoSaveFilter = true; + bool usesFilterEngine = false; + bool readOnly = true; + + QHelpEngineCore *q; +}; + +QHelpEngineCorePrivate::QHelpEngineCorePrivate(const QString &collectionFile, + QHelpEngineCore *helpEngineCore) +{ + q = helpEngineCore; + filterEngine = new QHelpFilterEngine(q); + init(collectionFile); +} + +void QHelpEngineCorePrivate::init(const QString &collectionFile) +{ + collectionHandler.reset(new QHelpCollectionHandler(collectionFile, q)); + QObject::connect(collectionHandler.get(), &QHelpCollectionHandler::error, q, + [this](const QString &msg) { error = msg; }); + filterEngine->setCollectionHandler(collectionHandler.get()); + needsSetup = true; +} + +bool QHelpEngineCorePrivate::setup() +{ + error.clear(); + if (!needsSetup) + return true; + + needsSetup = false; + emit q->setupStarted(); + + collectionHandler->setReadOnly(q->isReadOnly()); + const bool opened = collectionHandler->openCollectionFile(); + if (opened) + q->currentFilter(); + + emit q->setupFinished(); + return opened; +} + +/*! + \class QHelpEngineCore + \since 4.4 + \inmodule QtHelp + \brief The QHelpEngineCore class provides the core functionality + of the help system. + + Before the help engine can be used, it must be initialized by + calling setupData(). At the beginning of the setup process the + signal setupStarted() is emitted. From this point on until + the signal setupFinished() is emitted, is the help data in an + undefined meaning unusable state. + + The core help engine can be used to perform different tasks. + By calling documentsForIdentifier() the engine returns + URLs specifying the file locations inside the help system. The + actual file data can then be retrieved by calling fileData(). + + The help engine can contain any number of custom filters. + The management of the filters, including adding new filters, + changing filter definitions, or removing existing filters, + is done through the QHelpFilterEngine class, which can be accessed + by the filterEngine() method. + + \note QHelpFilterEngine replaces the older filter API that is + deprecated since Qt 5.13. Call setUsesFilterEngine() with \c true to + enable the new functionality. + + The core help engine has two modes: + \list + \li Read-only mode, where the help collection file is not changed + unless explicitly requested. This also works if the + collection file is in a read-only location, + and is the default. + \li Fully writable mode, which requires the help collection + file to be writable. + \endlist + The mode can be changed by calling setReadOnly() method, prior to + calling setupData(). + + The help engine also offers the possibility to set and read values + in a persistent way comparable to ini files or Windows registry + entries. For more information see \l setCustomValue() or \l customValue(). + + This class does not offer any GUI components or functionality for + indices or contents. If you need one of those use QHelpEngine + instead. +*/ + +/*! + \fn void QHelpEngineCore::setupStarted() + + This signal is emitted when setup is started. +*/ + +/*! + \fn void QHelpEngineCore::setupFinished() + + This signal is emitted when the setup is complete. +*/ + +/*! + \fn void QHelpEngineCore::readersAboutToBeInvalidated() + \deprecated +*/ + +/*! + \fn void QHelpEngineCore::currentFilterChanged(const QString &newFilter) + \deprecated + + QHelpFilterEngine::filterActivated() should be used instead. + + This signal is emitted when the current filter is changed to + \a newFilter. +*/ + +/*! + \fn void QHelpEngineCore::warning(const QString &msg) + + This signal is emitted when a non critical error occurs. + The warning message is stored in \a msg. +*/ + +/*! + Constructs a new core help engine with a \a parent. The help engine + uses the information stored in the \a collectionFile to provide help. + If the collection file does not exist yet, it'll be created. +*/ +QHelpEngineCore::QHelpEngineCore(const QString &collectionFile, QObject *parent) + : QObject(parent) + , d(new QHelpEngineCorePrivate(collectionFile, this)) +{} + +/*! + \internal +*/ +#if QT_DEPRECATED_SINCE(6, 8) +QHelpEngineCore::QHelpEngineCore(QHelpEngineCorePrivate *helpEngineCorePrivate, QObject *parent) + : QObject(parent) + , d(helpEngineCorePrivate) +{} +#endif + +/*! + Destructs the help engine. +*/ +QHelpEngineCore::~QHelpEngineCore() +{ + delete d; +} + +/*! + \property QHelpEngineCore::collectionFile + \brief the absolute file name of the collection file currently used. + \since 4.5 + + Setting this property leaves the help engine in an invalid state. It is + important to invoke setupData() or any getter function in order to setup + the help engine again. +*/ +QString QHelpEngineCore::collectionFile() const +{ + return d->collectionHandler->collectionFile(); +} + +void QHelpEngineCore::setCollectionFile(const QString &fileName) +{ + if (fileName != collectionFile()) + d->init(fileName); +} + +/*! + \property QHelpEngineCore::readOnly + \brief whether the help engine is read-only. + \since 6.0 + + In read-only mode, the user can use the help engine + with a collection file installed in a read-only location. + In this case, some functionality won't be accessible, + like registering additional documentation, filter editing, + or any action that would require changes to the + collection file. Setting it to \c false enables the full + functionality of the help engine. + + By default, this property is \c true. +*/ +bool QHelpEngineCore::isReadOnly() const +{ + return d->readOnly; +} + +void QHelpEngineCore::setReadOnly(bool enable) +{ + if (d->readOnly == enable) + return; + + d->readOnly = enable; + d->init(collectionFile()); +} + +/*! + \since 5.13 + + Returns the filter engine associated with this help engine. + The filter engine allows for adding, changing, and removing existing + filters for this help engine. To use the engine you also have to call + \l setUsesFilterEngine() set to \c true. +*/ +QHelpFilterEngine *QHelpEngineCore::filterEngine() const +{ + return d->filterEngine; +} + +/*! + Sets up the help engine by processing the information found + in the collection file and returns true if successful; otherwise + returns false. + + By calling the function, the help + engine is forced to initialize itself immediately. Most of + the times, this function does not have to be called + explicitly because getter functions which depend on a correctly + set up help engine do that themselves. + + \note \c{qsqlite4.dll} needs to be deployed with the application as the + help system uses the sqlite driver when loading help collections. +*/ +bool QHelpEngineCore::setupData() +{ + d->needsSetup = true; + return d->setup(); +} + +/*! + Creates the file \a fileName and copies all contents from + the current collection file into the newly created file, + and returns true if successful; otherwise returns false. + + The copying process makes sure that file references to Qt + Collection files (\c{.qch}) files are updated accordingly. +*/ +bool QHelpEngineCore::copyCollectionFile(const QString &fileName) +{ + if (!d->setup()) + return false; + return d->collectionHandler->copyCollectionFile(fileName); +} + +/*! + Returns the namespace name defined for the Qt compressed help file (.qch) + specified by its \a documentationFileName. If the file is not valid, an + empty string is returned. + + \sa documentationFileName() +*/ +QString QHelpEngineCore::namespaceName(const QString &documentationFileName) +{ + void *pointer = const_cast(&documentationFileName); + QHelpDBReader reader(documentationFileName, QHelpGlobal::uniquifyConnectionName( + "GetNamespaceName"_L1, pointer), nullptr); + if (reader.init()) + return reader.namespaceName(); + return {}; +} + +/*! + Registers the Qt compressed help file (.qch) contained in the file + \a documentationFileName. One compressed help file, uniquely + identified by its namespace can only be registered once. + True is returned if the registration was successful, otherwise + false. + + \sa unregisterDocumentation(), error() +*/ +bool QHelpEngineCore::registerDocumentation(const QString &documentationFileName) +{ + d->error.clear(); + d->needsSetup = true; + return d->collectionHandler->registerDocumentation(documentationFileName); +} + +/*! + Unregisters the Qt compressed help file (.qch) identified by its + \a namespaceName from the help collection. Returns true + on success, otherwise false. + + \sa registerDocumentation(), error() +*/ +bool QHelpEngineCore::unregisterDocumentation(const QString &namespaceName) +{ + d->error.clear(); + d->needsSetup = true; + return d->collectionHandler->unregisterDocumentation(namespaceName); +} + +/*! + Returns the absolute file name of the Qt compressed help file (.qch) + identified by the \a namespaceName. If there is no Qt compressed help file + with the specified namespace registered, an empty string is returned. + + \sa namespaceName() +*/ +QString QHelpEngineCore::documentationFileName(const QString &namespaceName) +{ + if (!d->setup()) + return {}; + + const QHelpCollectionHandler::FileInfo fileInfo = + d->collectionHandler->registeredDocumentation(namespaceName); + + if (fileInfo.namespaceName.isEmpty()) + return {}; + + if (QDir::isAbsolutePath(fileInfo.fileName)) + return fileInfo.fileName; + + return QFileInfo(QFileInfo(d->collectionHandler->collectionFile()).absolutePath() + + u'/' + fileInfo.fileName).absoluteFilePath(); +} + +/*! + Returns a list of all registered Qt compressed help files of the current collection file. + The returned names are the namespaces of the registered Qt compressed help files (.qch). +*/ +QStringList QHelpEngineCore::registeredDocumentations() const +{ + if (!d->setup()) + return {}; + const auto &docList = d->collectionHandler->registeredDocumentations(); + QStringList list; + for (const QHelpCollectionHandler::FileInfo &info : docList) + list.append(info.namespaceName); + return list; +} + +/*! + \deprecated + + QHelpFilterEngine::filters() should be used instead. + + Returns a list of custom filters. + + \sa addCustomFilter(), removeCustomFilter() +*/ +QStringList QHelpEngineCore::customFilters() const +{ + if (!d->setup()) + return {}; + return d->collectionHandler->customFilters(); +} + +/*! + \deprecated + + QHelpFilterEngine::setFilterData() should be used instead. + + Adds the new custom filter \a filterName. The filter attributes + are specified by \a attributes. If the filter already exists, + its attribute set is replaced. The function returns true if + the operation succeeded, otherwise it returns false. + + \sa customFilters(), removeCustomFilter() +*/ +bool QHelpEngineCore::addCustomFilter(const QString &filterName, + const QStringList &attributes) +{ + d->error.clear(); + d->needsSetup = true; + return d->collectionHandler->addCustomFilter(filterName, attributes); +} + +/*! + \deprecated + + QHelpFilterEngine::removeFilter() should be used instead. + + Returns true if the filter \a filterName was removed successfully, + otherwise false. + + \sa addCustomFilter(), customFilters() +*/ +bool QHelpEngineCore::removeCustomFilter(const QString &filterName) +{ + d->error.clear(); + d->needsSetup = true; + return d->collectionHandler->removeCustomFilter(filterName); +} + +/*! + \deprecated + + QHelpFilterEngine::availableComponents() should be used instead. + + Returns a list of all defined filter attributes. +*/ +QStringList QHelpEngineCore::filterAttributes() const +{ + if (!d->setup()) + return {}; + return d->collectionHandler->filterAttributes(); +} + +/*! + \deprecated + + QHelpFilterEngine::filterData() should be used instead. + + Returns a list of filter attributes used by the custom + filter \a filterName. +*/ +QStringList QHelpEngineCore::filterAttributes(const QString &filterName) const +{ + if (!d->setup()) + return {}; + return d->collectionHandler->filterAttributes(filterName); +} + +/*! + \deprecated + \property QHelpEngineCore::currentFilter + \brief the name of the custom filter currently applied. + \since 4.5 + + QHelpFilterEngine::activeFilter() should be used instead. + + Setting this property will save the new custom filter permanently in the + help collection file. To set a custom filter without saving it + permanently, disable the auto save filter mode. + + \sa autoSaveFilter() +*/ +QString QHelpEngineCore::currentFilter() const +{ + if (!d->setup()) + return {}; + + if (d->currentFilter.isEmpty()) { + const QString &filter = + d->collectionHandler->customValue("CurrentFilter"_L1, QString()).toString(); + if (!filter.isEmpty() && d->collectionHandler->customFilters().contains(filter)) + d->currentFilter = filter; + } + return d->currentFilter; +} + +void QHelpEngineCore::setCurrentFilter(const QString &filterName) +{ + if (!d->setup() || filterName == d->currentFilter) + return; + d->currentFilter = filterName; + if (d->autoSaveFilter) + d->collectionHandler->setCustomValue("CurrentFilter"_L1, d->currentFilter); + emit currentFilterChanged(d->currentFilter); +} + +/*! + \deprecated + + QHelpFilterEngine::filterData() should be used instead. + + Returns a list of filter attributes for the different filter sections + defined in the Qt compressed help file with the given namespace + \a namespaceName. +*/ +QList QHelpEngineCore::filterAttributeSets(const QString &namespaceName) const +{ + if (!d->setup()) + return {}; + return d->collectionHandler->filterAttributeSets(namespaceName); +} + +/*! + \deprecated + + files() should be used instead. + + Returns a list of files contained in the Qt compressed help file \a + namespaceName. The files can be filtered by \a filterAttributes as + well as by their extension \a extensionFilter (e.g. 'html'). +*/ +QList QHelpEngineCore::files(const QString namespaceName, + const QStringList &filterAttributes, + const QString &extensionFilter) +{ + QList res; + if (!d->setup()) + return res; + + QUrl url; + url.setScheme("qthelp"_L1); + url.setAuthority(namespaceName); + + const QStringList &files = d->collectionHandler->files( + namespaceName, filterAttributes, extensionFilter); + for (const QString &file : files) { + url.setPath("/"_L1 + file); + res.append(url); + } + return res; +} + +/*! + Returns a list of files contained in the Qt compressed help file + for \a namespaceName. The files can be filtered by \a filterName as + well as by their extension \a extensionFilter (for example, 'html'). +*/ +QList QHelpEngineCore::files(const QString namespaceName, + const QString &filterName, + const QString &extensionFilter) +{ + QList res; + if (!d->setup()) + return res; + + QUrl url; + url.setScheme("qthelp"_L1); + url.setAuthority(namespaceName); + + const QStringList &files = d->collectionHandler->files( + namespaceName, filterName, extensionFilter); + for (const QString &file : files) { + url.setPath("/"_L1 + file); + res.append(url); + } + return res; +} + +/*! + Returns the corrected URL for the \a url that may refer to + a different namespace defined by the virtual folder defined + as a part of the \a url. If the virtual folder matches the namespace + of the \a url, the method just checks if the file exists and returns + the same \a url. When the virtual folder doesn't match the namespace + of the \a url, it tries to find the best matching namespace according + to the active filter. When the namespace is found, it returns the + corrected URL if the file exists, otherwise it returns an invalid URL. +*/ +QUrl QHelpEngineCore::findFile(const QUrl &url) const +{ + if (!d->setup()) + return url; + + QUrl result = d->usesFilterEngine + ? d->collectionHandler->findFile(url, d->filterEngine->activeFilter()) + : d->collectionHandler->findFile(url, filterAttributes(currentFilter())); // obsolete + if (!result.isEmpty()) + return result; + + result = d->usesFilterEngine + ? d->collectionHandler->findFile(url, QString()) + : d->collectionHandler->findFile(url, QStringList()); // obsolete + if (!result.isEmpty()) + return result; + + return url; +} + +/*! + Returns the data of the file specified by \a url. If the + file does not exist, an empty QByteArray is returned. + + \sa findFile() +*/ +QByteArray QHelpEngineCore::fileData(const QUrl &url) const +{ + if (!d->setup()) + return {}; + return d->collectionHandler->fileData(url); +} + +/*! + \since 5.15 + + Returns a list of all the document links found for the \a id. + The returned list contents depend on the current filter, and therefore only the keywords + registered for the current filter will be returned. +*/ +QList QHelpEngineCore::documentsForIdentifier(const QString &id) const +{ + return documentsForIdentifier( + id, d->usesFilterEngine ? d->filterEngine->activeFilter() : d->currentFilter); +} + +/*! + \since 5.15 + + Returns a list of the document links found for the \a id, filtered by \a filterName. + The returned list contents depend on the passed filter, and therefore only the keywords + registered for this filter will be returned. If you want to get all results unfiltered, + pass empty string as \a filterName. +*/ +QList QHelpEngineCore::documentsForIdentifier(const QString &id, const QString &filterName) const +{ + if (!d->setup()) + return {}; + + if (d->usesFilterEngine) + return d->collectionHandler->documentsForIdentifier(id, filterName); + return d->collectionHandler->documentsForIdentifier(id, filterAttributes(filterName)); +} + +/*! + \since 5.15 + + Returns a list of all the document links found for the \a keyword. + The returned list contents depend on the current filter, and therefore only the keywords + registered for the current filter will be returned. +*/ +QList QHelpEngineCore::documentsForKeyword(const QString &keyword) const +{ + return documentsForKeyword( + keyword, d->usesFilterEngine ? d->filterEngine->activeFilter() : d->currentFilter); +} + +/*! + \since 5.15 + + Returns a list of the document links found for the \a keyword, filtered by \a filterName. + The returned list contents depend on the passed filter, and therefore only the keywords + registered for this filter will be returned. If you want to get all results unfiltered, + pass empty string as \a filterName. +*/ +QList QHelpEngineCore::documentsForKeyword(const QString &keyword, const QString &filterName) const +{ + if (!d->setup()) + return {}; + + if (d->usesFilterEngine) + return d->collectionHandler->documentsForKeyword(keyword, filterName); + return d->collectionHandler->documentsForKeyword(keyword, filterAttributes(filterName)); +} + +/*! + Removes the \a key from the settings section in the + collection file. Returns true if the value was removed + successfully, otherwise false. + + \sa customValue(), setCustomValue() +*/ +bool QHelpEngineCore::removeCustomValue(const QString &key) +{ + d->error.clear(); + return d->collectionHandler->removeCustomValue(key); +} + +/*! + Returns the value assigned to the \a key. If the requested + key does not exist, the specified \a defaultValue is + returned. + + \sa setCustomValue(), removeCustomValue() +*/ +QVariant QHelpEngineCore::customValue(const QString &key, const QVariant &defaultValue) const +{ + if (!d->setup()) + return {}; + return d->collectionHandler->customValue(key, defaultValue); +} + +/*! + Save the \a value under the \a key. If the key already exist, + the value will be overwritten. Returns true if the value was + saved successfully, otherwise false. + + \sa customValue(), removeCustomValue() +*/ +bool QHelpEngineCore::setCustomValue(const QString &key, const QVariant &value) +{ + d->error.clear(); + return d->collectionHandler->setCustomValue(key, value); +} + +/*! + Returns the meta data for the Qt compressed help file \a + documentationFileName. If there is no data available for + \a name, an invalid QVariant() is returned. The meta + data is defined when creating the Qt compressed help file and + cannot be modified later. Common meta data includes e.g. + the author of the documentation. +*/ +QVariant QHelpEngineCore::metaData(const QString &documentationFileName, + const QString &name) +{ + QHelpDBReader reader(documentationFileName, "GetMetaData"_L1, nullptr); + + if (reader.init()) + return reader.metaData(name); + return {}; +} + +/*! + Returns a description of the last error that occurred. +*/ +QString QHelpEngineCore::error() const +{ + return d->error; +} + +/*! + \property QHelpEngineCore::autoSaveFilter + \brief whether QHelpEngineCore is in auto save filter mode or not. + \since 4.5 + + If QHelpEngineCore is in auto save filter mode, the current filter is + automatically saved when it is changed by the QHelpFilterEngine::setActiveFilter() + function. The filter is saved persistently in the help collection file. + + By default, this mode is on. +*/ +void QHelpEngineCore::setAutoSaveFilter(bool save) +{ + d->autoSaveFilter = save; +} + +bool QHelpEngineCore::autoSaveFilter() const +{ + return d->autoSaveFilter; +} + +/*! + \since 5.13 + + Enables or disables the new filter engine functionality + inside the help engine, according to the passed \a uses parameter. + + \sa filterEngine() +*/ +void QHelpEngineCore::setUsesFilterEngine(bool uses) +{ + d->usesFilterEngine = uses; +} + +/*! + \since 5.13 + + Returns whether the help engine uses the new filter functionality. + + \sa filterEngine() +*/ +bool QHelpEngineCore::usesFilterEngine() const +{ + return d->usesFilterEngine; +} + +#if QT_CONFIG(future) +static QUrl constructUrl(const QString &namespaceName, const QString &folderName, + const QString &relativePath) +{ + const int idx = relativePath.indexOf(u'#'); + const QString &rp = idx < 0 ? relativePath : relativePath.left(idx); + const QString anchor = idx < 0 ? QString() : relativePath.mid(idx + 1); + return QHelpCollectionHandler::buildQUrl(namespaceName, folderName, rp, anchor); +} + +using ContentProviderResult = QList; +using ContentProvider = std::function; +using ContentResult = std::shared_ptr; + +// This trick is needed because the c'tor of QHelpContentItem is private. +QHelpContentItem *createContentItem(const QString &name = {}, const QUrl &link = {}, + QHelpContentItem *parent = {}) +{ + return new QHelpContentItem(name, link, parent); +} + +static void requestContentHelper(QPromise &promise, const ContentProvider &provider, + const QString &collectionFile) +{ + ContentResult rootItem(createContentItem()); + const ContentProviderResult result = provider(collectionFile); + for (const auto &contentsData : result) { + const QString namespaceName = contentsData.namespaceName; + const QString folderName = contentsData.folderName; + for (const QByteArray &contents : contentsData.contentsList) { + if (promise.isCanceled()) + return; + + if (contents.isEmpty()) + continue; + + QList stack; + QDataStream s(contents); + while (true) { + int depth = 0; + QString link, title; + s >> depth; + s >> link; + s >> title; + if (title.isEmpty()) + break; + +// The example input (depth, link, title): +// +// 0 "graphicaleffects5.html" "Qt 5 Compatibility APIs: Qt Graphical Effects" +// 1 "qtgraphicaleffects5-index.html" "QML Types" +// 2 "qml-qt5compat-graphicaleffects-blend.html" "Blend Type Reference" +// 3 "qml-qt5compat-graphicaleffects-blend-members.html" "List of all members" +// 2 "qml-qt5compat-graphicaleffects-brightnesscontrast.html" "BrightnessContrast Type Reference" +// +// Thus, the valid order of depths is: +// 1. Whenever the item's depth is < 0, we insert the item as its depth is 0. +// 2. The first item's depth must be 0, otherwise we insert the item as its depth is 0. +// 3. When the previous depth was N, the next depth must be in range [0, N+1] inclusively. +// If next item's depth is M > N+1, we insert the item as its depth is N+1. + + if (depth <= 0) { + stack.clear(); + } else if (depth < stack.size()) { + stack = stack.sliced(0, depth); + } else if (depth > stack.size()) { + // Fill the gaps with the last item from the stack (or with the root). + // This branch handles the case when depths are broken, e.g. 0, 2, 2, 1. + // In this case, the 1st item is a root, and 2nd - 4th are all direct + // children of the 1st. + QHelpContentItem *substituteItem = + stack.isEmpty() ? rootItem.get() : stack.constLast(); + while (depth > stack.size()) + stack.append(substituteItem); + } + + const QUrl url = constructUrl(namespaceName, folderName, link); + QHelpContentItem *parent = stack.isEmpty() ? rootItem.get() : stack.constLast(); + stack.push_back(createContentItem(title, url, parent)); + } + } + } + promise.addResult(rootItem); +} + +static ContentProvider contentProviderFromFilterEngine(const QString &filter) +{ + return [filter](const QString &collectionFile) -> ContentProviderResult { + QHelpCollectionHandler collectionHandler(collectionFile); + if (!collectionHandler.openCollectionFile()) + return {}; + return collectionHandler.contentsForFilter(filter); + }; +} + +static ContentProvider contentProviderFromAttributes(const QStringList &attributes) +{ + return [attributes](const QString &collectionFile) -> ContentProviderResult { + QHelpCollectionHandler collectionHandler(collectionFile); + if (!collectionHandler.openCollectionFile()) + return {}; + return collectionHandler.contentsForFilter(attributes); + }; +} + +QFuture QHelpEngineCore::requestContentForCurrentFilter() const +{ + const ContentProvider provider = usesFilterEngine() + ? contentProviderFromFilterEngine(filterEngine()->activeFilter()) + : contentProviderFromAttributes(filterAttributes(d->currentFilter)); + return QtConcurrent::run(requestContentHelper, provider, collectionFile()); +} + +QFuture QHelpEngineCore::requestContent(const QString &filter) const +{ + const ContentProvider provider = usesFilterEngine() + ? contentProviderFromFilterEngine(filter) + : contentProviderFromAttributes(filterAttributes(filter)); + return QtConcurrent::run(requestContentHelper, provider, collectionFile()); +} + +using IndexProvider = std::function; +using IndexResult = QStringList; + +static IndexProvider indexProviderFromFilterEngine(const QString &filter) +{ + return [filter](const QString &collectionFile) -> IndexResult { + QHelpCollectionHandler collectionHandler(collectionFile); + if (!collectionHandler.openCollectionFile()) + return {}; + return collectionHandler.indicesForFilter(filter); + }; +} + +static IndexProvider indexProviderFromAttributes(const QStringList &attributes) +{ + return [attributes](const QString &collectionFile) -> IndexResult { + QHelpCollectionHandler collectionHandler(collectionFile); + if (!collectionHandler.openCollectionFile()) + return {}; + return collectionHandler.indicesForFilter(attributes); + }; +} + +QFuture QHelpEngineCore::requestIndexForCurrentFilter() const +{ + const IndexProvider provider = usesFilterEngine() + ? indexProviderFromFilterEngine(filterEngine()->activeFilter()) + : indexProviderFromAttributes(filterAttributes(d->currentFilter)); + return QtConcurrent::run(std::move(provider), collectionFile()); +} + +QFuture QHelpEngineCore::requestIndex(const QString &filter) const +{ + const IndexProvider provider = usesFilterEngine() + ? indexProviderFromFilterEngine(filter) + : indexProviderFromAttributes(filterAttributes(filter)); + return QtConcurrent::run(std::move(provider), collectionFile()); +} +#endif + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpenginecore.h b/src/assistant/help/qhelpenginecore.h new file mode 100644 index 0000000..8d3861d --- /dev/null +++ b/src/assistant/help/qhelpenginecore.h @@ -0,0 +1,126 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPENGINECORE_H +#define QHELPENGINECORE_H + +#include +#include + +#if QT_CONFIG(future) +#include +#endif + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpEngineCorePrivate; +class QHelpFilterEngine; +struct QHelpLink; + +class QHELP_EXPORT QHelpEngineCore : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool autoSaveFilter READ autoSaveFilter WRITE setAutoSaveFilter) + Q_PROPERTY(QString collectionFile READ collectionFile WRITE setCollectionFile) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) +#if QT_DEPRECATED_SINCE(5, 15) + Q_PROPERTY(QString currentFilter READ currentFilter WRITE setCurrentFilter) +#endif + +public: + explicit QHelpEngineCore(const QString &collectionFile, QObject *parent = nullptr); + virtual ~QHelpEngineCore(); + + bool isReadOnly() const; + void setReadOnly(bool enable); + + QHelpFilterEngine *filterEngine() const; + + bool setupData(); + + QString collectionFile() const; + void setCollectionFile(const QString &fileName); + + bool copyCollectionFile(const QString &fileName); + + static QString namespaceName(const QString &documentationFileName); + bool registerDocumentation(const QString &documentationFileName); + bool unregisterDocumentation(const QString &namespaceName); + QString documentationFileName(const QString &namespaceName); + QStringList registeredDocumentations() const; + QByteArray fileData(const QUrl &url) const; + +// #if QT_DEPRECATED_SINCE(5,13) + QStringList customFilters() const; + bool removeCustomFilter(const QString &filterName); + bool addCustomFilter(const QString &filterName, + const QStringList &attributes); + + QStringList filterAttributes() const; + QStringList filterAttributes(const QString &filterName) const; + + QString currentFilter() const; + void setCurrentFilter(const QString &filterName); + + QList filterAttributeSets(const QString &namespaceName) const; + QList files(const QString namespaceName, const QStringList &filterAttributes, + const QString &extensionFilter = {}); +// #endif + + QList files(const QString namespaceName, const QString &filterName, + const QString &extensionFilter = {}); + QUrl findFile(const QUrl &url) const; + + QList documentsForIdentifier(const QString &id) const; + QList documentsForIdentifier(const QString &id, const QString &filterName) const; + QList documentsForKeyword(const QString &keyword) const; + QList documentsForKeyword(const QString &keyword, const QString &filterName) const; + + bool removeCustomValue(const QString &key); + QVariant customValue(const QString &key, const QVariant &defaultValue = {}) const; + bool setCustomValue(const QString &key, const QVariant &value); + + static QVariant metaData(const QString &documentationFileName, const QString &name); + + QString error() const; + + void setAutoSaveFilter(bool save); + bool autoSaveFilter() const; + + void setUsesFilterEngine(bool uses); + bool usesFilterEngine() const; + +#if QT_CONFIG(future) + QFuture> requestContentForCurrentFilter() const; + QFuture> requestContent(const QString &filter) const; + + QFuture requestIndexForCurrentFilter() const; + QFuture requestIndex(const QString &filter) const; +#endif + +Q_SIGNALS: + void setupStarted(); + void setupFinished(); + void warning(const QString &msg); + +// #if QT_DEPRECATED_SINCE(5,13) + void currentFilterChanged(const QString &newFilter); + void readersAboutToBeInvalidated(); +// #endif + +protected: +#if QT_DEPRECATED_SINCE(6, 8) + QHelpEngineCore(QHelpEngineCorePrivate *helpEngineCorePrivate, QObject *parent); +#endif + +private: + QHelpEngineCorePrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QHELPENGINECORE_H diff --git a/src/assistant/help/qhelpfilterdata.cpp b/src/assistant/help/qhelpfilterdata.cpp new file mode 100644 index 0000000..2baa7ce --- /dev/null +++ b/src/assistant/help/qhelpfilterdata.cpp @@ -0,0 +1,119 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpfilterdata.h" + +#include + +QT_BEGIN_NAMESPACE + +class QHelpFilterDataPrivate : public QSharedData +{ +public: + QHelpFilterDataPrivate() = default; + QHelpFilterDataPrivate(const QHelpFilterDataPrivate &other) + : QSharedData(other) + , m_components(other.m_components) + , m_versions(other.m_versions) + {} + + QStringList m_components; + QList m_versions; +}; + +/*! + \class QHelpFilterData + \since 5.13 + \inmodule QtHelp + \brief The QHelpFilterData class provides details for the filters + used by QHelpFilterEngine. + + By using setComponents() you may constrain the search results to + documents that belong only to components specified on the given list. + By using setVersions() you may constrain the search results to + documents that belong only to versions specified on the given list. + + \sa QHelpFilterEngine +*/ + +/*! + Constructs the empty filter. +*/ +QHelpFilterData::QHelpFilterData() : d(new QHelpFilterDataPrivate) { } + +/*! + Constructs a copy of \a other. +*/ +QHelpFilterData::QHelpFilterData(const QHelpFilterData &) = default; + +/*! + Move-constructs a QHelpFilterData instance, making it point at the same object that \a other was pointing to. +*/ +QHelpFilterData::QHelpFilterData(QHelpFilterData &&) = default; + +/*! + Destroys the filter. +*/ +QHelpFilterData::~QHelpFilterData() = default; + +/*! + Assigns \a other to this filter and returns a reference to this filter. +*/ +QHelpFilterData &QHelpFilterData::operator=(const QHelpFilterData &) = default; + +/*! + Move-assigns \a other to this QHelpFilterData instance. +*/ +QHelpFilterData &QHelpFilterData::operator=(QHelpFilterData &&) = default; + +/*! + \fn void QHelpFilterData::swap(QHelpFilterData &other) + + Swaps the filter \a other with this filter. This + operation is very fast and never fails. +*/ + +bool QHelpFilterData::operator==(const QHelpFilterData &other) const +{ + return (d->m_components == other.d->m_components && d->m_versions == other.d->m_versions); +} + +/*! + Specifies the component list that is used for filtering + the search results. Only results from components in the list + \a components shall be returned. +*/ +void QHelpFilterData::setComponents(const QStringList &components) +{ + d->m_components = components; +} + +/*! + Specifies the version list that is used for filtering + the search results. Only results from versions in the list + \a versions shall be returned. +*/ +void QHelpFilterData::setVersions(const QList &versions) +{ + d->m_versions = versions; +} + +/*! + Returns the component list that is used for filtering + the search results. +*/ +QStringList QHelpFilterData::components() const +{ + return d->m_components; +} + +/*! + Returns the version list that is used for filtering + the search results. +*/ +QList QHelpFilterData::versions() const +{ + return d->m_versions; +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpfilterdata.h b/src/assistant/help/qhelpfilterdata.h new file mode 100644 index 0000000..aaf35a2 --- /dev/null +++ b/src/assistant/help/qhelpfilterdata.h @@ -0,0 +1,42 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPFILTERDATA_H +#define QHELPFILTERDATA_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QVersionNumber; +class QHelpFilterDataPrivate; + +class QHELP_EXPORT QHelpFilterData final +{ +public: + QHelpFilterData(); + QHelpFilterData(const QHelpFilterData &other); + QHelpFilterData(QHelpFilterData &&other); + ~QHelpFilterData(); + + QHelpFilterData &operator=(const QHelpFilterData &other); + QHelpFilterData &operator=(QHelpFilterData &&other); + bool operator==(const QHelpFilterData &other) const; + + void swap(QHelpFilterData &other) Q_DECL_NOTHROW + { d.swap(other.d); } + + void setComponents(const QStringList &components); + void setVersions(const QList &versions); + + QStringList components() const; + QList versions() const; +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +#endif // QHELPFILTERDATA_H diff --git a/src/assistant/help/qhelpfilterengine.cpp b/src/assistant/help/qhelpfilterengine.cpp new file mode 100644 index 0000000..dc139bf --- /dev/null +++ b/src/assistant/help/qhelpfilterengine.cpp @@ -0,0 +1,295 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpfilterengine.h" +#include "qhelpcollectionhandler_p.h" +#include "qhelpenginecore.h" +#include "qhelpfilterdata.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +static const char ActiveFilter[] = "activeFilter"; + +class QHelpFilterEnginePrivate +{ +public: + bool setup(); + + QHelpFilterEngine *q = nullptr; + QHelpEngineCore *m_helpEngine = nullptr; + QHelpCollectionHandler *m_collectionHandler = nullptr; + QString m_currentFilter; + bool m_needsSetup = true; +}; + +bool QHelpFilterEnginePrivate::setup() +{ + if (!m_collectionHandler) + return false; + + if (!m_needsSetup) + return true; + + // Prevent endless loop when connected to setupFinished() signal + // and using from there QHelpFilterEngine, causing setup() to be + // called in turn. + m_needsSetup = false; + + if (!m_helpEngine->setupData()) { + m_needsSetup = true; + return false; + } + + const QString filter = m_collectionHandler->customValue( + QLatin1StringView(ActiveFilter), QString()).toString(); + if (!filter.isEmpty() && m_collectionHandler->filters().contains(filter)) + m_currentFilter = filter; + + emit q->filterActivated(m_currentFilter); + return true; +} + +////////////// + +/*! + \class QHelpFilterEngine + \since 5.13 + \inmodule QtHelp + \brief The QHelpFilterEngine class provides a filtered view of the + help contents. + + The filter engine allows the management of filters associated with + a QHelpEngineCore instance. The help engine internally creates an + instance of the filter engine, which can be accessed by calling + QHelpEngineCore::filterEngine(). Therefore, the public constructor + of this class is disabled. + + The filters are identified by a filter name string. Filter details are + described by the \l QHelpFilterData class. + + The filter engine allows for adding new filters and changing the existing + filters' data through the setFilterData() method. An existing filter can + be removed through the removeFilter() method. + + Out of the registered filters one can be marked as the active one. + The active filter will be used by the associated help engine for returning + filtered results of many different functions, such as content, index, or + search results. If no filter is marked active, the help engine returns the + full results list available. + + The active filter is returned by activeFilter() and it can be changed by + setActiveFilter(). + + \sa QHelpEngineCore +*/ + +/*! + \fn void QHelpFilterEngine::filterActivated(const QString &newFilter) + + This signal is emitted when the active filter is set. \a newFilter + specifies the name of the filter. + + \sa setActiveFilter() +*/ + +/*! + \internal + Constructs the filter engine for \a helpEngine. +*/ +QHelpFilterEngine::QHelpFilterEngine(QHelpEngineCore *helpEngine) + : QObject(helpEngine), + d(new QHelpFilterEnginePrivate) +{ + d->q = this; + d->m_helpEngine = helpEngine; +} + +/*! + \internal + Destroys the existing filter engine. +*/ +QHelpFilterEngine::~QHelpFilterEngine() +{ + delete d; +} + +/*! + \internal + Sets the \a collectionHandler to be used for this filter engine. +*/ +void QHelpFilterEngine::setCollectionHandler(QHelpCollectionHandler *collectionHandler) +{ + d->m_collectionHandler = collectionHandler; + d->m_currentFilter.clear(); + d->m_needsSetup = true; +} + +/*! + Returns the map of all the available namespaces as keys + together with their associated components as values. +*/ +QMap QHelpFilterEngine::namespaceToComponent() const +{ + if (!d->setup()) + return {}; + return d->m_collectionHandler->namespaceToComponent(); +} + +/*! + Returns the map of all the available namespaces as keys + together with their associated versions as values. +*/ +QMap QHelpFilterEngine::namespaceToVersion() const +{ + if (!d->setup()) + return {}; + return d->m_collectionHandler->namespaceToVersion(); +} + +/*! + Returns the list of all filter names defined inside the filter engine. +*/ +QStringList QHelpFilterEngine::filters() const +{ + if (!d->setup()) + return {}; + return d->m_collectionHandler->filters(); +} + +/*! + Returns the list of all available components defined in all + registered documentation files. +*/ +QStringList QHelpFilterEngine::availableComponents() const +{ + if (!d->setup()) + return {}; + return d->m_collectionHandler->availableComponents(); +} + +/*! + \since 5.15 + + Returns the list of all available versions defined in all + registered documentation files. +*/ +QList QHelpFilterEngine::availableVersions() const +{ + if (!d->setup()) + return {}; + return d->m_collectionHandler->availableVersions(); +} + +/*! + Returns the filter details associated with \a filterName. +*/ +QHelpFilterData QHelpFilterEngine::filterData(const QString &filterName) const +{ + if (!d->setup()) + return {}; + return d->m_collectionHandler->filterData(filterName); +} + +/*! + Changes the existing filter details of the filter identified by + \a filterName to \a filterData. If the filter does not exist, a + new filter is created. + + Returns \c true if setting the filter succeeded, otherwise returns \c false. +*/ +bool QHelpFilterEngine::setFilterData(const QString &filterName, const QHelpFilterData &filterData) +{ + if (!d->setup()) + return false; + return d->m_collectionHandler->setFilterData(filterName, filterData); +} + +/*! + Removes the filter identified by \a filterName. + + Returns \c true if removing the filter succeeded, otherwise returns + \c false. +*/ +bool QHelpFilterEngine::removeFilter(const QString &filterName) +{ + if (!d->setup()) + return false; + return d->m_collectionHandler->removeFilter(filterName); +} + +/*! + Returns the name of the currently active filter. +*/ +QString QHelpFilterEngine::activeFilter() const +{ + if (!d->setup()) + return {}; + return d->m_currentFilter; +} + +/*! + Changes the currently active filter to \a filterName. + + Returns \c true if changing the filter succeeded, otherwise + returns \c false. +*/ +bool QHelpFilterEngine::setActiveFilter(const QString &filterName) +{ + if (!d->setup()) + return false; + + if (filterName == d->m_currentFilter) + return true; + + if (!filterName.isEmpty() && !d->m_collectionHandler->filters().contains(filterName)) + return false; + + d->m_currentFilter = filterName; + d->m_collectionHandler->setCustomValue(QLatin1StringView(ActiveFilter), d->m_currentFilter); + emit filterActivated(d->m_currentFilter); + return true; +} + +/*! + Returns the list of all registered documentation namespaces that match + the filter identified by \a filterName. +*/ +QStringList QHelpFilterEngine::namespacesForFilter(const QString &filterName) const +{ + if (!d->setup()) + return {}; + return d->m_collectionHandler->namespacesForFilter(filterName); +} + +/*! + \since 5.15 + + Returns a sorted list of available indices. + The returned list contents depend on the active filter, and therefore only + the indices registered for the active filter will be returned. +*/ +QStringList QHelpFilterEngine::indices() const +{ + return indices(activeFilter()); +} + +/*! + \since 5.15 + + Returns a sorted list of available indices, filtered by \a filterName. + The returned list contents depend on the passed filter, and therefore only + the indices registered for this filter will be returned. + If you want to get all available indices unfiltered, + pass empty string as \a filterName. +*/ +QStringList QHelpFilterEngine::indices(const QString &filterName) const +{ + if (!d->setup()) + return {}; + return d->m_collectionHandler->indicesForFilter(filterName); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpfilterengine.h b/src/assistant/help/qhelpfilterengine.h new file mode 100644 index 0000000..a16a204 --- /dev/null +++ b/src/assistant/help/qhelpfilterengine.h @@ -0,0 +1,62 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPFILTERENGINE_H +#define QHELPFILTERENGINE_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QHelpCollectionHandler; +class QHelpEngineCore; +class QHelpFilterData; +class QHelpFilterEnginePrivate; +template +class QMap; +class QVersionNumber; + +class QHELP_EXPORT QHelpFilterEngine : public QObject +{ + Q_OBJECT +public: + QMap namespaceToComponent() const; + QMap namespaceToVersion() const; + + QStringList filters() const; + + QString activeFilter() const; + bool setActiveFilter(const QString &filterName); + + QStringList availableComponents() const; + QList availableVersions() const; + + QHelpFilterData filterData(const QString &filterName) const; + bool setFilterData(const QString &filterName, const QHelpFilterData &filterData); + + bool removeFilter(const QString &filterName); + + QStringList namespacesForFilter(const QString &filterName) const; + + QStringList indices() const; + QStringList indices(const QString &filterName) const; + +Q_SIGNALS: + void filterActivated(const QString &newFilter); + +protected: + explicit QHelpFilterEngine(QHelpEngineCore *helpEngine); + virtual ~QHelpFilterEngine(); + +private: + void setCollectionHandler(QHelpCollectionHandler *collectionHandler); + + QHelpFilterEnginePrivate *d; + friend class QHelpEngineCorePrivate; +}; + +QT_END_NAMESPACE + +#endif // QHELPFILTERENGINE_H diff --git a/src/assistant/help/qhelpfiltersettingswidget.cpp b/src/assistant/help/qhelpfiltersettingswidget.cpp new file mode 100644 index 0000000..319111f --- /dev/null +++ b/src/assistant/help/qhelpfiltersettingswidget.cpp @@ -0,0 +1,447 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpfilterdata.h" +#include "qfilternamedialog_p.h" +#include "qhelpfiltersettingswidget.h" +#include "ui_qhelpfiltersettingswidget.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QHelpFilterSettings final +{ +public: + void setFilter(const QString &filterName, const QHelpFilterData &filterData) + { + m_filterToData.insert(filterName, filterData); + } + void removeFilter(const QString &filterName) { m_filterToData.remove(filterName); } + QHelpFilterData filterData(const QString &filterName) const + { + return m_filterToData.value(filterName); + } + QMap filters() const { return m_filterToData; } + + void setCurrentFilter(const QString &filterName) { m_currentFilter = filterName; } + QString currentFilter() const { return m_currentFilter; } + +private: + QMap m_filterToData; + QString m_currentFilter; +}; + +static QHelpFilterSettings readSettingsHelper(const QHelpFilterEngine *filterEngine) +{ + QHelpFilterSettings filterSettings; + + const QStringList allFilters = filterEngine->filters(); + for (const QString &filter : allFilters) + filterSettings.setFilter(filter, filterEngine->filterData(filter)); + + filterSettings.setCurrentFilter(filterEngine->activeFilter()); + return filterSettings; +} + +static QMap subtract(const QMap &minuend, + const QMap &subtrahend) +{ + QMap result = minuend; + + for (auto itSubtrahend = subtrahend.cbegin(); itSubtrahend != subtrahend.cend(); ++itSubtrahend) { + auto itResult = result.find(itSubtrahend.key()); + if (itResult != result.end() && itSubtrahend.value() == itResult.value()) + result.erase(itResult); + } + return result; +} + +static bool applySettingsHelper(QHelpFilterEngine *filterEngine, const QHelpFilterSettings &settings) +{ + bool changed = false; + const QHelpFilterSettings oldSettings = readSettingsHelper(filterEngine); + + const auto filtersToRemove = subtract(oldSettings.filters(), settings.filters()); + const auto filtersToAdd = subtract(settings.filters(), oldSettings.filters()); + + const QString ¤tFilter = filterEngine->activeFilter(); + + for (auto it = filtersToRemove.cbegin(); it != filtersToRemove.cend(); ++it) { + filterEngine->removeFilter(it.key()); + if (currentFilter == it.key() && !filtersToAdd.contains(it.key())) + filterEngine->setActiveFilter({}); + changed = true; + } + + for (auto it = filtersToAdd.cbegin(); it != filtersToAdd.cend(); ++it) { + filterEngine->setFilterData(it.key(), it.value()); + changed = true; + } + + if (changed) + filterEngine->setActiveFilter(settings.currentFilter()); + return changed; +} + +static QStringList versionsToStringList(const QList &versions) +{ + QStringList versionList; + for (const QVersionNumber &version : versions) + versionList.append(version.isNull() ? QString() : version.toString()); + return versionList; +} + +static QList stringListToVersions(const QStringList &versionList) +{ + QList versions; + for (const QString &versionString : versionList) + versions.append(QVersionNumber::fromString(versionString)); + return versions; +} + +class QHelpFilterSettingsWidgetPrivate +{ + QHelpFilterSettingsWidget *q_ptr; + Q_DECLARE_PUBLIC(QHelpFilterSettingsWidget) // TODO: remove Q_DECLARE_PUBLIC +public: + QHelpFilterSettingsWidgetPrivate() = default; + + QHelpFilterSettings filterSettings() const { return m_filterSettings; } + void setFilterSettings(const QHelpFilterSettings &settings); + + void updateCurrentFilter(); + void componentsChanged(const QStringList &components); + void versionsChanged(const QStringList &versions); + void addFilterClicked(); + void renameFilterClicked(); + void removeFilterClicked(); + void addFilter(const QString &filterName, const QHelpFilterData &filterData = {}); + void removeFilter(const QString &filterName); + QString getUniqueFilterName(const QString &windowTitle, const QString &initialFilterName); + QString suggestedNewFilterName(const QString &initialFilterName) const; + + QMap m_filterToItem; + QHash m_itemToFilter; + + Ui::QHelpFilterSettingsWidget m_ui; + QStringList m_components; + QList m_versions; + QHelpFilterSettings m_filterSettings; +}; + +void QHelpFilterSettingsWidgetPrivate::setFilterSettings(const QHelpFilterSettings &settings) +{ + QString currentFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) { + if (!m_filterSettings.currentFilter().isEmpty()) + currentFilter = m_filterSettings.currentFilter(); + else + currentFilter = settings.currentFilter(); + } + + m_filterSettings = settings; + + m_ui.filterWidget->clear(); + m_ui.componentWidget->clear(); + m_ui.versionWidget->clear(); + m_itemToFilter.clear(); + m_filterToItem.clear(); + + const auto filters = m_filterSettings.filters(); + for (auto it = filters.cbegin(); it != filters.cend(); ++it) { + const QString &filterName = it.key(); + QListWidgetItem *item = new QListWidgetItem(filterName); + m_ui.filterWidget->addItem(item); + m_itemToFilter.insert(item, filterName); + m_filterToItem.insert(filterName, item); + if (filterName == currentFilter) + m_ui.filterWidget->setCurrentItem(item); + } + + if (!m_ui.filterWidget->currentItem() && !m_filterToItem.isEmpty()) + m_ui.filterWidget->setCurrentItem(m_filterToItem.first()); + + updateCurrentFilter(); +} + +void QHelpFilterSettingsWidgetPrivate::updateCurrentFilter() +{ + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + + const bool filterSelected = !currentFilter.isEmpty(); + m_ui.componentWidget->setEnabled(filterSelected); + m_ui.versionWidget->setEnabled(filterSelected); + m_ui.renameButton->setEnabled(filterSelected); + m_ui.removeButton->setEnabled(filterSelected); + + m_ui.componentWidget->setOptions(m_components, + m_filterSettings.filterData(currentFilter).components()); + m_ui.versionWidget->setOptions(versionsToStringList(m_versions), + versionsToStringList(m_filterSettings.filterData(currentFilter).versions())); +} + +void QHelpFilterSettingsWidgetPrivate::componentsChanged(const QStringList &components) +{ + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) + return; + + QHelpFilterData filterData = m_filterSettings.filterData(currentFilter); + filterData.setComponents(components); + m_filterSettings.setFilter(currentFilter, filterData); +} + +void QHelpFilterSettingsWidgetPrivate::versionsChanged(const QStringList &versions) +{ + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) + return; + + QHelpFilterData filterData = m_filterSettings.filterData(currentFilter); + filterData.setVersions(stringListToVersions(versions)); + m_filterSettings.setFilter(currentFilter, filterData); +} + +void QHelpFilterSettingsWidgetPrivate::addFilterClicked() +{ + const QString newFilterName = getUniqueFilterName(QHelpFilterSettingsWidget::tr("Add Filter"), + suggestedNewFilterName(QHelpFilterSettingsWidget::tr("New Filter"))); + if (newFilterName.isEmpty()) + return; + + addFilter(newFilterName); +} + +void QHelpFilterSettingsWidgetPrivate::renameFilterClicked() +{ + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) + return; + + const QString newFilterName = getUniqueFilterName(QHelpFilterSettingsWidget::tr("Rename Filter"), currentFilter); + if (newFilterName.isEmpty()) + return; + + const QHelpFilterData oldFilterData = m_filterSettings.filterData(currentFilter); + removeFilter(currentFilter); + addFilter(newFilterName, oldFilterData); + + if (m_filterSettings.currentFilter() == currentFilter) + m_filterSettings.setCurrentFilter(newFilterName); +} + +void QHelpFilterSettingsWidgetPrivate::removeFilterClicked() +{ + Q_Q(QHelpFilterSettingsWidget); + + const QString ¤tFilter = m_itemToFilter.value(m_ui.filterWidget->currentItem()); + if (currentFilter.isEmpty()) + return; + + if (QMessageBox::question(q, QHelpFilterSettingsWidget::tr("Remove Filter"), + QHelpFilterSettingsWidget::tr("Are you sure you want to remove the \"%1\" filter?") + .arg(currentFilter), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + + removeFilter(currentFilter); + + if (m_filterSettings.currentFilter() == currentFilter) + m_filterSettings.setCurrentFilter({}); +} + +void QHelpFilterSettingsWidgetPrivate::addFilter(const QString &filterName, + const QHelpFilterData &filterData) +{ + QListWidgetItem *item = new QListWidgetItem(filterName); + m_filterSettings.setFilter(filterName, filterData); + m_filterToItem.insert(filterName, item); + m_itemToFilter.insert(item, filterName); + m_ui.filterWidget->insertItem(m_filterToItem.keys().indexOf(filterName), item); + + m_ui.filterWidget->setCurrentItem(item); + updateCurrentFilter(); +} + +void QHelpFilterSettingsWidgetPrivate::removeFilter(const QString &filterName) +{ + QListWidgetItem *item = m_filterToItem.value(filterName); + m_itemToFilter.remove(item); + m_filterToItem.remove(filterName); + delete item; + m_filterSettings.removeFilter(filterName); +} + +QString QHelpFilterSettingsWidgetPrivate::getUniqueFilterName(const QString &windowTitle, + const QString &initialFilterName) +{ + Q_Q(QHelpFilterSettingsWidget); + + QString newFilterName = initialFilterName; + while (true) { + QFilterNameDialog dialog(q); + dialog.setWindowTitle(windowTitle); + dialog.setFilterName(newFilterName); + if (dialog.exec() == QDialog::Rejected) + return {}; + + newFilterName = dialog.filterName(); + if (!m_filterToItem.contains(newFilterName)) + break; + + if (QMessageBox::warning(q, QHelpFilterSettingsWidget::tr("Filter Exists"), + QHelpFilterSettingsWidget::tr("The filter \"%1\" already exists.").arg(newFilterName), + QMessageBox::Retry | QMessageBox::Cancel) == QMessageBox::Cancel) { + return {}; + } + } + return newFilterName; +} + +QString QHelpFilterSettingsWidgetPrivate::suggestedNewFilterName(const QString &initialFilterName) const +{ + QString newFilterName = initialFilterName; + int counter = 1; + while (m_filterToItem.contains(newFilterName)) + newFilterName = initialFilterName + u' ' + QString::number(++counter); + return newFilterName; +} + +/*! + \class QHelpFilterSettingsWidget + \inmodule QtHelp + \since 5.15 + \brief The QHelpFilterSettingsWidget class provides a widget that allows + for creating, editing and removing filters. + + The instance of QHelpFilterSettingsWidget may be a part of + a preferences dialog. Before showing the dialog, \l setAvailableComponents() + and \l setAvailableVersions() should be called, otherwise the filter + settings widget will only offer a creation of empty filters, + which wouldn't be useful. In addition, \l readSettings should also + be called to fill up the filter settings widget with the list of filters + already stored in the filter engine. The creation of new filters, + modifications to existing filters and removal of unneeded filters are + handled by the widget automatically. If you want to store the current + state of the widget and apply it to the filter engine e.g. after + the user clicked the apply button - call \l applySettings(). +*/ + +/*! + Constructs a filter settings widget with \a parent as parent widget. +*/ +QHelpFilterSettingsWidget::QHelpFilterSettingsWidget(QWidget *parent) + : QWidget(parent) + , d_ptr(new QHelpFilterSettingsWidgetPrivate()) +{ + Q_D(QHelpFilterSettingsWidget); + d->q_ptr = this; + d->m_ui.setupUi(this); + + // TODO: make resources configurable + QString resourcePath = ":/qt-project.org/assistant/images/"_L1; +#ifdef Q_OS_MACOS + resourcePath.append("mac"_L1); +#else + resourcePath.append("win"_L1); +#endif + d->m_ui.addButton->setIcon(QIcon(resourcePath + "/plus.png"_L1)); + d->m_ui.removeButton->setIcon(QIcon(resourcePath + "/minus.png"_L1)); + + connect(d->m_ui.componentWidget, &QOptionsWidget::optionSelectionChanged, + this, [this](const QStringList &options) { + Q_D(QHelpFilterSettingsWidget); + d->componentsChanged(options); + }); + connect(d->m_ui.versionWidget, &QOptionsWidget::optionSelectionChanged, + this, [this](const QStringList &options) { + Q_D(QHelpFilterSettingsWidget); + d->versionsChanged(options); + }); + connect(d->m_ui.filterWidget, &QListWidget::currentItemChanged, + this, [this](QListWidgetItem *) { + Q_D(QHelpFilterSettingsWidget); + d->updateCurrentFilter(); + }); + connect(d->m_ui.filterWidget, &QListWidget::itemDoubleClicked, + this, [this](QListWidgetItem *) { + Q_D(QHelpFilterSettingsWidget); + d->renameFilterClicked(); + }); + + // TODO: repeat these actions on context menu + connect(d->m_ui.addButton, &QAbstractButton::clicked, this, [this] { + Q_D(QHelpFilterSettingsWidget); + d->addFilterClicked(); + }); + connect(d->m_ui.renameButton, &QAbstractButton::clicked, this, [this] { + Q_D(QHelpFilterSettingsWidget); + d->renameFilterClicked(); + }); + connect(d->m_ui.removeButton, &QAbstractButton::clicked, this, [this] { + Q_D(QHelpFilterSettingsWidget); + d->removeFilterClicked(); + }); + + d->m_ui.componentWidget->setNoOptionText(tr("No Component")); + d->m_ui.componentWidget->setInvalidOptionText(tr("Invalid Component")); + d->m_ui.versionWidget->setNoOptionText(tr("No Version")); + d->m_ui.versionWidget->setInvalidOptionText(tr("Invalid Version")); +} + +/*! + Destroys the filter settings widget. +*/ +QHelpFilterSettingsWidget::~QHelpFilterSettingsWidget() = default; + +/*! + Sets the list of all available components to \a components. + \sa QHelpFilterEngine::availableComponents() +*/ +void QHelpFilterSettingsWidget::setAvailableComponents(const QStringList &components) +{ + Q_D(QHelpFilterSettingsWidget); + d->m_components = components; + d->updateCurrentFilter(); +} + +/*! + Sets the list of all available version numbers to \a versions. + \sa QHelpFilterEngine::availableVersions() +*/ +void QHelpFilterSettingsWidget::setAvailableVersions(const QList &versions) +{ + Q_D(QHelpFilterSettingsWidget); + d->m_versions = versions; + d->updateCurrentFilter(); +} + +/*! + Reads the filter settings stored inside \a filterEngine and sets up + this filter settings widget accordingly. +*/ +void QHelpFilterSettingsWidget::readSettings(const QHelpFilterEngine *filterEngine) +{ + Q_D(QHelpFilterSettingsWidget); + const QHelpFilterSettings settings = readSettingsHelper(filterEngine); + d->setFilterSettings(settings); +} + +/*! + Writes the filter settings, currently presented in this filter settings + widget, to the \a filterEngine. The old settings stored in the filter + engine will be overwritten. Returns \c true on success. +*/ +bool QHelpFilterSettingsWidget::applySettings(QHelpFilterEngine *filterEngine) const +{ + Q_D(const QHelpFilterSettingsWidget); + return applySettingsHelper(filterEngine, d->filterSettings()); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpfiltersettingswidget.h b/src/assistant/help/qhelpfiltersettingswidget.h new file mode 100644 index 0000000..136c4f7 --- /dev/null +++ b/src/assistant/help/qhelpfiltersettingswidget.h @@ -0,0 +1,40 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPFILTERSETTINGSWIDGET_H +#define QHELPFILTERSETTINGSWIDGET_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QHelpFilterEngine; +class QHelpFilterSettingsWidgetPrivate; +class QVersionNumber; + +class QHELP_EXPORT QHelpFilterSettingsWidget : public QWidget +{ + Q_OBJECT +public: + explicit QHelpFilterSettingsWidget(QWidget *parent = nullptr); + + ~QHelpFilterSettingsWidget(); + + void setAvailableComponents(const QStringList &components); + void setAvailableVersions(const QList &versions); + + // TODO: filterEngine may be moved to c'tor or to setFilterEngine() setter + void readSettings(const QHelpFilterEngine *filterEngine); + bool applySettings(QHelpFilterEngine *filterEngine) const; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QHelpFilterSettingsWidget) + Q_DISABLE_COPY_MOVE(QHelpFilterSettingsWidget) +}; + +QT_END_NAMESPACE + +#endif // QHELPFILTERSETTINGSWIDGET_H diff --git a/src/assistant/help/qhelpfiltersettingswidget.ui b/src/assistant/help/qhelpfiltersettingswidget.ui new file mode 100644 index 0000000..7e16e3f --- /dev/null +++ b/src/assistant/help/qhelpfiltersettingswidget.ui @@ -0,0 +1,83 @@ + + + QHelpFilterSettingsWidget + + + + 0 + 0 + 347 + 127 + + + + Form + + + + + + Filter + + + + + + + QFrame::NoFrame + + + Components + + + + + + + Versions + + + + + + + + + + + + + + + + Add... + + + + + + + Rename... + + + + + + + Remove + + + + + + + + QOptionsWidget + QWidget +
qoptionswidget_p.h
+ 1 +
+
+ + +
diff --git a/src/assistant/help/qhelpindexwidget.cpp b/src/assistant/help/qhelpindexwidget.cpp new file mode 100644 index 0000000..39fc9f9 --- /dev/null +++ b/src/assistant/help/qhelpindexwidget.cpp @@ -0,0 +1,317 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpindexwidget.h" +#include "qhelpenginecore.h" +#include "qhelplink.h" + +#if QT_CONFIG(future) +#include +#endif + +QT_BEGIN_NAMESPACE + +class QHelpIndexModelPrivate +{ +#if QT_CONFIG(future) + using FutureProvider = std::function()>; + + struct WatcherDeleter + { + void operator()(QFutureWatcherBase *watcher) { + watcher->disconnect(); + watcher->cancel(); + watcher->waitForFinished(); + delete watcher; + } + }; +#endif + +public: +#if QT_CONFIG(future) + void createIndex(const FutureProvider &futureProvider); +#endif + + QHelpIndexModel *q = nullptr; + QHelpEngineCore *helpEngine = nullptr; + QStringList indices = {}; +#if QT_CONFIG(future) + std::unique_ptr, WatcherDeleter> watcher = {}; +#endif +}; + +#if QT_CONFIG(future) +void QHelpIndexModelPrivate::createIndex(const FutureProvider &futureProvider) +{ + const bool wasRunning = bool(watcher); + watcher.reset(new QFutureWatcher); + QObject::connect(watcher.get(), &QFutureWatcherBase::finished, q, [this] { + if (!watcher->isCanceled()) { + indices = watcher->result(); + q->filter({}); + } + watcher.release()->deleteLater(); + emit q->indexCreated(); + }); + watcher->setFuture(futureProvider()); + + if (wasRunning) + return; + + indices.clear(); + q->filter({}); + emit q->indexCreationStarted(); +} +#endif + +/*! + \class QHelpIndexModel + \since 4.4 + \inmodule QtHelp + \brief The QHelpIndexModel class provides a model that + supplies index keywords to views. +*/ + +/*! + \fn void QHelpIndexModel::indexCreationStarted() + + This signal is emitted when the creation of a new index + has started. The current index is invalid from this + point on until the signal indexCreated() is emitted. + + \sa isCreatingIndex() +*/ + +/*! + \fn void QHelpIndexModel::indexCreated() + + This signal is emitted when the index has been created. +*/ + +QHelpIndexModel::QHelpIndexModel(QHelpEngineCore *helpEngine) + : QStringListModel(helpEngine) + , d(new QHelpIndexModelPrivate{this, helpEngine}) +{} + +QHelpIndexModel::~QHelpIndexModel() +{ + delete d; +} + +/*! + \since 6.8 + + Creates a new index by querying the help system for keywords for the current filter. +*/ +void QHelpIndexModel::createIndexForCurrentFilter() +{ +#if QT_CONFIG(future) + d->createIndex([this] { return d->helpEngine->requestIndexForCurrentFilter(); }); +#endif +} + +/*! + Creates a new index by querying the help system for + keywords for the specified custom \a filter name. +*/ +void QHelpIndexModel::createIndex(const QString &filter) +{ +#if QT_CONFIG(future) + d->createIndex([this, filter] { return d->helpEngine->requestIndex(filter); }); +#endif +} + +// TODO: Remove me +void QHelpIndexModel::insertIndices() +{} + +/*! + Returns true if the index is currently built up, otherwise + false. +*/ +bool QHelpIndexModel::isCreatingIndex() const +{ +#if QT_CONFIG(future) + return bool(d->watcher); +#else + return false; +#endif +} + +/*! + \since 5.15 + + Returns the associated help engine that manages this model. +*/ +QHelpEngineCore *QHelpIndexModel::helpEngine() const +{ + return d->helpEngine; +} + +/*! + Filters the indices and returns the model index of the best + matching keyword. In a first step, only the keywords containing + \a filter are kept in the model's index list. Analogously, if + \a wildcard is not empty, only the keywords matched are left + in the index list. In a second step, the best match is + determined and its index model returned. When specifying a + wildcard expression, the \a filter string is used to + search for the best match. +*/ +QModelIndex QHelpIndexModel::filter(const QString &filter, const QString &wildcard) +{ + if (filter.isEmpty()) { + setStringList(d->indices); + return index(-1, 0, {}); + } + + using Checker = std::function; + const auto checkIndices = [this, filter](const Checker &checker) { + QStringList filteredList; + int goodMatch = -1; + int perfectMatch = -1; + for (const QString &index : std::as_const(d->indices)) { + if (checker(index)) { + filteredList.append(index); + if (perfectMatch == -1 && index.startsWith(filter, Qt::CaseInsensitive)) { + if (goodMatch == -1) + goodMatch = filteredList.size() - 1; + if (filter.size() == index.size()) + perfectMatch = filteredList.size() - 1; + } else if (perfectMatch > -1 && index == filter) { + perfectMatch = filteredList.size() - 1; + } + } + } + setStringList(filteredList); + return perfectMatch >= 0 ? perfectMatch : qMax(0, goodMatch); + }; + + int perfectMatch = -1; + if (!wildcard.isEmpty()) { + const auto re = QRegularExpression::wildcardToRegularExpression(wildcard, + QRegularExpression::UnanchoredWildcardConversion); + const QRegularExpression regExp(re, QRegularExpression::CaseInsensitiveOption); + perfectMatch = checkIndices([regExp](const QString &index) { + return index.contains(regExp); + }); + } else { + perfectMatch = checkIndices([filter](const QString &index) { + return index.contains(filter, Qt::CaseInsensitive); + }); + } + return index(perfectMatch, 0, {}); +} + +/*! + \class QHelpIndexWidget + \inmodule QtHelp + \since 4.4 + \brief The QHelpIndexWidget class provides a list view + displaying the QHelpIndexModel. +*/ + +/*! + \fn void QHelpIndexWidget::linkActivated(const QUrl &link, + const QString &keyword) + + \deprecated + + Use documentActivated() instead. + + This signal is emitted when an item is activated and its + associated \a link should be shown. To know where the link + belongs to, the \a keyword is given as a second parameter. +*/ + +/*! + \fn void QHelpIndexWidget::documentActivated(const QHelpLink &document, + const QString &keyword) + + \since 5.15 + + This signal is emitted when an item is activated and its + associated \a document should be shown. To know where the link + belongs to, the \a keyword is given as a second parameter. +*/ + +/*! + \fn void QHelpIndexWidget::documentsActivated(const QList &documents, + const QString &keyword) + + \since 5.15 + + This signal is emitted when the item representing the \a keyword + is activated and the item has more than one document associated. + The \a documents consist of the document titles and their URLs. +*/ + +QHelpIndexWidget::QHelpIndexWidget() +{ + setEditTriggers(QAbstractItemView::NoEditTriggers); + setUniformItemSizes(true); + connect(this, &QAbstractItemView::activated, this, &QHelpIndexWidget::showLink); +} + +void QHelpIndexWidget::showLink(const QModelIndex &index) +{ + if (!index.isValid()) + return; + + QHelpIndexModel *indexModel = qobject_cast(model()); + if (!indexModel) + return; + + const QVariant &v = indexModel->data(index, Qt::DisplayRole); + const QString name = v.isValid() ? v.toString() : QString(); + + const QList &docs = indexModel->helpEngine()->documentsForKeyword(name); + if (docs.size() > 1) { + emit documentsActivated(docs, name); +#if QT_DEPRECATED_SINCE(5, 15) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + QMultiMap links; + for (const auto &doc : docs) + links.insert(doc.title, doc.url); + emit linksActivated(links, name); + QT_WARNING_POP +#endif + } else if (!docs.isEmpty()) { + emit documentActivated(docs.first(), name); +#if QT_DEPRECATED_SINCE(5, 15) + QT_WARNING_PUSH + QT_WARNING_DISABLE_DEPRECATED + emit linkActivated(docs.first().url, name); + QT_WARNING_POP +#endif + } +} + +/*! + Activates the current item which will result eventually in + the emitting of a linkActivated() or linksActivated() + signal. +*/ +void QHelpIndexWidget::activateCurrentItem() +{ + showLink(currentIndex()); +} + +/*! + Filters the indices according to \a filter or \a wildcard. + The item with the best match is set as current item. + + \sa QHelpIndexModel::filter() +*/ +void QHelpIndexWidget::filterIndices(const QString &filter, const QString &wildcard) +{ + QHelpIndexModel *indexModel = qobject_cast(model()); + if (!indexModel) + return; + const QModelIndex &idx = indexModel->filter(filter, wildcard); + if (idx.isValid()) + setCurrentIndex(idx); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpindexwidget.h b/src/assistant/help/qhelpindexwidget.h new file mode 100644 index 0000000..c351836 --- /dev/null +++ b/src/assistant/help/qhelpindexwidget.h @@ -0,0 +1,77 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPINDEXWIDGET_H +#define QHELPINDEXWIDGET_H + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QHelpEngineCore; +class QHelpEnginePrivate; +class QHelpIndexModelPrivate; +struct QHelpLink; + +class QHELP_EXPORT QHelpIndexModel : public QStringListModel +{ + Q_OBJECT + +public: + void createIndexForCurrentFilter(); + void createIndex(const QString &customFilterName); + QModelIndex filter(const QString &filter, const QString &wildcard = {}); + + bool isCreatingIndex() const; + QHelpEngineCore *helpEngine() const; + +Q_SIGNALS: + void indexCreationStarted(); + void indexCreated(); + +private Q_SLOTS: + void insertIndices(); + +private: + QHelpIndexModel(QHelpEngineCore *helpEngine); + ~QHelpIndexModel(); + + QHelpIndexModelPrivate *d; + friend class QHelpEnginePrivate; +}; + +class QHELP_EXPORT QHelpIndexWidget : public QListView +{ + Q_OBJECT + Q_MOC_INCLUDE() + +Q_SIGNALS: +#if QT_DEPRECATED_SINCE(5, 15) + QT_DEPRECATED_X("Use documentActivated() instead") + void linkActivated(const QUrl &link, const QString &keyword); + QT_DEPRECATED_X("Use documentsActivated() instead") + void linksActivated(const QMultiMap &links, const QString &keyword); +#endif + void documentActivated(const QHelpLink &document, const QString &keyword); + void documentsActivated(const QList &documents, const QString &keyword); + +public Q_SLOTS: + void filterIndices(const QString &filter, const QString &wildcard = {}); + void activateCurrentItem(); + +private Q_SLOTS: + void showLink(const QModelIndex &index); + +private: + QHelpIndexWidget(); + friend class QHelpEngine; +}; + +QT_END_NAMESPACE + +#endif // QHELPINDEXWIDGET_H diff --git a/src/assistant/help/qhelplink.cpp b/src/assistant/help/qhelplink.cpp new file mode 100644 index 0000000..91c1f4f --- /dev/null +++ b/src/assistant/help/qhelplink.cpp @@ -0,0 +1,24 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelplink.h" + +/*! + \class QHelpLink + \since 5.15 + \inmodule QtHelp + \brief The QHelpLink struct provides the data associated with a help link. + + The QHelpLink object is a data object that describes a link to a documentation file. + The description of the help link contains the document title and URL of the document. + \sa QHelpEngineCore +*/ + +/*! + \variable QHelpLink::url + \brief The target url of the link. +*/ +/*! + \variable QHelpLink::title + \brief The title of the link. +*/ diff --git a/src/assistant/help/qhelplink.h b/src/assistant/help/qhelplink.h new file mode 100644 index 0000000..831c4a3 --- /dev/null +++ b/src/assistant/help/qhelplink.h @@ -0,0 +1,21 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPLINK_H +#define QHELPLINK_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +struct QHELP_EXPORT QHelpLink final +{ + QUrl url; + QString title; +}; + +QT_END_NAMESPACE + +#endif // QHELPLINK_H diff --git a/src/assistant/help/qhelpsearchengine.cpp b/src/assistant/help/qhelpsearchengine.cpp new file mode 100644 index 0000000..cd5e94d --- /dev/null +++ b/src/assistant/help/qhelpsearchengine.cpp @@ -0,0 +1,338 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpsearchengine.h" +#include "qhelpenginecore.h" +#include "qhelpsearchenginecore.h" +#include "qhelpsearchquerywidget.h" +#include "qhelpsearchresultwidget.h" + +QT_BEGIN_NAMESPACE + +class QHelpSearchEnginePrivate +{ +public: + QHelpSearchEngineCore m_searchEngine; + QHelpSearchQueryWidget *queryWidget = nullptr; + QHelpSearchResultWidget *resultWidget = nullptr; +}; + +/*! + \class QHelpSearchQuery + \deprecated + \since 4.4 + \inmodule QtHelp + \brief The QHelpSearchQuery class contains the field name and the associated + search term. + + The QHelpSearchQuery class contains the field name and the associated search + term. Depending on the field the search term might get split up into separate + terms to be parsed differently by the search engine. + + \note This class has been deprecated in favor of QString. + + \sa QHelpSearchQueryWidget +*/ + +/*! + \fn QHelpSearchQuery::QHelpSearchQuery() + + Constructs a new empty QHelpSearchQuery. +*/ + +/*! + \fn QHelpSearchQuery::QHelpSearchQuery(FieldName field, const QStringList &wordList) + + Constructs a new QHelpSearchQuery and initializes it with the given \a field and \a wordList. +*/ + +/*! + \enum QHelpSearchQuery::FieldName + This enum type specifies the field names that are handled by the search engine. + + \value DEFAULT the default field provided by the search widget, several terms should be + split and stored in the word list except search terms enclosed in quotes. + \value FUZZY \deprecated Terms should be split in separate + words and passed to the search engine. + \value WITHOUT \deprecated Terms should be split in separate + words and passed to the search engine. + \value PHRASE \deprecated Terms should not be split in separate words. + \value ALL \deprecated Terms should be split in separate + words and passed to the search engine + \value ATLEAST \deprecated Terms should be split in separate + words and passed to the search engine +*/ + +/*! + \class QHelpSearchEngine + \since 4.4 + \inmodule QtHelp + \brief The QHelpSearchEngine class provides access to widgets reusable + to integrate fulltext search as well as to index and search documentation. + + Before the search engine can be used, one has to instantiate at least a + QHelpEngineCore object that needs to be passed to the search engines constructor. + This is required as the search engine needs to be connected to the help + engines setupFinished() signal to know when it can start to index documentation. + + After starting the indexing process the signal indexingStarted() is emitted and + on the end of the indexing process the indexingFinished() is emitted. To stop + the indexing one can call cancelIndexing(). + + When the indexing process has finished, the search engine can be used to + search through the index for a given term using the search() function. When + the search input is passed to the search engine, the searchingStarted() + signal is emitted. When the search finishes, the searchingFinished() signal + is emitted. The search process can be stopped by calling cancelSearching(). + + If the search succeeds, searchingFinished() is called with the search result + count to fetch the search results from the search engine. Calling the + searchResults() function with a range returns a list of QHelpSearchResult + objects within the range. The results consist of the document title and URL, + as well as a snippet from the document that contains the best match for the + search input. + + To display the given search results use the QHelpSearchResultWidget or build up your own one if you need + more advanced functionality. Note that the QHelpSearchResultWidget can not be instantiated + directly, you must retrieve the widget from the search engine in use as all connections will be + established for you by the widget itself. +*/ + +/*! + \fn void QHelpSearchEngine::indexingStarted() + + This signal is emitted when indexing process is started. +*/ + +/*! + \fn void QHelpSearchEngine::indexingFinished() + + This signal is emitted when the indexing process is complete. +*/ + +/*! + \fn void QHelpSearchEngine::searchingStarted() + + This signal is emitted when the search process is started. +*/ + +/*! + \fn void QHelpSearchEngine::searchingFinished(int searchResultCount) + + This signal is emitted when the search process is complete. + The search result count is stored in \a searchResultCount. +*/ + +/*! + Constructs a new search engine with the given \a parent. The search engine + uses the given \a helpEngine to access the documentation that needs to be indexed. + The QHelpEngine's setupFinished() signal is automatically connected to the + QHelpSearchEngine's indexing function, so that new documentation will be indexed + after the signal is emitted. +*/ +QHelpSearchEngine::QHelpSearchEngine(QHelpEngineCore *helpEngine, QObject *parent) + : QObject(parent) + , d(new QHelpSearchEnginePrivate{QHelpSearchEngineCore(helpEngine)}) +{ + connect(&d->m_searchEngine, &QHelpSearchEngineCore::indexingStarted, + this, &QHelpSearchEngine::indexingStarted); + connect(&d->m_searchEngine, &QHelpSearchEngineCore::indexingFinished, + this, &QHelpSearchEngine::indexingFinished); + connect(&d->m_searchEngine, &QHelpSearchEngineCore::searchingStarted, + this, &QHelpSearchEngine::searchingStarted); + connect(&d->m_searchEngine, &QHelpSearchEngineCore::searchingFinished, + this, [this] { emit searchingFinished(d->m_searchEngine.searchResultCount()); }); +} + +/*! + Destructs the search engine. +*/ +QHelpSearchEngine::~QHelpSearchEngine() +{ + delete d; +} + +/*! + Returns a widget to use as input widget. Depending on your search engine + configuration you will get a different widget with more or less subwidgets. +*/ +QHelpSearchQueryWidget* QHelpSearchEngine::queryWidget() +{ + if (!d->queryWidget) + d->queryWidget = new QHelpSearchQueryWidget(); + return d->queryWidget; +} + +/*! + Returns a widget that can hold and display the search results. +*/ +QHelpSearchResultWidget* QHelpSearchEngine::resultWidget() +{ + if (!d->resultWidget) + d->resultWidget = new QHelpSearchResultWidget(this); + return d->resultWidget; +} + +#if QT_DEPRECATED_SINCE(5, 9) +/*! + \deprecated + Use searchResultCount() instead. +*/ +int QHelpSearchEngine::hitsCount() const +{ + return searchResultCount(); +} + +/*! + \since 4.6 + \deprecated + Use searchResultCount() instead. +*/ +int QHelpSearchEngine::hitCount() const +{ + return searchResultCount(); +} +#endif // QT_DEPRECATED_SINCE(5, 9) + +/*! + \since 5.9 + Returns the number of results the search engine found. +*/ +int QHelpSearchEngine::searchResultCount() const +{ + return d->m_searchEngine.searchResultCount(); +} + +#if QT_DEPRECATED_SINCE(5, 9) +/*! + \typedef QHelpSearchEngine::SearchHit + \deprecated + + Use QHelpSearchResult instead. + + Typedef for QPair. + The values of that pair are the documentation file path and the page title. + + \sa hits() +*/ + +/*! + \deprecated + Use searchResults() instead. +*/ +QList QHelpSearchEngine::hits(int start, int end) const +{ + QList hits; + for (const QHelpSearchResult &result : searchResults(start, end)) + hits.append(qMakePair(result.url().toString(), result.title())); + return hits; +} +#endif // QT_DEPRECATED_SINCE(5, 9) + +/*! + \since 5.9 + Returns a list of search results within the range from the index + specified by \a start to the index specified by \a end. +*/ +QList QHelpSearchEngine::searchResults(int start, int end) const +{ + return d->m_searchEngine.searchResults(start, end); +} + +/*! + \since 5.9 + Returns the phrase that was last searched for. +*/ +QString QHelpSearchEngine::searchInput() const +{ + return d->m_searchEngine.searchInput(); +} + +#if QT_DEPRECATED_SINCE(5, 9) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +/*! + \deprecated + \since 4.5 + Use searchInput() instead. +*/ +QList QHelpSearchEngine::query() const +{ + return {{QHelpSearchQuery::DEFAULT, searchInput().split(QChar::Space)}}; +} +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(5, 9) + +/*! + Forces the search engine to reindex all documentation files. +*/ +void QHelpSearchEngine::reindexDocumentation() +{ + d->m_searchEngine.reindexDocumentation(); +} + +/*! + Stops the indexing process. +*/ +void QHelpSearchEngine::cancelIndexing() +{ + d->m_searchEngine.cancelIndexing(); +} + +/*! + Stops the search process. +*/ +void QHelpSearchEngine::cancelSearching() +{ + d->m_searchEngine.cancelSearching(); +} + +/*! + \since 5.9 + Starts the search process using the given search phrase \a searchInput. + + The phrase may consist of several words. By default, the search engine returns + the list of documents that contain all the specified words. + The phrase may contain any combination of the logical operators AND, OR, and + NOT. The operator must be written in all capital letters, otherwise it will + be considered a part of the search phrase. + + If double quotation marks are used to group the words, + the search engine will search for an exact match of the quoted phrase. + + For more information about the text query syntax, + see \l {https://sqlite.org/fts5.html#full_text_query_syntax} + {SQLite FTS5 Extension}. +*/ +void QHelpSearchEngine::search(const QString &searchInput) +{ + d->m_searchEngine.search(searchInput); +} + +#if QT_DEPRECATED_SINCE(5, 9) +/*! + \deprecated + Use search(const QString &searchInput) instead. +*/ +void QHelpSearchEngine::search(const QList &queryList) +{ + if (queryList.isEmpty()) + return; + + d->m_searchEngine.search(queryList.first().wordList.join(QChar::Space)); +} +#endif // QT_DEPRECATED_SINCE(5, 9) + +/*! + \internal +*/ +void QHelpSearchEngine::scheduleIndexDocumentation() +{ + d->m_searchEngine.scheduleIndexDocumentation(); +} + +// TODO: Deprecate me (but it's private???) +void QHelpSearchEngine::indexDocumentation() +{} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpsearchengine.h b/src/assistant/help/qhelpsearchengine.h new file mode 100644 index 0000000..f782e61 --- /dev/null +++ b/src/assistant/help/qhelpsearchengine.h @@ -0,0 +1,92 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPSEARCHENGINE_H +#define QHELPSEARCHENGINE_H + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpEngineCore; +class QHelpSearchEnginePrivate; +class QHelpSearchQueryWidget; +class QHelpSearchResultWidget; + +#if QT_DEPRECATED_SINCE(6, 7) +class QHELP_EXPORT QHelpSearchQuery +{ +public: + enum FieldName { DEFAULT = 0, FUZZY, WITHOUT, PHRASE, ALL, ATLEAST }; + + QT_DEPRECATED_VERSION_X_6_7("Use QString instead") + QHelpSearchQuery() + : fieldName(DEFAULT) { wordList.clear(); } + QT_DEPRECATED_VERSION_X_6_7("Use QString instead") + QHelpSearchQuery(FieldName field, const QStringList &wordList_) + : fieldName(field), wordList(wordList_) {} + + FieldName fieldName; + QStringList wordList; +}; +#endif // QT_DEPRECATED_SINCE(6, 7) + +class QHELP_EXPORT QHelpSearchEngine : public QObject +{ + Q_OBJECT + +public: + explicit QHelpSearchEngine(QHelpEngineCore *helpEngine, QObject *parent = nullptr); + ~QHelpSearchEngine(); + + QHelpSearchQueryWidget *queryWidget(); + QHelpSearchResultWidget *resultWidget(); + +#if QT_DEPRECATED_SINCE(5, 9) + typedef QPair SearchHit; + + QT_DEPRECATED int hitsCount() const; + QT_DEPRECATED int hitCount() const; + QT_DEPRECATED QList hits(int start, int end) const; + QT_DEPRECATED QList query() const; +#endif + + int searchResultCount() const; + QList searchResults(int start, int end) const; + QString searchInput() const; + +public Q_SLOTS: + void reindexDocumentation(); + void cancelIndexing(); + +#if QT_DEPRECATED_SINCE(5, 9) + QT_DEPRECATED void search(const QList &queryList); +#endif + + void search(const QString &searchInput); + void cancelSearching(); + + void scheduleIndexDocumentation(); + +Q_SIGNALS: + void indexingStarted(); + void indexingFinished(); + + void searchingStarted(); + void searchingFinished(int searchResultCount); + +private Q_SLOTS: + void indexDocumentation(); + +private: + QHelpSearchEnginePrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QHELPSEARCHENGINE_H diff --git a/src/assistant/help/qhelpsearchenginecore.cpp b/src/assistant/help/qhelpsearchenginecore.cpp new file mode 100644 index 0000000..eef832f --- /dev/null +++ b/src/assistant/help/qhelpsearchenginecore.cpp @@ -0,0 +1,264 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpsearchenginecore.h" +#include "qhelpenginecore.h" + +#include "qhelpsearchindexreader_p.h" +#include "qhelpsearchindexwriter_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace fulltextsearch; +using namespace Qt::StringLiterals; + +class QHelpSearchEngineCorePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QHelpSearchEngineCore) + +public: + QString indexFilesFolder() const + { + QString indexFilesFolder = ".fulltextsearch"_L1; + if (m_helpEngine && !m_helpEngine->collectionFile().isEmpty()) { + const QFileInfo fi(m_helpEngine->collectionFile()); + indexFilesFolder = fi.absolutePath() + QDir::separator() + u'.' + + fi.fileName().left(fi.fileName().lastIndexOf(".qhc"_L1)); + } + return indexFilesFolder; + } + + void updateIndex(bool reindex) + { + Q_Q(QHelpSearchEngineCore); + if (m_helpEngine.isNull()) + return; + + if (!QFile::exists(QFileInfo(m_helpEngine->collectionFile()).path())) + return; + + if (!m_indexWriter) { + m_indexWriter.reset(new QHelpSearchIndexWriter); + + QObject::connect(m_indexWriter.get(), &QHelpSearchIndexWriter::indexingStarted, + q, &QHelpSearchEngineCore::indexingStarted); + QObject::connect(m_indexWriter.get(), &QHelpSearchIndexWriter::indexingFinished, + q, &QHelpSearchEngineCore::indexingFinished); + } + + m_indexWriter->cancelIndexing(); + m_indexWriter->updateIndex(m_helpEngine->collectionFile(), indexFilesFolder(), reindex); + } + + void search(const QString &searchInput) + { + Q_Q(QHelpSearchEngineCore); + if (m_helpEngine.isNull()) + return; + + if (!QFile::exists(QFileInfo(m_helpEngine->collectionFile()).path())) + return; + + if (!m_indexReader) { + m_indexReader.reset(new QHelpSearchIndexReader); + QObject::connect(m_indexReader.get(), &QHelpSearchIndexReader::searchingStarted, + q, &QHelpSearchEngineCore::searchingStarted); + QObject::connect(m_indexReader.get(), &QHelpSearchIndexReader::searchingFinished, + q, &QHelpSearchEngineCore::searchingFinished); + } + + m_searchInput = searchInput; + m_indexReader->cancelSearching(); + m_indexReader->search(m_helpEngine->collectionFile(), indexFilesFolder(), searchInput, + m_helpEngine->usesFilterEngine()); + } + + bool m_isIndexingScheduled = false; + + std::unique_ptr m_indexReader; + std::unique_ptr m_indexWriter; + + QPointer m_helpEngine; + QString m_searchInput; +}; + +/*! + \class QHelpSearchEngineCore + \since 6.8 + \inmodule QtHelp + \brief The QHelpSearchEngineCore class provides access to index and + search documentation. + + Before the search engine can be used, one has to instantiate at least a + QHelpEngineCore object that needs to be passed to the search engines constructor. + This is required as the search engine needs to be connected to the help + engines setupFinished() signal to know when it can start to index documentation. + + After starting the indexing process the signal indexingStarted() is emitted and + on the end of the indexing process the indexingFinished() is emitted. To stop + the indexing one can call cancelIndexing(). + + When the indexing process has finished, the search engine can be used to + search through the index for a given term using the search() function. When + the search input is passed to the search engine, the searchingStarted() + signal is emitted. When the search finishes, the searchingFinished() signal + is emitted. The search process can be stopped by calling cancelSearching(). + + If the search succeeds, searchingFinished() is called with the search result + count to fetch the search results from the search engine. Calling the + searchResults() function with a range returns a list of QHelpSearchResult + objects within the range. The results consist of the document title and URL, + as well as a snippet from the document that contains the best match for the + search input. +*/ + +/*! + \fn void QHelpSearchEngineCore::indexingStarted() + + This signal is emitted when indexing process is started. +*/ + +/*! + \fn void QHelpSearchEngineCore::indexingFinished() + + This signal is emitted when the indexing process is complete. +*/ + +/*! + \fn void QHelpSearchEngineCore::searchingStarted() + + This signal is emitted when the search process is started. +*/ + +/*! + \fn void QHelpSearchEngineCore::searchingFinished() + + This signal is emitted when the search process is complete. +*/ + +/*! + Constructs a new search engine with the given \a parent. The search engine + uses the given \a helpEngine to access the documentation that needs to be indexed. + The QHelpEngineCore's setupFinished() signal is automatically connected to the + QHelpSearchEngineCore's indexing function, so that new documentation will + be indexed after the signal is emitted. +*/ +QHelpSearchEngineCore::QHelpSearchEngineCore(QHelpEngineCore *helpEngine, QObject *parent) + : QObject(*new QHelpSearchEngineCorePrivate, parent) +{ + Q_D(QHelpSearchEngineCore); + d->m_helpEngine = helpEngine; + connect(helpEngine, &QHelpEngineCore::setupFinished, + this, &QHelpSearchEngineCore::scheduleIndexDocumentation); +} + +/*! + Destructs the search engine. +*/ +QHelpSearchEngineCore::~QHelpSearchEngineCore() = default; + +/*! + Returns the number of results the search engine found. +*/ +int QHelpSearchEngineCore::searchResultCount() const +{ + Q_D(const QHelpSearchEngineCore); + return d->m_indexReader ? d->m_indexReader->searchResultCount() : 0;; +} + +/*! + Returns a list of search results within the range from the index + specified by \a start to the index specified by \a end. +*/ +QList QHelpSearchEngineCore::searchResults(int start, int end) const +{ + Q_D(const QHelpSearchEngineCore); + return d->m_indexReader ? d->m_indexReader->searchResults(start, end) + : QList(); +} + +/*! + Returns the phrase that was last searched for. +*/ +QString QHelpSearchEngineCore::searchInput() const +{ + Q_D(const QHelpSearchEngineCore); + return d->m_searchInput; +} + +/*! + Forces the search engine to reindex all documentation files. +*/ +void QHelpSearchEngineCore::reindexDocumentation() +{ + Q_D(QHelpSearchEngineCore); + d->updateIndex(true); +} + +/*! + Stops the indexing process. +*/ +void QHelpSearchEngineCore::cancelIndexing() +{ + Q_D(QHelpSearchEngineCore); + if (d->m_indexWriter) + d->m_indexWriter->cancelIndexing(); +} + +/*! + Stops the search process. +*/ +void QHelpSearchEngineCore::cancelSearching() +{ + Q_D(QHelpSearchEngineCore); + if (d->m_indexReader) + d->m_indexReader->cancelSearching(); +} + +/*! + Starts the search process using the given search phrase \a searchInput. + + The phrase may consist of several words. By default, the search engine returns + the list of documents that contain all the specified words. + The phrase may contain any combination of the logical operators AND, OR, and + NOT. The operator must be written in all capital letters, otherwise it will + be considered a part of the search phrase. + + If double quotation marks are used to group the words, + the search engine will search for an exact match of the quoted phrase. + + For more information about the text query syntax, + see \l {https://sqlite.org/fts5.html#full_text_query_syntax} + {SQLite FTS5 Extension}. +*/ +void QHelpSearchEngineCore::search(const QString &searchInput) +{ + Q_D(QHelpSearchEngineCore); + d->search(searchInput); +} + +/*! + \internal +*/ +void QHelpSearchEngineCore::scheduleIndexDocumentation() +{ + Q_D(QHelpSearchEngineCore); + if (d->m_isIndexingScheduled) + return; + + d->m_isIndexingScheduled = true; + QTimer::singleShot(0, this, [this] { + Q_D(QHelpSearchEngineCore); + d->m_isIndexingScheduled = false; + d->updateIndex(false); + }); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpsearchenginecore.h b/src/assistant/help/qhelpsearchenginecore.h new file mode 100644 index 0000000..bf698af --- /dev/null +++ b/src/assistant/help/qhelpsearchenginecore.h @@ -0,0 +1,50 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPSEARCHENGINECORE_H +#define QHELPSEARCHENGINECORE_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpEngineCore; +class QHelpSearchEngineCorePrivate; + +class QHELP_EXPORT QHelpSearchEngineCore : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QHelpSearchEngineCore) + +public: + explicit QHelpSearchEngineCore(QHelpEngineCore *helpEngine, QObject *parent = nullptr); + ~QHelpSearchEngineCore() override; + + int searchResultCount() const; + QList searchResults(int start, int end) const; + QString searchInput() const; + +public Q_SLOTS: + void reindexDocumentation(); + void cancelIndexing(); + + void search(const QString &searchInput); + void cancelSearching(); + + void scheduleIndexDocumentation(); + +Q_SIGNALS: + void indexingStarted(); + void indexingFinished(); + + void searchingStarted(); + void searchingFinished(); +}; + +QT_END_NAMESPACE + +#endif // QHELPSEARCHENGINECORE_H diff --git a/src/assistant/help/qhelpsearchindexreader.cpp b/src/assistant/help/qhelpsearchindexreader.cpp new file mode 100644 index 0000000..28a0653 --- /dev/null +++ b/src/assistant/help/qhelpsearchindexreader.cpp @@ -0,0 +1,292 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpsearchindexreader_p.h" +#include "qhelpenginecore.h" +#include "qhelpfilterengine.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace fulltextsearch { + +class Reader +{ +public: + void setIndexPath(const QString &path) + { + m_indexPath = path; + m_namespaceAttributes.clear(); + m_filterEngineNamespaceList.clear(); + m_useFilterEngine = false; + } + void addNamespaceAttributes(const QString &namespaceName, const QStringList &attributes) + { + m_namespaceAttributes.insert(namespaceName, attributes); + } + void setFilterEngineNamespaceList(const QStringList &namespaceList) + { + m_useFilterEngine = true; + m_filterEngineNamespaceList = namespaceList; + } + + void searchInDB(const QString &term); + QList searchResults() const { return m_searchResults; } + +private: + QList queryTable(const QSqlDatabase &db, const QString &tableName, + const QString &searchInput) const; + + QMultiMap m_namespaceAttributes; + QStringList m_filterEngineNamespaceList; + QList m_searchResults; + QString m_indexPath; + bool m_useFilterEngine = false; +}; + +static QString namespacePlaceholders(const QMultiMap &namespaces) +{ + QString placeholders; + const auto &namespaceList = namespaces.uniqueKeys(); + bool firstNS = true; + for (const QString &ns : namespaceList) { + if (firstNS) + firstNS = false; + else + placeholders += " OR "_L1; + placeholders += "(namespace = ?"_L1; + + const QList &attributeSets = namespaces.values(ns); + bool firstAS = true; + for (const QStringList &attributeSet : attributeSets) { + if (!attributeSet.isEmpty()) { + if (firstAS) { + firstAS = false; + placeholders += " AND ("_L1; + } else { + placeholders += " OR "_L1; + } + placeholders += "attributes = ?"_L1; + } + } + if (!firstAS) + placeholders += u')'; // close "AND (" + placeholders += u')'; + } + return placeholders; +} + +static void bindNamespacesAndAttributes(QSqlQuery *query, + const QMultiMap &namespaces) +{ + const auto &namespaceList = namespaces.uniqueKeys(); + for (const QString &ns : namespaceList) { + query->addBindValue(ns); + + const QList &attributeSets = namespaces.values(ns); + for (const QStringList &attributeSet : attributeSets) { + if (!attributeSet.isEmpty()) + query->addBindValue(attributeSet.join(u'|')); + } + } +} + +static QString namespacePlaceholders(const QStringList &namespaceList) +{ + QString placeholders; + bool firstNS = true; + for (int i = namespaceList.size(); i; --i) { + if (firstNS) + firstNS = false; + else + placeholders += " OR "_L1; + placeholders += "namespace = ?"_L1; + } + return placeholders; +} + +static void bindNamespacesAndAttributes(QSqlQuery *query, const QStringList &namespaceList) +{ + for (const QString &ns : namespaceList) + query->addBindValue(ns); +} + +QList Reader::queryTable(const QSqlDatabase &db, const QString &tableName, + const QString &searchInput) const +{ + const QString nsPlaceholders = m_useFilterEngine + ? namespacePlaceholders(m_filterEngineNamespaceList) + : namespacePlaceholders(m_namespaceAttributes); + QSqlQuery query(db); + query.prepare("SELECT url, title, snippet("_L1 + tableName + + ", -1, '', '', '...', '10') FROM "_L1 + tableName + + " WHERE ("_L1 + nsPlaceholders + + ") AND "_L1 + tableName + + " MATCH ? ORDER BY rank"_L1); + m_useFilterEngine + ? bindNamespacesAndAttributes(&query, m_filterEngineNamespaceList) + : bindNamespacesAndAttributes(&query, m_namespaceAttributes); + query.addBindValue(searchInput); + query.exec(); + + QList results; + + while (query.next()) { + const QString &url = query.value("url"_L1).toString(); + const QString &title = query.value("title"_L1).toString(); + const QString &snippet = query.value(2).toString(); + results.append(QHelpSearchResult(url, title, snippet)); + } + return results; +} + +void Reader::searchInDB(const QString &searchInput) +{ + const QString &uniqueId = QHelpGlobal::uniquifyConnectionName("QHelpReader"_L1, this); + { + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, uniqueId); + db.setConnectOptions("QSQLITE_OPEN_READONLY"_L1); + db.setDatabaseName(m_indexPath + "/fts"_L1); + + if (db.open()) { + const QList titleResults = queryTable(db, "titles"_L1, searchInput); + const QList contentResults = + queryTable(db, "contents"_L1, searchInput); + + // merge results form title and contents searches + m_searchResults.clear(); + QSet urls; + for (const QHelpSearchResult &result : titleResults) { + const auto size = urls.size(); + urls.insert(result.url()); + if (size != urls.size()) // insertion took place + m_searchResults.append(result); + } + for (const QHelpSearchResult &result : contentResults) { + const auto size = urls.size(); + urls.insert(result.url()); + if (size != urls.size()) // insertion took place + m_searchResults.append(result); + } + } + } + QSqlDatabase::removeDatabase(uniqueId); +} + +static bool attributesMatchFilter(const QStringList &attributes, const QStringList &filter) +{ + for (const QString &attribute : filter) { + if (!attributes.contains(attribute, Qt::CaseInsensitive)) + return false; + } + return true; +} + +QHelpSearchIndexReader::~QHelpSearchIndexReader() +{ + cancelSearching(); + wait(); +} + +void QHelpSearchIndexReader::cancelSearching() +{ + QMutexLocker lock(&m_mutex); + m_cancel = true; +} + +void QHelpSearchIndexReader::search(const QString &collectionFile, const QString &indexFilesFolder, + const QString &searchInput, bool usesFilterEngine) +{ + wait(); + + m_searchResults.clear(); + m_cancel = false; + m_searchInput = searchInput; + m_collectionFile = collectionFile; + m_indexFilesFolder = indexFilesFolder; + m_usesFilterEngine = usesFilterEngine; + + start(QThread::NormalPriority); +} + +int QHelpSearchIndexReader::searchResultCount() const +{ + QMutexLocker lock(&m_mutex); + return m_searchResults.size(); +} + +QList QHelpSearchIndexReader::searchResults(int start, int end) const +{ + QMutexLocker lock(&m_mutex); + return m_searchResults.mid(start, end - start); +} + +void QHelpSearchIndexReader::run() +{ + QMutexLocker lock(&m_mutex); + + if (m_cancel) + return; + + const QString searchInput = m_searchInput; + const QString collectionFile = m_collectionFile; + const QString indexPath = m_indexFilesFolder; + const bool usesFilterEngine = m_usesFilterEngine; + + lock.unlock(); + + QHelpEngineCore engine(collectionFile, nullptr); + if (!engine.setupData()) + return; + + emit searchingStarted(); + + // setup the reader + Reader reader; + reader.setIndexPath(indexPath); + + if (usesFilterEngine) { + reader.setFilterEngineNamespaceList( + engine.filterEngine()->namespacesForFilter(engine.filterEngine()->activeFilter())); + } else { + const QStringList ®isteredDocs = engine.registeredDocumentations(); + const QStringList ¤tFilter = engine.filterAttributes(engine.currentFilter()); + + for (const QString &namespaceName : registeredDocs) { + const QList &attributeSets = + engine.filterAttributeSets(namespaceName); + + for (const QStringList &attributes : attributeSets) { + if (attributesMatchFilter(attributes, currentFilter)) + reader.addNamespaceAttributes(namespaceName, attributes); + } + } + } + + lock.relock(); + if (m_cancel) { + lock.unlock(); + emit searchingFinished(); + return; + } + m_searchResults.clear(); + lock.unlock(); + + reader.searchInDB(searchInput); // TODO: should this be interruptible as well ??? + + lock.relock(); + m_searchResults = reader.searchResults(); + lock.unlock(); + + emit searchingFinished(); +} + +} // namespace fulltextsearch + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpsearchindexreader_p.h b/src/assistant/help/qhelpsearchindexreader_p.h new file mode 100644 index 0000000..fc8c86c --- /dev/null +++ b/src/assistant/help/qhelpsearchindexreader_p.h @@ -0,0 +1,62 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPSEARCHINDEXREADER_H +#define QHELPSEARCHINDEXREADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the help generator tools. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include "qhelpsearchresult.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace fulltextsearch { + +// TODO: Employ QFuture / QtConcurrent::run() ? +class QHelpSearchIndexReader : public QThread +{ + Q_OBJECT + +public: + ~QHelpSearchIndexReader() override; + + void cancelSearching(); + void search(const QString &collectionFile, const QString &indexFilesFolder, + const QString &searchInput, bool usesFilterEngine = false); + int searchResultCount() const; + QList searchResults(int start, int end) const; + +signals: + void searchingStarted(); + void searchingFinished(); + +private: + void run() override; + + mutable QMutex m_mutex; + QList m_searchResults; + bool m_cancel = false; + QString m_collectionFile; + QString m_searchInput; + QString m_indexFilesFolder; + bool m_usesFilterEngine = false; +}; + +} // namespace fulltextsearch + +QT_END_NAMESPACE + +#endif // QHELPSEARCHINDEXREADER_H diff --git a/src/assistant/help/qhelpsearchindexwriter.cpp b/src/assistant/help/qhelpsearchindexwriter.cpp new file mode 100644 index 0000000..ea46583 --- /dev/null +++ b/src/assistant/help/qhelpsearchindexwriter.cpp @@ -0,0 +1,520 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpsearchindexwriter_p.h" +#include "qhelp_global.h" +#include "qhelpdbreader_p.h" +#include "qhelpenginecore.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(fullqthelp) +# include +#endif +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace fulltextsearch { + +const char FTS_DB_NAME[] = "fts"; + +class Writer +{ +public: + Writer(const QString &path); + ~Writer(); + + bool tryInit(bool reindex); + void flush(); + + void removeNamespace(const QString &namespaceName); + bool hasNamespace(const QString &namespaceName); + void insertDoc(const QString &namespaceName, + const QString &attributes, + const QString &url, + const QString &title, + const QString &contents); + void startTransaction(); + void endTransaction(); + +private: + void init(bool reindex); + bool hasDB(); + void clearLegacyIndex(); + + const QString m_dbDir; + QString m_uniqueId; + + bool m_needOptimize = false; + QSqlDatabase m_db; + QVariantList m_namespaces; + QVariantList m_attributes; + QVariantList m_urls; + QVariantList m_titles; + QVariantList m_contents; +}; + +Writer::Writer(const QString &path) + : m_dbDir(path) +{ + clearLegacyIndex(); + QDir().mkpath(m_dbDir); + m_uniqueId = QHelpGlobal::uniquifyConnectionName("QHelpWriter"_L1, this); + m_db = QSqlDatabase::addDatabase("QSQLITE"_L1, m_uniqueId); + const QString dbPath = m_dbDir + u'/' + QLatin1StringView(FTS_DB_NAME); + m_db.setDatabaseName(dbPath); + if (!m_db.open()) { + const QString &error = QHelpSearchIndexWriter::tr( + "Cannot open database \"%1\" using connection \"%2\": %3") + .arg(dbPath, m_uniqueId, m_db.lastError().text()); + qWarning("%s", qUtf8Printable(error)); + m_db = {}; + QSqlDatabase::removeDatabase(m_uniqueId); + m_uniqueId.clear(); + } else { + startTransaction(); + } +} + +bool Writer::tryInit(bool reindex) +{ + if (!m_db.isValid()) + return true; + + QSqlQuery query(m_db); + // HACK: we try to perform any modifying command just to check if + // we don't get SQLITE_BUSY code (SQLITE_BUSY is defined to 5 in sqlite driver) + if (!query.exec("CREATE TABLE foo ();"_L1) && query.lastError().nativeErrorCode() == "5"_L1) // db is locked + return false; + + // HACK: clear what we have created + query.exec("DROP TABLE foo;"_L1); + + init(reindex); + return true; +} + +bool Writer::hasDB() +{ + if (!m_db.isValid()) + return false; + + QSqlQuery query(m_db); + query.prepare("SELECT id FROM info LIMIT 1"_L1); + query.exec(); + return query.next(); +} + +void Writer::clearLegacyIndex() +{ + // Clear old legacy clucene index. + // More important in case of Creator, since + // the index folder is common for all Creator versions + QDir dir(m_dbDir); + if (!dir.exists()) + return; + + const QStringList &list = dir.entryList(QDir::Files | QDir::Hidden); + if (!list.contains(QLatin1StringView(FTS_DB_NAME))) { + for (const QString &item : list) + dir.remove(item); + } +} + +void Writer::init(bool reindex) +{ + if (!m_db.isValid()) + return; + + QSqlQuery query(m_db); + + if (reindex && hasDB()) { + m_needOptimize = true; + + query.exec("DROP TABLE titles;"_L1); + query.exec("DROP TABLE contents;"_L1); + query.exec("DROP TABLE info;"_L1); + } + + query.exec("CREATE TABLE info (id INTEGER PRIMARY KEY, namespace, attributes, url, title, data);"_L1); + + query.exec("CREATE VIRTUAL TABLE titles USING fts5(" + "namespace UNINDEXED, attributes UNINDEXED, " + "url UNINDEXED, title, " + "tokenize = 'porter unicode61', content = 'info', content_rowid='id');"_L1); + query.exec("CREATE TRIGGER titles_insert AFTER INSERT ON info BEGIN " + "INSERT INTO titles(rowid, namespace, attributes, url, title) " + "VALUES(new.id, new.namespace, new.attributes, new.url, new.title); " + "END;"_L1); + query.exec("CREATE TRIGGER titles_delete AFTER DELETE ON info BEGIN " + "INSERT INTO titles(titles, rowid, namespace, attributes, url, title) " + "VALUES('delete', old.id, old.namespace, old.attributes, old.url, old.title); " + "END;"_L1); + query.exec("CREATE TRIGGER titles_update AFTER UPDATE ON info BEGIN " + "INSERT INTO titles(titles, rowid, namespace, attributes, url, title) " + "VALUES('delete', old.id, old.namespace, old.attributes, old.url, old.title); " + "INSERT INTO titles(rowid, namespace, attributes, url, title) " + "VALUES(new.id, new.namespace, new.attributes, new.url, new.title); " + "END;"_L1); + + query.exec("CREATE VIRTUAL TABLE contents USING fts5(" + "namespace UNINDEXED, attributes UNINDEXED, " + "url UNINDEXED, title, data, " + "tokenize = 'porter unicode61', content = 'info', content_rowid='id');"_L1); + query.exec("CREATE TRIGGER contents_insert AFTER INSERT ON info BEGIN " + "INSERT INTO contents(rowid, namespace, attributes, url, title, data) " + "VALUES(new.id, new.namespace, new.attributes, new.url, new.title, new.data); " + "END;"_L1); + query.exec("CREATE TRIGGER contents_delete AFTER DELETE ON info BEGIN " + "INSERT INTO contents(contents, rowid, namespace, attributes, url, title, data) " + "VALUES('delete', old.id, old.namespace, old.attributes, old.url, old.title, old.data); " + "END;"_L1); + query.exec("CREATE TRIGGER contents_update AFTER UPDATE ON info BEGIN " + "INSERT INTO contents(contents, rowid, namespace, attributes, url, title, data) " + "VALUES('delete', old.id, old.namespace, old.attributes, old.url, old.title, old.data); " + "INSERT INTO contents(rowid, namespace, attributes, url, title, data) " + "VALUES(new.id, new.namespace, new.attributes, new.url, new.title, new.data); " + "END;"_L1); +} + +Writer::~Writer() +{ + if (m_db.isValid()) + m_db.close(); + m_db = {}; + if (!m_uniqueId.isEmpty()) + QSqlDatabase::removeDatabase(m_uniqueId); +} + +void Writer::flush() +{ + if (!m_db.isValid()) + return; + + QSqlQuery query(m_db); + query.prepare("INSERT INTO info (namespace, attributes, url, title, data) VALUES (?, ?, ?, ?, ?)"_L1); + query.addBindValue(m_namespaces); + query.addBindValue(m_attributes); + query.addBindValue(m_urls); + query.addBindValue(m_titles); + query.addBindValue(m_contents); + query.execBatch(); + + m_namespaces.clear(); + m_attributes.clear(); + m_urls.clear(); + m_titles.clear(); + m_contents.clear(); +} + +void Writer::removeNamespace(const QString &namespaceName) +{ + if (!m_db.isValid() || !hasNamespace(namespaceName)) // no data to delete + return; + + m_needOptimize = true; + QSqlQuery query(m_db); + query.prepare("DELETE FROM info WHERE namespace = ?"_L1); + query.addBindValue(namespaceName); + query.exec(); +} + +bool Writer::hasNamespace(const QString &namespaceName) +{ + if (!m_db.isValid()) + return false; + + QSqlQuery query(m_db); + query.prepare("SELECT id FROM info WHERE namespace = ? LIMIT 1"_L1); + query.addBindValue(namespaceName); + query.exec(); + return query.next(); +} + +void Writer::insertDoc(const QString &namespaceName, + const QString &attributes, + const QString &url, + const QString &title, + const QString &contents) +{ + m_namespaces.append(namespaceName); + m_attributes.append(attributes); + m_urls.append(url); + m_titles.append(title); + m_contents.append(contents); +} + +void Writer::startTransaction() +{ + if (!m_db.isValid()) + return; + + m_needOptimize = false; + if (m_db.driver()->hasFeature(QSqlDriver::Transactions)) + m_db.transaction(); +} + +void Writer::endTransaction() +{ + if (!m_db.isValid()) + return; + + QSqlQuery query(m_db); + + if (m_needOptimize) { + query.exec("INSERT INTO titles(titles) VALUES('rebuild')"_L1); + query.exec("INSERT INTO contents(contents) VALUES('rebuild')"_L1); + } + + if (m_db.driver()->hasFeature(QSqlDriver::Transactions)) + m_db.commit(); + + if (m_needOptimize) + query.exec("VACUUM"_L1); +} + +QHelpSearchIndexWriter::~QHelpSearchIndexWriter() +{ + m_mutex.lock(); + this->m_cancel = true; + m_mutex.unlock(); + wait(); +} + +void QHelpSearchIndexWriter::cancelIndexing() +{ + QMutexLocker lock(&m_mutex); + m_cancel = true; +} + +void QHelpSearchIndexWriter::updateIndex(const QString &collectionFile, + const QString &indexFilesFolder, bool reindex) +{ + wait(); + QMutexLocker lock(&m_mutex); + + m_cancel = false; + m_reindex = reindex; + m_collectionFile = collectionFile; + m_indexFilesFolder = indexFilesFolder; + + lock.unlock(); + + start(QThread::LowestPriority); +} + +static const char IndexedNamespacesKey[] = "FTS5IndexedNamespaces"; + +static QMap readIndexMap(const QHelpEngineCore &engine) +{ + QMap indexMap; + QDataStream dataStream( + engine.customValue(QLatin1StringView(IndexedNamespacesKey)).toByteArray()); + dataStream >> indexMap; + return indexMap; +} + +static bool writeIndexMap(QHelpEngineCore *engine, const QMap &indexMap) +{ + QByteArray data; + QDataStream dataStream(&data, QIODevice::ReadWrite); + dataStream << indexMap; + return engine->setCustomValue(QLatin1StringView(IndexedNamespacesKey), data); +} + +static bool clearIndexMap(QHelpEngineCore *engine) +{ + return engine->removeCustomValue(QLatin1StringView(IndexedNamespacesKey)); +} + +void QHelpSearchIndexWriter::run() +{ + QMutexLocker lock(&m_mutex); + + if (m_cancel) + return; + + const bool reindex(m_reindex); + const QString collectionFile(m_collectionFile); + const QString indexPath(m_indexFilesFolder); + + lock.unlock(); + + QHelpEngineCore engine(collectionFile, nullptr); + if (!engine.setupData()) + return; + + if (reindex) + clearIndexMap(&engine); + + emit indexingStarted(); + + Writer writer(indexPath); + + while (!writer.tryInit(reindex)) + sleep(1); + + const QStringList ®isteredDocs = engine.registeredDocumentations(); + QMap indexMap = readIndexMap(engine); + + if (!reindex) { + for (const QString &namespaceName : registeredDocs) { + const auto it = indexMap.constFind(namespaceName); + if (it != indexMap.constEnd()) { + const QString path = engine.documentationFileName(namespaceName); + if (*it < QFileInfo(path).lastModified()) { + // Remove some outdated indexed stuff + indexMap.erase(it); + writer.removeNamespace(namespaceName); + } else if (!writer.hasNamespace(namespaceName)) { + // No data in fts db for namespace. + // The namespace could have been removed from fts db + // or the whole fts db have been removed + // without removing it from indexMap. + indexMap.erase(it); + } + } else { + // Needed in case namespaceName was removed from indexMap + // without removing it from fts db. + // May happen when e.g. qch file was removed manually + // without removing fts db. + writer.removeNamespace(namespaceName); + } + // TODO: we may also detect if there are any other data + // and remove it + } + } else { + indexMap.clear(); + } + + auto it = indexMap.begin(); + while (it != indexMap.end()) { + if (!registeredDocs.contains(it.key())) { + writer.removeNamespace(it.key()); + it = indexMap.erase(it); + } else { + ++it; + } + } + + for (const QString &namespaceName : registeredDocs) { + lock.relock(); + if (m_cancel) { + // store what we have done so far + writeIndexMap(&engine, indexMap); + writer.endTransaction(); + emit indexingFinished(); + return; + } + lock.unlock(); + + // if indexed, continue + if (indexMap.contains(namespaceName)) + continue; + + const QString fileName = engine.documentationFileName(namespaceName); + QHelpDBReader reader(fileName, QHelpGlobal::uniquifyConnectionName( + fileName, this), nullptr); + if (!reader.init()) + continue; + + const QString virtualFolder = reader.virtualFolder(); + + const QList &attributeSets = + engine.filterAttributeSets(namespaceName); + + for (const QStringList &attributes : attributeSets) { + const QString &attributesString = attributes.join(u'|'); + + const auto htmlFiles = reader.filesData(attributes, "html"_L1); + const auto htmFiles = reader.filesData(attributes, "htm"_L1); + const auto txtFiles = reader.filesData(attributes, "txt"_L1); + + auto files = htmlFiles; + files.unite(htmFiles); + files.unite(txtFiles); + + for (auto it = files.cbegin(), end = files.cend(); it != end ; ++it) { + lock.relock(); + if (m_cancel) { + // store what we have done so far + writeIndexMap(&engine, indexMap); + writer.endTransaction(); + emit indexingFinished(); + return; + } + lock.unlock(); + + const QString &file = it.key(); + const QByteArray &data = it.value(); + + if (data.isEmpty()) + continue; + + QUrl url; + url.setScheme("qthelp"_L1); + url.setAuthority(namespaceName); + url.setPath(u'/' + virtualFolder + u'/' + file); + + if (url.hasFragment()) + url.setFragment({}); + + const QString &fullFileName = url.toString(); + if (!fullFileName.endsWith(".html"_L1) && !fullFileName.endsWith(".htm"_L1) + && !fullFileName.endsWith(".txt"_L1)) { + continue; + } + + QTextStream s(data); + auto encoding = QStringDecoder::encodingForHtml(data); + if (encoding) + s.setEncoding(*encoding); + + const QString &text = s.readAll(); + if (text.isEmpty()) + continue; + + QString title; + QString contents; + if (fullFileName.endsWith(".txt"_L1)) { + title = fullFileName.mid(fullFileName.lastIndexOf(u'/') + 1); + contents = text.toHtmlEscaped(); +#if QT_CONFIG(fullqthelp) + } else { + QTextDocument doc; + doc.setHtml(text); + + title = doc.metaInformation(QTextDocument::DocumentTitle).toHtmlEscaped(); + contents = doc.toPlainText().toHtmlEscaped(); +#endif + } + + writer.insertDoc(namespaceName, attributesString, fullFileName, title, contents); + } + } + writer.flush(); + const QString &path = engine.documentationFileName(namespaceName); + indexMap.insert(namespaceName, QFileInfo(path).lastModified()); + } + + writeIndexMap(&engine, indexMap); + + writer.endTransaction(); + emit indexingFinished(); +} + +} // namespace fulltextsearch + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpsearchindexwriter_p.h b/src/assistant/help/qhelpsearchindexwriter_p.h new file mode 100644 index 0000000..61e0047 --- /dev/null +++ b/src/assistant/help/qhelpsearchindexwriter_p.h @@ -0,0 +1,58 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPSEARCHINDEXWRITER_H +#define QHELPSEARCHINDEXWRITER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the help generator tools. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QSqlDatabase; + +namespace fulltextsearch { + +// TODO: Employ QFuture / QtConcurrent::run() ? +class QHelpSearchIndexWriter : public QThread +{ + Q_OBJECT + +public: + ~QHelpSearchIndexWriter() override; + + void cancelIndexing(); + void updateIndex(const QString &collectionFile, const QString &indexFilesFolder, bool reindex); + +signals: + void indexingStarted(); + void indexingFinished(); + +private: + void run() override; + +private: + QMutex m_mutex; + + bool m_cancel = false; + bool m_reindex; + QString m_collectionFile; + QString m_indexFilesFolder; +}; + +} // namespace fulltextsearch + +QT_END_NAMESPACE + +#endif // QHELPSEARCHINDEXWRITER_H diff --git a/src/assistant/help/qhelpsearchquerywidget.cpp b/src/assistant/help/qhelpsearchquerywidget.cpp new file mode 100644 index 0000000..d5e84ba --- /dev/null +++ b/src/assistant/help/qhelpsearchquerywidget.cpp @@ -0,0 +1,324 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpsearchquerywidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpSearchQueryWidgetPrivate : public QObject +{ +public: + struct QueryHistory { + explicit QueryHistory() : curQuery(-1) {} + QStringList queries; + int curQuery = 0; + }; + + class CompleterModel : public QAbstractListModel + { + public: + explicit CompleterModel(QObject *parent) : QAbstractListModel(parent) { } + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + return parent.isValid() ? 0 : termList.size(); + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if (!index.isValid() || index.row() >= termList.size()|| + (role != Qt::EditRole && role != Qt::DisplayRole)) + return {}; + return termList.at(index.row()); + } + + void addTerm(const QString &term) + { + if (!termList.contains(term)) { + beginResetModel(); + termList.append(term); + endResetModel(); + } + } + + private: + QStringList termList; + }; + + QHelpSearchQueryWidgetPrivate() : m_searchCompleter(new CompleterModel(this), this) {} + + void retranslate() + { + m_searchLabel->setText(QHelpSearchQueryWidget::tr("Search for:")); + m_searchButton->setText(QHelpSearchQueryWidget::tr("Search")); +#if QT_CONFIG(tooltip) + m_prevQueryButton->setToolTip(QHelpSearchQueryWidget::tr("Previous search")); + m_nextQueryButton->setToolTip(QHelpSearchQueryWidget::tr("Next search")); +#endif + } + + void saveQuery(const QString &query) + { + // We only add the query to the list if it is different from the last one. + if (!m_queries.queries.isEmpty() && m_queries.queries.last() == query) + return; + + m_queries.queries.append(query); + static_cast(m_searchCompleter.model())->addTerm(query); + } + + void nextOrPrevQuery(int maxOrMinIndex, int addend, QToolButton *thisButton, + QToolButton *otherButton) + { + m_lineEdit->clear(); + + // Otherwise, the respective button would be disabled. + Q_ASSERT(m_queries.curQuery != maxOrMinIndex); + + m_queries.curQuery = qBound(0, m_queries.curQuery + addend, m_queries.queries.size() - 1); + const QString &query = m_queries.queries.at(m_queries.curQuery); + m_lineEdit->setText(query); + + if (m_queries.curQuery == maxOrMinIndex) + thisButton->setEnabled(false); + otherButton->setEnabled(true); + } + + void enableOrDisableToolButtons() + { + m_prevQueryButton->setEnabled(m_queries.curQuery > 0); + m_nextQueryButton->setEnabled(m_queries.curQuery < m_queries.queries.size() - 1); + } + + bool eventFilter(QObject *ob, QEvent *event) override + { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *const keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Down) { + if (m_queries.curQuery + 1 < m_queries.queries.size()) + nextQuery(); + return true; + } + if (keyEvent->key() == Qt::Key_Up) { + if (m_queries.curQuery > 0) + prevQuery(); + return true; + } + } + return QObject::eventFilter(ob, event); + } + + void searchRequested() + { + saveQuery(m_lineEdit->text()); + m_queries.curQuery = m_queries.queries.size() - 1; + if (m_queries.curQuery > 0) + m_prevQueryButton->setEnabled(true); + m_nextQueryButton->setEnabled(false); + } + + void nextQuery() + { + nextOrPrevQuery(m_queries.queries.size() - 1, 1, m_nextQueryButton, m_prevQueryButton); + } + + void prevQuery() { nextOrPrevQuery(0, -1, m_prevQueryButton, m_nextQueryButton); } + + QLabel *m_searchLabel = nullptr; + QPushButton *m_searchButton = nullptr; + QLineEdit *m_lineEdit = nullptr; + QToolButton *m_nextQueryButton = nullptr; + QToolButton *m_prevQueryButton = nullptr; + QueryHistory m_queries; + QCompleter m_searchCompleter; + bool m_compactMode = false; +}; + +/*! + \class QHelpSearchQueryWidget + \since 4.4 + \inmodule QtHelp + \brief The QHelpSearchQueryWidget class provides a simple line edit or + an advanced widget to enable the user to input a search term in a + standardized input mask. +*/ + +/*! + \fn void QHelpSearchQueryWidget::search() + + This signal is emitted when a the user has the search button invoked. + After receiving the signal you can ask the QHelpSearchQueryWidget for the + search input that you may pass to the QHelpSearchEngine::search() function. +*/ + +/*! + Constructs a new search query widget with the given \a parent. +*/ +QHelpSearchQueryWidget::QHelpSearchQueryWidget(QWidget *parent) + : QWidget(parent) + , d(new QHelpSearchQueryWidgetPrivate) +{ + QVBoxLayout *vLayout = new QVBoxLayout(this); + vLayout->setContentsMargins({}); + + QHBoxLayout* hBoxLayout = new QHBoxLayout; + d->m_searchLabel = new QLabel(this); + d->m_lineEdit = new QLineEdit(this); + d->m_lineEdit->setClearButtonEnabled(true); + d->m_lineEdit->setCompleter(&d->m_searchCompleter); + d->m_lineEdit->installEventFilter(d); + d->m_prevQueryButton = new QToolButton(this); + d->m_prevQueryButton->setArrowType(Qt::LeftArrow); + d->m_prevQueryButton->setEnabled(false); + d->m_nextQueryButton = new QToolButton(this); + d->m_nextQueryButton->setArrowType(Qt::RightArrow); + d->m_nextQueryButton->setEnabled(false); + d->m_searchButton = new QPushButton(this); + hBoxLayout->addWidget(d->m_searchLabel); + hBoxLayout->addWidget(d->m_lineEdit); + hBoxLayout->addWidget(d->m_prevQueryButton); + hBoxLayout->addWidget(d->m_nextQueryButton); + hBoxLayout->addWidget(d->m_searchButton); + + vLayout->addLayout(hBoxLayout); + + connect(d->m_prevQueryButton, &QAbstractButton::clicked, this, [this] { d->prevQuery(); }); + connect(d->m_nextQueryButton, &QAbstractButton::clicked, this, [this] { d->nextQuery(); }); + connect(d->m_searchButton, &QAbstractButton::clicked, this, &QHelpSearchQueryWidget::search); + connect(d->m_lineEdit, &QLineEdit::returnPressed, this, &QHelpSearchQueryWidget::search); + + d->retranslate(); + connect(this, &QHelpSearchQueryWidget::search, this, [this] { d->searchRequested(); }); + setCompactMode(true); +} + +/*! + Destroys the search query widget. +*/ +QHelpSearchQueryWidget::~QHelpSearchQueryWidget() +{ + delete d; +} + +/*! + Expands the search query widget so that the extended search fields are shown. +*/ +void QHelpSearchQueryWidget::expandExtendedSearch() +{ + // TODO: no extended search anymore, deprecate it? +} + +/*! + Collapses the search query widget so that only the default search field is + shown. +*/ +void QHelpSearchQueryWidget::collapseExtendedSearch() +{ + // TODO: no extended search anymore, deprecate it? +} + +#if QT_DEPRECATED_SINCE(5, 9) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +/*! + \deprecated + + Use searchInput() instead. +*/ +QList QHelpSearchQueryWidget::query() const +{ + return {{QHelpSearchQuery::DEFAULT, searchInput().split(QChar::Space, Qt::SkipEmptyParts)}}; +} + +/*! + \deprecated + + Use setSearchInput() instead. +*/ +void QHelpSearchQueryWidget::setQuery(const QList &queryList) +{ + if (queryList.isEmpty()) + return; + + setSearchInput(queryList.first().wordList.join(QChar::Space)); +} +QT_WARNING_POP +#endif // QT_DEPRECATED_SINCE(5, 9) + +/*! + \since 5.9 + + Returns a search phrase to use in combination with the + QHelpSearchEngine::search(const QString &searchInput) function. +*/ +QString QHelpSearchQueryWidget::searchInput() const +{ + if (d->m_queries.queries.isEmpty()) + return {}; + return d->m_queries.queries.last(); +} + +/*! + \since 5.9 + + Sets the QHelpSearchQueryWidget input field to the value specified by + \a searchInput. + + \note The QHelpSearchEngine::search(const QString &searchInput) function has + to be called to perform the actual search. +*/ +void QHelpSearchQueryWidget::setSearchInput(const QString &searchInput) +{ + d->m_lineEdit->clear(); + d->m_lineEdit->setText(searchInput); + d->searchRequested(); +} + +bool QHelpSearchQueryWidget::isCompactMode() const +{ + return d->m_compactMode; +} + +void QHelpSearchQueryWidget::setCompactMode(bool on) +{ + if (d->m_compactMode != on) { + d->m_compactMode = on; + d->m_prevQueryButton->setVisible(!on); + d->m_nextQueryButton->setVisible(!on); + d->m_searchLabel->setVisible(!on); + } +} + +/*! + \reimp +*/ +void QHelpSearchQueryWidget::focusInEvent(QFocusEvent *focusEvent) +{ + if (focusEvent->reason() != Qt::MouseFocusReason) { + d->m_lineEdit->selectAll(); + d->m_lineEdit->setFocus(); + } +} + +/*! + \reimp +*/ +void QHelpSearchQueryWidget::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::LanguageChange) + d->retranslate(); + else + QWidget::changeEvent(event); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpsearchquerywidget.h b/src/assistant/help/qhelpsearchquerywidget.h new file mode 100644 index 0000000..148ae0f --- /dev/null +++ b/src/assistant/help/qhelpsearchquerywidget.h @@ -0,0 +1,54 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPSEARCHQUERYWIDGET_H +#define QHELPSEARCHQUERYWIDGET_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QFocusEvent; +class QHelpSearchQueryWidgetPrivate; + +class QHELP_EXPORT QHelpSearchQueryWidget : public QWidget +{ + Q_OBJECT + +public: + explicit QHelpSearchQueryWidget(QWidget *parent = nullptr); + ~QHelpSearchQueryWidget() override; + + void expandExtendedSearch(); + void collapseExtendedSearch(); + +#if QT_DEPRECATED_SINCE(5, 9) + QT_DEPRECATED QList query() const; + QT_DEPRECATED void setQuery(const QList &queryList); +#endif + + QString searchInput() const; + void setSearchInput(const QString &searchInput); + + bool isCompactMode() const; + +public Q_SLOTS: + void setCompactMode(bool on); + +Q_SIGNALS: + void search(); + +private: + void focusInEvent(QFocusEvent *focusEvent) override; + void changeEvent(QEvent *event) override; + +private: + QHelpSearchQueryWidgetPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QHELPSEARCHQUERYWIDGET_H diff --git a/src/assistant/help/qhelpsearchresult.cpp b/src/assistant/help/qhelpsearchresult.cpp new file mode 100644 index 0000000..a813249 --- /dev/null +++ b/src/assistant/help/qhelpsearchresult.cpp @@ -0,0 +1,90 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpsearchresult.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpSearchResultData : public QSharedData +{ +public: + QUrl m_url; + QString m_title; + QString m_snippet; +}; + +/*! + \class QHelpSearchResult + \since 5.9 + \inmodule QtHelp + \brief The QHelpSearchResult class provides the data associated with the + search result. + + The QHelpSearchResult object is a data object that describes a single search result. + The vector of search result objects is returned by QHelpSearchEngine::searchResults(). + The description of the search result contains the document title and URL + that the search input matched. It also contains the snippet from + the document content containing the best match of the search input. + \sa QHelpSearchEngine +*/ + +/*! + Constructs a new empty QHelpSearchResult. +*/ +QHelpSearchResult::QHelpSearchResult() : d(new QHelpSearchResultData) { } + +/*! + Constructs a copy of \a other. +*/ +QHelpSearchResult::QHelpSearchResult(const QHelpSearchResult &other) = default; + +/*! + Constructs the search result containing \a url, \a title and \a snippet + as the description of the result. +*/ +QHelpSearchResult::QHelpSearchResult(const QUrl &url, const QString &title, const QString &snippet) + : d(new QHelpSearchResultData) +{ + d->m_url = url; + d->m_title = title; + d->m_snippet = snippet; +} + +/*! + Destroys the search result. +*/ +QHelpSearchResult::~QHelpSearchResult() = default; + +/*! + Assigns \a other to this search result and returns a reference to this search result. +*/ +QHelpSearchResult &QHelpSearchResult::operator=(const QHelpSearchResult &other) = default; + +/*! + Returns the document title of the search result. +*/ +QString QHelpSearchResult::title() const +{ + return d->m_title; +} + +/*! + Returns the document URL of the search result. +*/ +QUrl QHelpSearchResult::url() const +{ + return d->m_url; +} + +/*! + Returns the document snippet containing the search phrase of the search result. +*/ +QString QHelpSearchResult::snippet() const +{ + return d->m_snippet; +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qhelpsearchresult.h b/src/assistant/help/qhelpsearchresult.h new file mode 100644 index 0000000..d8b42c1 --- /dev/null +++ b/src/assistant/help/qhelpsearchresult.h @@ -0,0 +1,37 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPSEARCHRESULT_H +#define QHELPSEARCHRESULT_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QHelpSearchResultData; +class QString; +class QUrl; + +class QHELP_EXPORT QHelpSearchResult +{ +public: + QHelpSearchResult(); + QHelpSearchResult(const QHelpSearchResult &other); + QHelpSearchResult(const QUrl &url, const QString &title, const QString &snippet); + ~QHelpSearchResult(); + + QHelpSearchResult &operator=(const QHelpSearchResult &other); + + QString title() const; + QUrl url() const; + QString snippet() const; + +private: + QSharedDataPointer d; +}; + +QT_END_NAMESPACE + +#endif // QHELPSEARCHRESULT_H diff --git a/src/assistant/help/qhelpsearchresultwidget.cpp b/src/assistant/help/qhelpsearchresultwidget.cpp new file mode 100644 index 0000000..3de64dd --- /dev/null +++ b/src/assistant/help/qhelpsearchresultwidget.cpp @@ -0,0 +1,268 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpsearchresultwidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static constexpr int ResultsRange = 20; + +class QResultWidget : public QTextBrowser +{ + Q_OBJECT + Q_PROPERTY(QColor linkColor READ linkColor WRITE setLinkColor) + +public: + QResultWidget(QWidget *parent = nullptr) + : QTextBrowser(parent) + { + connect(this, &QTextBrowser::anchorClicked, this, &QResultWidget::requestShowLink); + setContextMenuPolicy(Qt::NoContextMenu); + setLinkColor(palette().color(QPalette::Link)); + } + + QColor linkColor() const { return m_linkColor; } + void setLinkColor(const QColor &color) + { + m_linkColor = color; + const QString sheet = QString::fromLatin1("a { text-decoration: underline; color: %1 }") + .arg(m_linkColor.name()); + document()->setDefaultStyleSheet(sheet); + } + + void showResultPage(const QList &results, bool isIndexing) + { + QString htmlFile; + QTextStream str(&htmlFile); + str << "" << tr("Search Results") << ""; + + const int count = results.size(); + if (count != 0) { + if (isIndexing) { + str << "
" << tr("Note:") + << " " + << tr("The search results may not be complete since the " + "documentation is still being indexed.") + << "

"; + } + + for (const QHelpSearchResult &result : results) { + str << "
" + "
" << result.snippet() << "
"; + } + } else { + str << "


" + << tr("Your search did not match any documents.") + << "

"; + if (isIndexing) { + str << "

" + << tr("(The reason for this might be that the documentation " + "is still being indexed.)") << "

"; + } + } + + str << ""; + setHtml(htmlFile); + } + +signals: + void requestShowLink(const QUrl &url); + +private slots: + void doSetSource(const QUrl & /*name*/, QTextDocument::ResourceType /*type*/) override {} + +private: + QColor m_linkColor; +}; + +class QHelpSearchResultWidgetPrivate +{ +public: + ~QHelpSearchResultWidgetPrivate() + { + delete searchEngine; // TODO: This it probably wrong, why the widget owns the engine? + } + + QToolButton* setupToolButton(const QString &iconPath) + { + QToolButton *button = new QToolButton; + button->setEnabled(false); + button->setAutoRaise(true); + button->setIcon(QIcon(iconPath)); + button->setIconSize({12, 12}); + button->setMaximumSize({16, 16}); + return button; + } + + void updateHitRange() + { + int last = 0; + int first = 0; + int count = 0; + + if (!searchEngine.isNull()) { + count = searchEngine->searchResultCount(); + if (count > 0) { + last = qMin(resultFirstToShow + ResultsRange, count); + first = resultFirstToShow + 1; + } + resultTextBrowser->showResultPage(searchEngine->searchResults(resultFirstToShow, last), + isIndexing); + } + + hitsLabel->setText(QHelpSearchResultWidget::tr("%1 - %2 of %n Hits", nullptr, count) + .arg(first).arg(last)); + firstResultPage->setEnabled(resultFirstToShow); + previousResultPage->setEnabled(resultFirstToShow); + lastResultPage->setEnabled(count - last); + nextResultPage->setEnabled(count - last); + } + + QHelpSearchResultWidget *q = nullptr; + QPointer searchEngine; + + QResultWidget *resultTextBrowser = nullptr; + + QToolButton *firstResultPage = nullptr; + QToolButton *previousResultPage = nullptr; + QToolButton *nextResultPage = nullptr; + QToolButton *lastResultPage = nullptr; + QLabel *hitsLabel = nullptr; + int resultFirstToShow = 0; + bool isIndexing = false; +}; + +/*! + \class QHelpSearchResultWidget + \since 4.4 + \inmodule QtHelp + \brief The QHelpSearchResultWidget class provides a text browser to display + search results. +*/ + +/*! + \fn void QHelpSearchResultWidget::requestShowLink(const QUrl &link) + + This signal is emitted when a item is activated and its associated + \a link should be shown. +*/ + +QHelpSearchResultWidget::QHelpSearchResultWidget(QHelpSearchEngine *engine) + : QWidget(0) + , d(new QHelpSearchResultWidgetPrivate) +{ + d->q = this; + d->searchEngine = engine; + + connect(engine, &QHelpSearchEngine::indexingStarted, this, [this] { d->isIndexing = true; }); + connect(engine, &QHelpSearchEngine::indexingFinished, this, [this] { d->isIndexing = false; }); + + QVBoxLayout *vLayout = new QVBoxLayout(this); + vLayout->setContentsMargins({}); + vLayout->setSpacing(0); + + QHBoxLayout *hBoxLayout = new QHBoxLayout(); +#ifndef Q_OS_MAC + hBoxLayout->setContentsMargins({}); + hBoxLayout->setSpacing(0); +#endif + hBoxLayout->addWidget(d->firstResultPage = d->setupToolButton( + QString::fromUtf8(":/qt-project.org/assistant/images/3leftarrow.png"))); + + hBoxLayout->addWidget(d->previousResultPage = d->setupToolButton( + QString::fromUtf8(":/qt-project.org/assistant/images/1leftarrow.png"))); + + d->hitsLabel = new QLabel(tr("0 - 0 of 0 Hits"), this); + hBoxLayout->addWidget(d->hitsLabel); + d->hitsLabel->setAlignment(Qt::AlignCenter); + d->hitsLabel->setMinimumSize(QSize(150, d->hitsLabel->height())); + + hBoxLayout->addWidget(d->nextResultPage = d->setupToolButton( + QString::fromUtf8(":/qt-project.org/assistant/images/1rightarrow.png"))); + + hBoxLayout->addWidget(d->lastResultPage = d->setupToolButton( + QString::fromUtf8(":/qt-project.org/assistant/images/3rightarrow.png"))); + + QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + hBoxLayout->addItem(spacer); + + vLayout->addLayout(hBoxLayout); + + d->resultTextBrowser = new QResultWidget(this); + vLayout->addWidget(d->resultTextBrowser); + + connect(d->resultTextBrowser, &QResultWidget::requestShowLink, + this, &QHelpSearchResultWidget::requestShowLink); + + connect(d->nextResultPage, &QAbstractButton::clicked, this, [this] { + if (!d->searchEngine.isNull() + && d->resultFirstToShow + ResultsRange < d->searchEngine->searchResultCount()) { + d->resultFirstToShow += ResultsRange; + } + d->updateHitRange(); + }); + connect(d->previousResultPage, &QAbstractButton::clicked, this, [this] { + if (!d->searchEngine.isNull()) { + d->resultFirstToShow -= ResultsRange; + if (d->resultFirstToShow < 0) + d->resultFirstToShow = 0; + } + d->updateHitRange(); + }); + connect(d->lastResultPage, &QAbstractButton::clicked, this, [this] { + if (!d->searchEngine.isNull()) + d->resultFirstToShow = (d->searchEngine->searchResultCount() - 1) / ResultsRange * ResultsRange; + d->updateHitRange(); + }); + const auto showFirstPage = [this] { + if (!d->searchEngine.isNull()) + d->resultFirstToShow = 0; + d->updateHitRange(); + }; + connect(d->firstResultPage, &QAbstractButton::clicked, this, showFirstPage); + connect(engine, &QHelpSearchEngine::searchingFinished, this, showFirstPage); +} + +/*! \reimp +*/ +void QHelpSearchResultWidget::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::LanguageChange) + d->updateHitRange(); +} + +/*! + Destroys the search result widget. +*/ +QHelpSearchResultWidget::~QHelpSearchResultWidget() +{ + delete d; +} + +/*! + Returns a reference of the URL that the item at \a point owns, or an + empty URL if no item exists at that point. +*/ +QUrl QHelpSearchResultWidget::linkAt(const QPoint &point) +{ + if (d->resultTextBrowser) + return d->resultTextBrowser->anchorAt(point); + return {}; +} + +QT_END_NAMESPACE + +#include "qhelpsearchresultwidget.moc" diff --git a/src/assistant/help/qhelpsearchresultwidget.h b/src/assistant/help/qhelpsearchresultwidget.h new file mode 100644 index 0000000..859477e --- /dev/null +++ b/src/assistant/help/qhelpsearchresultwidget.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPSEARCHRESULTWIDGET_H +#define QHELPSEARCHRESULTWIDGET_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QHelpSearchResultWidgetPrivate; +class QPoint; +class QUrl; + +class QHELP_EXPORT QHelpSearchResultWidget : public QWidget +{ + Q_OBJECT + +public: + ~QHelpSearchResultWidget() override; + QUrl linkAt(const QPoint &point); + +Q_SIGNALS: + void requestShowLink(const QUrl &url); + +private: + friend class QHelpSearchEngine; + + QHelpSearchResultWidgetPrivate *d; + QHelpSearchResultWidget(QHelpSearchEngine *engine); + void changeEvent(QEvent *event) override; +}; + +QT_END_NAMESPACE + +#endif // QHELPSEARCHRESULTWIDGET_H diff --git a/src/assistant/help/qoptionswidget.cpp b/src/assistant/help/qoptionswidget.cpp new file mode 100644 index 0000000..e529b65 --- /dev/null +++ b/src/assistant/help/qoptionswidget.cpp @@ -0,0 +1,186 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qoptionswidget_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class ListWidgetDelegate : public QItemDelegate +{ +public: + ListWidgetDelegate(QWidget *w) : QItemDelegate(w), m_widget(w) {} + + static bool isSeparator(const QModelIndex &index) { + return index.data(Qt::AccessibleDescriptionRole).toString() == "separator"_L1; + } + static void setSeparator(QListWidgetItem *item) { + item->setData(Qt::AccessibleDescriptionRole, QString::fromLatin1("separator")); + item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); + } + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + if (isSeparator(index)) { + QRect rect = option.rect; + if (const QAbstractItemView *view = qobject_cast(option.widget)) + rect.setWidth(view->viewport()->width()); + QStyleOption opt; + opt.rect = rect; + m_widget->style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, painter, + m_widget); + } else { + QItemDelegate::paint(painter, option, index); + } + } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + if (isSeparator(index)) { + int pm = m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, m_widget); + return {pm, pm}; + } + return QItemDelegate::sizeHint(option, index); + } + +private: + QWidget *m_widget; +}; + +static QStringList subtract(const QStringList &minuend, const QStringList &subtrahend) +{ + QStringList result = minuend; + for (const QString &str : subtrahend) + result.removeOne(str); + return result; +} + +QOptionsWidget::QOptionsWidget(QWidget *parent) + : QWidget(parent) + , m_noOptionText(tr("No Option")) + , m_invalidOptionText(tr("Invalid Option")) +{ + m_listWidget = new QListWidget(this); + m_listWidget->setItemDelegate(new ListWidgetDelegate(m_listWidget)); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(m_listWidget); + layout->setContentsMargins({}); + connect(m_listWidget, &QListWidget::itemChanged, this, &QOptionsWidget::itemChanged); +} + +void QOptionsWidget::setOptions(const QStringList &validOptions, const QStringList &selectedOptions) +{ + m_listWidget->clear(); + m_optionToItem.clear(); + m_itemToOption.clear(); + + m_validOptions = validOptions; + m_validOptions.removeDuplicates(); + std::sort(m_validOptions.begin(), m_validOptions.end()); + + m_selectedOptions = selectedOptions; + m_selectedOptions.removeDuplicates(); + std::sort(m_selectedOptions.begin(), m_selectedOptions.end()); + + m_invalidOptions = subtract(m_selectedOptions, m_validOptions); + const QStringList validSelectedOptions = subtract(m_selectedOptions, m_invalidOptions); + const QStringList validUnselectedOptions = subtract(m_validOptions, m_selectedOptions); + + for (const QString &option : validSelectedOptions) + appendItem(option, true, true); + + for (const QString &option : m_invalidOptions) + appendItem(option, false, true); + + if ((validSelectedOptions.size() + m_invalidOptions.size()) && validUnselectedOptions.size()) + appendSeparator(); + + for (const QString &option : validUnselectedOptions) { + appendItem(option, true, false); + if (option.isEmpty() && validUnselectedOptions.size() > 1) // special No Option item + appendSeparator(); + } +} + +void QOptionsWidget::setNoOptionText(const QString &text) +{ + if (m_noOptionText == text) + return; + + m_noOptionText = text; + + // update GUI + const auto itEnd = m_optionToItem.constEnd(); + for (auto it = m_optionToItem.constBegin(); it != itEnd; ++it) { + const QString optionName = it.key(); + if (optionName.isEmpty()) + it.value()->setText(optionText(optionName, m_validOptions.contains(optionName))); + } +} + +void QOptionsWidget::setInvalidOptionText(const QString &text) +{ + if (m_invalidOptionText == text) + return; + + m_invalidOptionText = text; + + // update GUI + for (const QString &option : m_invalidOptions) + m_optionToItem.value(option)->setText(optionText(option, false)); +} + +QString QOptionsWidget::optionText(const QString &optionName, bool valid) const +{ + QString text = optionName; + if (optionName.isEmpty()) + text = u'[' + m_noOptionText + u']'; + if (!valid) + text += "\t["_L1 + m_invalidOptionText + u']'; + return text; +} + +QListWidgetItem *QOptionsWidget::appendItem(const QString &optionName, bool valid, bool selected) +{ + QListWidgetItem *optionItem = new QListWidgetItem(optionText(optionName, valid), m_listWidget); + optionItem->setCheckState(selected ? Qt::Checked : Qt::Unchecked); + m_listWidget->addItem(optionItem); + m_optionToItem[optionName] = optionItem; + m_itemToOption[optionItem] = optionName; + return optionItem; +} + +void QOptionsWidget::appendSeparator() +{ + QListWidgetItem *separatorItem = new QListWidgetItem(m_listWidget); + ListWidgetDelegate::setSeparator(separatorItem); + m_listWidget->addItem(separatorItem); +} + +void QOptionsWidget::itemChanged(QListWidgetItem *item) +{ + const auto it = m_itemToOption.constFind(item); + if (it == m_itemToOption.constEnd()) + return; + + const QString option = *it; + + if (item->checkState() == Qt::Checked && !m_selectedOptions.contains(option)) { + m_selectedOptions.append(option); + std::sort(m_selectedOptions.begin(), m_selectedOptions.end()); + } else if (item->checkState() == Qt::Unchecked && m_selectedOptions.contains(option)) { + m_selectedOptions.removeOne(option); + } else { + return; + } + emit optionSelectionChanged(m_selectedOptions); +} + +QT_END_NAMESPACE diff --git a/src/assistant/help/qoptionswidget_p.h b/src/assistant/help/qoptionswidget_p.h new file mode 100644 index 0000000..a62fbe2 --- /dev/null +++ b/src/assistant/help/qoptionswidget_p.h @@ -0,0 +1,61 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QOPTIONSWIDGET_H +#define QOPTIONSWIDGET_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the help generator tools. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QListWidget; +class QListWidgetItem; + +class QOptionsWidget : public QWidget +{ + Q_OBJECT +public: + QOptionsWidget(QWidget *parent = nullptr); + + void clear() { setOptions({}, {}); } + void setOptions(const QStringList &validOptions, const QStringList &selectedOptions); + QStringList validOptions() const { return m_validOptions; } + QStringList selectedOptions() const { return m_selectedOptions; } + + void setNoOptionText(const QString &text); + void setInvalidOptionText(const QString &text); + +signals: + void optionSelectionChanged(const QStringList &options); + +private: + QString optionText(const QString &optionName, bool valid) const; + QListWidgetItem *appendItem(const QString &optionName, bool valid, bool selected); + void appendSeparator(); + void itemChanged(QListWidgetItem *item); + + QListWidget *m_listWidget = nullptr; + QString m_noOptionText; + QString m_invalidOptionText; + QStringList m_validOptions; + QStringList m_invalidOptions; + QStringList m_selectedOptions; + QHash m_optionToItem; + QHash m_itemToOption; +}; + +QT_END_NAMESPACE + +#endif // QOPTIONSWIDGET_H diff --git a/src/assistant/plugins/CMakeLists.txt b/src/assistant/plugins/CMakeLists.txt new file mode 100644 index 0000000..9e096a4 --- /dev/null +++ b/src/assistant/plugins/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(TARGET Qt::Help AND TARGET Qt::QmlLSPrivate) + add_subdirectory(help) +endif() diff --git a/src/assistant/plugins/help/CMakeLists.txt b/src/assistant/plugins/help/CMakeLists.txt new file mode 100644 index 0000000..00f0991 --- /dev/null +++ b/src/assistant/plugins/help/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(QHelpEnginePlugin + OUTPUT_NAME helpplugin + PLUGIN_TYPE help + SOURCES + qhelpengineplugin.h + qhelpengineplugin.cpp + LIBRARIES + Qt::Core + Qt::QmlLSPrivate + Qt::Help +) diff --git a/src/assistant/plugins/help/qhelpengineplugin.cpp b/src/assistant/plugins/help/qhelpengineplugin.cpp new file mode 100644 index 0000000..55296c8 --- /dev/null +++ b/src/assistant/plugins/help/qhelpengineplugin.cpp @@ -0,0 +1,95 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qhelpengineplugin.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static std::vector +transformQHelpLink(QList &&qhelplinklist) +{ + std::vector result(qhelplinklist.size()); + std::transform(qhelplinklist.begin(), qhelplinklist.end(), result.begin(), + [&](const auto &item) { + QQmlLSHelpProvider::DocumentLink element; + element.title = item.title; + element.url = item.url; + return element; + }); + return result; +} + +QQmlLSHelpProvider::QQmlLSHelpProvider(const QString &qhcFilePath, QObject *parent) +{ + m_helpEngine.emplace(qhcFilePath, parent); + m_helpEngine->setReadOnly(false); + m_helpEngine->setupData(); +} + +bool QQmlLSHelpProvider::registerDocumentation(const QString &documentationFileName) +{ + Q_ASSERT(m_helpEngine.has_value()); + return m_helpEngine->registerDocumentation(documentationFileName); +} + +QByteArray QQmlLSHelpProvider::fileData(const QUrl &url) const +{ + Q_ASSERT(m_helpEngine.has_value()); + return m_helpEngine->fileData(url); +} + +std::vector +QQmlLSHelpProvider::documentsForIdentifier(const QString &id) const +{ + Q_ASSERT(m_helpEngine.has_value()); + return transformQHelpLink(m_helpEngine->documentsForIdentifier(id)); +} + +std::vector +QQmlLSHelpProvider::documentsForIdentifier(const QString &id, const QString &filterName) const +{ + Q_ASSERT(m_helpEngine.has_value()); + return transformQHelpLink(m_helpEngine->documentsForIdentifier(id, filterName)); +} + +std::vector +QQmlLSHelpProvider::documentsForKeyword(const QString &keyword) const +{ + Q_ASSERT(m_helpEngine.has_value()); + return transformQHelpLink(m_helpEngine->documentsForKeyword(keyword)); +} + +std::vector +QQmlLSHelpProvider::documentsForKeyword(const QString &keyword, const QString &filter) const +{ + Q_ASSERT(m_helpEngine.has_value()); + return transformQHelpLink(m_helpEngine->documentsForKeyword(keyword, filter)); +} + +QStringList QQmlLSHelpProvider::registeredNamespaces() const +{ + Q_ASSERT(m_helpEngine.has_value()); + return m_helpEngine->registeredDocumentations(); +} + +QString QQmlLSHelpProvider::error() const +{ + Q_ASSERT(m_helpEngine.has_value()); + return m_helpEngine->error(); +} + +QHelpEnginePlugin::QHelpEnginePlugin(QObject *parent) : QObject(parent) { } + +std::unique_ptr QHelpEnginePlugin::initialize(const QString &collectionFile, + QObject *parent) +{ + return std::make_unique(collectionFile, parent); +} + +QT_END_NAMESPACE + +#include "moc_qhelpengineplugin.cpp" diff --git a/src/assistant/plugins/help/qhelpengineplugin.h b/src/assistant/plugins/help/qhelpengineplugin.h new file mode 100644 index 0000000..9b17c2d --- /dev/null +++ b/src/assistant/plugins/help/qhelpengineplugin.h @@ -0,0 +1,65 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QHELPENGINEPLUGIN_H +#define QHELPENGINEPLUGIN_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +// TODO (Qt 7.0) +// Remove this plugin from QtTools when the QtHelp lib is split into +// QtHelpCore and QtHelp. Then QmlLS can depend only on QtHelpCore. +class QQmlLSHelpProvider : public QQmlLSHelpProviderBase +{ +public: + QQmlLSHelpProvider(const QString &qhcFile, QObject *parent = nullptr); + bool registerDocumentation(const QString &documentationFileName) override; + [[nodiscard]] QByteArray fileData(const QUrl &url) const override; + [[nodiscard]] std::vector documentsForIdentifier(const QString &id) const override; + [[nodiscard]] std::vector documentsForIdentifier(const QString &id, const QString &filterName) const override; + [[nodiscard]] std::vector documentsForKeyword(const QString &keyword) const override; + [[nodiscard]] std::vector documentsForKeyword(const QString &keyword, const QString &filterName) const override; + [[nodiscard]] QStringList registeredNamespaces() const override; + [[nodiscard]] QString error() const override; + +private: + std::optional m_helpEngine; +}; + +class QHelpEnginePlugin : public QObject, public QQmlLSHelpPluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlLSHelpPluginInterface_iid) + Q_INTERFACES(QQmlLSHelpPluginInterface) +public: + QHelpEnginePlugin(QObject *parent = nullptr); + Q_DISABLE_COPY_MOVE(QHelpEnginePlugin) + + std::unique_ptr initialize(const QString &collectionFile, + QObject *parent) override; +}; + +QT_END_NAMESPACE + +#endif // QHELPENGINEPLUGIN_H diff --git a/src/assistant/qhelpgenerator/CMakeLists.txt b/src/assistant/qhelpgenerator/CMakeLists.txt new file mode 100644 index 0000000..9d2541d --- /dev/null +++ b/src/assistant/qhelpgenerator/CMakeLists.txt @@ -0,0 +1,64 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## qhelpgenerator Tool: +##################################################################### + +qt_get_tool_target_name(target_name qhelpgenerator) +qt_internal_add_tool(${target_name} + TARGET_DESCRIPTION "Qt Compressed Help File Generator" + TOOLS_TARGET Tools + INSTALL_DIR "${INSTALL_LIBEXECDIR}" + SOURCES + ../shared/collectionconfiguration.cpp ../shared/collectionconfiguration.h + collectionconfigreader.cpp collectionconfigreader.h + helpgenerator.cpp helpgenerator.h + main.cpp + qhelpdatainterface.cpp qhelpdatainterface_p.h + qhelpprojectdata.cpp qhelpprojectdata_p.h + LIBRARIES + Qt::Gui + Qt::HelpPrivate +) +qt_internal_return_unless_building_tools() + +set(needed_plugins + QMinimalIntegrationPlugin + QSQLiteDriverPlugin +) +list(TRANSFORM needed_plugins PREPEND Qt:: OUTPUT_VARIABLE needed_plugin_targets) + +if(NOT QT_BUILD_SHARED_LIBS) + if(QT_SUPERBUILD) + # In a top-level build, qt_import_plugins() is a no-op because + # __qt_internal_add_static_plugins_once() is not called. + # So we need to initialize and link the plugin manually. + set(out_file_path "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_plugin_imports_custom.cpp") + + # Create a string with the necessary Q_IMPORT_PLUGIN(...) statements. + list(TRANSFORM needed_plugins PREPEND "Q_IMPORT_PLUGIN(" OUTPUT_VARIABLE import_plugin_code) + list(TRANSFORM import_plugin_code APPEND ")") + list(JOIN import_plugin_code "\n" import_plugin_code) + + file(GENERATE OUTPUT "${out_file_path}" CONTENT +"// This file is auto-generated. Do not edit. +#include + +${import_plugin_code} +") + + _qt_internal_set_source_file_generated(SOURCES "${out_file_path}") + + target_sources(${target_name} PRIVATE "${out_file_path}") + target_link_libraries(${target_name} PRIVATE ${needed_plugin_targets}) + else() + qt_import_plugins(${target_name} + INCLUDE ${needed_plugin_targets} + ) + endif() +else() # QT_BUILD_SHARED_LIBS + if(TARGET ${target_name} AND QT_SUPERBUILD) + add_dependencies(${target_name} ${needed_plugins}) + endif() +endif() diff --git a/src/assistant/qhelpgenerator/collectionconfigreader.cpp b/src/assistant/qhelpgenerator/collectionconfigreader.cpp new file mode 100644 index 0000000..d395e63 --- /dev/null +++ b/src/assistant/qhelpgenerator/collectionconfigreader.cpp @@ -0,0 +1,222 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "collectionconfigreader.h" + +#include + +class QCG { + Q_DECLARE_TR_FUNCTIONS(QCollectionGenerator) +}; + +void CollectionConfigReader::raiseErrorWithLine() +{ + raiseError(QCG::tr("Unknown token at line %1.").arg(lineNumber())); +} + +void CollectionConfigReader::readData(const QByteArray &contents) +{ + m_enableFilterFunctionality = true; + m_hideFilterFunctionality = true; + m_enableAddressBar = true; + m_hideAddressBar = true; + m_enableDocumentationManager = true; + m_enableFullTextSearchFallback = false; + + addData(contents); + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("QHelpCollectionProject") + && attributes().value(QLatin1String("version")) == QLatin1String("1.0")) + readConfig(); + else + raiseError(QCG::tr("Unknown token at line %1. " + "Expected \"QtHelpCollectionProject\".") + .arg(lineNumber())); + } + } +} + +void CollectionConfigReader::readConfig() +{ + bool ok = false; + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("assistant")) + readAssistantSettings(); + else if (name() == QLatin1String("docFiles")) + readDocFiles(); + else + raiseErrorWithLine(); + } else if (isEndElement() && name() == QLatin1String("QHelpCollectionProject")) { + ok = true; + } + } + if (!ok && !hasError()) + raiseError(QCG::tr("Missing end tags.")); +} + +void CollectionConfigReader::readAssistantSettings() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("title")) { + m_title = readElementText(); + } else if (name() == QLatin1String("homePage")) { + m_homePage = readElementText(); + } else if (name() == QLatin1String("startPage")) { + m_startPage = readElementText(); + } else if (name() == QLatin1String("currentFilter")) { + m_currentFilter = readElementText(); + } else if (name() == QLatin1String("applicationIcon")) { + m_applicationIcon = readElementText(); + } else if (name() == QLatin1String("enableFilterFunctionality")) { + if (attributes().value(QLatin1String("visible")) == QLatin1String("true")) + m_hideFilterFunctionality = false; + if (readElementText() == QLatin1String("false")) + m_enableFilterFunctionality = false; + } else if (name() == QLatin1String("enableDocumentationManager")) { + if (readElementText() == QLatin1String("false")) + m_enableDocumentationManager = false; + } else if (name() == QLatin1String("enableAddressBar")) { + if (attributes().value(QLatin1String("visible")) == QLatin1String("true")) + m_hideAddressBar = false; + if (readElementText() == QLatin1String("false")) + m_enableAddressBar = false; + } else if (name() == QLatin1String("aboutMenuText")) { + readMenuTexts(); + } else if (name() == QLatin1String("aboutDialog")) { + readAboutDialog(); + } else if (name() == u"cacheDirectory") { + m_cacheDirRelativeToCollection = + attributes().value(QLatin1String("base")) + == QLatin1String("collection"); + m_cacheDirectory = readElementText(); + } else if (name() == QLatin1String("enableFullTextSearchFallback")) { + if (readElementText() == QLatin1String("true")) + m_enableFullTextSearchFallback = true; + } else { + raiseErrorWithLine(); + } + } else if (isEndElement() && name() == QLatin1String("assistant")) { + break; + } + } +} + +void CollectionConfigReader::readMenuTexts() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("text")) { + QString lang = attributes().value(QLatin1String("language")).toString(); + if (lang.isEmpty()) + lang = QLatin1String("default"); + m_aboutMenuTexts.insert(lang, readElementText()); + } else { + raiseErrorWithLine(); + } + } else if (isEndElement() && name() == QLatin1String("aboutMenuText")) { + break; + } + } +} + +void CollectionConfigReader::readAboutDialog() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("file")) { + QString lang = attributes().value(QLatin1String("language")).toString(); + if (lang.isEmpty()) + lang = QLatin1String("default"); + m_aboutTextFiles.insert(lang, readElementText()); + } else if (name() == QLatin1String("icon")) { + m_aboutIcon = readElementText(); + } else { + raiseErrorWithLine(); + } + } else if (isEndElement() && name() == QLatin1String("aboutDialog")) { + break; + } + } +} + +void CollectionConfigReader::readDocFiles() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("generate")) { + readGenerate(); + } else if (name() == QLatin1String("register")) { + readRegister(); + } else { + raiseErrorWithLine(); + } + } else if (isEndElement() && name() == QLatin1String("docFiles")) { + break; + } + } +} + +void CollectionConfigReader::readGenerate() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("file")) + readFiles(); + else + raiseErrorWithLine(); + } else if (isEndElement() && name() == QLatin1String("generate")) { + break; + } + } +} + +void CollectionConfigReader::readFiles() +{ + QString input; + QString output; + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("input")) + input = readElementText(); + else if (name() == QLatin1String("output")) + output = readElementText(); + else + raiseErrorWithLine(); + } else if (isEndElement() && name() == QLatin1String("file")) { + break; + } + } + if (input.isEmpty() || output.isEmpty()) { + raiseError(QCG::tr("Missing input or output file for help file generation.")); + return; + } + m_filesToGenerate.insert(input, output); +} + +void CollectionConfigReader::readRegister() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("file")) + m_filesToRegister.append(readElementText()); + else + raiseErrorWithLine(); + } else if (isEndElement() && name() == QLatin1String("register")) { + break; + } + } +} + + diff --git a/src/assistant/qhelpgenerator/collectionconfigreader.h b/src/assistant/qhelpgenerator/collectionconfigreader.h new file mode 100644 index 0000000..0d6de5d --- /dev/null +++ b/src/assistant/qhelpgenerator/collectionconfigreader.h @@ -0,0 +1,81 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef COLLECTIONCONFIGREADER_H +#define COLLECTIONCONFIGREADER_H + +#include +#include +#include + +QT_USE_NAMESPACE + +class CollectionConfigReader : public QXmlStreamReader +{ +public: + void readData(const QByteArray &contents); + + QString title() const { return m_title; } + QString homePage() const { return m_homePage; } + QString startPage() const { return m_startPage; } + QString applicationIcon() const { return m_applicationIcon; } + QString currentFilter() const { return m_currentFilter; } + bool enableFilterFunctionality() const + { return m_enableFilterFunctionality; } + bool hideFilterFunctionality() const + { return m_hideFilterFunctionality; } + bool enableAddressBar() const { return m_enableAddressBar; } + bool hideAddressBar() const { return m_hideAddressBar; } + bool enableDocumentationManager() const + { return m_enableDocumentationManager; } + + QMap aboutMenuTexts() const + { return m_aboutMenuTexts; } + QString aboutIcon() const { return m_aboutIcon; } + QMap aboutTextFiles() const + { return m_aboutTextFiles; } + + QMap filesToGenerate() const + { return m_filesToGenerate; } + + QStringList filesToRegister() const { return m_filesToRegister; } + + QString cacheDirectory() const { return m_cacheDirectory; } + bool cacheDirRelativeToCollection() const { return m_cacheDirRelativeToCollection; } + + bool fullTextSearchFallbackEnabled() const { + return m_enableFullTextSearchFallback; + } + +private: + void raiseErrorWithLine(); + void readConfig(); + void readAssistantSettings(); + void readMenuTexts(); + void readAboutDialog(); + void readDocFiles(); + void readGenerate(); + void readFiles(); + void readRegister(); + + QMap m_aboutMenuTexts; + QMap m_aboutTextFiles; + QMap m_filesToGenerate; + QStringList m_filesToRegister; + QString m_title; + QString m_homePage; + QString m_startPage; + QString m_applicationIcon; + QString m_currentFilter; + QString m_aboutIcon; + QString m_cacheDirectory; + bool m_enableFilterFunctionality; + bool m_hideFilterFunctionality; + bool m_enableAddressBar; + bool m_hideAddressBar; + bool m_enableDocumentationManager; + bool m_cacheDirRelativeToCollection; + bool m_enableFullTextSearchFallback; +}; + +#endif diff --git a/src/assistant/qhelpgenerator/helpgenerator.cpp b/src/assistant/qhelpgenerator/helpgenerator.cpp new file mode 100644 index 0000000..802830a --- /dev/null +++ b/src/assistant/qhelpgenerator/helpgenerator.cpp @@ -0,0 +1,851 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "helpgenerator.h" +#include "qhelpprojectdata_p.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class HelpGeneratorPrivate : public QObject +{ + Q_OBJECT + +public: + HelpGeneratorPrivate(QObject *parent = nullptr) : QObject(parent) {} + + bool generate(QHelpProjectData *helpData, + const QString &outputFileName); + bool checkLinks(const QHelpProjectData &helpData); + QString error() const; + +Q_SIGNALS: + void statusChanged(const QString &msg); + void progressChanged(double progress); + void warning(const QString &msg); + +private: + struct FileNameTableData + { + QString name; + int fileId; + QString title; + }; + + void writeTree(QDataStream &s, QHelpDataContentItem *item, int depth); + bool createTables(); + bool insertFileNotFoundFile(); + bool registerCustomFilter(const QString &filterName, + const QStringList &filterAttribs, bool forceUpdate = false); + bool registerVirtualFolder(const QString &folderName, const QString &ns); + bool insertFilterAttributes(const QStringList &attributes); + bool insertKeywords(const QList &keywords, + const QStringList &filterAttributes); + bool insertFiles(const QStringList &files, const QString &rootPath, + const QStringList &filterAttributes); + bool insertContents(const QByteArray &ba, + const QStringList &filterAttributes); + bool insertMetaData(const QMap &metaData); + void cleanupDB(); + void setupProgress(QHelpProjectData *helpData); + void addProgress(double step); + + QString m_error; + QSqlQuery *m_query = nullptr; + + int m_namespaceId = -1; + int m_virtualFolderId = -1; + + QMap m_fileMap; + QMap > m_fileFilterMap; + + double m_progress; + double m_oldProgress; + double m_contentStep; + double m_fileStep; + double m_indexStep; +}; + +/*! + Takes the \a helpData and generates a new documentation + set from it. The Qt compressed help file is written to \a + outputFileName. Returns true on success, otherwise false. +*/ +bool HelpGeneratorPrivate::generate(QHelpProjectData *helpData, + const QString &outputFileName) +{ + emit progressChanged(0); + m_error.clear(); + if (!helpData || helpData->namespaceName().isEmpty()) { + m_error = tr("Invalid help data."); + return false; + } + + QString outFileName = outputFileName; + if (outFileName.isEmpty()) { + m_error = tr("No output file name specified."); + return false; + } + + QFileInfo fi(outFileName); + if (fi.exists()) { + if (!fi.dir().remove(fi.fileName())) { + m_error = tr("The file %1 cannot be overwritten.").arg(outFileName); + return false; + } + } + + setupProgress(helpData); + + emit statusChanged(tr("Building up file structure...")); + bool openingOk = true; + { + QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), QLatin1String("builder")); + db.setDatabaseName(outFileName); + openingOk = db.open(); + if (openingOk) + m_query = new QSqlQuery(db); + } + + if (!openingOk) { + m_error = tr("Cannot open data base file %1.").arg(outFileName); + cleanupDB(); + return false; + } + + m_query->exec(QLatin1String("PRAGMA synchronous=OFF")); + m_query->exec(QLatin1String("PRAGMA cache_size=3000")); + + addProgress(1.0); + createTables(); + insertFileNotFoundFile(); + insertMetaData(helpData->metaData()); + + if (!registerVirtualFolder(helpData->virtualFolder(), helpData->namespaceName())) { + m_error = tr("Cannot register namespace \"%1\".").arg(helpData->namespaceName()); + cleanupDB(); + return false; + } + addProgress(1.0); + + emit statusChanged(tr("Insert custom filters...")); + for (const QHelpDataCustomFilter &f : helpData->customFilters()) { + if (!registerCustomFilter(f.name, f.filterAttributes, true)) { + cleanupDB(); + return false; + } + } + addProgress(1.0); + + int i = 1; + for (const QHelpDataFilterSection &fs : helpData->filterSections()) { + emit statusChanged(tr("Insert help data for filter section (%1 of %2)...") + .arg(i++).arg(helpData->filterSections().size())); + insertFilterAttributes(fs.filterAttributes()); + QByteArray ba; + QDataStream s(&ba, QIODevice::WriteOnly); + for (QHelpDataContentItem *itm : fs.contents()) + writeTree(s, itm, 0); + if (!insertFiles(fs.files(), helpData->rootPath(), fs.filterAttributes()) + || !insertContents(ba, fs.filterAttributes()) + || !insertKeywords(fs.indices(), fs.filterAttributes())) { + cleanupDB(); + return false; + } + } + + cleanupDB(); + emit progressChanged(100); + emit statusChanged(tr("Documentation successfully generated.")); + return true; +} + +void HelpGeneratorPrivate::setupProgress(QHelpProjectData *helpData) +{ + m_progress = 0; + m_oldProgress = 0; + + int numberOfFiles = 0; + int numberOfIndices = 0; + for (const QHelpDataFilterSection &fs : helpData->filterSections()) { + numberOfFiles += fs.files().size(); + numberOfIndices += fs.indices().size(); + } + // init 2% + // filters 1% + // contents 10% + // files 60% + // indices 27% + m_contentStep = 10.0 / qMax(helpData->customFilters().size(), 1); + m_fileStep = 60.0 / qMax(numberOfFiles, 1); + m_indexStep = 27.0 / qMax(numberOfIndices, 1); +} + +void HelpGeneratorPrivate::addProgress(double step) +{ + m_progress += step; + if ((m_progress - m_oldProgress) >= 1.0 && m_progress <= 100.0) { + m_oldProgress = m_progress; + emit progressChanged(qCeil(m_progress)); + } +} + +void HelpGeneratorPrivate::cleanupDB() +{ + if (m_query) { + m_query->clear(); + delete m_query; + m_query = nullptr; + } + QSqlDatabase::removeDatabase(QLatin1String("builder")); +} + +void HelpGeneratorPrivate::writeTree(QDataStream &s, QHelpDataContentItem *item, int depth) +{ + s << depth; + s << item->reference(); + s << item->title(); + for (QHelpDataContentItem *i : item->children()) + writeTree(s, i, depth + 1); +} + +/*! + Returns the last error message. +*/ +QString HelpGeneratorPrivate::error() const +{ + return m_error; +} + +bool HelpGeneratorPrivate::createTables() +{ + if (!m_query) + return false; + + m_query->exec(QLatin1String("SELECT COUNT(*) FROM sqlite_master WHERE TYPE=\'table\'" + "AND Name=\'NamespaceTable\'")); + m_query->next(); + if (m_query->value(0).toInt() > 0) { + m_error = tr("Some tables already exist."); + return false; + } + + const QStringList tables = QStringList() + << QLatin1String("CREATE TABLE NamespaceTable (" + "Id INTEGER PRIMARY KEY," + "Name TEXT )") + << QLatin1String("CREATE TABLE FilterAttributeTable (" + "Id INTEGER PRIMARY KEY, " + "Name TEXT )") + << QLatin1String("CREATE TABLE FilterNameTable (" + "Id INTEGER PRIMARY KEY, " + "Name TEXT )") + << QLatin1String("CREATE TABLE FilterTable (" + "NameId INTEGER, " + "FilterAttributeId INTEGER )") + << QLatin1String("CREATE TABLE IndexTable (" + "Id INTEGER PRIMARY KEY, " + "Name TEXT, " + "Identifier TEXT, " + "NamespaceId INTEGER, " + "FileId INTEGER, " + "Anchor TEXT )") + << QLatin1String("CREATE TABLE IndexFilterTable (" + "FilterAttributeId INTEGER, " + "IndexId INTEGER )") + << QLatin1String("CREATE TABLE ContentsTable (" + "Id INTEGER PRIMARY KEY, " + "NamespaceId INTEGER, " + "Data BLOB )") + << QLatin1String("CREATE TABLE ContentsFilterTable (" + "FilterAttributeId INTEGER, " + "ContentsId INTEGER )") + << QLatin1String("CREATE TABLE FileAttributeSetTable (" + "Id INTEGER, " + "FilterAttributeId INTEGER )") + << QLatin1String("CREATE TABLE FileDataTable (" + "Id INTEGER PRIMARY KEY, " + "Data BLOB )") + << QLatin1String("CREATE TABLE FileFilterTable (" + "FilterAttributeId INTEGER, " + "FileId INTEGER )") + << QLatin1String("CREATE TABLE FileNameTable (" + "FolderId INTEGER, " + "Name TEXT, " + "FileId INTEGER, " + "Title TEXT )") + << QLatin1String("CREATE TABLE FolderTable(" + "Id INTEGER PRIMARY KEY, " + "Name Text, " + "NamespaceID INTEGER )") + << QLatin1String("CREATE TABLE MetaDataTable(" + "Name Text, " + "Value BLOB )"); + + for (const QString &q : tables) { + if (!m_query->exec(q)) { + m_error = tr("Cannot create tables."); + return false; + } + } + + m_query->exec(QLatin1String("INSERT INTO MetaDataTable VALUES('qchVersion', '1.0')")); + + return true; +} + +bool HelpGeneratorPrivate::insertFileNotFoundFile() +{ + if (!m_query) + return false; + + m_query->exec(QLatin1String("SELECT id FROM FileNameTable WHERE Name=\'\'")); + if (m_query->next() && m_query->isValid()) + return true; + + m_query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES (Null, ?)")); + m_query->bindValue(0, QByteArray()); + if (!m_query->exec()) + return false; + + const int fileId = m_query->lastInsertId().toInt(); + m_query->prepare(QLatin1String("INSERT INTO FileNameTable (FolderId, Name, FileId, Title) " + " VALUES (0, '', ?, '')")); + m_query->bindValue(0, fileId); + if (fileId > -1 && m_query->exec()) { + m_fileMap.insert({}, fileId); + return true; + } + return false; +} + +bool HelpGeneratorPrivate::registerVirtualFolder(const QString &folderName, const QString &ns) +{ + if (!m_query || folderName.isEmpty() || ns.isEmpty()) + return false; + + m_query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE Name=?")); + m_query->bindValue(0, folderName); + m_query->exec(); + m_query->next(); + if (m_query->isValid() && m_query->value(0).toInt() > 0) + return true; + + m_namespaceId = -1; + m_query->prepare(QLatin1String("SELECT Id FROM NamespaceTable WHERE Name=?")); + m_query->bindValue(0, ns); + m_query->exec(); + while (m_query->next()) { + m_namespaceId = m_query->value(0).toInt(); + break; + } + + if (m_namespaceId < 0) { + m_query->prepare(QLatin1String("INSERT INTO NamespaceTable VALUES(NULL, ?)")); + m_query->bindValue(0, ns); + if (m_query->exec()) + m_namespaceId = m_query->lastInsertId().toInt(); + } + + if (m_namespaceId > 0) { + m_query->prepare(QLatin1String("SELECT Id FROM FolderTable WHERE Name=?")); + m_query->bindValue(0, folderName); + m_query->exec(); + while (m_query->next()) + m_virtualFolderId = m_query->value(0).toInt(); + + if (m_virtualFolderId > 0) + return true; + + m_query->prepare(QLatin1String("INSERT INTO FolderTable (NamespaceId, Name) " + "VALUES (?, ?)")); + m_query->bindValue(0, m_namespaceId); + m_query->bindValue(1, folderName); + if (m_query->exec()) { + m_virtualFolderId = m_query->lastInsertId().toInt(); + return m_virtualFolderId > 0; + } + } + m_error = tr("Cannot register virtual folder."); + return false; +} + +bool HelpGeneratorPrivate::insertFiles(const QStringList &files, const QString &rootPath, + const QStringList &filterAttributes) +{ + if (!m_query) + return false; + + emit statusChanged(tr("Insert files...")); + QSet filterAtts; + for (const QString &filterAtt : filterAttributes) { + m_query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable " + "WHERE Name=?")); + m_query->bindValue(0, filterAtt); + m_query->exec(); + if (m_query->next()) + filterAtts.insert(m_query->value(0).toInt()); + } + + int filterSetId = -1; + m_query->exec(QLatin1String("SELECT MAX(Id) FROM FileAttributeSetTable")); + if (m_query->next()) + filterSetId = m_query->value(0).toInt(); + if (filterSetId < 0) + return false; + ++filterSetId; + QList attValues = filterAtts.values(); + std::sort(attValues.begin(), attValues.end()); + for (int attId : std::as_const(attValues)) { + m_query->prepare(QLatin1String("INSERT INTO FileAttributeSetTable " + "VALUES(?, ?)")); + m_query->bindValue(0, filterSetId); + m_query->bindValue(1, attId); + m_query->exec(); + } + + int tableFileId = 1; + m_query->exec(QLatin1String("SELECT MAX(Id) FROM FileDataTable")); + if (m_query->next()) + tableFileId = m_query->value(0).toInt() + 1; + + QString title; + QString charSet; + QList fileDataList; + QMap > tmpFileFilterMap; + QList fileNameDataList; + + int i = 0; + for (const QString &file : files) { + const QString fileName = QDir::cleanPath(file); + + QFile fi(rootPath + QDir::separator() + fileName); + if (!fi.exists()) { + emit warning(tr("The file %1 does not exist, skipping it...") + .arg(QDir::cleanPath(rootPath + QDir::separator() + fileName))); + continue; + } + + if (!fi.open(QIODevice::ReadOnly)) { + emit warning(tr("Cannot open file %1, skipping it...") + .arg(QDir::cleanPath(rootPath + QDir::separator() + fileName))); + continue; + } + + QByteArray data = fi.readAll(); + if (fileName.endsWith(QLatin1String(".html")) + || fileName.endsWith(QLatin1String(".htm"))) { + auto encoding = QStringDecoder::encodingForHtml(data); + if (!encoding) + encoding = QStringDecoder::Utf8; + title = QHelpGlobal::documentTitle(QStringDecoder(*encoding)(data)); + } else { + title = fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1); + } + + int fileId = -1; + const auto &it = m_fileMap.constFind(fileName); + if (it == m_fileMap.cend()) { + fileDataList.append(qCompress(data)); + + FileNameTableData fileNameData; + fileNameData.name = fileName; + fileNameData.fileId = tableFileId; + fileNameData.title = title; + fileNameDataList.append(fileNameData); + + m_fileMap.insert(fileName, tableFileId); + m_fileFilterMap.insert(tableFileId, filterAtts); + tmpFileFilterMap.insert(tableFileId, filterAtts); + + ++tableFileId; + } else { + fileId = it.value(); + QSet &fileFilterSet = m_fileFilterMap[fileId]; + QSet &tmpFileFilterSet = tmpFileFilterMap[fileId]; + for (int filter : std::as_const(filterAtts)) { + if (!fileFilterSet.contains(filter) + && !tmpFileFilterSet.contains(filter)) { + fileFilterSet.insert(filter); + tmpFileFilterSet.insert(filter); + } + } + } + } + + if (!tmpFileFilterMap.isEmpty()) { + m_query->exec(QLatin1String("BEGIN")); + for (auto it = tmpFileFilterMap.cbegin(), end = tmpFileFilterMap.cend(); it != end; ++it) { + QList filterValues = it.value().values(); + std::sort(filterValues.begin(), filterValues.end()); + for (int fv : std::as_const(filterValues)) { + m_query->prepare(QLatin1String("INSERT INTO FileFilterTable " + "VALUES(?, ?)")); + m_query->bindValue(0, fv); + m_query->bindValue(1, it.key()); + m_query->exec(); + } + } + + for (const QByteArray &fileData : std::as_const(fileDataList)) { + m_query->prepare(QLatin1String("INSERT INTO FileDataTable VALUES " + "(Null, ?)")); + m_query->bindValue(0, fileData); + m_query->exec(); + if (++i % 20 == 0) + addProgress(m_fileStep * 20.0); + } + + for (const FileNameTableData &fnd : std::as_const(fileNameDataList)) { + m_query->prepare(QLatin1String("INSERT INTO FileNameTable " + "(FolderId, Name, FileId, Title) VALUES (?, ?, ?, ?)")); + m_query->bindValue(0, 1); + m_query->bindValue(1, fnd.name); + m_query->bindValue(2, fnd.fileId); + m_query->bindValue(3, fnd.title); + m_query->exec(); + } + m_query->exec(QLatin1String("COMMIT")); + } + + m_query->exec(QLatin1String("SELECT MAX(Id) FROM FileDataTable")); + if (m_query->next() + && m_query->value(0).toInt() == tableFileId - 1) { + addProgress(m_fileStep*(i % 20)); + return true; + } + return false; +} + +bool HelpGeneratorPrivate::registerCustomFilter(const QString &filterName, + const QStringList &filterAttribs, bool forceUpdate) +{ + if (!m_query) + return false; + + m_query->exec(QLatin1String("SELECT Id, Name FROM FilterAttributeTable")); + QStringList idsToInsert = filterAttribs; + QMap attributeMap; + while (m_query->next()) { + attributeMap.insert(m_query->value(1).toString(), + m_query->value(0).toInt()); + idsToInsert.removeAll(m_query->value(1).toString()); + } + + for (const QString &id : std::as_const(idsToInsert)) { + m_query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)")); + m_query->bindValue(0, id); + m_query->exec(); + attributeMap.insert(id, m_query->lastInsertId().toInt()); + } + + int nameId = -1; + m_query->prepare(QLatin1String("SELECT Id FROM FilterNameTable WHERE Name=?")); + m_query->bindValue(0, filterName); + m_query->exec(); + while (m_query->next()) { + nameId = m_query->value(0).toInt(); + break; + } + + if (nameId < 0) { + m_query->prepare(QLatin1String("INSERT INTO FilterNameTable VALUES(NULL, ?)")); + m_query->bindValue(0, filterName); + if (m_query->exec()) + nameId = m_query->lastInsertId().toInt(); + } else if (!forceUpdate) { + m_error = tr("The filter %1 is already registered.").arg(filterName); + return false; + } + + if (nameId < 0) { + m_error = tr("Cannot register filter %1.").arg(filterName); + return false; + } + + m_query->prepare(QLatin1String("DELETE FROM FilterTable WHERE NameId=?")); + m_query->bindValue(0, nameId); + m_query->exec(); + + for (const QString &att : filterAttribs) { + m_query->prepare(QLatin1String("INSERT INTO FilterTable VALUES(?, ?)")); + m_query->bindValue(0, nameId); + m_query->bindValue(1, attributeMap[att]); + if (!m_query->exec()) + return false; + } + return true; +} + +bool HelpGeneratorPrivate::insertKeywords(const QList &keywords, + const QStringList &filterAttributes) +{ + if (!m_query) + return false; + + emit statusChanged(tr("Insert indices...")); + int indexId = 1; + m_query->exec(QLatin1String("SELECT MAX(Id) FROM IndexTable")); + if (m_query->next()) + indexId = m_query->value(0).toInt() + 1; + + QList filterAtts; + for (const QString &filterAtt : filterAttributes) { + m_query->prepare(QLatin1String("SELECT Id FROM FilterAttributeTable WHERE Name=?")); + m_query->bindValue(0, filterAtt); + m_query->exec(); + if (m_query->next()) + filterAtts.append(m_query->value(0).toInt()); + } + + QList indexFilterTable; + + int i = 0; + m_query->exec(QLatin1String("BEGIN")); + QSet indices; + for (const QHelpDataIndexItem &itm : keywords) { + // Identical ids make no sense and just confuse the Assistant user, + // so we ignore all repetitions. + if (indices.contains(itm.identifier)) + continue; + + // Still empty ids should be ignored, as otherwise we will include only + // the first keyword with an empty id. + if (!itm.identifier.isEmpty()) + indices.insert(itm.identifier); + + const int pos = itm.reference.indexOf(QLatin1Char('#')); + const QString &fileName = itm.reference.left(pos); + const QString anchor = pos < 0 ? QString() : itm.reference.mid(pos + 1); + + const QString &fName = QDir::cleanPath(fileName); + + const auto &it = m_fileMap.constFind(fName); + const int fileId = it == m_fileMap.cend() ? 1 : it.value(); + + m_query->prepare(QLatin1String("INSERT INTO IndexTable (Name, Identifier, NamespaceId, FileId, Anchor) " + "VALUES(?, ?, ?, ?, ?)")); + m_query->bindValue(0, itm.name); + m_query->bindValue(1, itm.identifier); + m_query->bindValue(2, m_namespaceId); + m_query->bindValue(3, fileId); + m_query->bindValue(4, anchor); + m_query->exec(); + + indexFilterTable.append(indexId++); + if (++i % 100 == 0) + addProgress(m_indexStep * 100.0); + } + m_query->exec(QLatin1String("COMMIT")); + + m_query->exec(QLatin1String("BEGIN")); + for (int idx : std::as_const(indexFilterTable)) { + for (int a : std::as_const(filterAtts)) { + m_query->prepare(QLatin1String("INSERT INTO IndexFilterTable (FilterAttributeId, IndexId) " + "VALUES(?, ?)")); + m_query->bindValue(0, a); + m_query->bindValue(1, idx); + m_query->exec(); + } + } + m_query->exec(QLatin1String("COMMIT")); + + m_query->exec(QLatin1String("SELECT COUNT(Id) FROM IndexTable")); + if (m_query->next() && m_query->value(0).toInt() >= indices.size()) + return true; + return false; +} + +bool HelpGeneratorPrivate::insertContents(const QByteArray &ba, + const QStringList &filterAttributes) +{ + if (!m_query) + return false; + + emit statusChanged(tr("Insert contents...")); + m_query->prepare(QLatin1String("INSERT INTO ContentsTable (NamespaceId, Data) " + "VALUES(?, ?)")); + m_query->bindValue(0, m_namespaceId); + m_query->bindValue(1, ba); + m_query->exec(); + int contentId = m_query->lastInsertId().toInt(); + if (contentId < 1) { + m_error = tr("Cannot insert contents."); + return false; + } + + // associate the filter attributes + for (const QString &filterAtt : filterAttributes) { + m_query->prepare(QLatin1String("INSERT INTO ContentsFilterTable (FilterAttributeId, ContentsId) " + "SELECT Id, ? FROM FilterAttributeTable WHERE Name=?")); + m_query->bindValue(0, contentId); + m_query->bindValue(1, filterAtt); + m_query->exec(); + if (!m_query->isActive()) { + m_error = tr("Cannot register contents."); + return false; + } + } + addProgress(m_contentStep); + return true; +} + +bool HelpGeneratorPrivate::insertFilterAttributes(const QStringList &attributes) +{ + if (!m_query) + return false; + + m_query->exec(QLatin1String("SELECT Name FROM FilterAttributeTable")); + QSet atts; + while (m_query->next()) + atts.insert(m_query->value(0).toString()); + + for (const QString &s : attributes) { + if (!atts.contains(s)) { + m_query->prepare(QLatin1String("INSERT INTO FilterAttributeTable VALUES(NULL, ?)")); + m_query->bindValue(0, s); + m_query->exec(); + } + } + return true; +} + +bool HelpGeneratorPrivate::insertMetaData(const QMap &metaData) +{ + if (!m_query) + return false; + + for (auto it = metaData.cbegin(), end = metaData.cend(); it != end; ++it) { + m_query->prepare(QLatin1String("INSERT INTO MetaDataTable VALUES(?, ?)")); + m_query->bindValue(0, it.key()); + m_query->bindValue(1, it.value()); + m_query->exec(); + } + return true; +} + +bool HelpGeneratorPrivate::checkLinks(const QHelpProjectData &helpData) +{ + /* + * Step 1: Gather the canoncal file paths of all files in the project. + * We use a set, because there will be a lot of look-ups. + */ + QSet files; + for (const QHelpDataFilterSection &filterSection : helpData.filterSections()) { + for (const QString &file : filterSection.files()) { + const QFileInfo fileInfo(helpData.rootPath() + QDir::separator() + file); + const QString &canonicalFileName = fileInfo.canonicalFilePath(); + if (!fileInfo.exists()) + emit warning(tr("File \"%1\" does not exist.").arg(file)); + else + files.insert(canonicalFileName); + } + } + + /* + * Step 2: Check the hypertext and image references of all HTML files. + * Note that we don't parse the files, but simply grep for the + * respective HTML elements. Therefore. contents that are e.g. + * commented out can cause false warning. + */ + bool allLinksOk = true; + for (const QString &fileName : std::as_const(files)) { + if (!fileName.endsWith(QLatin1String("html")) + && !fileName.endsWith(QLatin1String("htm"))) + continue; + QFile htmlFile(fileName); + if (!htmlFile.open(QIODevice::ReadOnly)) { + emit warning(tr("File \"%1\" cannot be opened.").arg(fileName)); + continue; + } + const QRegularExpression linkPattern(QLatin1String("<(?:a href|img src)=\"?([^#\">]+)[#\">]")); + QByteArray data = htmlFile.readAll(); + auto encoding = QStringDecoder::encodingForHtml(data); + if (!encoding) + encoding = QStringDecoder::Utf8; + const QString &content = QStringDecoder(*encoding)(data); + QStringList invalidLinks; + QRegularExpressionMatch match; + int pos = 0; + while ((match = linkPattern.match(content, pos)).hasMatch()) { + pos = match.capturedEnd(); + const QString &linkedFileName = match.captured(1); + if (linkedFileName.contains(QLatin1String("://"))) + continue; + const QString &curDir = QFileInfo(fileName).dir().path(); + const QString &canonicalLinkedFileName = + QFileInfo(curDir + QDir::separator() + linkedFileName).canonicalFilePath(); + if (!files.contains(canonicalLinkedFileName) + && !invalidLinks.contains(canonicalLinkedFileName)) { + emit warning(tr("File \"%1\" contains an invalid link to file \"%2\""). + arg(fileName).arg(linkedFileName)); + allLinksOk = false; + invalidLinks.append(canonicalLinkedFileName); + } + } + } + + if (!allLinksOk) + m_error = tr("Invalid links in HTML files."); + return allLinksOk; +} + +////////////////////////////// + +HelpGenerator::HelpGenerator(bool silent) +{ + m_private = new HelpGeneratorPrivate(this); + if (!silent) { + connect(m_private, &HelpGeneratorPrivate::statusChanged, + this, &HelpGenerator::printStatus); + } + connect(m_private, &HelpGeneratorPrivate::warning, + this, &HelpGenerator::printWarning); +} + +bool HelpGenerator::generate(QHelpProjectData *helpData, + const QString &outputFileName) +{ + return m_private->generate(helpData, outputFileName); +} + +bool HelpGenerator::checkLinks(const QHelpProjectData &helpData) +{ + return m_private->checkLinks(helpData); +} + +QString HelpGenerator::error() const +{ + return m_private->error(); +} + +void HelpGenerator::printStatus(const QString &msg) +{ + puts(qPrintable(msg)); +} + +void HelpGenerator::printWarning(const QString &msg) +{ + puts(qPrintable(tr("Warning: %1").arg(msg))); +} + +QT_END_NAMESPACE + +#include "helpgenerator.moc" diff --git a/src/assistant/qhelpgenerator/helpgenerator.h b/src/assistant/qhelpgenerator/helpgenerator.h new file mode 100644 index 0000000..8c5ef34 --- /dev/null +++ b/src/assistant/qhelpgenerator/helpgenerator.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HELPGENERATOR_H +#define HELPGENERATOR_H + +#include + +QT_BEGIN_NAMESPACE + +class QHelpProjectData; +class HelpGeneratorPrivate; + +class HelpGenerator : public QObject +{ + Q_OBJECT + +public: + HelpGenerator(bool silent = false); + bool generate(QHelpProjectData *helpData, + const QString &outputFileName); + bool checkLinks(const QHelpProjectData &helpData); + QString error() const; + +private slots: + void printStatus(const QString &msg); + void printWarning(const QString &msg); + +private: + HelpGeneratorPrivate *m_private; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/assistant/qhelpgenerator/main.cpp b/src/assistant/qhelpgenerator/main.cpp new file mode 100644 index 0000000..ca55499 --- /dev/null +++ b/src/assistant/qhelpgenerator/main.cpp @@ -0,0 +1,386 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "../shared/collectionconfiguration.h" +#include "helpgenerator.h" +#include "collectionconfigreader.h" +#include "qhelpprojectdata_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + +QT_USE_NAMESPACE + +class QHG { + Q_DECLARE_TR_FUNCTIONS(QHelpGenerator) +}; + +static const char QHP[] = "qhp"; +static const char QCH[] = "qch"; + +static const char QHCP[] = "qhcp"; +static const char QHC[] = "qhc"; + +namespace { + QString absoluteFilePath(const QString &basePath, const QString &fileName) + { + return QDir(basePath).absoluteFilePath(fileName); + } +} + +int generateCollectionFile(const QByteArray &data, const QString &basePath, const QString outputFile) +{ + fputs(qPrintable(QHG::tr("Reading collection config file...\n")), stdout); + CollectionConfigReader config; + config.readData(data); + if (config.hasError()) { + fputs(qPrintable(QHG::tr("Collection config file error: %1\n") + .arg(config.errorString())), stderr); + return 1; + } + + const QMap &filesToGenerate = config.filesToGenerate(); + for (auto it = filesToGenerate.cbegin(), end = filesToGenerate.cend(); it != end; ++it) { + fputs(qPrintable(QHG::tr("Generating help for %1...\n").arg(it.key())), stdout); + QHelpProjectData helpData; + if (!helpData.readData(absoluteFilePath(basePath, it.key()))) { + fprintf(stderr, "%s\n", qPrintable(helpData.errorMessage())); + return 1; + } + + HelpGenerator helpGenerator; + if (!helpGenerator.generate(&helpData, absoluteFilePath(basePath, it.value()))) { + fprintf(stderr, "%s\n", qPrintable(helpGenerator.error())); + return 1; + } + } + + fputs(qPrintable(QHG::tr("Creating collection file...\n")), stdout); + + QFileInfo colFi(outputFile); + if (colFi.exists()) { + if (!colFi.dir().remove(colFi.fileName())) { + fputs(qPrintable(QHG::tr("The file %1 cannot be overwritten.\n") + .arg(outputFile)), stderr); + return 1; + } + } + + QHelpEngineCore helpEngine(outputFile); + helpEngine.setReadOnly(false); + if (!helpEngine.setupData()) { + fprintf(stderr, "%s\n", qPrintable(helpEngine.error())); + return 1; + } + + for (const QString &file : config.filesToRegister()) { + if (!helpEngine.registerDocumentation(absoluteFilePath(basePath, file))) { + fprintf(stderr, "%s\n", qPrintable(helpEngine.error())); + return 1; + } + } + if (!config.filesToRegister().isEmpty()) { + if (Q_UNLIKELY(qEnvironmentVariableIsSet("SOURCE_DATE_EPOCH"))) { + QDateTime dt; + dt.setTimeZone(QTimeZone::UTC); + dt.setSecsSinceEpoch(qEnvironmentVariableIntValue("SOURCE_DATE_EPOCH")); + CollectionConfiguration::updateLastRegisterTime(helpEngine, dt); + } else { + CollectionConfiguration::updateLastRegisterTime(helpEngine); + } + } + + if (!config.title().isEmpty()) + CollectionConfiguration::setWindowTitle(helpEngine, config.title()); + + if (!config.homePage().isEmpty()) { + CollectionConfiguration::setDefaultHomePage(helpEngine, + config.homePage()); + } + + if (!config.startPage().isEmpty()) { + CollectionConfiguration::setLastShownPages(helpEngine, + QStringList(config.startPage())); + } + + if (!config.currentFilter().isEmpty()) { + helpEngine.setCurrentFilter(config.currentFilter()); + } + + if (!config.cacheDirectory().isEmpty()) { + CollectionConfiguration::setCacheDir(helpEngine, config.cacheDirectory(), + config.cacheDirRelativeToCollection()); + } + + CollectionConfiguration::setFilterFunctionalityEnabled(helpEngine, + config.enableFilterFunctionality()); + CollectionConfiguration::setFilterToolbarVisible(helpEngine, + !config.hideFilterFunctionality()); + CollectionConfiguration::setDocumentationManagerEnabled(helpEngine, + config.enableDocumentationManager()); + CollectionConfiguration::setAddressBarEnabled(helpEngine, + config.enableAddressBar()); + CollectionConfiguration::setAddressBarVisible(helpEngine, + !config.hideAddressBar()); + uint time = QDateTime::currentMSecsSinceEpoch() / 1000; + if (Q_UNLIKELY(qEnvironmentVariableIsSet("SOURCE_DATE_EPOCH"))) + time = qEnvironmentVariableIntValue("SOURCE_DATE_EPOCH"); + CollectionConfiguration::setCreationTime(helpEngine, time); + CollectionConfiguration::setFullTextSearchFallbackEnabled(helpEngine, + config.fullTextSearchFallbackEnabled()); + + if (!config.applicationIcon().isEmpty()) { + QFile icon(absoluteFilePath(basePath, config.applicationIcon())); + if (!icon.open(QIODevice::ReadOnly)) { + fputs(qPrintable(QHG::tr("Cannot open %1.\n").arg(icon.fileName())), stderr); + return 1; + } + CollectionConfiguration::setApplicationIcon(helpEngine, icon.readAll()); + } + + if (config.aboutMenuTexts().size()) { + QByteArray ba; + QDataStream s(&ba, QIODevice::WriteOnly); + const QMap &aboutMenuTexts = config.aboutMenuTexts(); + for (auto it = aboutMenuTexts.cbegin(), end = aboutMenuTexts.cend(); it != end; ++it) + s << it.key() << it.value(); + CollectionConfiguration::setAboutMenuTexts(helpEngine, ba); + } + + if (!config.aboutIcon().isEmpty()) { + QFile icon(absoluteFilePath(basePath, config.aboutIcon())); + if (!icon.open(QIODevice::ReadOnly)) { + fputs(qPrintable(QHG::tr("Cannot open %1.\n").arg(icon.fileName())), stderr); + return 1; + } + CollectionConfiguration::setAboutIcon(helpEngine, icon.readAll()); + } + + if (config.aboutTextFiles().size()) { + QByteArray ba; + QDataStream s(&ba, QIODevice::WriteOnly); + QMap imgData; + + QRegularExpression srcRegExp(QLatin1String("src=(\"(.+)\"|([^\"\\s]+)).*>"), QRegularExpression::InvertedGreedinessOption); + QRegularExpression imgRegExp(QLatin1String("(]+>)"), QRegularExpression::InvertedGreedinessOption); + + const QMap &aboutMenuTexts = config.aboutTextFiles(); + for (auto it = aboutMenuTexts.cbegin(), end = aboutMenuTexts.cend(); it != end; ++it) { + s << it.key(); + QFileInfo fi(absoluteFilePath(basePath, it.value())); + QFile f(fi.absoluteFilePath()); + if (!f.open(QIODevice::ReadOnly)) { + fputs(qPrintable(QHG::tr("Cannot open %1.\n").arg(f.fileName())), stderr); + return 1; + } + QByteArray data = f.readAll(); + s << data; + + QString contents = QString::fromUtf8(data); + int pos = 0; + QRegularExpressionMatch match; + while ((match = imgRegExp.match(contents, pos)).hasMatch()) { + QString imgTag = match.captured(1); + pos = match.capturedEnd(); + + if ((match = srcRegExp.match(imgTag)).hasMatch()) { + QString src = match.captured(2); + if (src.isEmpty()) + src = match.captured(3); + + QFile img(fi.absolutePath() + QDir::separator() + src); + if (img.open(QIODevice::ReadOnly)) { + if (!imgData.contains(src)) + imgData.insert(src, img.readAll()); + } else { + fputs(qPrintable(QHG::tr("Cannot open referenced image file %1.\n") + .arg(img.fileName())), stderr); + } + } + } + } + CollectionConfiguration::setAboutTexts(helpEngine, ba); + if (imgData.size()) { + QByteArray imageData; + QBuffer buffer(&imageData); + buffer.open(QIODevice::WriteOnly); + QDataStream out(&buffer); + out << imgData; + CollectionConfiguration::setAboutImages(helpEngine, imageData); + } + } + return 0; +} + +int main(int argc, char *argv[]) +{ + QString error; + QString outputFile; + QString inputFile; + QString basePath; + bool showHelp = false; + bool showVersion = false; + bool checkLinks = false; + bool silent = false; + + // don't require a window manager even though we're a QGuiApplication + qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal")); + + QGuiApplication app(argc, argv); +#ifndef Q_OS_WIN32 + QTranslator translator; + QTranslator qtTranslator; + QTranslator qt_helpTranslator; + QString sysLocale = QLocale::system().name(); + QString resourceDir = QLibraryInfo::path(QLibraryInfo::TranslationsPath); + if (translator.load(QLatin1String("assistant_") + sysLocale, resourceDir) + && qtTranslator.load(QLatin1String("qt_") + sysLocale, resourceDir) + && qt_helpTranslator.load(QLatin1String("qt_help_") + sysLocale, resourceDir)) { + app.installTranslator(&translator); + app.installTranslator(&qtTranslator); + app.installTranslator(&qt_helpTranslator); + } +#endif // Q_OS_WIN32 + + for (int i = 1; i < argc; ++i) { + const QString arg = QString::fromLocal8Bit(argv[i]); + if (arg == QLatin1String("-o")) { + if (++i < argc) { + QFileInfo fi(QString::fromLocal8Bit(argv[i])); + outputFile = fi.absoluteFilePath(); + } else { + error = QHG::tr("Missing output file name."); + } + } else if (arg == QLatin1String("-v")) { + showVersion = true; + } else if (arg == QLatin1String("-h")) { + showHelp = true; + } else if (arg == QLatin1String("-c")) { + checkLinks = true; + } else if (arg == QLatin1String("-s")) { + silent = true; + } else { + const QFileInfo fi(arg); + inputFile = fi.absoluteFilePath(); + basePath = fi.absolutePath(); + } + } + + if (showVersion) { + fputs(qPrintable(QHG::tr("Qt Help Generator version 1.0 (Qt %1)\n") + .arg(QT_VERSION_STR)), stdout); + return 0; + } + + enum InputType { + InputQhp, + InputQhcp, + InputUnknown + }; + + InputType inputType = InputUnknown; + + if (!showHelp) { + if (inputFile.isEmpty()) { + error = QHG::tr("Missing input file name."); + } else { + const QFileInfo fi(inputFile); + if (fi.suffix() == QHP) + inputType = InputQhp; + else if (fi.suffix() == QHCP) + inputType = InputQhcp; + + if (inputType == InputUnknown) + error = QHG::tr("Unknown input file type."); + } + } + + const QString help = QHG::tr("\nUsage:\n\n" + "qhelpgenerator [options]\n\n" + " -o Generates a Qt compressed help\n" + " called (*.qch) for the\n" + " Qt help project (*.qhp).\n" + " Generates a Qt help collection\n" + " called (*.qhc) for the\n" + " Qt help collection project (*.qhcp).\n" + " If this option is not specified\n" + " a default name will be used\n" + " (*.qch for *.qhp and *.qhc for *.qhcp).\n" + " -c Checks whether all links in HTML files\n" + " point to files in this help project.\n" + " -s Suppresses status messages.\n" + " -v Displays the version of \n" + " qhelpgenerator.\n\n"); + + if (showHelp) { + fputs(qPrintable(help), stdout); + return 0; + } else if (!error.isEmpty()) { + fprintf(stderr, "%s\n\n%s", qPrintable(error), qPrintable(help)); + return 1; + } + + // detect input file type (qhp or qhcp) + + QFile file(inputFile); + if (!file.open(QIODevice::ReadOnly)) { + fputs(qPrintable(QHG::tr("Could not open %1.\n").arg(inputFile)), stderr); + return 1; + } + + const QString outputExtension = inputType == InputQhp ? QCH : QHC; + + if (outputFile.isEmpty()) { + if (inputType == InputQhcp || !checkLinks) { + QFileInfo fi(inputFile); + outputFile = basePath + QDir::separator() + + fi.baseName() + QLatin1Char('.') + outputExtension; + } + } else { + // check if the output dir exists -- create if it doesn't + QFileInfo fi(outputFile); + QDir parentDir = fi.dir(); + if (!parentDir.exists()) { + if (!parentDir.mkpath(QLatin1String("."))) { + fputs(qPrintable(QHG::tr("Could not create output directory: %1\n") + .arg(parentDir.path())), stderr); + } + } + } + + if (inputType == InputQhp) { + QHelpProjectData *helpData = new QHelpProjectData(); + if (!helpData->readData(inputFile)) { + fprintf(stderr, "%s\n", qPrintable(helpData->errorMessage())); + return 1; + } + + HelpGenerator generator(silent); + bool success = true; + if (checkLinks) + success = generator.checkLinks(*helpData); + if (success && !outputFile.isEmpty()) + success = generator.generate(helpData, outputFile); + delete helpData; + if (!success) { + fprintf(stderr, "%s\n", qPrintable(generator.error())); + return 1; + } + } else { + const QByteArray data = file.readAll(); + return generateCollectionFile(data, basePath, outputFile); + + } + + return 0; +} diff --git a/src/assistant/qhelpgenerator/qhelpdatainterface.cpp b/src/assistant/qhelpgenerator/qhelpdatainterface.cpp new file mode 100644 index 0000000..f5a9afd --- /dev/null +++ b/src/assistant/qhelpgenerator/qhelpdatainterface.cpp @@ -0,0 +1,232 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qhelpdatainterface_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QHelpDataContentItem + \since 4.4 + \brief The QHelpDataContentItem class provides an item which represents + a topic or section of the contents. + + Every item holds several pieces of information, most notably the title + which can later be displayed in a contents overview. The reference is used + to store a relative file link to the corresponding section in the + documentation. +*/ + +/*! + Constructs a new content item with \a parent as parent item. + The constucted item has the title \a title and links to the + location specified by \a reference. +*/ +QHelpDataContentItem::QHelpDataContentItem(QHelpDataContentItem *parent, + const QString &title, const QString &reference) + : m_title(title), m_reference(reference) +{ + if (parent) + parent->m_children.append(this); +} + +/*! + Destructs the item and its children. +*/ +QHelpDataContentItem::~QHelpDataContentItem() +{ + qDeleteAll(m_children); +} + +/*! + Returns the title of the item. +*/ +QString QHelpDataContentItem::title() const +{ + return m_title; +} + +/*! + Returns the file reference of the item. +*/ +QString QHelpDataContentItem::reference() const +{ + return m_reference; +} + +/*! + Returns a list of all its child items. +*/ +QList QHelpDataContentItem::children() const +{ + return m_children; +} + +bool QHelpDataIndexItem::operator==(const QHelpDataIndexItem &other) const +{ + return (other.name == name) && (other.reference == reference); +} + +/*! + \internal + \class QHelpDataFilterSection + \since 4.4 +*/ + +/*! + Constructs a help data filter section. +*/ +QHelpDataFilterSection::QHelpDataFilterSection() +{ + d = new QHelpDataFilterSectionData(); +} + +/*! + Adds the filter attribute \a filter to the filter attributes of + this section. +*/ +void QHelpDataFilterSection::addFilterAttribute(const QString &filter) +{ + d->filterAttributes.append(filter); +} + +/*! + Returns a list of all filter attributes defined for this section. +*/ +QStringList QHelpDataFilterSection::filterAttributes() const +{ + return d->filterAttributes; +} + +/*! + Adds the index item \a index to the list of indices. +*/ +void QHelpDataFilterSection::addIndex(const QHelpDataIndexItem &index) +{ + d->indices.append(index); +} + +/*! + Sets the filter sections list of indices to \a indices. +*/ +void QHelpDataFilterSection::setIndices(const QList &indices) +{ + d->indices = indices; +} + +/*! + Returns the list of indices. +*/ +QList QHelpDataFilterSection::indices() const +{ + return d->indices; +} + +/*! + Adds the top level content item \a content to the filter section. +*/ +void QHelpDataFilterSection::addContent(QHelpDataContentItem *content) +{ + d->contents.append(content); +} + +/*! + Sets the list of top level content items of the filter section to + \a contents. +*/ +void QHelpDataFilterSection::setContents(const QList &contents) +{ + qDeleteAll(d->contents); + d->contents = contents; +} + +/*! + Returns a list of top level content items. +*/ +QList QHelpDataFilterSection::contents() const +{ + return d->contents; +} + +/*! + Adds the file \a file to the filter section. +*/ +void QHelpDataFilterSection::addFile(const QString &file) +{ + d->files.append(file); +} + +/*! + Set the list of files to \a files. +*/ +void QHelpDataFilterSection::setFiles(const QStringList &files) +{ + d->files = files; +} + +/*! + Returns the list of files. +*/ +QStringList QHelpDataFilterSection::files() const +{ + return d->files; +} + +/*! + \internal + \class QHelpDataInterface + \since 4.4 +*/ + +/*! + \fn QHelpDataInterface::QHelpDataInterface() + + Constructs a new help data interface. +*/ + +/*! + \fn QHelpDataInterface::~QHelpDataInterface() + + Destroys the help data interface. +*/ + +/*! + \fn QString QHelpDataInterface::namespaceName() const = 0 + + Returns the namespace name of the help data set. +*/ + +/*! + \fn QString QHelpDataInterface::virtualFolder() const = 0 + + Returns the virtual folder of the help data set. +*/ + +/*! + \fn QList QHelpDataInterface::customFilters () const = 0 + + Returns a list of custom filters. Defining custom filters is optional. +*/ + +/*! + \fn QList QHelpDataInterface::filterSections() const = 0 + + Returns a list of filter sections. +*/ + +/*! + \fn QMap QHelpDataInterface::metaData() const = 0 + + Returns a map of meta data. A meta data item can hold almost any data + and is identified by its name. +*/ + +/*! + \fn QString QHelpDataInterface::rootPath() const = 0 + + Returns the root file path of the documentation data. All referenced file + path or links of content items are relative to this path. +*/ + +QT_END_NAMESPACE diff --git a/src/assistant/qhelpgenerator/qhelpdatainterface_p.h b/src/assistant/qhelpgenerator/qhelpdatainterface_p.h new file mode 100644 index 0000000..6f21531 --- /dev/null +++ b/src/assistant/qhelpgenerator/qhelpdatainterface_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QHELPDATAINTERFACE_H +#define QHELPDATAINTERFACE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the help generator tools. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpDataContentItem +{ +public: + QHelpDataContentItem(QHelpDataContentItem *parent, const QString &title, + const QString &reference); + ~QHelpDataContentItem(); + + QString title() const; + QString reference() const; + QList children() const; + +private: + QString m_title; + QString m_reference; + QList m_children; +}; + +struct QHelpDataIndexItem { + QHelpDataIndexItem() {} + QHelpDataIndexItem(const QString &n, const QString &id, const QString &r) + : name(n), identifier(id), reference(r) {} + + QString name; + QString identifier; + QString reference; + + bool operator==(const QHelpDataIndexItem & other) const; +}; + +class QHelpDataFilterSectionData : public QSharedData +{ +public: + ~QHelpDataFilterSectionData() + { + qDeleteAll(contents); + } + + QStringList filterAttributes; + QList indices; + QList contents; + QStringList files; +}; + +class QHelpDataFilterSection +{ +public: + QHelpDataFilterSection(); + + void addFilterAttribute(const QString &filter); + QStringList filterAttributes() const; + + void addIndex(const QHelpDataIndexItem &index); + void setIndices(const QList &indices); + QList indices() const; + + void addContent(QHelpDataContentItem *content); + void setContents(const QList &contents); + QList contents() const; + + void addFile(const QString &file); + void setFiles(const QStringList &files); + QStringList files() const; + +private: + QSharedDataPointer d; +}; + +struct QHelpDataCustomFilter { + QStringList filterAttributes; + QString name; +}; + +QT_END_NAMESPACE + +#endif // QHELPDATAINTERFACE_H diff --git a/src/assistant/qhelpgenerator/qhelpprojectdata.cpp b/src/assistant/qhelpgenerator/qhelpprojectdata.cpp new file mode 100644 index 0000000..01cf238 --- /dev/null +++ b/src/assistant/qhelpgenerator/qhelpprojectdata.cpp @@ -0,0 +1,432 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qhelpprojectdata_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpProjectDataPrivate : public QXmlStreamReader +{ +public: + void readData(const QByteArray &contents); + + QString virtualFolder; + QString namespaceName; + QString fileName; + QString rootPath; + + QList customFilterList; + QList filterSectionList; + QMap metaData; + + QString errorMsg; + +private: + void readProject(); + void readCustomFilter(); + void readFilterSection(); + void readTOC(); + void readKeywords(); + void readFiles(); + void skipUnknownToken(); + void addMatchingFiles(const QString &pattern); + bool hasValidSyntax(const QString &nameSpace, const QString &vFolder) const; + + QMap dirEntriesCache; +}; + +void QHelpProjectDataPrivate::skipUnknownToken() +{ + const QString message = QCoreApplication::translate("QHelpProject", + "Skipping unknown token <%1> in file \"%2\".") + .arg(name()).arg(fileName) + QLatin1Char('\n'); + fputs(qPrintable(message), stdout); + + skipCurrentElement(); +} + +void QHelpProjectDataPrivate::readData(const QByteArray &contents) +{ + addData(contents); + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("QtHelpProject") + && attributes().value(QLatin1String("version")) + == QLatin1String("1.0")) { + readProject(); + } else { + raiseError(QCoreApplication::translate("QHelpProject", + "Unknown token. Expected \"QtHelpProject\".")); + } + } + } + + if (hasError()) { + raiseError(QCoreApplication::translate("QHelpProject", + "Error in line %1: %2").arg(lineNumber()) + .arg(errorString())); + } +} + +void QHelpProjectDataPrivate::readProject() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("virtualFolder")) { + virtualFolder = readElementText(); + if (!hasValidSyntax(QLatin1String("test"), virtualFolder)) + raiseError(QCoreApplication::translate("QHelpProject", + "Virtual folder has invalid syntax in file: \"%1\"").arg(fileName)); + } else if (name() == QLatin1String("namespace")) { + namespaceName = readElementText(); + if (!hasValidSyntax(namespaceName, QLatin1String("test"))) + raiseError(QCoreApplication::translate("QHelpProject", + "Namespace \"%1\" has invalid syntax in file: \"%2\"").arg(namespaceName, fileName)); + } else if (name() == QLatin1String("customFilter")) { + readCustomFilter(); + } else if (name() == QLatin1String("filterSection")) { + readFilterSection(); + } else if (name() == QLatin1String("metaData")) { + QString n = attributes().value(QLatin1String("name")).toString(); + if (!metaData.contains(n)) + metaData[n] + = attributes().value(QLatin1String("value")).toString(); + else + metaData.insert(n, attributes(). + value(QLatin1String("value")).toString()); + } else { + skipUnknownToken(); + } + } else if (isEndElement() && name() == QLatin1String("QtHelpProject")) { + if (namespaceName.isEmpty()) + raiseError(QCoreApplication::translate("QHelpProject", + "Missing namespace in QtHelpProject file: \"%1\"").arg(fileName)); + else if (virtualFolder.isEmpty()) + raiseError(QCoreApplication::translate("QHelpProject", + "Missing virtual folder in QtHelpProject file: \"%1\"").arg(fileName)); + break; + } + } +} + +void QHelpProjectDataPrivate::readCustomFilter() +{ + QHelpDataCustomFilter filter; + filter.name = attributes().value(QLatin1String("name")).toString(); + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("filterAttribute")) + filter.filterAttributes.append(readElementText()); + else + skipUnknownToken(); + } else if (isEndElement() && name() == QLatin1String("customFilter")) { + break; + } + } + customFilterList.append(filter); +} + +void QHelpProjectDataPrivate::readFilterSection() +{ + filterSectionList.append(QHelpDataFilterSection()); + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("filterAttribute")) + filterSectionList.last().addFilterAttribute(readElementText()); + else if (name() == QLatin1String("toc")) + readTOC(); + else if (name() == QLatin1String("keywords")) + readKeywords(); + else if (name() == QLatin1String("files")) + readFiles(); + else + skipUnknownToken(); + } else if (isEndElement() && name() == QLatin1String("filterSection")) { + break; + } + } +} + +void QHelpProjectDataPrivate::readTOC() +{ + QStack contentStack; + QHelpDataContentItem *itm = nullptr; + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("section")) { + const QString &title = attributes().value(QLatin1String("title")).toString(); + const QString &ref = attributes().value(QLatin1String("ref")).toString(); + if (contentStack.isEmpty()) { + itm = new QHelpDataContentItem(nullptr, title, ref); + filterSectionList.last().addContent(itm); + } else { + itm = new QHelpDataContentItem(contentStack.top(), title, ref); + } + contentStack.push(itm); + } else { + skipUnknownToken(); + } + } else if (isEndElement()) { + if (name() == QLatin1String("section")) { + contentStack.pop(); + continue; + } else if (name() == QLatin1String("toc") && contentStack.isEmpty()) { + return; + } else { + skipUnknownToken(); + } + } + } +} + +static inline QString msgMissingAttribute(const QString &fileName, qint64 lineNumber, const QString &name) +{ + QString result; + QTextStream str(&result); + str << QDir::toNativeSeparators(fileName) << ':' << lineNumber + << ": Missing attribute in ."; + return result; +} + +void QHelpProjectDataPrivate::readKeywords() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("keyword")) { + const QString &refAttribute = attributes().value(QStringLiteral("ref")).toString(); + const QString &nameAttribute = attributes().value(QStringLiteral("name")).toString(); + const QString &idAttribute = attributes().value(QStringLiteral("id")).toString(); + if (refAttribute.isEmpty() || (nameAttribute.isEmpty() && idAttribute.isEmpty())) { + qWarning("%s", qPrintable(msgMissingAttribute(fileName, lineNumber(), nameAttribute))); + continue; + } + filterSectionList.last() + .addIndex(QHelpDataIndexItem(nameAttribute, idAttribute, refAttribute)); + } else { + skipUnknownToken(); + } + } else if (isEndElement()) { + if (name() == QLatin1String("keyword")) + continue; + else if (name() == QLatin1String("keywords")) + return; + else + skipUnknownToken(); + } + } +} + +void QHelpProjectDataPrivate::readFiles() +{ + while (!atEnd()) { + readNext(); + if (isStartElement()) { + if (name() == QLatin1String("file")) + addMatchingFiles(readElementText()); + else + skipUnknownToken(); + } else if (isEndElement()) { + if (name() == QLatin1String("file")) + continue; + else if (name() == QLatin1String("files")) + return; + else + skipUnknownToken(); + } + } +} + +// Expand file pattern and add matches into list. If the pattern does not match +// any files, insert the pattern itself so the QHelpGenerator will emit a +// meaningful warning later. +void QHelpProjectDataPrivate::addMatchingFiles(const QString &pattern) +{ + // The pattern matching is expensive, so we skip it if no + // wildcard symbols occur in the string. + if (!pattern.contains(QLatin1Char('?')) && !pattern.contains(QLatin1Char('*')) + && !pattern.contains(QLatin1Char('[')) && !pattern.contains(QLatin1Char(']'))) { + filterSectionList.last().addFile(pattern); + return; + } + + const QFileInfo fileInfo(rootPath + QLatin1Char('/') + pattern); + const QDir &dir = fileInfo.dir(); + const QString &path = dir.canonicalPath(); + + // QDir::entryList() is expensive, so we cache the results. + const auto &it = dirEntriesCache.constFind(path); + const QStringList &entries = it != dirEntriesCache.cend() ? + it.value() : dir.entryList(QDir::Files); + if (it == dirEntriesCache.cend()) + dirEntriesCache.insert(path, entries); + + bool matchFound = false; +#ifdef Q_OS_WIN + auto cs = QRegularExpression::CaseInsensitiveOption; +#else + auto cs = QRegularExpression::NoPatternOption; +#endif + const QRegularExpression regExp(QRegularExpression::wildcardToRegularExpression(fileInfo.fileName()), cs); + for (const QString &file : entries) { + auto match = regExp.match(file); + if (match.hasMatch()) { + matchFound = true; + filterSectionList.last(). + addFile(QFileInfo(pattern).dir().path() + QLatin1Char('/') + file); + } + } + if (!matchFound) + filterSectionList.last().addFile(pattern); +} + +bool QHelpProjectDataPrivate::hasValidSyntax(const QString &nameSpace, + const QString &vFolder) const +{ + const QLatin1Char slash('/'); + if (nameSpace.contains(slash) || vFolder.contains(slash)) + return false; + QUrl url; + const QLatin1String scheme("qthelp"); + url.setScheme(scheme); + const QString &canonicalNamespace = nameSpace.toLower(); + url.setHost(canonicalNamespace); + url.setPath(slash + vFolder); + + const QString expectedUrl(scheme + QLatin1String("://") + + canonicalNamespace + slash + vFolder); + return url.isValid() && url.toString() == expectedUrl; +} + +/*! + \internal + \class QHelpProjectData + \since 4.4 + \brief The QHelpProjectData class stores all information found + in a Qt help project file. + + The structure is filled with data by calling readData(). The + specified file has to have the Qt help project file format in + order to be read successfully. Possible reading errors can be + retrieved by calling errorMessage(). +*/ + +/*! + Constructs a Qt help project data structure. +*/ +QHelpProjectData::QHelpProjectData() +{ + d = new QHelpProjectDataPrivate; +} + +/*! + Destroys the help project data. +*/ +QHelpProjectData::~QHelpProjectData() +{ + delete d; +} + +/*! + Reads the file \a fileName and stores the help data. The file has to + have the Qt help project file format. Returns true if the file + was successfully read, otherwise false. + + \sa errorMessage() +*/ +bool QHelpProjectData::readData(const QString &fileName) +{ + d->fileName = fileName; + d->rootPath = QFileInfo(fileName).absolutePath(); + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + d->errorMsg = QCoreApplication::translate("QHelpProject", + "The input file %1 could not be opened.").arg(fileName); + return false; + } + + d->readData(file.readAll()); + return !d->hasError(); +} + +/*! + Returns an error message if the reading of the Qt help project + file failed. Otherwise, an empty QString is returned. + + \sa readData() +*/ +QString QHelpProjectData::errorMessage() const +{ + if (d->hasError()) + return d->errorString(); + return d->errorMsg; +} + +/*! + \internal +*/ +QString QHelpProjectData::namespaceName() const +{ + return d->namespaceName; +} + +/*! + \internal +*/ +QString QHelpProjectData::virtualFolder() const +{ + return d->virtualFolder; +} + +/*! + \internal +*/ +QList QHelpProjectData::customFilters() const +{ + return d->customFilterList; +} + +/*! + \internal +*/ +QList QHelpProjectData::filterSections() const +{ + return d->filterSectionList; +} + +/*! + \internal +*/ +QMap QHelpProjectData::metaData() const +{ + return d->metaData; +} + +/*! + \internal +*/ +QString QHelpProjectData::rootPath() const +{ + return d->rootPath; +} + +QT_END_NAMESPACE diff --git a/src/assistant/qhelpgenerator/qhelpprojectdata_p.h b/src/assistant/qhelpgenerator/qhelpprojectdata_p.h new file mode 100644 index 0000000..6eab286 --- /dev/null +++ b/src/assistant/qhelpgenerator/qhelpprojectdata_p.h @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QHELPPROJECTDATA_H +#define QHELPPROJECTDATA_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the help generator tools. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include "qhelpdatainterface_p.h" + +QT_BEGIN_NAMESPACE + +class QHelpProjectDataPrivate; + +class QHelpProjectData +{ +public: + QHelpProjectData(); + ~QHelpProjectData(); + + bool readData(const QString &fileName); + QString errorMessage() const; + + QString namespaceName() const; + QString virtualFolder() const; + QList customFilters() const; + QList filterSections() const; + QMap metaData() const; + QString rootPath() const; + +private: + QHelpProjectDataPrivate *d; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/assistant/qlitehtml/.clang-format b/src/assistant/qlitehtml/.clang-format new file mode 100644 index 0000000..97f7f2b --- /dev/null +++ b/src/assistant/qlitehtml/.clang-format @@ -0,0 +1,117 @@ +# .clang-format for Qt Creator +# +# This is for clang-format >= 5.0. +# +# The configuration below follows the Qt Creator Coding Rules [1] as closely as +# possible. For documentation of the options, see [2]. +# +# Use ../../tests/manual/clang-format-for-qtc/test.cpp for documenting problems +# or testing changes. +# +# In case you update this configuration please also update the qtcStyle() in src\plugins\clangformat\clangformatutils.cpp +# +# [1] https://doc-snapshots.qt.io/qtcreator-extending/coding-style.html +# [2] https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - forever # avoids { wrapped to next line + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeCategories: + - Regex: '^ . + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/assistant/qlitehtml/LICENSES/GPL-3.0-only.txt b/src/assistant/qlitehtml/LICENSES/GPL-3.0-only.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/src/assistant/qlitehtml/LICENSES/GPL-3.0-only.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/src/assistant/qlitehtml/LICENSES/LicenseRef-Qt-Commercial.txt b/src/assistant/qlitehtml/LICENSES/LicenseRef-Qt-Commercial.txt new file mode 100644 index 0000000..825b1f3 --- /dev/null +++ b/src/assistant/qlitehtml/LICENSES/LicenseRef-Qt-Commercial.txt @@ -0,0 +1,8 @@ +Licensees holding valid commercial Qt licenses may use this software in +accordance with the the terms contained in a written agreement between +you and The Qt Company. Alternatively, the terms and conditions that were +accepted by the licensee when buying and/or downloading the +software do apply. + +For the latest licensing terms and conditions, see https://www.qt.io/terms-conditions. +For further information use the contact form at https://www.qt.io/contact-us. diff --git a/src/assistant/qlitehtml/LICENSES/MIT.txt b/src/assistant/qlitehtml/LICENSES/MIT.txt new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/src/assistant/qlitehtml/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/assistant/qlitehtml/LICENSES/Qt-GPL-exception-1.0.txt b/src/assistant/qlitehtml/LICENSES/Qt-GPL-exception-1.0.txt new file mode 100644 index 0000000..d0322bf --- /dev/null +++ b/src/assistant/qlitehtml/LICENSES/Qt-GPL-exception-1.0.txt @@ -0,0 +1,22 @@ +The Qt Company GPL Exception 1.0 + +Exception 1: + +As a special exception you may create a larger work which contains the +output of this application and distribute that work under terms of your +choice, so long as the work is not otherwise derived from or based on +this application and so long as the work does not in itself generate +output that contains the output from this application in its original +or modified form. + +Exception 2: + +As a special exception, you have permission to combine this application +with Plugins licensed under the terms of your choice, to produce an +executable, and to copy and distribute the resulting executable under +the terms of your choice. However, the executable must be accompanied +by a prominent notice offering all users of the executable the entire +source code to this application, excluding the source code of the +independent modules, but including any changes you have made to this +application, under the terms of this license. + diff --git a/src/assistant/qlitehtml/README.md b/src/assistant/qlitehtml/README.md new file mode 100644 index 0000000..777f5bb --- /dev/null +++ b/src/assistant/qlitehtml/README.md @@ -0,0 +1,14 @@ +qlitehtml +========= + +A lightweight HTML viewer for Qt. + +This project provides + +* A QPainter based rendering backend for the lightweight HTML/CSS rendering + engine [litehtml]. +* A QWidget that uses the QPainter based backend and provides API for simply + setting the HTML text and a base URL plus hook that are used for requesting + referenced resources. + +[litehtml]: https://github.com/litehtml/litehtml diff --git a/src/assistant/qlitehtml/src/3rdparty/GUMBO-AUTHORS.txt b/src/assistant/qlitehtml/src/3rdparty/GUMBO-AUTHORS.txt new file mode 100644 index 0000000..54ec74b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/GUMBO-AUTHORS.txt @@ -0,0 +1,2 @@ +Copyright 2010, 2011 Google Inc. +Copyright 2008-2009 Bjoern Hoehrmann \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/.github/FUNDING.yml b/src/assistant/qlitehtml/src/3rdparty/litehtml/.github/FUNDING.yml new file mode 100644 index 0000000..1299f24 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: ["http://www.litehtml.com/donate.html"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/.github/workflows/cmake.yml b/src/assistant/qlitehtml/src/3rdparty/litehtml/.github/workflows/cmake.yml new file mode 100644 index 0000000..137cea8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/.github/workflows/cmake.yml @@ -0,0 +1,38 @@ +name: CMake + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} --rerun-failed --output-on-failure + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/.gitignore b/src/assistant/qlitehtml/src/3rdparty/litehtml/.gitignore new file mode 100644 index 0000000..5dcb07d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/.gitignore @@ -0,0 +1,186 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +cmake-build-*/ +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +_build/ +_build32/ +_build64/ +*.inc + +# Test results +*-FAILED.png + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +[Tt]esting/ + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# NuGet Packages Directory +packages/ +## TODO: If the tool you use requires repositories.config uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +.idea/ +.vs/ +.vscode/ +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/CMakeLists.txt b/src/assistant/qlitehtml/src/3rdparty/litehtml/CMakeLists.txt new file mode 100644 index 0000000..595689f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/CMakeLists.txt @@ -0,0 +1,243 @@ +cmake_minimum_required(VERSION 3.11) + +project(litehtml LANGUAGES C CXX) + + +option(LITEHTML_BUILD_TESTING "enable testing for litehtml" ON) + +if(LITEHTML_BUILD_TESTING) + include(CTest) + enable_testing() +endif() + +# Soname +# MAJOR is incremented when symbols are removed or changed in an incompatible way +# MINOR is incremented when new symbols are added +set(PROJECT_MAJOR 0) +set(PROJECT_MINOR 0) + +option(EXTERNAL_GUMBO "Link against external gumbo instead of shipping a bundled copy" OFF) + +if(NOT EXTERNAL_GUMBO) + add_subdirectory(src/gumbo) +endif() + +set(SOURCE_LITEHTML + src/codepoint.cpp + src/css_length.cpp + src/css_selector.cpp + src/document.cpp + src/document_container.cpp + src/el_anchor.cpp + src/el_base.cpp + src/el_before_after.cpp + src/el_body.cpp + src/el_break.cpp + src/el_cdata.cpp + src/el_comment.cpp + src/el_div.cpp + src/element.cpp + src/el_font.cpp + src/el_image.cpp + src/el_link.cpp + src/el_para.cpp + src/el_script.cpp + src/el_space.cpp + src/el_style.cpp + src/el_table.cpp + src/el_td.cpp + src/el_text.cpp + src/el_title.cpp + src/el_tr.cpp + src/html.cpp + src/html_tag.cpp + src/iterators.cpp + src/media_query.cpp + src/style.cpp + src/stylesheet.cpp + src/table.cpp + src/tstring_view.cpp + src/url.cpp + src/url_path.cpp + src/utf8_strings.cpp + src/web_color.cpp + src/num_cvt.cpp + src/strtod.cpp + src/string_id.cpp + src/css_properties.cpp + src/line_box.cpp + src/css_borders.cpp + src/render_item.cpp + src/render_block_context.cpp + src/render_block.cpp + src/render_inline_context.cpp + src/render_table.cpp + src/render_flex.cpp + src/render_image.cpp + src/formatting_context.cpp + src/flex_item.cpp + src/flex_line.cpp) + +set(HEADER_LITEHTML + include/litehtml.h + include/litehtml/background.h + include/litehtml/borders.h + include/litehtml/codepoint.h + include/litehtml/css_length.h + include/litehtml/css_margins.h + include/litehtml/css_offsets.h + include/litehtml/css_position.h + include/litehtml/css_selector.h + include/litehtml/document.h + include/litehtml/document_container.h + include/litehtml/el_anchor.h + include/litehtml/el_base.h + include/litehtml/el_before_after.h + include/litehtml/el_body.h + include/litehtml/el_break.h + include/litehtml/el_cdata.h + include/litehtml/el_comment.h + include/litehtml/el_div.h + include/litehtml/el_font.h + include/litehtml/el_image.h + include/litehtml/el_link.h + include/litehtml/el_para.h + include/litehtml/el_script.h + include/litehtml/el_space.h + include/litehtml/el_style.h + include/litehtml/el_table.h + include/litehtml/el_td.h + include/litehtml/el_text.h + include/litehtml/el_title.h + include/litehtml/el_tr.h + include/litehtml/element.h + include/litehtml/html.h + include/litehtml/html_tag.h + include/litehtml/iterators.h + include/litehtml/media_query.h + include/litehtml/os_types.h + include/litehtml/style.h + include/litehtml/stylesheet.h + include/litehtml/table.h + include/litehtml/tstring_view.h + include/litehtml/types.h + include/litehtml/url.h + include/litehtml/url_path.h + include/litehtml/utf8_strings.h + include/litehtml/web_color.h + include/litehtml/num_cvt.h + include/litehtml/css_properties.h + include/litehtml/line_box.h + include/litehtml/render_item.h + include/litehtml/render_flex.h + include/litehtml/render_image.h + include/litehtml/render_inline.h + include/litehtml/render_table.h + include/litehtml/render_inline_context.h + include/litehtml/render_block_context.h + include/litehtml/render_block.h + include/litehtml/master_css.h + include/litehtml/string_id.h + include/litehtml/formatting_context.h + include/litehtml/flex_item.h + include/litehtml/flex_line.h +) + +set(TEST_LITEHTML + test/cssTest.cpp + test/mediaQueryTest.cpp + test/codepoint_test.cpp + test/tstring_view_test.cpp + test/url_test.cpp + test/url_path_test.cpp + test/render_test.cpp + containers/test/test_container.cpp + containers/test/Font.cpp + containers/test/Bitmap.cpp + containers/test/lodepng.cpp +) + +set(PROJECT_LIB_VERSION ${PROJECT_MAJOR}.${PROJECT_MINOR}.0) +set(PROJECT_SO_VERSION ${PROJECT_MAJOR}) + +add_library(${PROJECT_NAME} ${SOURCE_LITEHTML}) +set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_LIB_VERSION} SOVERSION ${PROJECT_SO_VERSION}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 11 + C_STANDARD 99 + PUBLIC_HEADER "${HEADER_LITEHTML}" +) + +# Export litehtml includes. +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ + $) +target_include_directories(${PROJECT_NAME} PRIVATE include/${PROJECT_NAME}) + +# Gumbo +target_link_libraries(${PROJECT_NAME} PUBLIC gumbo) + +# install and export +install(TARGETS ${PROJECT_NAME} + EXPORT litehtmlTargets + RUNTIME DESTINATION bin COMPONENT libraries + ARCHIVE DESTINATION lib${LIB_SUFFIX} COMPONENT libraries + LIBRARY DESTINATION lib${LIB_SUFFIX} COMPONENT libraries + PUBLIC_HEADER DESTINATION include/litehtml +) +install(FILES cmake/litehtmlConfig.cmake DESTINATION lib${LIB_SUFFIX}/cmake/litehtml) +install(EXPORT litehtmlTargets FILE litehtmlTargets.cmake DESTINATION lib${LIB_SUFFIX}/cmake/litehtml) + +# Tests + +if (LITEHTML_BUILD_TESTING) + option(EXTERNAL_GTEST "Use external GoogleTest instead of fetching from GitHub" OFF) + + if (EXTERNAL_GTEST) + link_libraries("-Wl,--copy-dt-needed-entries") + else() + include(FetchContent) + FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip + ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + FetchContent_GetProperties(googletest) + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) + endif() + endif() + + enable_testing() + + set(TEST_NAME ${PROJECT_NAME}_tests) + + add_executable( + ${TEST_NAME} + ${TEST_LITEHTML} + ) + + set_target_properties(${TEST_NAME} PROPERTIES + CXX_STANDARD 11 + C_STANDARD 99 + PUBLIC_HEADER "${HEADER_LITEHTML}" + ) + + target_include_directories( + ${TEST_NAME} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/containers + ) + + target_link_libraries( + ${TEST_NAME} + ${PROJECT_NAME} + gtest_main + ) + + include(GoogleTest) + gtest_discover_tests(${TEST_NAME}) +endif() diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/LICENSE b/src/assistant/qlitehtml/src/3rdparty/litehtml/LICENSE new file mode 100644 index 0000000..601e1c7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2013, Yuri Kobets (tordex) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/README.md b/src/assistant/qlitehtml/src/3rdparty/litehtml/README.md new file mode 100644 index 0000000..ab897f5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/README.md @@ -0,0 +1,38 @@ +# What is litehtml? + +**litehtml** is the lightweight HTML rendering engine with CSS2/CSS3 support. Note that **litehtml** itself does not draw any text, pictures or other graphics and that **litehtml** does not depend on any image/draw/font library. You are free to use any library to draw images, fonts and any other graphics. **litehtml** just parses HTML/CSS and places the HTML elements into the correct positions (renders HTML). To draw the HTML elements you have to implement the simple callback interface [document_container](https://github.com/litehtml/litehtml/wiki/document_container). This interface is really simple, check it out! The [document_container](https://github.com/litehtml/litehtml/wiki/document_container) implementation is required to render HTML correctly. + +# Where litehtml can be used + +**litehtml** can be used when you need to show HTML formatted text or even to create a mini-browser, but using it as a full-featured HTML engine is not recommended. Usually you don't need something like WebKit to show simple HTML tooltips or HTML-formatted text, **litehtml** is much better for these as it's more lightweight and easier to integrate into your application. + +## HTML Parser + +**litehtml** uses the [gumbo-parser](https://github.com/google/gumbo-parser) to parse HTML. Gumbo is an implementation of the HTML5 parsing algorithm implemented as a pure C99 library with no outside dependencies. It's designed to serve as a building block for other tools and libraries such as linters, validators, templating languages, and refactoring and analysis tools. + +## Compatibility + +**litehtml** is compatible with any platform supported by C++ and STL. For Windows MS Visual Studio 2013 is recommended. **litehtml** supports only UTF-8 strings. + +## Support for HTML and CSS standards + +Unfortunately **litehtml** is not fully compatible with HTML/CSS standards. There is lots of work to do to make **litehtml** work as well as modern browsers. But **litehtml** supports most HTML tags and CSS properties. You can find the list of supported CSS properties in [this table](https://docs.google.com/spreadsheet/ccc?key=0AvHXl5n24PuhdHdELUdhaUl4OGlncXhDcDJuM1JpMnc&usp=sharing). For most simple usecases the HTML/CSS features supported by **litehtml** are enough. Right now **litehtml** supports even some pages with very complex HTML/CSS designs. As an example the pages created with [bootstrap framework](http://getbootstrap.com/) are usually well formatted by **litehtml**. + +## Testing litehtml + +You can [download the simple browser](http://www.litehtml.com/download.html) (**litebrowser**) to test the **litehtml** rendering engine. + +The litebrowser source codes are available on GitHub: + * [For Windows](https://github.com/litehtml/litebrowser) + * [For Linux](https://github.com/litehtml/litebrowser-linux) + * [For Haiku](https://github.com/adamfowleruk/litebrowser-haiku) + +## License + +**litehtml** is distributed under [New BSD License](https://opensource.org/licenses/BSD-3-Clause). +The **gumbo-parser** is disributed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + +## Links + + * [source code](https://github.com/litehtml/litehtml) + * [website](http://www.litehtml.com/) diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/cmake/litehtmlConfig.cmake b/src/assistant/qlitehtml/src/3rdparty/litehtml/cmake/litehtmlConfig.cmake new file mode 100644 index 0000000..5eedcf4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/cmake/litehtmlConfig.cmake @@ -0,0 +1,3 @@ +include(CMakeFindDependencyMacro) +find_dependency(gumbo) +include(${CMAKE_CURRENT_LIST_DIR}/litehtmlTargets.cmake) diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_container.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_container.cpp new file mode 100644 index 0000000..c766d98 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_container.cpp @@ -0,0 +1,988 @@ +#include "cairo_container.h" +#define _USE_MATH_DEFINES +#include +#include "cairo_font.h" +#include + +cairo_container::cairo_container(void) +{ + m_temp_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2, 2); + m_temp_cr = cairo_create(m_temp_surface); + m_font_link = NULL; + CoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_ALL, IID_IMLangFontLink2, (void**) &m_font_link); + InitializeCriticalSection(&m_img_sync); +} + +cairo_container::~cairo_container(void) +{ + clear_images(); + if(m_font_link) + { + m_font_link->Release(); + } + cairo_surface_destroy(m_temp_surface); + cairo_destroy(m_temp_cr); + DeleteCriticalSection(&m_img_sync); +} + +litehtml::uint_ptr cairo_container::create_font( const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics* fm ) +{ + std::wstring fnt_name = L"sans-serif"; + + litehtml::string_vector fonts; + litehtml::split_string(faceName, fonts, ","); + if(!fonts.empty()) + { + litehtml::trim(fonts[0]); + wchar_t* f = cairo_font::utf8_to_wchar(fonts[0].c_str()); + fnt_name = f; + delete f; + if (fnt_name.front() == L'"' || fnt_name.front() == L'\'') + { + fnt_name.erase(0, 1); + } + if (fnt_name.back() == L'"' || fnt_name.back() == L'\'') + { + fnt_name.erase(fnt_name.length() - 1, 1); + } + } + + cairo_font* fnt = new cairo_font( m_font_link, + fnt_name.c_str(), + size, + weight, + (italic == litehtml::font_style_italic) ? TRUE : FALSE, + (decoration & litehtml::font_decoration_linethrough) ? TRUE : FALSE, + (decoration & litehtml::font_decoration_underline) ? TRUE : FALSE); + + cairo_save(m_temp_cr); + fnt->load_metrics(m_temp_cr); + + if(fm) + { + fm->ascent = fnt->metrics().ascent; + fm->descent = fnt->metrics().descent; + fm->height = fnt->metrics().height; + fm->x_height = fnt->metrics().x_height; + if(italic == litehtml::font_style_italic || decoration) + { + fm->draw_spaces = true; + } else + { + fm->draw_spaces = false; + } + } + + cairo_restore(m_temp_cr); + + return (litehtml::uint_ptr) fnt; +} + +void cairo_container::delete_font( litehtml::uint_ptr hFont ) +{ + cairo_font* fnt = (cairo_font*) hFont; + if(fnt) + { + delete fnt; + } +} + +int cairo_container::text_width( const char* text, litehtml::uint_ptr hFont ) +{ + cairo_font* fnt = (cairo_font*) hFont; + + cairo_save(m_temp_cr); + int ret = fnt->text_width(m_temp_cr, text); + cairo_restore(m_temp_cr); + return ret; +} + +void cairo_container::draw_text( litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos ) +{ + if(hFont) + { + cairo_font* fnt = (cairo_font*) hFont; + cairo_t* cr = (cairo_t*) hdc; + cairo_save(cr); + + apply_clip(cr); + + int x = pos.left(); + int y = pos.bottom() - fnt->metrics().descent; + + set_color(cr, color); + fnt->show_text(cr, x, y, text); + + cairo_restore(cr); + } +} + +int cairo_container::pt_to_px( int pt ) const +{ + HDC dc = GetDC(NULL); + int ret = MulDiv(pt, GetDeviceCaps(dc, LOGPIXELSY), 72); + ReleaseDC(NULL, dc); + return ret; +} + +int cairo_container::get_default_font_size() const +{ + return 16; +} + +void cairo_container::draw_list_marker( litehtml::uint_ptr hdc, const litehtml::list_marker& marker ) +{ + if(!marker.image.empty()) + { + std::wstring url; + make_url_utf8(marker.image.c_str(), marker.baseurl, url); + + lock_images_cache(); + images_map::iterator img_i = m_images.find(url.c_str()); + if(img_i != m_images.end()) + { + if(img_i->second) + { + draw_txdib((cairo_t*)hdc, img_i->second.get(), marker.pos.x, marker.pos.y, marker.pos.width, marker.pos.height); + unlock_images_cache(); + return; + } + } + unlock_images_cache(); + } + + switch(marker.marker_type) + { + case litehtml::list_style_type_circle: + { + draw_ellipse((cairo_t*) hdc, marker.pos.x, marker.pos.y, marker.pos.width, marker.pos.height, marker.color, 0.5); + } + break; + case litehtml::list_style_type_disc: + { + fill_ellipse((cairo_t*) hdc, marker.pos.x, marker.pos.y, marker.pos.width, marker.pos.height, marker.color); + } + break; + case litehtml::list_style_type_square: + if(hdc) + { + cairo_t* cr = (cairo_t*) hdc; + cairo_save(cr); + + cairo_new_path(cr); + cairo_rectangle(cr, marker.pos.x, marker.pos.y, marker.pos.width, marker.pos.height); + + set_color(cr, marker.color); + cairo_fill(cr); + cairo_restore(cr); + } + break; + } +} + +void cairo_container::load_image( const char* src, const char* baseurl, bool redraw_on_ready ) +{ + std::wstring url; + make_url_utf8(src, baseurl, url); + lock_images_cache(); + if(m_images.find(url.c_str()) == m_images.end()) + { + unlock_images_cache(); + image_ptr img = get_image(url.c_str(), redraw_on_ready); + lock_images_cache(); + m_images[url] = img; + unlock_images_cache(); + } else + { + unlock_images_cache(); + } + +} + +void cairo_container::get_image_size( const char* src, const char* baseurl, litehtml::size& sz ) +{ + std::wstring url; + make_url_utf8(src, baseurl, url); + + sz.width = 0; + sz.height = 0; + + lock_images_cache(); + images_map::iterator img = m_images.find(url.c_str()); + if(img != m_images.end()) + { + if(img->second) + { + sz.width = img->second->getWidth(); + sz.height = img->second->getHeight(); + } + } + unlock_images_cache(); +} + +void cairo_container::draw_image( litehtml::uint_ptr hdc, const char* src, const char* baseurl, const litehtml::position& pos ) +{ + cairo_t* cr = (cairo_t*) hdc; + cairo_save(cr); + apply_clip(cr); + + std::wstring url; + make_url_utf8(src, baseurl, url); + lock_images_cache(); + images_map::iterator img = m_images.find(url.c_str()); + if(img != m_images.end()) + { + if(img->second) + { + draw_txdib(cr, img->second.get(), pos.x, pos.y, pos.width, pos.height); + } + } + unlock_images_cache(); + cairo_restore(cr); +} + +void cairo_container::draw_background( litehtml::uint_ptr hdc, const std::vector& bgvec ) +{ + cairo_t* cr = (cairo_t*) hdc; + cairo_save(cr); + apply_clip(cr); + + const auto& bg = bgvec.back(); + + rounded_rectangle(cr, bg.border_box, bg.border_radius); + cairo_clip(cr); + + cairo_rectangle(cr, bg.clip_box.x, bg.clip_box.y, bg.clip_box.width, bg.clip_box.height); + cairo_clip(cr); + + if(bg.color.alpha) + { + set_color(cr, bg.color); + cairo_paint(cr); + } + + for (int i = (int)bgvec.size() - 1; i >= 0; i--) + { + const auto& bg = bgvec[i]; + + cairo_rectangle(cr, bg.clip_box.x, bg.clip_box.y, bg.clip_box.width, bg.clip_box.height); + cairo_clip(cr); + + std::wstring url; + make_url_utf8(bg.image.c_str(), bg.baseurl.c_str(), url); + + lock_images_cache(); + auto img_i = m_images.find(url); + if (img_i != m_images.end() && img_i->second) + { + image_ptr bgbmp = img_i->second; + + image_ptr new_img; + if (bg.image_size.width != bgbmp->getWidth() || bg.image_size.height != bgbmp->getHeight()) + { + new_img = image_ptr(new CTxDIB); + bgbmp->resample(bg.image_size.width, bg.image_size.height, new_img.get()); + bgbmp = new_img; + } + + + cairo_surface_t* img = cairo_image_surface_create_for_data((unsigned char*)bgbmp->getBits(), CAIRO_FORMAT_ARGB32, bgbmp->getWidth(), bgbmp->getHeight(), bgbmp->getWidth() * 4); + cairo_pattern_t* pattern = cairo_pattern_create_for_surface(img); + cairo_matrix_t flib_m; + cairo_matrix_init(&flib_m, 1, 0, 0, -1, 0, 0); + cairo_matrix_translate(&flib_m, -bg.position_x, -bg.position_y); + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_matrix(pattern, &flib_m); + + switch (bg.repeat) + { + case litehtml::background_repeat_no_repeat: + draw_txdib(cr, bgbmp.get(), bg.position_x, bg.position_y, bgbmp->getWidth(), bgbmp->getHeight()); + break; + + case litehtml::background_repeat_repeat_x: + cairo_set_source(cr, pattern); + cairo_rectangle(cr, bg.clip_box.left(), bg.position_y, bg.clip_box.width, bgbmp->getHeight()); + cairo_fill(cr); + break; + + case litehtml::background_repeat_repeat_y: + cairo_set_source(cr, pattern); + cairo_rectangle(cr, bg.position_x, bg.clip_box.top(), bgbmp->getWidth(), bg.clip_box.height); + cairo_fill(cr); + break; + + case litehtml::background_repeat_repeat: + cairo_set_source(cr, pattern); + cairo_rectangle(cr, bg.clip_box.left(), bg.clip_box.top(), bg.clip_box.width, bg.clip_box.height); + cairo_fill(cr); + break; + } + + cairo_pattern_destroy(pattern); + cairo_surface_destroy(img); + } + unlock_images_cache(); + } + + cairo_restore(cr); +} + +bool cairo_container::add_path_arc(cairo_t* cr, double x, double y, double rx, double ry, double a1, double a2, bool neg) +{ + if(rx > 0 && ry > 0) + { + cairo_save(cr); + + cairo_translate(cr, x, y); + cairo_scale(cr, 1, ry / rx); + cairo_translate(cr, -x, -y); + + if(neg) + { + cairo_arc_negative(cr, x, y, rx, a1, a2); + } else + { + cairo_arc(cr, x, y, rx, a1, a2); + } + + cairo_restore(cr); + return true; + } + return false; +} + +void cairo_container::draw_borders( litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root ) +{ + cairo_t* cr = (cairo_t*) hdc; + cairo_save(cr); + apply_clip(cr); + + cairo_new_path(cr); + + int bdr_top = 0; + int bdr_bottom = 0; + int bdr_left = 0; + int bdr_right = 0; + + if(borders.top.width != 0 && borders.top.style > litehtml::border_style_hidden) + { + bdr_top = (int) borders.top.width; + } + if(borders.bottom.width != 0 && borders.bottom.style > litehtml::border_style_hidden) + { + bdr_bottom = (int) borders.bottom.width; + } + if(borders.left.width != 0 && borders.left.style > litehtml::border_style_hidden) + { + bdr_left = (int) borders.left.width; + } + if(borders.right.width != 0 && borders.right.style > litehtml::border_style_hidden) + { + bdr_right = (int) borders.right.width; + } + + // draw right border + if (bdr_right) + { + set_color(cr, borders.right.color); + + double r_top = (double) borders.radius.top_right_x; + double r_bottom = (double) borders.radius.bottom_right_x; + + if(r_top) + { + double end_angle = 2.0 * M_PI; + double start_angle = end_angle - M_PI / 2.0 / ((double) bdr_top / (double) bdr_right + 0.5); + + if (!add_path_arc(cr, + draw_pos.right() - r_top, + draw_pos.top() + r_top, + r_top - bdr_right, + r_top - bdr_right + (bdr_right - bdr_top), + end_angle, + start_angle, true)) + { + cairo_move_to(cr, draw_pos.right() - bdr_right, draw_pos.top() + bdr_top); + } + + if (!add_path_arc(cr, + draw_pos.right() - r_top, + draw_pos.top() + r_top, + r_top, + r_top, + start_angle, + end_angle, false)) + { + cairo_line_to(cr, draw_pos.right(), draw_pos.top()); + } + } else + { + cairo_move_to(cr, draw_pos.right() - bdr_right, draw_pos.top() + bdr_top); + cairo_line_to(cr, draw_pos.right(), draw_pos.top()); + } + + if(r_bottom) + { + cairo_line_to(cr, draw_pos.right(), draw_pos.bottom() - r_bottom); + + double start_angle = 0; + double end_angle = start_angle + M_PI / 2.0 / ((double) bdr_bottom / (double) bdr_right + 0.5); + + if (!add_path_arc(cr, + draw_pos.right() - r_bottom, + draw_pos.bottom() - r_bottom, + r_bottom, + r_bottom, + start_angle, + end_angle, false)) + { + cairo_line_to(cr, draw_pos.right(), draw_pos.bottom()); + } + + if (!add_path_arc(cr, + draw_pos.right() - r_bottom, + draw_pos.bottom() - r_bottom, + r_bottom - bdr_right, + r_bottom - bdr_right + (bdr_right - bdr_bottom), + end_angle, + start_angle, true)) + { + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.bottom() - bdr_bottom); + } + } else + { + cairo_line_to(cr, draw_pos.right(), draw_pos.bottom()); + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.bottom() - bdr_bottom); + } + + cairo_fill(cr); + } + + // draw bottom border + if(bdr_bottom) + { + set_color(cr, borders.bottom.color); + + double r_left = borders.radius.bottom_left_x; + double r_right = borders.radius.bottom_right_x; + + if(r_left) + { + double start_angle = M_PI / 2.0; + double end_angle = start_angle + M_PI / 2.0 / ((double) bdr_left / (double) bdr_bottom + 0.5); + + if (!add_path_arc(cr, + draw_pos.left() + r_left, + draw_pos.bottom() - r_left, + r_left - bdr_bottom + (bdr_bottom - bdr_left), + r_left - bdr_bottom, + start_angle, + end_angle, false)) + { + cairo_move_to(cr, draw_pos.left() + bdr_left, draw_pos.bottom() - bdr_bottom); + } + + if (!add_path_arc(cr, + draw_pos.left() + r_left, + draw_pos.bottom() - r_left, + r_left, + r_left, + end_angle, + start_angle, true)) + { + cairo_line_to(cr, draw_pos.left(), draw_pos.bottom()); + } + } else + { + cairo_move_to(cr, draw_pos.left(), draw_pos.bottom()); + cairo_line_to(cr, draw_pos.left() + bdr_left, draw_pos.bottom() - bdr_bottom); + } + + if(r_right) + { + cairo_line_to(cr, draw_pos.right() - r_right, draw_pos.bottom()); + + double end_angle = M_PI / 2.0; + double start_angle = end_angle - M_PI / 2.0 / ((double) bdr_right / (double) bdr_bottom + 0.5); + + if (!add_path_arc(cr, + draw_pos.right() - r_right, + draw_pos.bottom() - r_right, + r_right, + r_right, + end_angle, + start_angle, true)) + { + cairo_line_to(cr, draw_pos.right(), draw_pos.bottom()); + } + + if (!add_path_arc(cr, + draw_pos.right() - r_right, + draw_pos.bottom() - r_right, + r_right - bdr_bottom + (bdr_bottom - bdr_right), + r_right - bdr_bottom, + start_angle, + end_angle, false)) + { + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.bottom() - bdr_bottom); + } + } else + { + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.bottom() - bdr_bottom); + cairo_line_to(cr, draw_pos.right(), draw_pos.bottom()); + } + + cairo_fill(cr); + } + + // draw top border + if(bdr_top) + { + set_color(cr, borders.top.color); + + double r_left = borders.radius.top_left_x; + double r_right = borders.radius.top_right_x; + + if(r_left) + { + double end_angle = M_PI * 3.0 / 2.0; + double start_angle = end_angle - M_PI / 2.0 / ((double) bdr_left / (double) bdr_top + 0.5); + + if (!add_path_arc(cr, + draw_pos.left() + r_left, + draw_pos.top() + r_left, + r_left, + r_left, + end_angle, + start_angle, true)) + { + cairo_move_to(cr, draw_pos.left(), draw_pos.top()); + } + + if (!add_path_arc(cr, + draw_pos.left() + r_left, + draw_pos.top() + r_left, + r_left - bdr_top + (bdr_top - bdr_left), + r_left - bdr_top, + start_angle, + end_angle, false)) + { + cairo_line_to(cr, draw_pos.left() + bdr_left, draw_pos.top() + bdr_top); + } + } else + { + cairo_move_to(cr, draw_pos.left(), draw_pos.top()); + cairo_line_to(cr, draw_pos.left() + bdr_left, draw_pos.top() + bdr_top); + } + + if(r_right) + { + cairo_line_to(cr, draw_pos.right() - r_right, draw_pos.top() + bdr_top); + + double start_angle = M_PI * 3.0 / 2.0; + double end_angle = start_angle + M_PI / 2.0 / ((double) bdr_right / (double) bdr_top + 0.5); + + if (!add_path_arc(cr, + draw_pos.right() - r_right, + draw_pos.top() + r_right, + r_right - bdr_top + (bdr_top - bdr_right), + r_right - bdr_top, + start_angle, + end_angle, false)) + { + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.top() + bdr_top); + } + + if (!add_path_arc(cr, + draw_pos.right() - r_right, + draw_pos.top() + r_right, + r_right, + r_right, + end_angle, + start_angle, true)) + { + cairo_line_to(cr, draw_pos.right(), draw_pos.top()); + } + } else + { + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.top() + bdr_top); + cairo_line_to(cr, draw_pos.right(), draw_pos.top()); + } + + cairo_fill(cr); + } + + // draw left border + if (bdr_left) + { + set_color(cr, borders.left.color); + + double r_top = borders.radius.top_left_x; + double r_bottom = borders.radius.bottom_left_x; + + if(r_top) + { + double start_angle = M_PI; + double end_angle = start_angle + M_PI / 2.0 / ((double) bdr_top / (double) bdr_left + 0.5); + + if (!add_path_arc(cr, + draw_pos.left() + r_top, + draw_pos.top() + r_top, + r_top - bdr_left, + r_top - bdr_left + (bdr_left - bdr_top), + start_angle, + end_angle, false)) + { + cairo_move_to(cr, draw_pos.left() + bdr_left, draw_pos.top() + bdr_top); + } + + if (!add_path_arc(cr, + draw_pos.left() + r_top, + draw_pos.top() + r_top, + r_top, + r_top, + end_angle, + start_angle, true)) + { + cairo_line_to(cr, draw_pos.left(), draw_pos.top()); + } + } else + { + cairo_move_to(cr, draw_pos.left() + bdr_left, draw_pos.top() + bdr_top); + cairo_line_to(cr, draw_pos.left(), draw_pos.top()); + } + + if(r_bottom) + { + cairo_line_to(cr, draw_pos.left(), draw_pos.bottom() - r_bottom); + + double end_angle = M_PI; + double start_angle = end_angle - M_PI / 2.0 / ((double) bdr_bottom / (double) bdr_left + 0.5); + + if (!add_path_arc(cr, + draw_pos.left() + r_bottom, + draw_pos.bottom() - r_bottom, + r_bottom, + r_bottom, + end_angle, + start_angle, true)) + { + cairo_line_to(cr, draw_pos.left(), draw_pos.bottom()); + } + + if (!add_path_arc(cr, + draw_pos.left() + r_bottom, + draw_pos.bottom() - r_bottom, + r_bottom - bdr_left, + r_bottom - bdr_left + (bdr_left - bdr_bottom), + start_angle, + end_angle, false)) + { + cairo_line_to(cr, draw_pos.left() + bdr_left, draw_pos.bottom() - bdr_bottom); + } + } else + { + cairo_line_to(cr, draw_pos.left(), draw_pos.bottom()); + cairo_line_to(cr, draw_pos.left() + bdr_left, draw_pos.bottom() - bdr_bottom); + } + + cairo_fill(cr); + } + cairo_restore(cr); +} + +void cairo_container::set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) +{ + m_clips.emplace_back(pos, bdr_radius); +} + +void cairo_container::del_clip() +{ + if(!m_clips.empty()) + { + m_clips.pop_back(); + } +} + +void cairo_container::apply_clip( cairo_t* cr ) +{ + for(const auto& clip_box : m_clips) + { + rounded_rectangle(cr, clip_box.box, clip_box.radius); + cairo_clip(cr); + } +} + +void cairo_container::draw_ellipse( cairo_t* cr, int x, int y, int width, int height, const litehtml::web_color& color, double line_width ) +{ + if(!cr) return; + cairo_save(cr); + + apply_clip(cr); + + cairo_new_path(cr); + + cairo_translate (cr, x + width / 2.0, y + height / 2.0); + cairo_scale (cr, width / 2.0, height / 2.0); + cairo_arc (cr, 0, 0, 1, 0, 2 * M_PI); + + set_color(cr, color); + cairo_set_line_width(cr, line_width); + cairo_stroke(cr); + + cairo_restore(cr); +} + +void cairo_container::fill_ellipse( cairo_t* cr, int x, int y, int width, int height, const litehtml::web_color& color ) +{ + if(!cr) return; + cairo_save(cr); + + apply_clip(cr); + + cairo_new_path(cr); + + cairo_translate (cr, x + width / 2.0, y + height / 2.0); + cairo_scale (cr, width / 2.0, height / 2.0); + cairo_arc (cr, 0, 0, 1, 0, 2 * M_PI); + + set_color(cr, color); + cairo_fill(cr); + + cairo_restore(cr); +} + +void cairo_container::clear_images() +{ + lock_images_cache(); + m_images.clear(); + unlock_images_cache(); +} + +const char* cairo_container::get_default_font_name() const +{ + return "Times New Roman"; +} + +void cairo_container::draw_txdib( cairo_t* cr, CTxDIB* bmp, int x, int y, int cx, int cy ) +{ + cairo_save(cr); + + cairo_matrix_t flib_m; + cairo_matrix_init(&flib_m, 1, 0, 0, -1, 0, 0); + + cairo_surface_t* img = NULL; + + CTxDIB rbmp; + + if(cx != bmp->getWidth() || cy != bmp->getHeight()) + { + bmp->resample(cx, cy, &rbmp); + img = cairo_image_surface_create_for_data((unsigned char*) rbmp.getBits(), CAIRO_FORMAT_ARGB32, rbmp.getWidth(), rbmp.getHeight(), rbmp.getWidth() * 4); + cairo_matrix_translate(&flib_m, 0, -rbmp.getHeight()); + cairo_matrix_translate(&flib_m, x, -y); + } else + { + img = cairo_image_surface_create_for_data((unsigned char*) bmp->getBits(), CAIRO_FORMAT_ARGB32, bmp->getWidth(), bmp->getHeight(), bmp->getWidth() * 4); + cairo_matrix_translate(&flib_m, 0, -bmp->getHeight()); + cairo_matrix_translate(&flib_m, x, -y); + } + + cairo_transform(cr, &flib_m); + cairo_set_source_surface(cr, img, 0, 0); + cairo_paint(cr); + + cairo_restore(cr); + cairo_surface_destroy(img); +} + +void cairo_container::rounded_rectangle(cairo_t* cr, const litehtml::position& pos, const litehtml::border_radiuses& radius) +{ + cairo_new_path(cr); + if(radius.top_left_x) + { + cairo_arc(cr, pos.left() + radius.top_left_x, pos.top() + radius.top_left_x, radius.top_left_x, M_PI, M_PI * 3.0 / 2.0); + } else + { + cairo_move_to(cr, pos.left(), pos.top()); + } + + cairo_line_to(cr, pos.right() - radius.top_right_x, pos.top()); + + if(radius.top_right_x) + { + cairo_arc(cr, pos.right() - radius.top_right_x, pos.top() + radius.top_right_x, radius.top_right_x, M_PI * 3.0 / 2.0, 2.0 * M_PI); + } + + cairo_line_to(cr, pos.right(), pos.bottom() - radius.bottom_right_x); + + if(radius.bottom_right_x) + { + cairo_arc(cr, pos.right() - radius.bottom_right_x, pos.bottom() - radius.bottom_right_x, radius.bottom_right_x, 0, M_PI / 2.0); + } + + cairo_line_to(cr, pos.left() - radius.bottom_left_x, pos.bottom()); + + if(radius.bottom_left_x) + { + cairo_arc(cr, pos.left() + radius.bottom_left_x, pos.bottom() - radius.bottom_left_x, radius.bottom_left_x, M_PI / 2.0, M_PI); + } +} + +void cairo_container::remove_image( std::wstring& url ) +{ + lock_images_cache(); + images_map::iterator i = m_images.find(url); + if(i != m_images.end()) + { + m_images.erase(i); + } + unlock_images_cache(); +} + +void cairo_container::add_image(std::wstring& url, image_ptr& img) +{ + lock_images_cache(); + images_map::iterator i = m_images.find(url); + if(i != m_images.end()) + { + if(img) + { + i->second = img; + } else + { + m_images.erase(i); + } + } + unlock_images_cache(); +} + +void cairo_container::lock_images_cache() +{ + EnterCriticalSection(&m_img_sync); +} + +void cairo_container::unlock_images_cache() +{ + LeaveCriticalSection(&m_img_sync); +} + +std::shared_ptr cairo_container::create_element(const char* tag_name, const litehtml::string_map& attributes, const std::shared_ptr& doc) +{ + return 0; +} + +void cairo_container::get_media_features(litehtml::media_features& media) const +{ + litehtml::position client; + get_client_rect(client); + HDC hdc = GetDC(NULL); + + media.type = litehtml::media_type_screen; + media.width = client.width; + media.height = client.height; + media.color = 8; + media.monochrome = 0; + media.color_index = 256; + media.resolution = GetDeviceCaps(hdc, LOGPIXELSX); + media.device_width = GetDeviceCaps(hdc, HORZRES); + media.device_height = GetDeviceCaps(hdc, VERTRES); + + ReleaseDC(NULL, hdc); +} + +void cairo_container::get_language(litehtml::string& language, litehtml::string & culture) const +{ + language = "en"; + culture = ""; +} + +void cairo_container::make_url_utf8( const char* url, const char* basepath, std::wstring& out ) +{ + wchar_t* urlW = cairo_font::utf8_to_wchar(url); + wchar_t* basepathW = cairo_font::utf8_to_wchar(basepath); + make_url(urlW, basepathW, out); + + if(urlW) delete urlW; + if(basepathW) delete basepathW; +} + +void cairo_container::transform_text( litehtml::string& text, litehtml::text_transform tt ) +{ + if(text.empty()) return; + + LPWSTR txt = cairo_font::utf8_to_wchar(text.c_str()); + switch(tt) + { + case litehtml::text_transform_capitalize: + CharUpperBuff(txt, 1); + break; + case litehtml::text_transform_uppercase: + CharUpperBuff(txt, lstrlen(txt)); + break; + case litehtml::text_transform_lowercase: + CharLowerBuff(txt, lstrlen(txt)); + break; + } + LPSTR txtA = cairo_font::wchar_to_utf8(txt); + text = txtA; + delete txtA; + delete txt; +} + +void cairo_container::link(const std::shared_ptr& doc, const litehtml::element::ptr& el) +{ +} + +litehtml::string cairo_container::resolve_color(const litehtml::string& color) const +{ + struct custom_color + { + const char* name; + int color_index; + }; + + static custom_color colors[] = { + { "ActiveBorder", COLOR_ACTIVEBORDER}, + { "ActiveCaption", COLOR_ACTIVECAPTION}, + { "AppWorkspace", COLOR_APPWORKSPACE }, + { "Background", COLOR_BACKGROUND }, + { "ButtonFace", COLOR_BTNFACE }, + { "ButtonHighlight", COLOR_BTNHIGHLIGHT }, + { "ButtonShadow", COLOR_BTNSHADOW }, + { "ButtonText", COLOR_BTNTEXT }, + { "CaptionText", COLOR_CAPTIONTEXT }, + { "GrayText", COLOR_GRAYTEXT }, + { "Highlight", COLOR_HIGHLIGHT }, + { "HighlightText", COLOR_HIGHLIGHTTEXT }, + { "InactiveBorder", COLOR_INACTIVEBORDER }, + { "InactiveCaption", COLOR_INACTIVECAPTION }, + { "InactiveCaptionText", COLOR_INACTIVECAPTIONTEXT }, + { "InfoBackground", COLOR_INFOBK }, + { "InfoText", COLOR_INFOTEXT }, + { "Menu", COLOR_MENU }, + { "MenuText", COLOR_MENUTEXT }, + { "Scrollbar", COLOR_SCROLLBAR }, + { "ThreeDDarkShadow", COLOR_3DDKSHADOW }, + { "ThreeDFace", COLOR_3DFACE }, + { "ThreeDHighlight", COLOR_3DHILIGHT }, + { "ThreeDLightShadow", COLOR_3DLIGHT }, + { "ThreeDShadow", COLOR_3DSHADOW }, + { "Window", COLOR_WINDOW }, + { "WindowFrame", COLOR_WINDOWFRAME }, + { "WindowText", COLOR_WINDOWTEXT } + }; + + for (auto& clr : colors) + { + if (!litehtml::t_strcasecmp(clr.name, color.c_str())) + { + char str_clr[20]; + DWORD rgb_color = GetSysColor(clr.color_index); + StringCchPrintfA(str_clr, 20, "#%02X%02X%02X", GetRValue(rgb_color), GetGValue(rgb_color), GetBValue(rgb_color)); + return std::move(litehtml::string(str_clr)); + } + } + return std::move(litehtml::string()); +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_container.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_container.h new file mode 100644 index 0000000..8b68f8d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_container.h @@ -0,0 +1,103 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "cairo.h" +#include "cairo-win32.h" +#include +#include +#include + +struct cairo_clip_box +{ + typedef std::vector vector; + litehtml::position box; + litehtml::border_radiuses radius; + + cairo_clip_box(const litehtml::position& vBox, litehtml::border_radiuses vRad) + { + box = vBox; + radius = vRad; + } + + cairo_clip_box(const cairo_clip_box& val) + { + box = val.box; + radius = val.radius; + } + cairo_clip_box& operator=(const cairo_clip_box& val) + { + box = val.box; + radius = val.radius; + return *this; + } +}; + +class cairo_container : public litehtml::document_container +{ +public: + typedef std::shared_ptr image_ptr; + typedef std::map images_map; + +protected: + cairo_surface_t* m_temp_surface; + cairo_t* m_temp_cr; + images_map m_images; + cairo_clip_box::vector m_clips; + IMLangFontLink2* m_font_link; + CRITICAL_SECTION m_img_sync; +public: + cairo_container(void); + virtual ~cairo_container(void); + + virtual litehtml::uint_ptr create_font(const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics* fm) override; + virtual void delete_font(litehtml::uint_ptr hFont) override; + virtual int text_width(const char* text, litehtml::uint_ptr hFont) override; + virtual void draw_text(litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) override; + + virtual int pt_to_px(int pt) const override; + virtual int get_default_font_size() const override; + virtual const char* get_default_font_name() const override; + virtual void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker& marker) override; + virtual void load_image(const char* src, const char* baseurl, bool redraw_on_ready) override; + virtual void get_image_size(const char* src, const char* baseurl, litehtml::size& sz) override; + virtual void draw_image(litehtml::uint_ptr hdc, const char* src, const char* baseurl, const litehtml::position& pos); + virtual void draw_background(litehtml::uint_ptr hdc, const std::vector& bg) override; + virtual void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) override; + + virtual void transform_text(litehtml::string& text, litehtml::text_transform tt) override; + virtual void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) override; + virtual void del_clip() override; + virtual std::shared_ptr create_element(const char* tag_name, const litehtml::string_map& attributes, const std::shared_ptr& doc) override; + virtual void get_media_features(litehtml::media_features& media) const override; + virtual void get_language(litehtml::string& language, litehtml::string& culture) const override; + virtual void link(const std::shared_ptr& doc, const litehtml::element::ptr& el) override; + virtual litehtml::string resolve_color(const litehtml::string& color) const override; + + + virtual void make_url( LPCWSTR url, LPCWSTR basepath, std::wstring& out ) = 0; + virtual image_ptr get_image(LPCWSTR url, bool redraw_on_ready) = 0; + void clear_images(); + void add_image(std::wstring& url, image_ptr& img); + void remove_image(std::wstring& url); + void make_url_utf8( const char* url, const char* basepath, std::wstring& out ); + +protected: + virtual void draw_ellipse(cairo_t* cr, int x, int y, int width, int height, const litehtml::web_color& color, double line_width); + virtual void fill_ellipse(cairo_t* cr, int x, int y, int width, int height, const litehtml::web_color& color); + virtual void rounded_rectangle( cairo_t* cr, const litehtml::position &pos, const litehtml::border_radiuses &radius ); + + void set_color(cairo_t* cr, litehtml::web_color color) { cairo_set_source_rgba(cr, color.red / 255.0, color.green / 255.0, color.blue / 255.0, color.alpha / 255.0); } +private: + simpledib::dib* get_dib(litehtml::uint_ptr hdc) { return (simpledib::dib*) hdc; } + void apply_clip(cairo_t* cr); + bool add_path_arc(cairo_t* cr, double x, double y, double rx, double ry, double a1, double a2, bool neg); + + void draw_txdib(cairo_t* cr, CTxDIB* bmp, int x, int y, int cx, int cy); + void lock_images_cache(); + void unlock_images_cache(); +}; diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_font.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_font.cpp new file mode 100644 index 0000000..2401f87 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_font.cpp @@ -0,0 +1,371 @@ +#include "cairo_font.h" + +cairo_font::cairo_font(IMLangFontLink2* fl, HFONT hFont, int size ) +{ + init(); + m_font_link = fl; + if(m_font_link) + { + m_font_link->AddRef(); + } + m_size = size; + set_font(hFont); +} + +cairo_font::cairo_font(IMLangFontLink2* fl, LPCWSTR facename, int size, int weight, BOOL italic, BOOL strikeout, BOOL underline ) +{ + init(); + m_size = size; + m_font_link = fl; + if(m_font_link) + { + m_font_link->AddRef(); + } + + LOGFONT lf; + ZeroMemory(&lf, sizeof(lf)); + if(!lstrcmpi(facename, L"monospace")) + { + wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Courier New"); + } else if(!lstrcmpi(facename, L"serif")) + { + wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Times New Roman"); + } else if(!lstrcmpi(facename, L"sans-serif")) + { + wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Arial"); + } else if(!lstrcmpi(facename, L"fantasy")) + { + wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Impact"); + } else if(!lstrcmpi(facename, L"cursive")) + { + wcscpy_s(lf.lfFaceName, LF_FACESIZE, L"Comic Sans MS"); + } else + { + wcscpy_s(lf.lfFaceName, LF_FACESIZE, facename); + } + + lf.lfHeight = -size; + lf.lfWeight = weight; + lf.lfItalic = italic; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfOutPrecision = OUT_DEFAULT_PRECIS; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfQuality = DEFAULT_QUALITY; + lf.lfStrikeOut = strikeout; + lf.lfUnderline = underline; + + HFONT fnt = CreateFontIndirect(&lf); + set_font(fnt); +} + +cairo_font::~cairo_font() +{ + if(m_font_face) + { + cairo_font_face_destroy(m_font_face); + } + for(size_t i = 0; i < m_linked_fonts.size(); i++) + { + if(m_linked_fonts[i]->hFont) + { + m_font_link->ReleaseFont(m_linked_fonts[i]->hFont); + } + if(m_linked_fonts[i]->font_face) + { + cairo_font_face_destroy(m_linked_fonts[i]->font_face); + } + } + m_linked_fonts.clear(); + if(m_font_link) + { + m_font_link->AddRef(); + } + if(m_hFont) + { + DeleteObject(m_hFont); + } +} + +void cairo_font::show_text( cairo_t* cr, int x, int y, const char* str ) +{ + lock(); + text_chunk::vector chunks; + split_text(str, chunks); + cairo_set_font_size(cr, m_size); + cairo_move_to(cr, x, y); + for(size_t i = 0; i < chunks.size(); i++) + { + if(chunks[i]->font) + { + cairo_set_font_face(cr, chunks[i]->font->font_face); + } else + { + cairo_set_font_face(cr, m_font_face); + } + cairo_show_text(cr, chunks[i]->text); + } + unlock(); + + if(m_bUnderline) + { + int tw = text_width(cr, chunks); + + lock(); + cairo_set_line_width(cr, 1); + cairo_move_to(cr, x, y + 1.5); + cairo_line_to(cr, x + tw, y + 1.5); + cairo_stroke(cr); + unlock(); + } + if(m_bStrikeOut) + { + int tw = text_width(cr, chunks); + + cairo_font_metrics fm; + get_metrics(cr, &fm); + + int ln_y = y - fm.x_height / 2; + + lock(); + cairo_set_line_width(cr, 1); + cairo_move_to(cr, x, (double) ln_y - 0.5); + cairo_line_to(cr, x + tw, (double) ln_y - 0.5); + cairo_stroke(cr); + unlock(); + } + + free_text_chunks(chunks); +} + +void cairo_font::split_text( const char* src, text_chunk::vector& chunks ) +{ + wchar_t* str = cairo_font::utf8_to_wchar(src); + wchar_t* str_start = str; + + int cch = lstrlen(str); + + HDC hdc = GetDC(NULL); + SelectObject(hdc, m_hFont); + HRESULT hr = S_OK; + while(cch > 0) + { + DWORD dwActualCodePages; + long cchActual; + if(m_font_link) + { + hr = m_font_link->GetStrCodePages(str, cch, m_font_code_pages, &dwActualCodePages, &cchActual); + } else + { + hr = S_FALSE; + } + + if(hr != S_OK) + { + break; + } + + text_chunk* chk = new text_chunk; + + int sz = WideCharToMultiByte(CP_UTF8, 0, str, cchActual, chk->text, 0, NULL, NULL) + 1; + chk->text = new CHAR[sz]; + sz = WideCharToMultiByte(CP_UTF8, 0, str, cchActual, chk->text, sz, NULL, NULL); + chk->text[sz] = 0; + chk->font = NULL; + + if(!(dwActualCodePages & m_font_code_pages)) + { + for(linked_font::vector::iterator i = m_linked_fonts.begin(); i != m_linked_fonts.end(); i++) + { + if((*i)->code_pages == dwActualCodePages) + { + chk->font = (*i); + break; + } + } + if(!chk->font) + { + linked_font* lkf = new linked_font; + lkf->code_pages = dwActualCodePages; + lkf->hFont = NULL; + m_font_link->MapFont(hdc, dwActualCodePages, 0, &lkf->hFont); + if (lkf->hFont) + { + lkf->font_face = create_font_face(lkf->hFont); + m_linked_fonts.push_back(lkf); + } + else + { + delete lkf; + lkf = NULL; + } + chk->font = lkf; + } + } + + chunks.push_back(chk); + + cch -= cchActual; + str += cchActual; + } + + if(hr != S_OK) + { + text_chunk* chk = new text_chunk; + + int sz = WideCharToMultiByte(CP_UTF8, 0, str, -1, chk->text, 0, NULL, NULL) + 1; + chk->text = new CHAR[sz]; + sz = WideCharToMultiByte(CP_UTF8, 0, str, -1, chk->text, sz, NULL, NULL); + chk->text[sz] = 0; + chk->font = NULL; + chunks.push_back(chk); + } + + ReleaseDC(NULL, hdc); + delete str_start; +} + +void cairo_font::free_text_chunks( text_chunk::vector& chunks ) +{ + for(size_t i = 0; i < chunks.size(); i++) + { + delete chunks[i]; + } + chunks.clear(); +} + +cairo_font_face_t* cairo_font::create_font_face( HFONT fnt ) +{ + LOGFONT lf; + GetObject(fnt, sizeof(LOGFONT), &lf); + return cairo_win32_font_face_create_for_logfontw(&lf); +} + +int cairo_font::text_width( cairo_t* cr, const char* str ) +{ + text_chunk::vector chunks; + split_text(str, chunks); + + int ret = text_width(cr, chunks); + + free_text_chunks(chunks); + + return (int) ret; +} + +int cairo_font::text_width( cairo_t* cr, text_chunk::vector& chunks ) +{ + lock(); + cairo_set_font_size(cr, m_size); + double ret = 0; + for(size_t i = 0; i < chunks.size(); i++) + { + if(chunks[i]->font) + { + cairo_set_font_face(cr, chunks[i]->font->font_face); + } else + { + cairo_set_font_face(cr, m_font_face); + } + cairo_text_extents_t ext; + cairo_text_extents(cr, chunks[i]->text, &ext); + ret += ext.x_advance; + } + unlock(); + + return (int) ret; +} + +void cairo_font::get_metrics(cairo_t* cr, cairo_font_metrics* fm ) +{ + lock(); + cairo_set_font_face(cr, m_font_face); + cairo_set_font_size(cr, m_size); + cairo_font_extents_t ext; + cairo_font_extents(cr, &ext); + + cairo_text_extents_t tex; + cairo_text_extents(cr, "x", &tex); + + fm->ascent = (int) ext.ascent; + fm->descent = (int) ext.descent; + fm->height = (int) (ext.ascent + ext.descent); + fm->x_height = (int) tex.height; + unlock(); +} + +void cairo_font::set_font( HFONT hFont ) +{ + clear(); + + m_hFont = hFont; + m_font_face = create_font_face(m_hFont); + m_font_code_pages = 0; + if(m_font_link) + { + HDC hdc = GetDC(NULL); + SelectObject(hdc, m_hFont); + m_font_link->GetFontCodePages(hdc, m_hFont, &m_font_code_pages); + ReleaseDC(NULL, hdc); + } + LOGFONT lf; + GetObject(m_hFont, sizeof(LOGFONT), &lf); + m_bUnderline = lf.lfUnderline; + m_bStrikeOut = lf.lfStrikeOut; +} + +void cairo_font::clear() +{ + if(m_font_face) + { + cairo_font_face_destroy(m_font_face); + m_font_face = NULL; + } + for(size_t i = 0; i < m_linked_fonts.size(); i++) + { + if(m_linked_fonts[i]->hFont && m_font_link) + { + m_font_link->ReleaseFont(m_linked_fonts[i]->hFont); + } + if(m_linked_fonts[i]->font_face) + { + cairo_font_face_destroy(m_linked_fonts[i]->font_face); + } + } + m_linked_fonts.clear(); + if(m_hFont) + { + DeleteObject(m_hFont); + m_hFont = NULL; + } +} + +void cairo_font::init() +{ + m_hFont = NULL; + m_font_face = NULL; + m_font_link = NULL; + m_font_code_pages = 0; + m_size = 0; + m_bUnderline = FALSE; + m_bStrikeOut = FALSE; +} + +wchar_t* cairo_font::utf8_to_wchar( const char* src ) +{ + if(!src) return NULL; + + int len = (int) strlen(src); + wchar_t* ret = new wchar_t[len + 1]; + MultiByteToWideChar(CP_UTF8, 0, src, -1, ret, len + 1); + return ret; +} + +char* cairo_font::wchar_to_utf8( const wchar_t* src ) +{ + if(!src) return NULL; + + int len = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL); + char* ret = new char[len]; + WideCharToMultiByte(CP_UTF8, 0, src, -1, ret, len, NULL, NULL); + return ret; +} \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_font.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_font.h new file mode 100644 index 0000000..238efe4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/cairo/cairo_font.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct linked_font +{ + typedef std::vector vector; + + DWORD code_pages; + HFONT hFont; + cairo_font_face_t* font_face; +}; + +struct text_chunk +{ + typedef std::vector vector; + + char* text; + linked_font* font; + + ~text_chunk() + { + if(text) + { + delete text; + } + } +}; + +struct cairo_font_metrics +{ + int height; + int ascent; + int descent; + int x_height; +}; + + +class cairo_font +{ + HFONT m_hFont; + cairo_font_face_t* m_font_face; + IMLangFontLink2* m_font_link; + DWORD m_font_code_pages; + linked_font::vector m_linked_fonts; + int m_size; + BOOL m_bUnderline; + BOOL m_bStrikeOut; + cairo_font_metrics m_metrics; +public: + // fonts are not thread safe :( + // you have to declare and initialize cairo_font::m_sync before the first using. + static CRITICAL_SECTION m_sync; + + cairo_font(IMLangFontLink2* fl, HFONT hFont, int size); + cairo_font(IMLangFontLink2* fl, LPCWSTR facename, int size, int weight, BOOL italic, BOOL strikeout, BOOL underline); + + void init(); + ~cairo_font(); + + void show_text(cairo_t* cr, int x, int y, const char*); + int text_width(cairo_t* cr, const char* str); + void load_metrics(cairo_t* cr); + cairo_font_metrics& metrics(); + static wchar_t* utf8_to_wchar(const char* src); + static char* wchar_to_utf8(const wchar_t* src); +private: + void split_text(const char* str, text_chunk::vector& chunks); + void free_text_chunks(text_chunk::vector& chunks); + cairo_font_face_t* create_font_face(HFONT fnt); + void set_font(HFONT hFont); + void clear(); + int text_width(cairo_t* cr, text_chunk::vector& chunks); + void lock(); + void unlock(); + int round_d(double val); + void get_metrics(cairo_t* cr, cairo_font_metrics* fm); +}; + +inline void cairo_font::lock() +{ + EnterCriticalSection(&m_sync); +} + +inline void cairo_font::unlock() +{ + LeaveCriticalSection(&m_sync); +} + +inline int cairo_font::round_d(double val) +{ + int int_val = (int) val; + if(val - int_val >= 0.5) + { + int_val++; + } + return int_val; +} + +inline cairo_font_metrics& cairo_font::metrics() +{ + return m_metrics; +} + +inline void cairo_font::load_metrics(cairo_t* cr) +{ + get_metrics(cr, &m_metrics); +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/gdiplus/gdiplus_container.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/gdiplus/gdiplus_container.cpp new file mode 100644 index 0000000..9813d38 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/gdiplus/gdiplus_container.cpp @@ -0,0 +1,238 @@ +#include +#include +#include "gdiplus_container.h" +#pragma comment(lib, "gdiplus.lib") +using namespace Gdiplus; +using namespace litehtml; + +gdiplus_container::gdiplus_container() +{ + GdiplusStartupInput gdiplusStartupInput; + GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); +} + +gdiplus_container::~gdiplus_container() +{ + clear_images(); + GdiplusShutdown(m_gdiplusToken); +} + +static Color gdiplus_color(web_color color) +{ + return Color(color.alpha, color.red, color.green, color.blue); +} + +void gdiplus_container::draw_ellipse(HDC hdc, int x, int y, int width, int height, web_color color, int line_width) +{ + Graphics graphics(hdc); + + graphics.SetCompositingQuality(CompositingQualityHighQuality); + graphics.SetSmoothingMode(SmoothingModeAntiAlias); + + Pen pen(gdiplus_color(color)); + graphics.DrawEllipse(&pen, x, y, width, height); +} + +void gdiplus_container::fill_ellipse(HDC hdc, int x, int y, int width, int height, web_color color) +{ + Graphics graphics(hdc); + + graphics.SetCompositingQuality(CompositingQualityHighQuality); + graphics.SetSmoothingMode(SmoothingModeAntiAlias); + + SolidBrush brush(gdiplus_color(color)); + graphics.FillEllipse(&brush, x, y, width, height); +} + +void gdiplus_container::fill_rect(HDC hdc, int x, int y, int width, int height, web_color color) +{ + Graphics graphics(hdc); + + SolidBrush brush(gdiplus_color(color)); + graphics.FillRectangle(&brush, x, y, width, height); +} + +void gdiplus_container::get_img_size(uint_ptr img, size& sz) +{ + Bitmap* bmp = (Bitmap*)img; + if (bmp) + { + sz.width = bmp->GetWidth(); + sz.height = bmp->GetHeight(); + } +} + +void gdiplus_container::free_image(uint_ptr img) +{ + Bitmap* bmp = (Bitmap*)img; + delete bmp; +} + +void gdiplus_container::draw_img_bg(HDC hdc, uint_ptr img, const background_paint& bg) +{ + Bitmap* bgbmp = (Bitmap*)img; + + Graphics graphics(hdc); + graphics.SetInterpolationMode(InterpolationModeNearestNeighbor); + graphics.SetPixelOffsetMode(PixelOffsetModeHalf); + + Region reg(Rect(bg.border_box.left(), bg.border_box.top(), bg.border_box.width, bg.border_box.height)); + graphics.SetClip(®); + + Bitmap* scaled_img = nullptr; + if (bg.image_size.width != bgbmp->GetWidth() || bg.image_size.height != bgbmp->GetHeight()) + { + scaled_img = new Bitmap(bg.image_size.width, bg.image_size.height); + Graphics gr(scaled_img); + gr.SetPixelOffsetMode(PixelOffsetModeHighQuality); + gr.DrawImage(bgbmp, 0, 0, bg.image_size.width, bg.image_size.height); + bgbmp = scaled_img; + } + + switch (bg.repeat) + { + case background_repeat_no_repeat: + { + graphics.DrawImage(bgbmp, bg.position_x, bg.position_y, bgbmp->GetWidth(), bgbmp->GetHeight()); + } + break; + case background_repeat_repeat_x: + { + CachedBitmap bmp(bgbmp, &graphics); + int x = bg.position_x; + while(x > bg.clip_box.left()) x -= bgbmp->GetWidth(); + for(; x < bg.clip_box.right(); x += bgbmp->GetWidth()) + { + graphics.DrawCachedBitmap(&bmp, x, bg.position_y); + } + } + break; + case background_repeat_repeat_y: + { + CachedBitmap bmp(bgbmp, &graphics); + int y = bg.position_y; + while(y > bg.clip_box.top()) y -= bgbmp->GetHeight(); + for(; y < bg.clip_box.bottom(); y += bgbmp->GetHeight()) + { + graphics.DrawCachedBitmap(&bmp, bg.position_x, y); + } + } + break; + case background_repeat_repeat: + { + CachedBitmap bmp(bgbmp, &graphics); + int x = bg.position_x; + while(x > bg.clip_box.left()) x -= bgbmp->GetWidth(); + int y0 = bg.position_y; + while(y0 > bg.clip_box.top()) y0 -= bgbmp->GetHeight(); + + for(; x < bg.clip_box.right(); x += bgbmp->GetWidth()) + { + for(int y = y0; y < bg.clip_box.bottom(); y += bgbmp->GetHeight()) + { + graphics.DrawCachedBitmap(&bmp, x, y); + } + } + } + break; + } + + delete scaled_img; +} + +// length of dash and space for "dashed" style, in multiples of pen width +const float dash = 3; +const float space = 2; + +static void draw_horz_border(Graphics& graphics, const border& border, int y, int left, int right) +{ + if (border.style != border_style_double || border.width < 3) + { + if (border.width == 1) right--; // 1px-wide lines are longer by one pixel in GDI+ (the endpoint is also drawn) + Pen pen(gdiplus_color(border.color), (float)border.width); + if (border.style == border_style_dotted) + { + float dashValues[2] = { 1, 1 }; + pen.SetDashPattern(dashValues, 2); + } + else if (border.style == border_style_dashed) + { + float dashValues[2] = { dash, space }; + pen.SetDashPattern(dashValues, 2); + } + graphics.DrawLine(&pen, + Point(left, y + border.width / 2), + Point(right, y + border.width / 2)); + } + else + { + int single_line_width = (int)round(border.width / 3.); + if (single_line_width == 1) right--; + Pen pen(gdiplus_color(border.color), (float)single_line_width); + graphics.DrawLine(&pen, + Point(left, y + single_line_width / 2), + Point(right, y + single_line_width / 2)); + graphics.DrawLine(&pen, + Point(left, y + border.width - 1 - single_line_width / 2), + Point(right, y + border.width - 1 - single_line_width / 2)); + } +} + +static void draw_vert_border(Graphics& graphics, const border& border, int x, int top, int bottom) +{ + if (border.style != border_style_double || border.width < 3) + { + if (border.width == 1) bottom--; + Pen pen(gdiplus_color(border.color), (float)border.width); + if (border.style == border_style_dotted) + { + float dashValues[2] = { 1, 1 }; + pen.SetDashPattern(dashValues, 2); + } + else if (border.style == border_style_dashed) + { + float dashValues[2] = { dash, space }; + pen.SetDashPattern(dashValues, 2); + } + graphics.DrawLine(&pen, + Point(x + border.width / 2, top), + Point(x + border.width / 2, bottom)); + } + else + { + int single_line_width = (int)round(border.width / 3.); + if (single_line_width == 1) bottom--; + Pen pen(gdiplus_color(border.color), (float)single_line_width); + graphics.DrawLine(&pen, + Point(x + single_line_width / 2, top), + Point(x + single_line_width / 2, bottom)); + graphics.DrawLine(&pen, + Point(x + border.width - 1 - single_line_width / 2, top), + Point(x + border.width - 1 - single_line_width / 2, bottom)); + } +} + +void gdiplus_container::draw_borders(uint_ptr hdc, const borders& borders, const position& draw_pos, bool root) +{ + apply_clip((HDC) hdc); + Graphics graphics((HDC)hdc); + + if (borders.left.width != 0) + { + draw_vert_border(graphics, borders.left, draw_pos.left(), draw_pos.top(), draw_pos.bottom()); + } + if (borders.right.width != 0) + { + draw_vert_border(graphics, borders.right, draw_pos.right() - borders.right.width, draw_pos.top(), draw_pos.bottom()); + } + if (borders.top.width != 0) + { + draw_horz_border(graphics, borders.top, draw_pos.top(), draw_pos.left(), draw_pos.right()); + } + if (borders.bottom.width != 0) + { + draw_horz_border(graphics, borders.bottom, draw_pos.bottom() - borders.bottom.width, draw_pos.left(), draw_pos.right()); + } + + release_clip((HDC) hdc); +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/gdiplus/gdiplus_container.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/gdiplus/gdiplus_container.h new file mode 100644 index 0000000..a5a6138 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/gdiplus/gdiplus_container.h @@ -0,0 +1,23 @@ +#pragma once +#include "..\win32\win32_container.h" + +class gdiplus_container : public win32_container +{ +public: + gdiplus_container(); + virtual ~gdiplus_container(); + +private: + ULONG_PTR m_gdiplusToken; + +protected: + // win32_container members + void draw_ellipse(HDC hdc, int x, int y, int width, int height, litehtml::web_color color, int line_width) override; + void fill_ellipse(HDC hdc, int x, int y, int width, int height, litehtml::web_color color) override; + void fill_rect(HDC hdc, int x, int y, int width, int height, litehtml::web_color color) override; + void get_img_size(uint_ptr img, litehtml::size& sz) override; + void free_image(uint_ptr img) override; + void draw_img_bg(HDC hdc, uint_ptr img, const litehtml::background_paint& bg) override; + // litehtml::document_container members + void draw_borders(uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) override; +}; diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/haiku/container_haiku.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/haiku/container_haiku.cpp new file mode 100644 index 0000000..7c49f22 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/haiku/container_haiku.cpp @@ -0,0 +1,568 @@ +/* + * Copyright 2019-2020 Haiku Inc. + * All rights reserved. Distributed under the terms of the BSD 3-clause license. + * Constributors + * 2019-2020 Adam Fowler + */ +#include "container_haiku.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +LiteHtmlView::LiteHtmlView(BRect frame, const char *name) + : BView(frame, name, B_FOLLOW_ALL, B_WILL_DRAW), + fContext(NULL), + m_html(NULL), + m_images(), + m_base_url(), + m_url() +{ + + BRect bounds(Bounds()); + BPoint topLeft = bounds.LeftTop(); + std::cout << "Initial bounds: topLeft x: " << +topLeft.x << ", y: " + << +topLeft.y << ", width: " << +bounds.Width() << ", height: " + << +bounds.Height() << std::endl; + + SetDrawingMode(B_OP_OVER); + SetFont(be_plain_font); + + //FillRect(bounds,B_SOLID_LOW); + + //SetLowColor(B_DOCUMENT_PANEL_COLOR); + //FillRect(rect); +} + +LiteHtmlView::~LiteHtmlView() +{ +} + +void +LiteHtmlView::SetContext(litehtml::context* ctx) +{ + fContext = ctx; +} + +void +LiteHtmlView::RenderFile(const char* localFilePath) +{ + std::cout << "RenderFile" << std::endl; + //BUrlRequest req; + // assume a local file for now, that is HTML + std::ifstream t(localFilePath); + std::string html((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + + //std::cout << "HTML output:-" << std::endl << html << std::endl; + + // Get parent folder for the base url + std::cout << "Loaded from file: " << localFilePath << std::endl; + BPath htmlPath(localFilePath); + BPath dirPath; + htmlPath.GetParent(&dirPath); + std::cout << "parent path: " << dirPath.Path() << std::endl; + set_base_url(dirPath.Path()); + //std::cout << " base url now:" << m_base_url << std::endl; + + RenderHTML(html); +} + +void +LiteHtmlView::RenderHTML(const std::string& htmlText) +{ + std::cout << "RenderHTML" << std::endl; + + // now use this string + m_html = litehtml::document::createFromString( + htmlText.c_str(), this, fContext); + if (m_html) + { + std::cout << "Successfully read html" << std::endl; + // success + // post-parse render operations, if required. + Invalidate(); + } else { + std::cout << "Failed to read html" << std::endl; + } + // always fire the rendering complete message + std::cout << "Sending html rendered message: " << M_HTML_RENDERED << std::endl; +} + +void +LiteHtmlView::Draw(BRect b) +{ + std::cout << "DRAW CALLED" << std::endl; + + BRect bounds(Bounds()); + FillRect(bounds,B_SOLID_LOW); + + // b only part of the window, but we need to draw the whole lot + + if (NULL != m_html) { + BPoint leftTop = bounds.LeftTop(); + litehtml::position clip(leftTop.x,leftTop.y, + bounds.Width(),bounds.Height()); + m_html->render(bounds.Width()); + m_html->draw((litehtml::uint_ptr) this,0,0,&clip); + } + SendNotices(M_HTML_RENDERED,new BMessage(M_HTML_RENDERED)); +} + +void +LiteHtmlView::GetPreferredSize(float* width,float* height) +{ + if (NULL == m_html) + { + BRect bounds(Bounds()); + *width = bounds.Width(); + *height = bounds.Height(); + } else { + *width = m_html->width(); + *height = m_html->height(); + } +} + +litehtml::uint_ptr +LiteHtmlView::create_font( const litehtml::tchar_t* faceName, int size, + int weight, litehtml::font_style italic, unsigned int decoration, + litehtml::font_metrics* fm ) +{ + //std::cout << "create_font" << std::endl; + litehtml::string_vector fonts; + litehtml::split_string(faceName, fonts, ","); + litehtml::trim(fonts[0]); + + uint16 face = B_REGULAR_FACE; // default + if (italic == litehtml::font_style_italic) + { + face |= B_ITALIC_FACE; + } + if (decoration & litehtml::font_decoration_underline) + { + face |= B_UNDERSCORE_FACE; + } + if (decoration & litehtml::font_decoration_linethrough) + { + face |= B_STRIKEOUT_FACE; + } + // Note: LIGHT, HEAVY, CONDENSED not supported in BeOS R5 +#ifdef __HAIKU__ + if(weight >= 0 && weight < 150) face |= B_LIGHT_FACE; + else if(weight >= 150 && weight < 250) face |= B_LIGHT_FACE; + else if(weight >= 250 && weight < 350) face |= B_LIGHT_FACE; + //else if(weight >= 350 && weight < 450) face |= B_REGULAR_FACE; + //else if(weight >= 450 && weight < 550) face |= B_REGULAR_FACE; + else if(weight >= 550 && weight < 650) face |= B_CONDENSED_FACE; +#else + else if(weight >= 550 && weight < 650) face |= B_BOLD_FACE; +#endif + else if(weight >= 650 && weight < 750) face |= B_BOLD_FACE; +#ifndef __HAIKU__ + else if(weight >= 750 && weight < 850) face |= B_BOLD_FACE; + else if(weight >= 950) face |= B_BOLD_FACE; +#else + else if(weight >= 750 && weight < 850) face |= B_HEAVY_FACE; + else if(weight >= 950) face |= B_HEAVY_FACE; +#endif + + BFont* tempFont = new BFont(); + bool found = false; + for(litehtml::string_vector::iterator i = fonts.begin(); + i != fonts.end(); i++) + { + if (B_OK == tempFont->SetFamilyAndFace(i->c_str(),face)) + { + found = true; + break; + } + } + + if (!found) + { + // default to the Be plain font + tempFont = new BFont(be_plain_font); + if (weight >= 550) + { + tempFont = new BFont(be_bold_font); + } + tempFont->SetFace(face); // chooses closest + } + + tempFont->SetSize(size); + + font_height hgt; + tempFont->GetHeight(&hgt); + fm->ascent = hgt.ascent; + fm->descent = hgt.descent; + fm->height = (int) (hgt.ascent + hgt.descent); + fm->x_height = (int) hgt.leading; + + return (litehtml::uint_ptr) tempFont; +} + +void +LiteHtmlView::delete_font( litehtml::uint_ptr hFont ) +{ + std::cout << "delete_font" << std::endl; +} + +int +LiteHtmlView::text_width( const litehtml::tchar_t* text, + litehtml::uint_ptr hFont ) +{ + //std::cout << "text_width" << std::endl; + BFont* fnt = (BFont*)hFont; + int width = fnt->StringWidth(text); + //std::cout << " Width: " << +width << std::endl; + return width; +} + +void +LiteHtmlView::draw_text( litehtml::uint_ptr hdc, const litehtml::tchar_t* text, + litehtml::uint_ptr hFont, litehtml::web_color color, + const litehtml::position& pos ) +{ + //std::cout << "draw_text" << std::endl; + if (!text) return; + if (0 == strlen(text)) return; + BFont* fnt = (BFont*)hFont; + + //std::cout << " left: " << +pos.left() << ", top: " << +pos.top() << std::endl; + //std::cout << " RGBA: " << +color.red << "," << +color.green << "," << +color.blue << "," << +color.alpha << std::endl; + //std::cout << " Font size: " << +fnt->Size() << std::endl; + //std::cout << " Text: " << text << std::endl; + BRect bounds(Bounds()); + + //FillRect(bounds,B_SOLID_LOW); + + BPoint leftTop = bounds.LeftTop(); + //std::cout << " Bounds left: " << +leftTop.x << ", top: " << +leftTop.y << ", Width: " << +bounds.Width() << ", Height: " << +bounds.Height() << std::endl; + + font_height fh; + fnt->GetHeight(&fh); + int baseline = fh.ascent + fh.descent;// + 10; + int leftbase = 0; //10; + MovePenTo(pos.left() + leftbase,pos.top() + baseline);//leftTop.x,leftTop.y); + SetFont(fnt); + //SetFont(be_plain_font); + rgb_color clr = ui_color(B_DOCUMENT_TEXT_COLOR); + /* + rgb_color clr; + clr.blue = 40; + clr.red = 40; + clr.green = 40; + */ + + clr.red = color.red; + clr.green = color.green; + clr.blue = color.blue; + clr.alpha = color.alpha; + + //std::cout << " Final RGBA: " << +clr.red << "," << +clr.green << "," << +clr.blue << "," << +clr.alpha << std::endl; + SetHighColor(clr); + SetLowColor(ui_color(B_DOCUMENT_BACKGROUND_COLOR)); + BString mystr(""); + //mystr << "text: "; + mystr << text; + DrawString(mystr); +} + +int +LiteHtmlView::pt_to_px( int pt ) +{ + std::cout << "pt_to_px" << std::endl; + return (int) ((double) pt * 1.3333333333); +} + +int +LiteHtmlView::get_default_font_size() const +{ + //std::cout << "get_default_font_size" << std::endl; + return 12; +} + +const litehtml::tchar_t* +LiteHtmlView::get_default_font_name() const +{ + //std::cout << "get_default_font_name" << std::endl; + font_family fam; + font_style style; + be_plain_font->GetFamilyAndStyle(&fam,&style); + char* cp = strdup(fam); + return (litehtml::tchar_t*)cp; +} + +void +LiteHtmlView::draw_list_marker( litehtml::uint_ptr hdc, + const litehtml::list_marker& marker ) +{ + std::cout << "draw_list_marker" << std::endl; + if (!marker.image.empty()) + { + std::cout << " image marker" << std::endl; + } +} + +void +LiteHtmlView::load_image( const litehtml::tchar_t* src, + const litehtml::tchar_t* baseurl, bool redraw_on_ready ) +{ + std::cout << "load_image" << std::endl; + + std::string url; + make_url(src, baseurl, url); + if(m_images.find(url.c_str()) == m_images.end()) + { + BEntry entry(url.c_str(), true); + if (entry.Exists()) { + std::cout << " Loading bitmap from file" << std::endl; + BBitmap* img = BTranslationUtils::GetBitmap(url.c_str()); + m_images[url] = img; + } + } +} + +void +LiteHtmlView::make_url(const litehtml::tchar_t* url, + const litehtml::tchar_t* basepath, litehtml::tstring& out) +{ + std::cout << "make_url" << std::endl; + std::cout << " url: " << url << std::endl; + if(!basepath || (basepath && !basepath[0])) + { + if(!m_base_url.empty()) + { + //out = urljoin(m_base_url, std::string(url)); + std::string ns(m_base_url); + ns += "/"; + ns += url; + out = ns; + } else + { + out = url; + } + } else + { + std::cout << " basepath: " << basepath << std::endl; + //out = urljoin(std::string(basepath), std::string(url)); + std::string ns(basepath); + ns += "/"; + ns += url; + out = ns; + } + std::cout << " Output url: " << out << std::endl; +} + +void +LiteHtmlView::set_base_url(const litehtml::tchar_t* base_url) +{ + std::cout << "set_base_url" << std::endl; +/* + if(base_url) + { + m_base_url = urljoin(m_url, std::string(base_url)); + } else + { + */ + m_base_url = base_url; + std::cout << " base url set to: " << m_base_url << std::endl; + //} +} + + +void +LiteHtmlView::get_image_size( const litehtml::tchar_t* src, + const litehtml::tchar_t* baseurl, litehtml::size& sz ) +{ + std::cout << "get_image_size" << std::endl; + std::string url; + make_url(src,NULL,url); + const auto& miter(m_images.find(url.c_str())); + if (m_images.end() != miter) + { + BBitmap* img = (BBitmap*)miter->second; + BRect size = img->Bounds(); + sz.width = size.Width(); + sz.height = size.Height(); + std::cout << " width: " << +sz.width << ", height: " << +sz.height << std::endl; + } +} + +void +LiteHtmlView::draw_image( litehtml::uint_ptr hdc, const litehtml::tchar_t* src, + const litehtml::tchar_t* baseurl, const litehtml::position& pos ) +{ + std::string url; + make_url(src, baseurl, url); + const auto& img = m_images.find(url.c_str()); + if(img != m_images.end()) + { + if(img->second) + { + DrawBitmap(img->second,BPoint(pos.x,pos.y)); // TODO support scaling + //draw_txdib(cr, img->second.get(), pos.x, pos.y, pos.width, pos.height); + } + } +} + +void +LiteHtmlView::draw_background( litehtml::uint_ptr hdc, + const litehtml::background_paint& bg ) +{ + std::cout << "draw_background" << std::endl; + if (0 < bg.image.length()) + { + std::cout << " background includes an image!" << std::endl; + draw_image(hdc,bg.image.c_str(),m_base_url.c_str(),litehtml::position(bg.position_x,bg.position_y,bg.image_size.width,bg.image_size.height)); + } +} + +void +LiteHtmlView::draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) +{ + std::cout << "draw_borders" << std::endl; + int bdr_top = 0; + int bdr_bottom = 0; + int bdr_left = 0; + int bdr_right = 0; + + //std::cout << " uint ptr: " << +hdc << std::endl; + //std::cout << " this ptr: " << +this << std::endl; + + if(borders.top.width != 0 && borders.top.style > litehtml::border_style_hidden) + { + bdr_top = (int) borders.top.width; + std::cout << " Border top: " << bdr_right << std::endl; + } + if(borders.bottom.width != 0 && borders.bottom.style > litehtml::border_style_hidden) + { + bdr_bottom = (int) borders.bottom.width; + std::cout << " Border bottom: " << bdr_right << std::endl; + } + if(borders.left.width != 0 && borders.left.style > litehtml::border_style_hidden) + { + bdr_left = (int) borders.left.width; + std::cout << " Border left: " << bdr_right << std::endl; + } + if(borders.right.width != 0 && borders.right.style > litehtml::border_style_hidden) + { + bdr_right = (int) borders.right.width; + std::cout << " Border right: " << bdr_right << std::endl; + } + + + if (bdr_bottom) + { + // draw rectangle for now - no check for radius + StrokeRect( + BRect( + BPoint(draw_pos.left(), draw_pos.bottom()), + BPoint(draw_pos.left() + bdr_left, draw_pos.bottom() - bdr_bottom) + ) + ); + } +} + +void +LiteHtmlView::transform_text(litehtml::tstring& text, litehtml::text_transform tt) +{ + std::cout << "transform_text" << std::endl; +} + +void +LiteHtmlView::set_clip( const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius ) +{ + std::cout << "set_clip" << std::endl; +} + +void +LiteHtmlView::del_clip() +{ + std::cout << "del_clip" << std::endl; +} + + + +std::shared_ptr +LiteHtmlView::create_element(const litehtml::tchar_t *tag_name, + const litehtml::string_map &attributes, + const std::shared_ptr &doc) +{ + //std::cout << "create_element" << std::endl; + return 0; +} + +void +LiteHtmlView::get_media_features(litehtml::media_features& media) const +{ + std::cout << "get_media_features" << std::endl; + litehtml::position client; + get_client_rect(client); + media.type = litehtml::media_type_screen; + media.width = client.width; + media.height = client.height; + BRect bounds(Bounds()); + media.device_width = bounds.Width(); + media.device_height = bounds.Height(); + media.color = 8; + media.monochrome = 0; + media.color_index = 256; + media.resolution = 96; +} + +void +LiteHtmlView::link(const std::shared_ptr &ptr, const litehtml::element::ptr& el) +{ + std::cout << "link" << std::endl; +} + +void +LiteHtmlView::set_caption(const char* caption) +{ + std::cout << "set_caption" << std::endl; +} + +void +LiteHtmlView::get_client_rect(litehtml::position& client) const +{ + //std::cout << "get_client_rect" << std::endl; + BRect bounds(Bounds()); + BPoint leftTop = bounds.LeftTop(); + client.width = bounds.Width(); + client.height = bounds.Height(); + client.x = leftTop.x; + client.y = leftTop.y; +} + +void +LiteHtmlView::on_anchor_click(const char* base, const litehtml::element::ptr& anchor) +{ + std::cout << "on_anchor_click" << std::endl; +} + +void +LiteHtmlView::set_cursor(const char* cursor) +{ + std::cout << "set_cursor" << std::endl; +} + +void +LiteHtmlView::import_css(litehtml::tstring& s1, const litehtml::tstring& s2, litehtml::tstring& s3) +{ + std::cout << "import_css" << std::endl; +} + +void +LiteHtmlView::get_language(litehtml::tstring& s1, litehtml::tstring& s2) const +{ + std::cout << "get_language" << std::endl; +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/haiku/container_haiku.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/haiku/container_haiku.h new file mode 100644 index 0000000..77d0344 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/haiku/container_haiku.h @@ -0,0 +1,87 @@ +/* + * Copyright 2019-2020 Haiku Inc. + * All rights reserved. Distributed under the terms of the BSD 3-clause license. + * Constributors + * 2019-2020 Adam Fowler + */ +#ifndef LITEHTMLVIEW_H +#define LITEHTMLVIEW_H + +#include "../../include/litehtml.h" + +#include +#include + +#include + +class BBitmap; + + +enum { + M_HTML_RENDERED = 'hrnd' +}; + +class LiteHtmlView : public BView, public litehtml::document_container +{ +public: + //LiteHtmlView(BMessage *archive); + LiteHtmlView(BRect frame, const char *name); + //LiteHtmlView(const char *name, uint32 flags, BLayout *layout=NULL); + + virtual ~LiteHtmlView(); + + void SetContext(litehtml::context* ctx); + void RenderFile(const char* localFilePath); + void RenderHTML(const std::string& htmlText); + + + virtual litehtml::uint_ptr create_font(const litehtml::tchar_t* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics* fm) override; + virtual void delete_font(litehtml::uint_ptr hFont) override; + virtual int text_width(const litehtml::tchar_t* text, litehtml::uint_ptr hFont) override; + virtual void draw_text(litehtml::uint_ptr hdc, const litehtml::tchar_t* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) override; + virtual int pt_to_px(int pt) override; + virtual int get_default_font_size() const override; + virtual const litehtml::tchar_t* get_default_font_name() const override; + virtual void load_image(const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, bool redraw_on_ready) override; + virtual void get_image_size(const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, litehtml::size& sz) override; + virtual void draw_background(litehtml::uint_ptr hdc, const litehtml::background_paint& bg) override; + virtual void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) override; + virtual void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker& marker) override; + virtual std::shared_ptr create_element(const litehtml::tchar_t *tag_name, + const litehtml::string_map &attributes, + const std::shared_ptr &doc) override; + virtual void get_media_features(litehtml::media_features& media) const override; + //virtual void get_language(litehtml::tstring& language, litehtml::tstring & culture) const override; + virtual void link(const std::shared_ptr &ptr, const litehtml::element::ptr& el) override; + + + virtual void transform_text(litehtml::tstring& text, litehtml::text_transform tt) override; + virtual void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) override; + virtual void del_clip() override; + + // unimplemented + virtual void set_caption(const char*); + virtual void get_client_rect(litehtml::position& client) const; + virtual void set_base_url(const char*); + virtual void on_anchor_click(const char*, const litehtml::element::ptr&); + virtual void set_cursor(const char*); + virtual void import_css(litehtml::tstring&, const litehtml::tstring&, litehtml::tstring&); + virtual void get_language(litehtml::tstring&, litehtml::tstring&) const; + + //BView + virtual void Draw(BRect updateRect) override; + virtual void GetPreferredSize(float* width, float* height) override; + +protected: + void make_url(const litehtml::tchar_t* url, const litehtml::tchar_t* basepath, litehtml::tstring& out); + virtual void draw_image( litehtml::uint_ptr hdc, const litehtml::tchar_t* src, const litehtml::tchar_t* baseurl, const litehtml::position& pos ); + +private: + litehtml::context* fContext; + litehtml::document::ptr m_html; + std::map m_images; + litehtml::tstring m_base_url; + litehtml::tstring m_url; +}; + +#endif diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/linux/container_linux.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/linux/container_linux.cpp new file mode 100644 index 0000000..b5d984d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/linux/container_linux.cpp @@ -0,0 +1,908 @@ +#include "container_linux.h" +#include + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +container_linux::container_linux() +{ + m_temp_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2, 2); + m_temp_cr = cairo_create(m_temp_surface); +} + +container_linux::~container_linux() +{ + clear_images(); + cairo_surface_destroy(m_temp_surface); + cairo_destroy(m_temp_cr); +} + +litehtml::uint_ptr container_linux::create_font( const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics* fm ) +{ + PangoFontDescription *desc = pango_font_description_from_string (faceName); + pango_font_description_set_absolute_size(desc, size * PANGO_SCALE); + if(italic == litehtml::font_style_italic) + { + pango_font_description_set_style(desc, PANGO_STYLE_ITALIC); + } else + { + pango_font_description_set_style(desc, PANGO_STYLE_NORMAL); + } + PangoWeight fnt_weight; + if(weight >= 0 && weight < 150) fnt_weight = PANGO_WEIGHT_THIN; + else if(weight >= 150 && weight < 250) fnt_weight = PANGO_WEIGHT_ULTRALIGHT; + else if(weight >= 250 && weight < 350) fnt_weight = PANGO_WEIGHT_LIGHT; + else if(weight >= 350 && weight < 450) fnt_weight = PANGO_WEIGHT_NORMAL; + else if(weight >= 450 && weight < 550) fnt_weight = PANGO_WEIGHT_MEDIUM; + else if(weight >= 550 && weight < 650) fnt_weight = PANGO_WEIGHT_SEMIBOLD; + else if(weight >= 650 && weight < 750) fnt_weight = PANGO_WEIGHT_BOLD; + else if(weight >= 750 && weight < 850) fnt_weight = PANGO_WEIGHT_ULTRABOLD; + else fnt_weight = PANGO_WEIGHT_HEAVY; + + pango_font_description_set_weight(desc, fnt_weight); + + cairo_font* ret = nullptr; + + if(fm) + { + cairo_save(m_temp_cr); + PangoLayout *layout = pango_cairo_create_layout(m_temp_cr); + PangoContext *context = pango_layout_get_context(layout); + PangoLanguage *language = pango_language_get_default(); + pango_layout_set_font_description(layout, desc); + PangoFontMetrics *metrics = pango_context_get_metrics(context, desc, language); + + fm->ascent = PANGO_PIXELS((double)pango_font_metrics_get_ascent(metrics)); + fm->descent = PANGO_PIXELS((double)pango_font_metrics_get_descent(metrics)); + fm->height = fm->ascent + fm->descent; + fm->x_height = fm->height; + + pango_layout_set_text(layout, "x", 1); + + int x_width, x_height; + pango_layout_get_pixel_size(layout, &x_width, &x_height); + + fm->x_height = x_height; + + cairo_restore(m_temp_cr); + + g_object_unref(layout); + pango_font_metrics_unref(metrics); + + ret = new cairo_font; + ret->font = desc; + ret->size = size; + ret->strikeout = (decoration & litehtml::font_decoration_linethrough) != 0; + ret->underline = (decoration & litehtml::font_decoration_underline) != 0; + ret->ascent = fm->ascent; + ret->descent = fm->descent; + + ret->underline_thickness = pango_font_metrics_get_underline_thickness(metrics); + ret->underline_position = -pango_font_metrics_get_underline_position(metrics); + pango_quantize_line_geometry(&ret->underline_thickness, &ret->underline_position); + ret->underline_thickness = PANGO_PIXELS(ret->underline_thickness); + ret->underline_position = -1;//PANGO_PIXELS(ret->underline_position); + + ret->strikethrough_thickness = pango_font_metrics_get_strikethrough_thickness(metrics); + ret->strikethrough_position = pango_font_metrics_get_strikethrough_position(metrics); + pango_quantize_line_geometry(&ret->strikethrough_thickness, &ret->strikethrough_position); + ret->strikethrough_thickness = PANGO_PIXELS(ret->strikethrough_thickness); + ret->strikethrough_position = PANGO_PIXELS(ret->strikethrough_position); + } + + return (litehtml::uint_ptr) ret; +} + +void container_linux::delete_font( litehtml::uint_ptr hFont ) +{ + auto* fnt = (cairo_font*) hFont; + if(fnt) + { + pango_font_description_free(fnt->font); + delete fnt; + } +} + +int container_linux::text_width( const char* text, litehtml::uint_ptr hFont ) +{ + auto* fnt = (cairo_font*) hFont; + + cairo_save(m_temp_cr); + + PangoLayout *layout = pango_cairo_create_layout(m_temp_cr); + pango_layout_set_font_description(layout, fnt->font); + + pango_layout_set_text(layout, text, -1); + pango_cairo_update_layout (m_temp_cr, layout); + + int x_width, x_height; + pango_layout_get_pixel_size(layout, &x_width, &x_height); + + cairo_restore(m_temp_cr); + + g_object_unref(layout); + + return (int) x_width; +} + +void container_linux::draw_text( litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos ) +{ + auto* fnt = (cairo_font*) hFont; + auto* cr = (cairo_t*) hdc; + cairo_save(cr); + + apply_clip(cr); + + set_color(cr, color); + + PangoLayout *layout = pango_cairo_create_layout(cr); + pango_layout_set_font_description (layout, fnt->font); + pango_layout_set_text (layout, text, -1); + + int baseline = PANGO_PIXELS(pango_layout_get_baseline(layout)); + + PangoRectangle ink_rect, logical_rect; + pango_layout_get_pixel_extents(layout, &ink_rect, &logical_rect); + + int text_baseline = pos.height - fnt->descent; + + int x = pos.left() + logical_rect.x; + int y = pos.top() + logical_rect.y + text_baseline - baseline; + + cairo_move_to(cr, x, y); + pango_cairo_update_layout (cr, layout); + pango_cairo_show_layout (cr, layout); + + int tw = 0; + + if(fnt->underline || fnt->strikeout) + { + tw = text_width(text, hFont); + } + + if(fnt->underline) + { + cairo_set_line_width(cr, fnt->underline_thickness); + cairo_move_to(cr, x, pos.top() + text_baseline - fnt->underline_position + 0.5); + cairo_line_to(cr, x + tw, pos.top() + text_baseline - fnt->underline_position + 0.5); + cairo_stroke(cr); + } + if(fnt->strikeout) + { + cairo_set_line_width(cr, fnt->strikethrough_thickness); + cairo_move_to(cr, x, pos.top() + text_baseline - fnt->strikethrough_position - 0.5); + cairo_line_to(cr, x + tw, pos.top() + text_baseline - fnt->strikethrough_position - 0.5); + cairo_stroke(cr); + } + + cairo_restore(cr); + + g_object_unref(layout); +} + +int container_linux::pt_to_px( int pt ) const +{ + GdkScreen* screen = gdk_screen_get_default(); + double dpi = gdk_screen_get_resolution(screen); + + return (int) ((double) pt * dpi / 72.0); +} + +int container_linux::get_default_font_size() const +{ + return pt_to_px(12); +} + +void container_linux::draw_list_marker( litehtml::uint_ptr hdc, const litehtml::list_marker& marker ) +{ + if(!marker.image.empty()) + { + /*litehtml::string url; + make_url(marker.image.c_str(), marker.baseurl, url); + + lock_images_cache(); + images_map::iterator img_i = m_images.find(url.c_str()); + if(img_i != m_images.end()) + { + if(img_i->second) + { + draw_txdib((cairo_t*) hdc, img_i->second, marker.pos.x, marker.pos.y, marker.pos.width, marker.pos.height); + } + } + unlock_images_cache();*/ + } else + { + switch(marker.marker_type) + { + case litehtml::list_style_type_circle: + { + draw_ellipse((cairo_t*) hdc, marker.pos.x, marker.pos.y, marker.pos.width, marker.pos.height, marker.color, 1); + } + break; + case litehtml::list_style_type_disc: + { + fill_ellipse((cairo_t*) hdc, marker.pos.x, marker.pos.y, marker.pos.width, marker.pos.height, marker.color); + } + break; + case litehtml::list_style_type_square: + if(hdc) + { + auto* cr = (cairo_t*) hdc; + cairo_save(cr); + + cairo_new_path(cr); + cairo_rectangle(cr, marker.pos.x, marker.pos.y, marker.pos.width, marker.pos.height); + + set_color(cr, marker.color); + cairo_fill(cr); + cairo_restore(cr); + } + break; + default: + /*do nothing*/ + break; + } + } +} + +void container_linux::load_image( const char* src, const char* baseurl, bool redraw_on_ready ) +{ + litehtml::string url; + make_url(src, baseurl, url); + if(m_images.find(url) == m_images.end()) + { + try + { + Glib::RefPtr img = get_image(url.c_str(), true); + if(img) + { + m_images[url.c_str()] = img; + } + } catch(...) + { + m_images[url.c_str()] = Glib::RefPtr(nullptr); + } + } +} + +void container_linux::get_image_size( const char* src, const char* baseurl, litehtml::size& sz ) +{ + litehtml::string url; + make_url(src, baseurl, url); + + auto img = m_images.find(url); + if(img != m_images.end()) + { + if(img->second) + { + sz.width = img->second->get_width(); + sz.height = img->second->get_height(); + } else + { + sz.width = 0; + sz.height = 0; + } + } else + { + sz.width = 0; + sz.height = 0; + } +} + +void container_linux::draw_background( litehtml::uint_ptr hdc, const std::vector& bgvec ) +{ + auto* cr = (cairo_t*) hdc; + cairo_save(cr); + apply_clip(cr); + + const auto& bg = bgvec.back(); + + rounded_rectangle(cr, bg.border_box, bg.border_radius); + cairo_clip(cr); + + cairo_rectangle(cr, bg.clip_box.x, bg.clip_box.y, bg.clip_box.width, bg.clip_box.height); + cairo_clip(cr); + + if(bg.color.alpha) + { + set_color(cr, bg.color); + cairo_paint(cr); + } + + for (int i = (int)bgvec.size() - 1; i >= 0; i--) + { + const auto& bg = bgvec[i]; + + if(bg.image_size.height == 0 || bg.image_size.width == 0) continue; + + cairo_rectangle(cr, bg.clip_box.x, bg.clip_box.y, bg.clip_box.width, bg.clip_box.height); + cairo_clip(cr); + + std::string url; + make_url(bg.image.c_str(), bg.baseurl.c_str(), url); + + //lock_images_cache(); + auto img_i = m_images.find(url); + if(img_i != m_images.end() && img_i->second) + { + Glib::RefPtr bgbmp = img_i->second; + + Glib::RefPtr new_img; + if(bg.image_size.width != bgbmp->get_width() || bg.image_size.height != bgbmp->get_height()) + { + new_img = bgbmp->scale_simple(bg.image_size.width, bg.image_size.height, Gdk::INTERP_BILINEAR); + bgbmp = new_img; + } + + cairo_surface_t* img = surface_from_pixbuf(bgbmp); + cairo_pattern_t *pattern = cairo_pattern_create_for_surface(img); + cairo_matrix_t flib_m; + cairo_matrix_init_identity(&flib_m); + cairo_matrix_translate(&flib_m, -bg.position_x, -bg.position_y); + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_matrix (pattern, &flib_m); + + switch(bg.repeat) + { + case litehtml::background_repeat_no_repeat: + draw_pixbuf(cr, bgbmp, bg.position_x, bg.position_y, bgbmp->get_width(), bgbmp->get_height()); + break; + + case litehtml::background_repeat_repeat_x: + cairo_set_source(cr, pattern); + cairo_rectangle(cr, bg.clip_box.left(), bg.position_y, bg.clip_box.width, bgbmp->get_height()); + cairo_fill(cr); + break; + + case litehtml::background_repeat_repeat_y: + cairo_set_source(cr, pattern); + cairo_rectangle(cr, bg.position_x, bg.clip_box.top(), bgbmp->get_width(), bg.clip_box.height); + cairo_fill(cr); + break; + + case litehtml::background_repeat_repeat: + cairo_set_source(cr, pattern); + cairo_rectangle(cr, bg.clip_box.left(), bg.clip_box.top(), bg.clip_box.width, bg.clip_box.height); + cairo_fill(cr); + break; + } + + cairo_pattern_destroy(pattern); + cairo_surface_destroy(img); + } + //unlock_images_cache(); + } + + cairo_restore(cr); +} + +void container_linux::make_url(const char* url, const char* basepath, litehtml::string& out) +{ + out = url; +} + +void container_linux::add_path_arc(cairo_t* cr, double x, double y, double rx, double ry, double a1, double a2, bool neg) +{ + if(rx > 0 && ry > 0) + { + + cairo_save(cr); + + cairo_translate(cr, x, y); + cairo_scale(cr, 1, ry / rx); + cairo_translate(cr, -x, -y); + + if(neg) + { + cairo_arc_negative(cr, x, y, rx, a1, a2); + } else + { + cairo_arc(cr, x, y, rx, a1, a2); + } + + cairo_restore(cr); + } else + { + cairo_move_to(cr, x, y); + } +} + +void container_linux::draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) +{ + auto* cr = (cairo_t*) hdc; + cairo_save(cr); + apply_clip(cr); + + cairo_new_path(cr); + + int bdr_top = 0; + int bdr_bottom = 0; + int bdr_left = 0; + int bdr_right = 0; + + if(borders.top.width != 0 && borders.top.style > litehtml::border_style_hidden) + { + bdr_top = (int) borders.top.width; + } + if(borders.bottom.width != 0 && borders.bottom.style > litehtml::border_style_hidden) + { + bdr_bottom = (int) borders.bottom.width; + } + if(borders.left.width != 0 && borders.left.style > litehtml::border_style_hidden) + { + bdr_left = (int) borders.left.width; + } + if(borders.right.width != 0 && borders.right.style > litehtml::border_style_hidden) + { + bdr_right = (int) borders.right.width; + } + + // draw right border + if(bdr_right) + { + set_color(cr, borders.right.color); + + if(borders.radius.top_right_x && borders.radius.top_right_y) + { + double end_angle = 2 * M_PI; + double start_angle = end_angle - M_PI / 2.0 / ((double) bdr_top / (double) bdr_right + 1); + + add_path_arc(cr, + draw_pos.right() - borders.radius.top_right_x, + draw_pos.top() + borders.radius.top_right_y, + borders.radius.top_right_x - bdr_right, + borders.radius.top_right_y - bdr_right + (bdr_right - bdr_top), + end_angle, + start_angle, true); + + add_path_arc(cr, + draw_pos.right() - borders.radius.top_right_x, + draw_pos.top() + borders.radius.top_right_y, + borders.radius.top_right_x, + borders.radius.top_right_y, + start_angle, + end_angle, false); + } else + { + cairo_move_to(cr, draw_pos.right() - bdr_right, draw_pos.top() + bdr_top); + cairo_line_to(cr, draw_pos.right(), draw_pos.top()); + } + + if(borders.radius.bottom_right_x && borders.radius.bottom_right_y) + { + cairo_line_to(cr, draw_pos.right(), draw_pos.bottom() - borders.radius.bottom_right_y); + + double start_angle = 0; + double end_angle = start_angle + M_PI / 2.0 / ((double) bdr_bottom / (double) bdr_right + 1); + + add_path_arc(cr, + draw_pos.right() - borders.radius.bottom_right_x, + draw_pos.bottom() - borders.radius.bottom_right_y, + borders.radius.bottom_right_x, + borders.radius.bottom_right_y, + start_angle, + end_angle, false); + + add_path_arc(cr, + draw_pos.right() - borders.radius.bottom_right_x, + draw_pos.bottom() - borders.radius.bottom_right_y, + borders.radius.bottom_right_x - bdr_right, + borders.radius.bottom_right_y - bdr_right + (bdr_right - bdr_bottom), + end_angle, + start_angle, true); + } else + { + cairo_line_to(cr, draw_pos.right(), draw_pos.bottom()); + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.bottom() - bdr_bottom); + } + + cairo_fill(cr); + } + + // draw bottom border + if(bdr_bottom) + { + set_color(cr, borders.bottom.color); + + if(borders.radius.bottom_left_x && borders.radius.bottom_left_y) + { + double start_angle = M_PI / 2.0; + double end_angle = start_angle + M_PI / 2.0 / ((double) bdr_left / (double) bdr_bottom + 1); + + add_path_arc(cr, + draw_pos.left() + borders.radius.bottom_left_x, + draw_pos.bottom() - borders.radius.bottom_left_y, + borders.radius.bottom_left_x - bdr_bottom + (bdr_bottom - bdr_left), + borders.radius.bottom_left_y - bdr_bottom, + start_angle, + end_angle, false); + + add_path_arc(cr, + draw_pos.left() + borders.radius.bottom_left_x, + draw_pos.bottom() - borders.radius.bottom_left_y, + borders.radius.bottom_left_x, + borders.radius.bottom_left_y, + end_angle, + start_angle, true); + } else + { + cairo_move_to(cr, draw_pos.left(), draw_pos.bottom()); + cairo_line_to(cr, draw_pos.left() + bdr_left, draw_pos.bottom() - bdr_bottom); + } + + if(borders.radius.bottom_right_x && borders.radius.bottom_right_y) + { + cairo_line_to(cr, draw_pos.right() - borders.radius.bottom_right_x, draw_pos.bottom()); + + double end_angle = M_PI / 2.0; + double start_angle = end_angle - M_PI / 2.0 / ((double) bdr_right / (double) bdr_bottom + 1); + + add_path_arc(cr, + draw_pos.right() - borders.radius.bottom_right_x, + draw_pos.bottom() - borders.radius.bottom_right_y, + borders.radius.bottom_right_x, + borders.radius.bottom_right_y, + end_angle, + start_angle, true); + + add_path_arc(cr, + draw_pos.right() - borders.radius.bottom_right_x, + draw_pos.bottom() - borders.radius.bottom_right_y, + borders.radius.bottom_right_x - bdr_bottom + (bdr_bottom - bdr_right), + borders.radius.bottom_right_y - bdr_bottom, + start_angle, + end_angle, false); + } else + { + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.bottom() - bdr_bottom); + cairo_line_to(cr, draw_pos.right(), draw_pos.bottom()); + } + + cairo_fill(cr); + } + + // draw top border + if(bdr_top) + { + set_color(cr, borders.top.color); + + if(borders.radius.top_left_x && borders.radius.top_left_y) + { + double end_angle = M_PI * 3.0 / 2.0; + double start_angle = end_angle - M_PI / 2.0 / ((double) bdr_left / (double) bdr_top + 1); + + add_path_arc(cr, + draw_pos.left() + borders.radius.top_left_x, + draw_pos.top() + borders.radius.top_left_y, + borders.radius.top_left_x, + borders.radius.top_left_y, + end_angle, + start_angle, true); + + add_path_arc(cr, + draw_pos.left() + borders.radius.top_left_x, + draw_pos.top() + borders.radius.top_left_y, + borders.radius.top_left_x - bdr_top + (bdr_top - bdr_left), + borders.radius.top_left_y - bdr_top, + start_angle, + end_angle, false); + } else + { + cairo_move_to(cr, draw_pos.left(), draw_pos.top()); + cairo_line_to(cr, draw_pos.left() + bdr_left, draw_pos.top() + bdr_top); + } + + if(borders.radius.top_right_x && borders.radius.top_right_y) + { + cairo_line_to(cr, draw_pos.right() - borders.radius.top_right_x, draw_pos.top() + bdr_top); + + double start_angle = M_PI * 3.0 / 2.0; + double end_angle = start_angle + M_PI / 2.0 / ((double) bdr_right / (double) bdr_top + 1); + + add_path_arc(cr, + draw_pos.right() - borders.radius.top_right_x, + draw_pos.top() + borders.radius.top_right_y, + borders.radius.top_right_x - bdr_top + (bdr_top - bdr_right), + borders.radius.top_right_y - bdr_top, + start_angle, + end_angle, false); + + add_path_arc(cr, + draw_pos.right() - borders.radius.top_right_x, + draw_pos.top() + borders.radius.top_right_y, + borders.radius.top_right_x, + borders.radius.top_right_y, + end_angle, + start_angle, true); + } else + { + cairo_line_to(cr, draw_pos.right() - bdr_right, draw_pos.top() + bdr_top); + cairo_line_to(cr, draw_pos.right(), draw_pos.top()); + } + + cairo_fill(cr); + } + + // draw left border + if(bdr_left) + { + set_color(cr, borders.left.color); + + if(borders.radius.top_left_x && borders.radius.top_left_y) + { + double start_angle = M_PI; + double end_angle = start_angle + M_PI / 2.0 / ((double) bdr_top / (double) bdr_left + 1); + + add_path_arc(cr, + draw_pos.left() + borders.radius.top_left_x, + draw_pos.top() + borders.radius.top_left_y, + borders.radius.top_left_x - bdr_left, + borders.radius.top_left_y - bdr_left + (bdr_left - bdr_top), + start_angle, + end_angle, false); + + add_path_arc(cr, + draw_pos.left() + borders.radius.top_left_x, + draw_pos.top() + borders.radius.top_left_y, + borders.radius.top_left_x, + borders.radius.top_left_y, + end_angle, + start_angle, true); + } else + { + cairo_move_to(cr, draw_pos.left() + bdr_left, draw_pos.top() + bdr_top); + cairo_line_to(cr, draw_pos.left(), draw_pos.top()); + } + + if(borders.radius.bottom_left_x && borders.radius.bottom_left_y) + { + cairo_line_to(cr, draw_pos.left(), draw_pos.bottom() - borders.radius.bottom_left_y); + + double end_angle = M_PI; + double start_angle = end_angle - M_PI / 2.0 / ((double) bdr_bottom / (double) bdr_left + 1); + + add_path_arc(cr, + draw_pos.left() + borders.radius.bottom_left_x, + draw_pos.bottom() - borders.radius.bottom_left_y, + borders.radius.bottom_left_x, + borders.radius.bottom_left_y, + end_angle, + start_angle, true); + + add_path_arc(cr, + draw_pos.left() + borders.radius.bottom_left_x, + draw_pos.bottom() - borders.radius.bottom_left_y, + borders.radius.bottom_left_x - bdr_left, + borders.radius.bottom_left_y - bdr_left + (bdr_left - bdr_bottom), + start_angle, + end_angle, false); + } else + { + cairo_line_to(cr, draw_pos.left(), draw_pos.bottom()); + cairo_line_to(cr, draw_pos.left() + bdr_left, draw_pos.bottom() - bdr_bottom); + } + + cairo_fill(cr); + } + cairo_restore(cr); +} + +void container_linux::transform_text(litehtml::string& text, litehtml::text_transform tt) +{ + +} + +void container_linux::set_clip( const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius ) +{ + m_clips.emplace_back(pos, bdr_radius); +} + +void container_linux::del_clip() +{ + if(!m_clips.empty()) + { + m_clips.pop_back(); + } +} + +void container_linux::apply_clip( cairo_t* cr ) +{ + for(const auto& clip_box : m_clips) + { + rounded_rectangle(cr, clip_box.box, clip_box.radius); + cairo_clip(cr); + } +} + +void container_linux::draw_ellipse( cairo_t* cr, int x, int y, int width, int height, const litehtml::web_color& color, int line_width ) +{ + if(!cr || !width || !height) return; + cairo_save(cr); + + apply_clip(cr); + + cairo_new_path(cr); + + cairo_translate (cr, x + width / 2.0, y + height / 2.0); + cairo_scale (cr, width / 2.0, height / 2.0); + cairo_arc (cr, 0, 0, 1, 0, 2 * M_PI); + + set_color(cr, color); + cairo_set_line_width(cr, line_width); + cairo_stroke(cr); + + cairo_restore(cr); +} + +void container_linux::fill_ellipse( cairo_t* cr, int x, int y, int width, int height, const litehtml::web_color& color ) +{ + if(!cr || !width || !height) return; + cairo_save(cr); + + apply_clip(cr); + + cairo_new_path(cr); + + cairo_translate (cr, x + width / 2.0, y + height / 2.0); + cairo_scale (cr, width / 2.0, height / 2.0); + cairo_arc (cr, 0, 0, 1, 0, 2 * M_PI); + + set_color(cr, color); + cairo_fill(cr); + + cairo_restore(cr); +} + +void container_linux::clear_images() +{ +/* for(images_map::iterator i = m_images.begin(); i != m_images.end(); i++) + { + if(i->second) + { + delete i->second; + } + } + m_images.clear(); +*/ +} + +const char* container_linux::get_default_font_name() const +{ + return "Times New Roman"; +} + +std::shared_ptr container_linux::create_element(const char *tag_name, + const litehtml::string_map &attributes, + const std::shared_ptr &doc) +{ + return nullptr; +} + +void container_linux::rounded_rectangle( cairo_t* cr, const litehtml::position &pos, const litehtml::border_radiuses &radius ) +{ + cairo_new_path(cr); + if(radius.top_left_x && radius.top_left_y) + { + add_path_arc(cr, + pos.left() + radius.top_left_x, + pos.top() + radius.top_left_y, + radius.top_left_x, + radius.top_left_y, + M_PI, + M_PI * 3.0 / 2.0, false); + } else + { + cairo_move_to(cr, pos.left(), pos.top()); + } + + cairo_line_to(cr, pos.right() - radius.top_right_x, pos.top()); + + if(radius.top_right_x && radius.top_right_y) + { + add_path_arc(cr, + pos.right() - radius.top_right_x, + pos.top() + radius.top_right_y, + radius.top_right_x, + radius.top_right_y, + M_PI * 3.0 / 2.0, + 2.0 * M_PI, false); + } + + cairo_line_to(cr, pos.right(), pos.bottom() - radius.bottom_right_x); + + if(radius.bottom_right_x && radius.bottom_right_y) + { + add_path_arc(cr, + pos.right() - radius.bottom_right_x, + pos.bottom() - radius.bottom_right_y, + radius.bottom_right_x, + radius.bottom_right_y, + 0, + M_PI / 2.0, false); + } + + cairo_line_to(cr, pos.left() - radius.bottom_left_x, pos.bottom()); + + if(radius.bottom_left_x && radius.bottom_left_y) + { + add_path_arc(cr, + pos.left() + radius.bottom_left_x, + pos.bottom() - radius.bottom_left_y, + radius.bottom_left_x, + radius.bottom_left_y, + M_PI / 2.0, + M_PI, false); + } +} + +void container_linux::draw_pixbuf(cairo_t* cr, const Glib::RefPtr& bmp, int x, int y, int cx, int cy) +{ + cairo_save(cr); + + { + Cairo::RefPtr crobj(new Cairo::Context(cr, false)); + + cairo_matrix_t flib_m; + cairo_matrix_init(&flib_m, 1, 0, 0, -1, 0, 0); + + if(cx != bmp->get_width() || cy != bmp->get_height()) + { + Glib::RefPtr new_img = bmp->scale_simple(cx, cy, Gdk::INTERP_BILINEAR); + Gdk::Cairo::set_source_pixbuf(crobj, new_img, x, y); + crobj->paint(); + } else + { + Gdk::Cairo::set_source_pixbuf(crobj, bmp, x, y); + crobj->paint(); + } + } + + cairo_restore(cr); +} + +cairo_surface_t* container_linux::surface_from_pixbuf(const Glib::RefPtr& bmp) +{ + cairo_surface_t* ret; + + if(bmp->get_has_alpha()) + { + ret = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bmp->get_width(), bmp->get_height()); + } else + { + ret = cairo_image_surface_create(CAIRO_FORMAT_RGB24, bmp->get_width(), bmp->get_height()); + } + + Cairo::RefPtr surface(new Cairo::Surface(ret, false)); + Cairo::RefPtr ctx = Cairo::Context::create(surface); + Gdk::Cairo::set_source_pixbuf(ctx, bmp, 0.0, 0.0); + ctx->paint(); + + return ret; +} + +void container_linux::get_media_features(litehtml::media_features& media) const +{ + litehtml::position client; + get_client_rect(client); + media.type = litehtml::media_type_screen; + media.width = client.width; + media.height = client.height; + media.device_width = Gdk::screen_width(); + media.device_height = Gdk::screen_height(); + media.color = 8; + media.monochrome = 0; + media.color_index = 256; + media.resolution = 96; +} + +void container_linux::get_language(litehtml::string& language, litehtml::string& culture) const +{ + language = "en"; + culture = ""; +} + +void container_linux::link(const std::shared_ptr &ptr, const litehtml::element::ptr& el) +{ + +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/linux/container_linux.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/linux/container_linux.h new file mode 100644 index 0000000..5acb648 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/linux/container_linux.h @@ -0,0 +1,104 @@ +#ifndef LH_CONTAINER_LINUX_H +#define LH_CONTAINER_LINUX_H + +#include "../../include/litehtml.h" +#include +#include +#include + +struct cairo_clip_box +{ + typedef std::vector vector; + litehtml::position box; + litehtml::border_radiuses radius; + + cairo_clip_box(const litehtml::position& vBox, const litehtml::border_radiuses& vRad) + { + box = vBox; + radius = vRad; + } + + cairo_clip_box(const cairo_clip_box& val) + { + box = val.box; + radius = val.radius; + } + cairo_clip_box& operator=(const cairo_clip_box& val) + { + box = val.box; + radius = val.radius; + return *this; + } +}; + +struct cairo_font +{ + PangoFontDescription* font; + int size; + bool underline; + bool strikeout; + int ascent; + int descent; + int underline_thickness; + int underline_position; + int strikethrough_thickness; + int strikethrough_position; +}; + +class container_linux : public litehtml::document_container +{ + typedef std::map > images_map; + +protected: + cairo_surface_t* m_temp_surface; + cairo_t* m_temp_cr; + images_map m_images; + cairo_clip_box::vector m_clips; +public: + container_linux(); + virtual ~container_linux(); + + litehtml::uint_ptr create_font(const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics* fm) override; + void delete_font(litehtml::uint_ptr hFont) override; + int text_width(const char* text, litehtml::uint_ptr hFont) override; + void draw_text(litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) override; + int pt_to_px(int pt) const override; + int get_default_font_size() const override; + const char* get_default_font_name() const override; + void load_image(const char* src, const char* baseurl, bool redraw_on_ready) override; + void get_image_size(const char* src, const char* baseurl, litehtml::size& sz) override; + void draw_background(litehtml::uint_ptr hdc, const std::vector& bg) override; + void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) override; + void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker& marker) override; + std::shared_ptr create_element(const char *tag_name, + const litehtml::string_map &attributes, + const std::shared_ptr &doc) override; + void get_media_features(litehtml::media_features& media) const override; + void get_language(litehtml::string& language, litehtml::string & culture) const override; + void link(const std::shared_ptr &ptr, const litehtml::element::ptr& el) override; + + + void transform_text(litehtml::string& text, litehtml::text_transform tt) override; + void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) override; + void del_clip() override; + + virtual void make_url( const char* url, const char* basepath, litehtml::string& out ); + virtual Glib::RefPtr get_image(const char* url, bool redraw_on_ready) = 0; + + void clear_images(); + +protected: + virtual void draw_ellipse(cairo_t* cr, int x, int y, int width, int height, const litehtml::web_color& color, int line_width); + virtual void fill_ellipse(cairo_t* cr, int x, int y, int width, int height, const litehtml::web_color& color); + virtual void rounded_rectangle( cairo_t* cr, const litehtml::position &pos, const litehtml::border_radiuses &radius ); + +private: + void apply_clip(cairo_t* cr); + + static void add_path_arc(cairo_t* cr, double x, double y, double rx, double ry, double a1, double a2, bool neg); + static void set_color(cairo_t* cr, const litehtml::web_color& color) { cairo_set_source_rgba(cr, color.red / 255.0, color.green / 255.0, color.blue / 255.0, color.alpha / 255.0); } + static cairo_surface_t* surface_from_pixbuf(const Glib::RefPtr& bmp); + static void draw_pixbuf(cairo_t* cr, const Glib::RefPtr& bmp, int x, int y, int cx, int cy); +}; + +#endif diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Bitmap.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Bitmap.cpp new file mode 100644 index 0000000..862d484 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Bitmap.cpp @@ -0,0 +1,129 @@ +#include "Bitmap.h" +#include "lodepng.h" +using namespace std; + +web_color Bitmap::get_pixel(int x, int y) const +{ + if (x < 0 || x >= width || y < 0 || y >= height) + return web_color::black; + else + return data[x + y * width]; +} + +void Bitmap::set_pixel(int x, int y, web_color color) +{ + if (x < 0 || x >= width || y < 0 || y >= height) return; + if (color.alpha == 0) return; + data[x + y * width] = color; +} + +// endpoint is not drawn, like in GDI +void Bitmap::draw_line(int x0, int y0, int x1, int y1, web_color color) +{ + if (x0 != x1 && y0 != y1) return; // only horz and vert lines supported + + if (x0 == x1) // vert line + { + if (y0 > y1) swap(y0, y1); + for (int y = y0; y < y1; y++) + set_pixel(x0, y, color); + } + else if (y0 == y1) // horz line + { + if (x0 > x1) swap(x0, x1); + for (int x = x0; x < x1; x++) + set_pixel(x, y0, color); + } +} + +void Bitmap::draw_rect(int x, int y, int width, int height, web_color color) +{ + draw_line(x, y, x + width, y, color); // top + draw_line(x, y + height - 1, x + width, y + height - 1, color); // bottom + draw_line(x, y, x, y + height, color); // left + draw_line(x + width - 1, y, x + width - 1, y + height, color); // right +} + +void Bitmap::fill_rect(position rect, web_color color) +{ + for (int y = rect.top(); y < rect.bottom(); y++) + for (int x = rect.left(); x < rect.right(); x++) + set_pixel(x, y, color); +} + +void Bitmap::draw_bitmap(int x0, int y0, const Bitmap& bmp) +{ + for (int y = 0; y < bmp.height; y++) + for (int x = 0; x < bmp.width; x++) + set_pixel(x0 + x, y0 + y, bmp.get_pixel(x, y)); +} + +void Bitmap::replace_color(web_color original, web_color replacement) +{ + for (auto& pixel : data) + { + if (pixel == original) + pixel = replacement; + } +} + +// find minimal rectangle containing pixels different from bgcolor +position Bitmap::find_picture(web_color bgcolor) +{ + auto horz_line_empty = [&](int y) { + for (int x = 0; x < width; x++) + if (data[x + y * width] != bgcolor) return false; + return true; + }; + auto vert_line_empty = [&](int x) { + for (int y = 0; y < height; y++) + if (data[x + y * width] != bgcolor) return false; + return true; + }; + + position pos; + int y; + for (y = 0; y < height && horz_line_empty(y); y++); + if (y == height) return pos; // no picture + pos.y = y; + for (y = height - 1; y >= 0 && horz_line_empty(y); y--); + pos.height = y + 1 - pos.y; + + int x; + for (x = 0; x < width && vert_line_empty(x); x++); + pos.x = x; + for (x = width - 1; x >= 0 && vert_line_empty(x); x--); + pos.width = x + 1 - pos.x; + + return pos; +} + +void Bitmap::resize(int new_width, int new_height) +{ + vector new_data(new_width * new_height, web_color::white); + for (int y = 0; y < min(new_height, height); y++) + for (int x = 0; x < min(new_width, width); x++) + new_data[x + y * new_width] = data[x + y * width]; + + width = new_width; + height = new_height; + data = new_data; +} + +void Bitmap::load(string filename) +{ + vector image; + unsigned w, h; + lodepng::decode(image, w, h, filename); + if (w * h == 0) return; + + width = w; + height = h; + data.resize(w * h); + memcpy(data.data(), image.data(), w * h * 4); +} + +void Bitmap::save(string filename) +{ + lodepng::encode(filename, (byte*)data.data(), width, height); +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Bitmap.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Bitmap.h new file mode 100644 index 0000000..bb66d19 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Bitmap.h @@ -0,0 +1,36 @@ +#include +using namespace litehtml; + +class Bitmap +{ +public: + int width = 0; + int height = 0; + std::vector data; + + Bitmap() {} + Bitmap(int width, int height, web_color color = web_color::white) : width(width), height(height) + { + data.resize(width * height, color); + } + Bitmap(string filename) + { + load(filename); + } + + bool operator==(const Bitmap& bmp) const { return width == bmp.width && height == bmp.height && data == bmp.data; } + bool operator!=(const Bitmap& bmp) const { return !(*this == bmp); } + + web_color get_pixel(int x, int y) const; + void set_pixel(int x, int y, web_color color); + void draw_line(int x0, int y0, int x1, int y1, web_color color); + void draw_rect(int x, int y, int width, int height, web_color color); + void fill_rect(position rect, web_color color); + void draw_bitmap(int x, int y, const Bitmap& bmp); + void replace_color(web_color original, web_color replacement); + + position find_picture(web_color bgcolor = web_color::white); + void resize(int new_width, int new_height); + void load(string filename); + void save(string filename); +}; \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Font.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Font.cpp new file mode 100644 index 0000000..82ae36c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Font.cpp @@ -0,0 +1,126 @@ +#define _CRT_SECURE_NO_WARNINGS +#include "Font.h" +string readfile(string filename); +using namespace std; + +string Font::font_dir = "../containers/test/fonts/"; // ctest is run from litehtml/build +Font::size_name Font::installed_fonts[] = +{ + { 12, "terminus-ascii-bold-12px.yaff" }, + { 14, "terminus-ascii-bold-14px.yaff" }, + { 16, "terminus-ascii-bold-16px.yaff" }, + { 18, "terminus-ascii-bold-18px.yaff" }, + { 20, "terminus-ascii-bold-20px.yaff" }, + { 22, "terminus-ascii-bold-22px.yaff" }, + { 24, "terminus-ascii-bold-24px.yaff" }, + { 28, "terminus-ascii-bold-28px.yaff" }, + { 32, "terminus-ascii-bold-32px.yaff" }, + { 0 } +}; + +Font::Font(int size) +{ + // find most suitable font + int min_diff = 1000; + int n = 0; + for (int i = 0; installed_fonts[i].size; i++) + { + int diff = abs(installed_fonts[i].size - size); + if (diff < min_diff) + { + min_diff = diff; + n = i; + } + } + + load(font_dir + installed_fonts[n].name); +} + +Bitmap Font::get_glyph(int ch, web_color color) +{ + if (ch < 0 || ch >= 128 || glyphs[ch].width == 0) + { + Bitmap bmp(width, height, web_color::transparent); + bmp.draw_rect(1, 1, width - 2, height - 2, color); + return bmp; + } + else if (color != web_color::black) + { + Bitmap bmp = glyphs[ch]; + bmp.replace_color(web_color::black, color); + return bmp; + } + else + { + return glyphs[ch]; + } +} + +// load .yaff font file in an ad hoc manner (can't parse arbitrary yaff files) +void Font::load(string filename) +{ + string text = readfile(filename); + + string_vector lines; + split_string(text, lines, "\n"); + + int i; + // parse header + for (i = 0; i < lines.size(); i++) + { + string line = lines[i]; + trim(line); + if (line == "" || line[0] == '#') continue; // skip empty lines and comments + + auto sep = line.find(':'); + if (sep == -1) return; // line without ':' - error + + auto key = line.substr(0, sep); trim(key); + auto val = line.substr(sep + 1); trim(val); + if (val == "") break; // end of header + + if (key == "cell-size") sscanf(val.c_str(), "%d %d", &width, &height); + else if (key == "ascent") ascent = atoi(val.c_str()); + else if (key == "descent") descent = atoi(val.c_str()); + } + + // parse glyphs + + // only u+NNNN: label is recognized, all others are skipped + auto parse_key = [&]() { + int ch = -1; + for (; i < lines.size(); i++) + { + string line = lines[i]; + trim(line); + if (line == "") continue; + if (line.find(':') == -1) break; // start of glyph data + if (line.substr(0, 2) == "u+") + sscanf(line.c_str(), "u+%X:", &ch); + } + return ch; + }; + + auto parse_glyph = [&](int ch) { + Bitmap& glyph = glyphs[ch] = Bitmap(width, height, web_color::transparent); + for (int y = 0; i < lines.size() && y < height; i++, y++) + { + string line = lines[i]; + trim(line); + for (int x = 0; x < min((int)line.size(), width); x++) + { + if (line[x] == '@') + glyph.set_pixel(x, y, web_color::black); + } + } + }; + + while (i < lines.size()) + { + int ch = parse_key(); + if (ch < 0 || ch >= 128) break; + parse_glyph(ch); + } + + x_height = glyphs['x'].find_picture(web_color::transparent).height; +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Font.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Font.h new file mode 100644 index 0000000..4f3c012 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/Font.h @@ -0,0 +1,19 @@ +#include "Bitmap.h" + +class Font +{ +public: + int width = 0; + int height = 0; + int ascent = 0; + int descent = 0; + int x_height = 0; + Bitmap glyphs[128]; + + static string font_dir; + static struct size_name { int size; string name; } installed_fonts[]; + + Font(int size); + Bitmap get_glyph(int ch, web_color color); + void load(string filename); +}; \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/OFL.txt b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/OFL.txt new file mode 100644 index 0000000..a5c2627 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/OFL.txt @@ -0,0 +1,94 @@ +Copyright (C) 2020 Dimitar Toshkov Zhekov, +with Reserved Font Name "Terminus Font". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/readme.txt b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/readme.txt new file mode 100644 index 0000000..a093284 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/readme.txt @@ -0,0 +1,12 @@ +Note: Fonts in this directory are not automatically picked up, you have to update Font::installed_fonts. + +Terminus Font: +Sizes: 6x12, 8x14, 8x16, 10x18, 10x20, 11x22, 12x24, 14x28, 16x32. +https://terminus-font.sourceforge.net +https://sourceforge.net/projects/terminus-font/files/terminus-font-4.49/terminus-font-4.49.1.tar.gz + +bdf -> yaff conversion: +https://github.com/robhagemans/monobit +pip install monobit +monobit-convert ter-u14b.bdf to ter-u14b.yaff +then manually removed non-ascii chars diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-12px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-12px.yaff new file mode 100644 index 0000000..a2c91c5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-12px.yaff @@ -0,0 +1,1463 @@ +name: Terminus Bold 6x12 +spacing: character-cell +cell-size: 6 12 +family: Terminus +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 12 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 6 +ascent: 10 +descent: 2 +shift-up: -2 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 6 +converter: monobit v0.32 +source-name: ter-u12b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + ...... + ...... + @@.@@. + @...@. + ...... + @...@. + @...@. + ...... + @...@. + @@.@@. + ...... + ...... + +u+0020: +space: + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + +u+0021: +exclam: + ...... + ...... + ..@... + ..@... + ..@... + ..@... + ..@... + ...... + ..@... + ..@... + ...... + ...... + +u+0022: +quotedbl: + ...... + .@.@.. + .@.@.. + .@.@.. + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + +u+0023: +numbersign: + ...... + ...... + .@.@.. + .@.@.. + @@@@@. + .@.@.. + .@.@.. + @@@@@. + .@.@.. + .@.@.. + ...... + ...... + +u+0024: +dollar: + ...... + ...... + ..@... + .@@@.. + @.@.@. + @.@... + .@@@.. + ..@.@. + @.@.@. + .@@@.. + ..@... + ...... + +u+0025: +percent: + ...... + ...... + .@..@. + @.@.@. + .@.@.. + ...@.. + ..@... + ..@.@. + .@.@.@ + .@..@. + ...... + ...... + +u+0026: +ampersand: + ...... + ...... + ..@... + .@.@.. + .@.@.. + ..@... + .@@.@. + @..@.. + @..@.. + .@@.@. + ...... + ...... + +u+0027: +quotesingle: + ...... + ..@... + ..@... + ..@... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + +u+0028: +parenleft: + ...... + ...... + ...@.. + ..@... + .@.... + .@.... + .@.... + .@.... + ..@... + ...@.. + ...... + ...... + +u+0029: +parenright: + ...... + ...... + .@.... + ..@... + ...@.. + ...@.. + ...@.. + ...@.. + ..@... + .@.... + ...... + ...... + +u+002a: +asterisk: + ...... + ...... + ...... + ...... + .@.@.. + ..@... + @@@@@. + ..@... + .@.@.. + ...... + ...... + ...... + +u+002b: +plus: + ...... + ...... + ...... + ...... + ..@... + ..@... + @@@@@. + ..@... + ..@... + ...... + ...... + ...... + +u+002c: +comma: + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ..@... + ..@... + .@.... + ...... + +u+002d: +hyphen: + ...... + ...... + ...... + ...... + ...... + ...... + @@@@@. + ...... + ...... + ...... + ...... + ...... + +u+002e: +period: + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ..@... + ..@... + ...... + ...... + +u+002f: +slash: + ...... + ...... + ....@. + ....@. + ...@.. + ...@.. + ..@... + ..@... + .@.... + .@.... + ...... + ...... + +u+0030: +zero: + ...... + ...... + .@@@.. + @...@. + @..@@. + @.@.@. + @@..@. + @...@. + @...@. + .@@@.. + ...... + ...... + +u+0031: +one: + ...... + ...... + ..@... + .@@... + ..@... + ..@... + ..@... + ..@... + ..@... + .@@@.. + ...... + ...... + +u+0032: +two: + ...... + ...... + .@@@.. + @...@. + @...@. + ....@. + ...@.. + ..@... + .@.... + @@@@@. + ...... + ...... + +u+0033: +three: + ...... + ...... + .@@@.. + @...@. + ....@. + ..@@.. + ....@. + ....@. + @...@. + .@@@.. + ...... + ...... + +u+0034: +four: + ...... + ...... + ....@. + ...@@. + ..@.@. + .@..@. + @...@. + @@@@@. + ....@. + ....@. + ...... + ...... + +u+0035: +five: + ...... + ...... + @@@@@. + @..... + @..... + @@@@.. + ....@. + ....@. + @...@. + .@@@.. + ...... + ...... + +u+0036: +six: + ...... + ...... + .@@@.. + @..... + @..... + @@@@.. + @...@. + @...@. + @...@. + .@@@.. + ...... + ...... + +u+0037: +seven: + ...... + ...... + @@@@@. + ....@. + ....@. + ...@.. + ...@.. + ..@... + ..@... + ..@... + ...... + ...... + +u+0038: +eight: + ...... + ...... + .@@@.. + @...@. + @...@. + .@@@.. + @...@. + @...@. + @...@. + .@@@.. + ...... + ...... + +u+0039: +nine: + ...... + ...... + .@@@.. + @...@. + @...@. + @...@. + .@@@@. + ....@. + ....@. + .@@@.. + ...... + ...... + +u+003a: +colon: + ...... + ...... + ...... + ...... + ..@... + ..@... + ...... + ...... + ..@... + ..@... + ...... + ...... + +u+003b: +semicolon: + ...... + ...... + ...... + ...... + ..@... + ..@... + ...... + ...... + ..@... + ..@... + .@.... + ...... + +u+003c: +less: + ...... + ...... + ...... + ....@. + ...@.. + ..@... + .@.... + ..@... + ...@.. + ....@. + ...... + ...... + +u+003d: +equal: + ...... + ...... + ...... + ...... + @@@@@. + ...... + ...... + @@@@@. + ...... + ...... + ...... + ...... + +u+003e: +greater: + ...... + ...... + ...... + .@.... + ..@... + ...@.. + ....@. + ...@.. + ..@... + .@.... + ...... + ...... + +u+003f: +question: + ...... + ...... + .@@@.. + @...@. + @...@. + ...@.. + ..@... + ...... + ..@... + ..@... + ...... + ...... + +u+0040: +at: + ...... + ...... + .@@@.. + @...@. + @..@@. + @.@.@. + @.@.@. + @..@@. + @..... + .@@@@. + ...... + ...... + +u+0041: +"A": + ...... + ...... + .@@@.. + @...@. + @...@. + @...@. + @@@@@. + @...@. + @...@. + @...@. + ...... + ...... + +u+0042: +"B": + ...... + ...... + @@@@.. + @...@. + @...@. + @@@@.. + @...@. + @...@. + @...@. + @@@@.. + ...... + ...... + +u+0043: +"C": + ...... + ...... + .@@@.. + @...@. + @..... + @..... + @..... + @..... + @...@. + .@@@.. + ...... + ...... + +u+0044: +"D": + ...... + ...... + @@@... + @..@.. + @...@. + @...@. + @...@. + @...@. + @..@.. + @@@... + ...... + ...... + +u+0045: +"E": + ...... + ...... + @@@@@. + @..... + @..... + @@@@.. + @..... + @..... + @..... + @@@@@. + ...... + ...... + +u+0046: +"F": + ...... + ...... + @@@@@. + @..... + @..... + @@@@.. + @..... + @..... + @..... + @..... + ...... + ...... + +u+0047: +"G": + ...... + ...... + .@@@.. + @...@. + @..... + @..... + @.@@@. + @...@. + @...@. + .@@@.. + ...... + ...... + +u+0048: +"H": + ...... + ...... + @...@. + @...@. + @...@. + @@@@@. + @...@. + @...@. + @...@. + @...@. + ...... + ...... + +u+0049: +"I": + ...... + ...... + .@@@.. + ..@... + ..@... + ..@... + ..@... + ..@... + ..@... + .@@@.. + ...... + ...... + +u+004a: +"J": + ...... + ...... + ..@@@. + ...@.. + ...@.. + ...@.. + ...@.. + @..@.. + @..@.. + .@@... + ...... + ...... + +u+004b: +"K": + ...... + ...... + @...@. + @..@.. + @.@... + @@.... + @@.... + @.@... + @..@.. + @...@. + ...... + ...... + +u+004c: +"L": + ...... + ...... + @..... + @..... + @..... + @..... + @..... + @..... + @..... + @@@@@. + ...... + ...... + +u+004d: +"M": + ...... + ...... + @...@. + @@.@@. + @.@.@. + @.@.@. + @...@. + @...@. + @...@. + @...@. + ...... + ...... + +u+004e: +"N": + ...... + ...... + @...@. + @...@. + @@..@. + @.@.@. + @..@@. + @...@. + @...@. + @...@. + ...... + ...... + +u+004f: +"O": + ...... + ...... + .@@@.. + @...@. + @...@. + @...@. + @...@. + @...@. + @...@. + .@@@.. + ...... + ...... + +u+0050: +"P": + ...... + ...... + @@@@.. + @...@. + @...@. + @...@. + @@@@.. + @..... + @..... + @..... + ...... + ...... + +u+0051: +"Q": + ...... + ...... + .@@@.. + @...@. + @...@. + @...@. + @...@. + @...@. + @.@.@. + .@@@.. + ....@. + ...... + +u+0052: +"R": + ...... + ...... + @@@@.. + @...@. + @...@. + @...@. + @@@@.. + @.@... + @..@.. + @...@. + ...... + ...... + +u+0053: +"S": + ...... + ...... + .@@@.. + @...@. + @..... + .@@@.. + ....@. + ....@. + @...@. + .@@@.. + ...... + ...... + +u+0054: +"T": + ...... + ...... + @@@@@. + ..@... + ..@... + ..@... + ..@... + ..@... + ..@... + ..@... + ...... + ...... + +u+0055: +"U": + ...... + ...... + @...@. + @...@. + @...@. + @...@. + @...@. + @...@. + @...@. + .@@@.. + ...... + ...... + +u+0056: +"V": + ...... + ...... + @...@. + @...@. + @...@. + .@.@.. + .@.@.. + .@.@.. + ..@... + ..@... + ...... + ...... + +u+0057: +"W": + ...... + ...... + @...@. + @...@. + @...@. + @...@. + @.@.@. + @.@.@. + @@.@@. + @...@. + ...... + ...... + +u+0058: +"X": + ...... + ...... + @...@. + @...@. + .@.@.. + ..@... + ..@... + .@.@.. + @...@. + @...@. + ...... + ...... + +u+0059: +"Y": + ...... + ...... + @...@. + @...@. + .@.@.. + .@.@.. + ..@... + ..@... + ..@... + ..@... + ...... + ...... + +u+005a: +"Z": + ...... + ...... + @@@@@. + ....@. + ...@.. + ..@... + .@.... + @..... + @..... + @@@@@. + ...... + ...... + +u+005b: +bracketleft: + ...... + ...... + .@@@.. + .@.... + .@.... + .@.... + .@.... + .@.... + .@.... + .@@@.. + ...... + ...... + +u+005c: +backslash: + ...... + ...... + .@.... + .@.... + ..@... + ..@... + ...@.. + ...@.. + ....@. + ....@. + ...... + ...... + +u+005d: +bracketright: + ...... + ...... + .@@@.. + ...@.. + ...@.. + ...@.. + ...@.. + ...@.. + ...@.. + .@@@.. + ...... + ...... + +u+005e: +asciicircum: + ...... + ..@... + .@.@.. + @...@. + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + +u+005f: +underscore: + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + @@@@@. + ...... + +u+0060: +grave: + .@.... + ..@... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... + +u+0061: +"a": + ...... + ...... + ...... + ...... + .@@@.. + ....@. + .@@@@. + @...@. + @...@. + .@@@@. + ...... + ...... + +u+0062: +"b": + ...... + ...... + @..... + @..... + @@@@.. + @...@. + @...@. + @...@. + @...@. + @@@@.. + ...... + ...... + +u+0063: +"c": + ...... + ...... + ...... + ...... + .@@@.. + @...@. + @..... + @..... + @...@. + .@@@.. + ...... + ...... + +u+0064: +"d": + ...... + ...... + ....@. + ....@. + .@@@@. + @...@. + @...@. + @...@. + @...@. + .@@@@. + ...... + ...... + +u+0065: +"e": + ...... + ...... + ...... + ...... + .@@@.. + @...@. + @@@@@. + @..... + @..... + .@@@@. + ...... + ...... + +u+0066: +"f": + ...... + ...... + ...@@. + ..@... + .@@@.. + ..@... + ..@... + ..@... + ..@... + ..@... + ...... + ...... + +u+0067: +"g": + ...... + ...... + ...... + ...... + .@@@@. + @...@. + @...@. + @...@. + @...@. + .@@@@. + ....@. + .@@@.. + +u+0068: +"h": + ...... + ...... + @..... + @..... + @@@@.. + @...@. + @...@. + @...@. + @...@. + @...@. + ...... + ...... + +u+0069: +"i": + ...... + ..@... + ..@... + ...... + .@@... + ..@... + ..@... + ..@... + ..@... + .@@@.. + ...... + ...... + +u+006a: +"j": + ...... + ....@. + ....@. + ...... + ...@@. + ....@. + ....@. + ....@. + ....@. + ....@. + .@..@. + ..@@.. + +u+006b: +"k": + ...... + ...... + .@.... + .@.... + .@..@. + .@.@.. + .@@... + .@@... + .@.@.. + .@..@. + ...... + ...... + +u+006c: +"l": + ...... + ...... + .@@... + ..@... + ..@... + ..@... + ..@... + ..@... + ..@... + .@@@.. + ...... + ...... + +u+006d: +"m": + ...... + ...... + ...... + ...... + @@@@.. + @.@.@. + @.@.@. + @.@.@. + @.@.@. + @.@.@. + ...... + ...... + +u+006e: +"n": + ...... + ...... + ...... + ...... + @@@@.. + @...@. + @...@. + @...@. + @...@. + @...@. + ...... + ...... + +u+006f: +"o": + ...... + ...... + ...... + ...... + .@@@.. + @...@. + @...@. + @...@. + @...@. + .@@@.. + ...... + ...... + +u+0070: +"p": + ...... + ...... + ...... + ...... + @@@@.. + @...@. + @...@. + @...@. + @...@. + @@@@.. + @..... + @..... + +u+0071: +"q": + ...... + ...... + ...... + ...... + .@@@@. + @...@. + @...@. + @...@. + @...@. + .@@@@. + ....@. + ....@. + +u+0072: +"r": + ...... + ...... + ...... + ...... + @.@@@. + @@.... + @..... + @..... + @..... + @..... + ...... + ...... + +u+0073: +"s": + ...... + ...... + ...... + ...... + .@@@@. + @..... + .@@@.. + ....@. + ....@. + @@@@.. + ...... + ...... + +u+0074: +"t": + ...... + ...... + ..@... + ..@... + .@@@.. + ..@... + ..@... + ..@... + ..@... + ...@@. + ...... + ...... + +u+0075: +"u": + ...... + ...... + ...... + ...... + @...@. + @...@. + @...@. + @...@. + @...@. + .@@@@. + ...... + ...... + +u+0076: +"v": + ...... + ...... + ...... + ...... + @...@. + @...@. + .@.@.. + .@.@.. + ..@... + ..@... + ...... + ...... + +u+0077: +"w": + ...... + ...... + ...... + ...... + @...@. + @...@. + @.@.@. + @.@.@. + @.@.@. + .@@@.. + ...... + ...... + +u+0078: +"x": + ...... + ...... + ...... + ...... + @...@. + .@.@.. + ..@... + ..@... + .@.@.. + @...@. + ...... + ...... + +u+0079: +"y": + ...... + ...... + ...... + ...... + @...@. + @...@. + @...@. + @...@. + @...@. + .@@@@. + ....@. + .@@@.. + +u+007a: +"z": + ...... + ...... + ...... + ...... + @@@@@. + ...@.. + ..@... + .@.... + @..... + @@@@@. + ...... + ...... + +u+007b: +braceleft: + ...... + ...... + ...@@. + ..@... + ..@... + .@.... + ..@... + ..@... + ..@... + ...@@. + ...... + ...... + +u+007c: +bar: + ...... + ...... + ..@... + ..@... + ..@... + ..@... + ..@... + ..@... + ..@... + ..@... + ...... + ...... + +u+007d: +braceright: + ...... + ...... + .@@... + ...@.. + ...@.. + ....@. + ...@.. + ...@.. + ...@.. + .@@... + ...... + ...... + +u+007e: +asciitilde: + ...... + .@..@. + @.@.@. + @..@.. + ...... + ...... + ...... + ...... + ...... + ...... + ...... + ...... diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-14px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-14px.yaff new file mode 100644 index 0000000..5ce69e8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-14px.yaff @@ -0,0 +1,1656 @@ +name: Terminus ASCII Bold 8x14 +spacing: character-cell +cell-size: 8 14 +family: Terminus ASCII +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 14 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 8 +ascent: 12 +descent: 2 +shift-up: -2 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 8 +converter: monobit v0.32 +source-name: ter-u14b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + ........ + ........ + @@@.@@@. + @@...@@. + ........ + @@...@@. + @@...@@. + @@...@@. + ........ + @@...@@. + @@...@@. + @@@.@@@. + ........ + ........ + +u+0020: +space: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+0021: +exclam: + ........ + ........ + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ........ + ...@@... + ...@@... + ........ + ........ + +u+0022: +quotedbl: + ........ + .@@..@@. + .@@..@@. + .@@..@@. + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+0023: +numbersign: + ........ + ........ + .@@.@@.. + .@@.@@.. + .@@.@@.. + @@@@@@@. + .@@.@@.. + .@@.@@.. + @@@@@@@. + .@@.@@.. + .@@.@@.. + .@@.@@.. + ........ + ........ + +u+0024: +dollar: + ........ + ...@.... + ...@.... + .@@@@@.. + @@.@.@@. + @@.@.... + @@.@.... + .@@@@@.. + ...@.@@. + ...@.@@. + @@.@.@@. + .@@@@@.. + ...@.... + ...@.... + +u+0025: +percent: + ........ + ........ + .@@..@@. + @@.@.@@. + .@@.@@.. + ....@@.. + ...@@... + ...@@... + ..@@.... + ..@@.@@. + .@@.@.@@ + .@@..@@. + ........ + ........ + +u+0026: +ampersand: + ........ + ........ + ..@@@... + .@@.@@.. + .@@.@@.. + ..@@@... + .@@@.@@. + @@.@@@.. + @@..@@.. + @@..@@.. + @@.@@@.. + .@@@.@@. + ........ + ........ + +u+0027: +quotesingle: + ........ + ...@@... + ...@@... + ...@@... + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+0028: +parenleft: + ........ + ........ + ....@@.. + ...@@... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ...@@... + ....@@.. + ........ + ........ + +u+0029: +parenright: + ........ + ........ + ..@@.... + ...@@... + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ...@@... + ..@@.... + ........ + ........ + +u+002a: +asterisk: + ........ + ........ + ........ + ........ + ........ + .@@.@@.. + ..@@@... + @@@@@@@. + ..@@@... + .@@.@@.. + ........ + ........ + ........ + ........ + +u+002b: +plus: + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + .@@@@@@. + ...@@... + ...@@... + ........ + ........ + ........ + ........ + +u+002c: +comma: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + ..@@.... + ........ + +u+002d: +hyphen: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + @@@@@@@. + ........ + ........ + ........ + ........ + ........ + ........ + +u+002e: +period: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + ........ + ........ + +u+002f: +slash: + ........ + ........ + .....@@. + .....@@. + ....@@.. + ....@@.. + ...@@... + ...@@... + ..@@.... + ..@@.... + .@@..... + .@@..... + ........ + ........ + +u+0030: +zero: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@..@@@. + @@.@@@@. + @@@@.@@. + @@@..@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0031: +one: + ........ + ........ + ...@@... + ..@@@... + .@@@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + .@@@@@@. + ........ + ........ + +u+0032: +two: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + .....@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + @@...... + @@@@@@@. + ........ + ........ + +u+0033: +three: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + .....@@. + ..@@@@.. + .....@@. + .....@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0034: +four: + ........ + ........ + .....@@. + ....@@@. + ...@@@@. + ..@@.@@. + .@@..@@. + @@...@@. + @@@@@@@. + .....@@. + .....@@. + .....@@. + ........ + ........ + +u+0035: +five: + ........ + ........ + @@@@@@@. + @@...... + @@...... + @@...... + @@@@@@.. + .....@@. + .....@@. + .....@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0036: +six: + ........ + ........ + ..@@@@.. + .@@..... + @@...... + @@...... + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0037: +seven: + ........ + ........ + @@@@@@@. + .....@@. + .....@@. + ....@@.. + ....@@.. + ...@@... + ...@@... + ..@@.... + ..@@.... + ..@@.... + ........ + ........ + +u+0038: +eight: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0039: +nine: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + .....@@. + .....@@. + ....@@.. + .@@@@... + ........ + ........ + +u+003a: +colon: + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + ........ + ........ + ........ + ...@@... + ...@@... + ........ + ........ + +u+003b: +semicolon: + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + ........ + ........ + ........ + ...@@... + ...@@... + ..@@.... + ........ + +u+003c: +less: + ........ + ........ + ........ + .....@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + ..@@.... + ...@@... + ....@@.. + .....@@. + ........ + ........ + +u+003d: +equal: + ........ + ........ + ........ + ........ + ........ + @@@@@@@. + ........ + ........ + @@@@@@@. + ........ + ........ + ........ + ........ + ........ + +u+003e: +greater: + ........ + ........ + ........ + .@@..... + ..@@.... + ...@@... + ....@@.. + .....@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + ........ + ........ + +u+003f: +question: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + ....@@.. + ...@@... + ...@@... + ........ + ...@@... + ...@@... + ........ + ........ + +u+0040: +at: + ........ + ........ + .@@@@@.. + @@...@@. + @@..@@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@..@@@. + @@...... + .@@@@@@. + ........ + ........ + +u+0041: +"A": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + +u+0042: +"B": + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + ........ + ........ + +u+0043: +"C": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...... + @@...... + @@...... + @@...... + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0044: +"D": + ........ + ........ + @@@@@... + @@..@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@..@@.. + @@@@@... + ........ + ........ + +u+0045: +"E": + ........ + ........ + @@@@@@@. + @@...... + @@...... + @@...... + @@@@@... + @@...... + @@...... + @@...... + @@...... + @@@@@@@. + ........ + ........ + +u+0046: +"F": + ........ + ........ + @@@@@@@. + @@...... + @@...... + @@...... + @@@@@... + @@...... + @@...... + @@...... + @@...... + @@...... + ........ + ........ + +u+0047: +"G": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...... + @@...... + @@.@@@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0048: +"H": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + +u+0049: +"I": + ........ + ........ + ..@@@@.. + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ..@@@@.. + ........ + ........ + +u+004a: +"J": + ........ + ........ + ...@@@@. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + @@..@@.. + @@..@@.. + .@@@@... + ........ + ........ + +u+004b: +"K": + ........ + ........ + @@...@@. + @@...@@. + @@..@@.. + @@.@@... + @@@@.... + @@@@.... + @@.@@... + @@..@@.. + @@...@@. + @@...@@. + ........ + ........ + +u+004c: +"L": + ........ + ........ + @@...... + @@...... + @@...... + @@...... + @@...... + @@...... + @@...... + @@...... + @@...... + @@@@@@@. + ........ + ........ + +u+004d: +"M": + ........ + ........ + @.....@. + @@...@@. + @@@.@@@. + @@@@@@@. + @@.@.@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + +u+004e: +"N": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@@..@@. + @@@@.@@. + @@.@@@@. + @@..@@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + +u+004f: +"O": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0050: +"P": + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + @@...... + @@...... + @@...... + @@...... + ........ + ........ + +u+0051: +"Q": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@.@@@@. + .@@@@@.. + .....@@. + ........ + +u+0052: +"R": + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + @@@@.... + @@.@@... + @@..@@.. + @@...@@. + ........ + ........ + +u+0053: +"S": + ........ + ........ + .@@@@@.. + @@...@@. + @@...... + @@...... + .@@@@@.. + .....@@. + .....@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0054: +"T": + ........ + ........ + @@@@@@@@ + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ........ + ........ + +u+0055: +"U": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0056: +"V": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@.@@.. + .@@.@@.. + .@@.@@.. + ..@@@... + ..@@@... + ........ + ........ + +u+0057: +"W": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@.@.@@. + @@@@@@@. + @@@.@@@. + @@...@@. + @.....@. + ........ + ........ + +u+0058: +"X": + ........ + ........ + @@...@@. + @@...@@. + .@@.@@.. + .@@.@@.. + ..@@@... + ..@@@... + .@@.@@.. + .@@.@@.. + @@...@@. + @@...@@. + ........ + ........ + +u+0059: +"Y": + ........ + ........ + @@....@@ + @@....@@ + .@@..@@. + .@@..@@. + ..@@@@.. + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ........ + ........ + +u+005a: +"Z": + ........ + ........ + @@@@@@@. + .....@@. + .....@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + @@...... + @@...... + @@@@@@@. + ........ + ........ + +u+005b: +bracketleft: + ........ + ........ + ..@@@@.. + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@@@.. + ........ + ........ + +u+005c: +backslash: + ........ + ........ + .@@..... + .@@..... + ..@@.... + ..@@.... + ...@@... + ...@@... + ....@@.. + ....@@.. + .....@@. + .....@@. + ........ + ........ + +u+005d: +bracketright: + ........ + ........ + ..@@@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ..@@@@.. + ........ + ........ + +u+005e: +asciicircum: + ........ + ...@@... + ..@@@@.. + .@@..@@. + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+005f: +underscore: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + @@@@@@@. + ........ + +u+0060: +grave: + ..@@.... + ...@@... + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+0061: +"a": + ........ + ........ + ........ + ........ + ........ + .@@@@@.. + .....@@. + .@@@@@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + ........ + ........ + +u+0062: +"b": + ........ + ........ + @@...... + @@...... + @@...... + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + ........ + ........ + +u+0063: +"c": + ........ + ........ + ........ + ........ + ........ + .@@@@@.. + @@...@@. + @@...... + @@...... + @@...... + @@...@@. + .@@@@@.. + ........ + ........ + +u+0064: +"d": + ........ + ........ + .....@@. + .....@@. + .....@@. + .@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + ........ + ........ + +u+0065: +"e": + ........ + ........ + ........ + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@@@@@@. + @@...... + @@...... + .@@@@@.. + ........ + ........ + +u+0066: +"f": + ........ + ........ + ...@@@@. + ..@@.... + ..@@.... + @@@@@@.. + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ........ + ........ + +u+0067: +"g": + ........ + ........ + ........ + ........ + ........ + .@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + .....@@. + .@@@@@.. + +u+0068: +"h": + ........ + ........ + @@...... + @@...... + @@...... + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + +u+0069: +"i": + ........ + ........ + ...@@... + ...@@... + ........ + ..@@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ..@@@@.. + ........ + ........ + +u+006a: +"j": + ........ + ........ + .....@@. + .....@@. + ........ + ....@@@. + .....@@. + .....@@. + .....@@. + .....@@. + .....@@. + .@@..@@. + .@@..@@. + ..@@@@.. + +u+006b: +"k": + ........ + ........ + @@...... + @@...... + @@...... + @@...@@. + @@..@@.. + @@.@@... + @@@@.... + @@.@@... + @@..@@.. + @@...@@. + ........ + ........ + +u+006c: +"l": + ........ + ........ + ..@@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ..@@@@.. + ........ + ........ + +u+006d: +"m": + ........ + ........ + ........ + ........ + ........ + @@@@@@.. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + ........ + ........ + +u+006e: +"n": + ........ + ........ + ........ + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + +u+006f: +"o": + ........ + ........ + ........ + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + +u+0070: +"p": + ........ + ........ + ........ + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + @@...... + @@...... + +u+0071: +"q": + ........ + ........ + ........ + ........ + ........ + .@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + .....@@. + .....@@. + +u+0072: +"r": + ........ + ........ + ........ + ........ + ........ + @@.@@@@. + @@@@.... + @@@..... + @@...... + @@...... + @@...... + @@...... + ........ + ........ + +u+0073: +"s": + ........ + ........ + ........ + ........ + ........ + .@@@@@@. + @@...... + @@...... + .@@@@@.. + .....@@. + .....@@. + @@@@@@.. + ........ + ........ + +u+0074: +"t": + ........ + ........ + ..@@.... + ..@@.... + ..@@.... + @@@@@@.. + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ...@@@@. + ........ + ........ + +u+0075: +"u": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + ........ + ........ + +u+0076: +"v": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + .@@.@@.. + .@@.@@.. + ..@@@... + ..@@@... + ........ + ........ + +u+0077: +"w": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + .@@@@@.. + ........ + ........ + +u+0078: +"x": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + .@@.@@.. + ..@@@... + .@@.@@.. + @@...@@. + @@...@@. + ........ + ........ + +u+0079: +"y": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + .....@@. + .@@@@@.. + +u+007a: +"z": + ........ + ........ + ........ + ........ + ........ + @@@@@@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + @@...... + @@@@@@@. + ........ + ........ + +u+007b: +braceleft: + ........ + ........ + ...@@@.. + ..@@.... + ..@@.... + ..@@.... + .@@..... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ...@@@.. + ........ + ........ + +u+007c: +bar: + ........ + ........ + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ........ + ........ + +u+007d: +braceright: + ........ + ........ + .@@@.... + ...@@... + ...@@... + ...@@... + ....@@.. + ...@@... + ...@@... + ...@@... + ...@@... + .@@@.... + ........ + ........ + +u+007e: +asciitilde: + ........ + .@@@..@@ + @@.@@.@@ + @@..@@@. + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-16px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-16px.yaff new file mode 100644 index 0000000..9316bb7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-16px.yaff @@ -0,0 +1,1848 @@ +name: Terminus ASCII Bold 8x16 +spacing: character-cell +cell-size: 8 16 +family: Terminus ASCII +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 16 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 8 +ascent: 12 +descent: 4 +shift-up: -4 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 8 +converter: monobit v0.32 +source-name: ter-u16b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + ........ + ........ + @@@.@@@. + @@...@@. + ........ + @@...@@. + @@...@@. + @@...@@. + ........ + @@...@@. + @@...@@. + @@@.@@@. + ........ + ........ + ........ + ........ + +u+0020: +space: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+0021: +exclam: + ........ + ........ + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ........ + ...@@... + ...@@... + ........ + ........ + ........ + ........ + +u+0022: +quotedbl: + ........ + .@@..@@. + .@@..@@. + .@@..@@. + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+0023: +numbersign: + ........ + ........ + .@@.@@.. + .@@.@@.. + .@@.@@.. + @@@@@@@. + .@@.@@.. + .@@.@@.. + @@@@@@@. + .@@.@@.. + .@@.@@.. + .@@.@@.. + ........ + ........ + ........ + ........ + +u+0024: +dollar: + ........ + ...@.... + ...@.... + .@@@@@.. + @@.@.@@. + @@.@.... + @@.@.... + .@@@@@.. + ...@.@@. + ...@.@@. + @@.@.@@. + .@@@@@.. + ...@.... + ...@.... + ........ + ........ + +u+0025: +percent: + ........ + ........ + .@@..@@. + @@.@.@@. + .@@.@@.. + ....@@.. + ...@@... + ...@@... + ..@@.... + ..@@.@@. + .@@.@.@@ + .@@..@@. + ........ + ........ + ........ + ........ + +u+0026: +ampersand: + ........ + ........ + ..@@@... + .@@.@@.. + .@@.@@.. + ..@@@... + .@@@.@@. + @@.@@@.. + @@..@@.. + @@..@@.. + @@.@@@.. + .@@@.@@. + ........ + ........ + ........ + ........ + +u+0027: +quotesingle: + ........ + ...@@... + ...@@... + ...@@... + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+0028: +parenleft: + ........ + ........ + ....@@.. + ...@@... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ...@@... + ....@@.. + ........ + ........ + ........ + ........ + +u+0029: +parenright: + ........ + ........ + ..@@.... + ...@@... + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ...@@... + ..@@.... + ........ + ........ + ........ + ........ + +u+002a: +asterisk: + ........ + ........ + ........ + ........ + ........ + .@@.@@.. + ..@@@... + @@@@@@@. + ..@@@... + .@@.@@.. + ........ + ........ + ........ + ........ + ........ + ........ + +u+002b: +plus: + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + .@@@@@@. + ...@@... + ...@@... + ........ + ........ + ........ + ........ + ........ + ........ + +u+002c: +comma: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + ..@@.... + ........ + ........ + ........ + +u+002d: +hyphen: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + @@@@@@@. + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+002e: +period: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + ........ + ........ + ........ + ........ + +u+002f: +slash: + ........ + ........ + .....@@. + .....@@. + ....@@.. + ....@@.. + ...@@... + ...@@... + ..@@.... + ..@@.... + .@@..... + .@@..... + ........ + ........ + ........ + ........ + +u+0030: +zero: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@..@@@. + @@.@@@@. + @@@@.@@. + @@@..@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0031: +one: + ........ + ........ + ...@@... + ..@@@... + .@@@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + .@@@@@@. + ........ + ........ + ........ + ........ + +u+0032: +two: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + .....@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + @@...... + @@@@@@@. + ........ + ........ + ........ + ........ + +u+0033: +three: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + .....@@. + ..@@@@.. + .....@@. + .....@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0034: +four: + ........ + ........ + .....@@. + ....@@@. + ...@@@@. + ..@@.@@. + .@@..@@. + @@...@@. + @@@@@@@. + .....@@. + .....@@. + .....@@. + ........ + ........ + ........ + ........ + +u+0035: +five: + ........ + ........ + @@@@@@@. + @@...... + @@...... + @@...... + @@@@@@.. + .....@@. + .....@@. + .....@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0036: +six: + ........ + ........ + ..@@@@.. + .@@..... + @@...... + @@...... + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0037: +seven: + ........ + ........ + @@@@@@@. + .....@@. + .....@@. + ....@@.. + ....@@.. + ...@@... + ...@@... + ..@@.... + ..@@.... + ..@@.... + ........ + ........ + ........ + ........ + +u+0038: +eight: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0039: +nine: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + .....@@. + .....@@. + ....@@.. + .@@@@... + ........ + ........ + ........ + ........ + +u+003a: +colon: + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + ........ + ........ + ........ + ...@@... + ...@@... + ........ + ........ + ........ + ........ + +u+003b: +semicolon: + ........ + ........ + ........ + ........ + ........ + ...@@... + ...@@... + ........ + ........ + ........ + ...@@... + ...@@... + ..@@.... + ........ + ........ + ........ + +u+003c: +less: + ........ + ........ + ........ + .....@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + ..@@.... + ...@@... + ....@@.. + .....@@. + ........ + ........ + ........ + ........ + +u+003d: +equal: + ........ + ........ + ........ + ........ + ........ + @@@@@@@. + ........ + ........ + @@@@@@@. + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+003e: +greater: + ........ + ........ + ........ + .@@..... + ..@@.... + ...@@... + ....@@.. + .....@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + ........ + ........ + ........ + ........ + +u+003f: +question: + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + ....@@.. + ...@@... + ...@@... + ........ + ...@@... + ...@@... + ........ + ........ + ........ + ........ + +u+0040: +at: + ........ + ........ + .@@@@@.. + @@...@@. + @@..@@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@..@@@. + @@...... + .@@@@@@. + ........ + ........ + ........ + ........ + +u+0041: +"A": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+0042: +"B": + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + ........ + ........ + ........ + ........ + +u+0043: +"C": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...... + @@...... + @@...... + @@...... + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0044: +"D": + ........ + ........ + @@@@@... + @@..@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@..@@.. + @@@@@... + ........ + ........ + ........ + ........ + +u+0045: +"E": + ........ + ........ + @@@@@@@. + @@...... + @@...... + @@...... + @@@@@... + @@...... + @@...... + @@...... + @@...... + @@@@@@@. + ........ + ........ + ........ + ........ + +u+0046: +"F": + ........ + ........ + @@@@@@@. + @@...... + @@...... + @@...... + @@@@@... + @@...... + @@...... + @@...... + @@...... + @@...... + ........ + ........ + ........ + ........ + +u+0047: +"G": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...... + @@...... + @@.@@@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0048: +"H": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+0049: +"I": + ........ + ........ + ..@@@@.. + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ..@@@@.. + ........ + ........ + ........ + ........ + +u+004a: +"J": + ........ + ........ + ...@@@@. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + @@..@@.. + @@..@@.. + .@@@@... + ........ + ........ + ........ + ........ + +u+004b: +"K": + ........ + ........ + @@...@@. + @@...@@. + @@..@@.. + @@.@@... + @@@@.... + @@@@.... + @@.@@... + @@..@@.. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+004c: +"L": + ........ + ........ + @@...... + @@...... + @@...... + @@...... + @@...... + @@...... + @@...... + @@...... + @@...... + @@@@@@@. + ........ + ........ + ........ + ........ + +u+004d: +"M": + ........ + ........ + @.....@. + @@...@@. + @@@.@@@. + @@@@@@@. + @@.@.@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+004e: +"N": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@@..@@. + @@@@.@@. + @@.@@@@. + @@..@@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+004f: +"O": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0050: +"P": + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + @@...... + @@...... + @@...... + @@...... + ........ + ........ + ........ + ........ + +u+0051: +"Q": + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@.@@@@. + .@@@@@.. + .....@@. + ........ + ........ + ........ + +u+0052: +"R": + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + @@@@.... + @@.@@... + @@..@@.. + @@...@@. + ........ + ........ + ........ + ........ + +u+0053: +"S": + ........ + ........ + .@@@@@.. + @@...@@. + @@...... + @@...... + .@@@@@.. + .....@@. + .....@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0054: +"T": + ........ + ........ + @@@@@@@@ + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ........ + ........ + ........ + ........ + +u+0055: +"U": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0056: +"V": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@.@@.. + .@@.@@.. + .@@.@@.. + ..@@@... + ..@@@... + ........ + ........ + ........ + ........ + +u+0057: +"W": + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@.@.@@. + @@@@@@@. + @@@.@@@. + @@...@@. + @.....@. + ........ + ........ + ........ + ........ + +u+0058: +"X": + ........ + ........ + @@...@@. + @@...@@. + .@@.@@.. + .@@.@@.. + ..@@@... + ..@@@... + .@@.@@.. + .@@.@@.. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+0059: +"Y": + ........ + ........ + @@....@@ + @@....@@ + .@@..@@. + .@@..@@. + ..@@@@.. + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ........ + ........ + ........ + ........ + +u+005a: +"Z": + ........ + ........ + @@@@@@@. + .....@@. + .....@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + @@...... + @@...... + @@@@@@@. + ........ + ........ + ........ + ........ + +u+005b: +bracketleft: + ........ + ........ + ..@@@@.. + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@@@.. + ........ + ........ + ........ + ........ + +u+005c: +backslash: + ........ + ........ + .@@..... + .@@..... + ..@@.... + ..@@.... + ...@@... + ...@@... + ....@@.. + ....@@.. + .....@@. + .....@@. + ........ + ........ + ........ + ........ + +u+005d: +bracketright: + ........ + ........ + ..@@@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ....@@.. + ..@@@@.. + ........ + ........ + ........ + ........ + +u+005e: +asciicircum: + ........ + ...@@... + ..@@@@.. + .@@..@@. + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+005f: +underscore: + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + @@@@@@@. + ........ + ........ + +u+0060: +grave: + ..@@.... + ...@@... + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + +u+0061: +"a": + ........ + ........ + ........ + ........ + ........ + .@@@@@.. + .....@@. + .@@@@@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + ........ + ........ + ........ + ........ + +u+0062: +"b": + ........ + ........ + @@...... + @@...... + @@...... + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + ........ + ........ + ........ + ........ + +u+0063: +"c": + ........ + ........ + ........ + ........ + ........ + .@@@@@.. + @@...@@. + @@...... + @@...... + @@...... + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0064: +"d": + ........ + ........ + .....@@. + .....@@. + .....@@. + .@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + ........ + ........ + ........ + ........ + +u+0065: +"e": + ........ + ........ + ........ + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@@@@@@. + @@...... + @@...... + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0066: +"f": + ........ + ........ + ...@@@@. + ..@@.... + ..@@.... + @@@@@@.. + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ........ + ........ + ........ + ........ + +u+0067: +"g": + ........ + ........ + ........ + ........ + ........ + .@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + .....@@. + .....@@. + .@@@@@.. + ........ + +u+0068: +"h": + ........ + ........ + @@...... + @@...... + @@...... + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+0069: +"i": + ........ + ........ + ...@@... + ...@@... + ........ + ..@@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ..@@@@.. + ........ + ........ + ........ + ........ + +u+006a: +"j": + ........ + ........ + .....@@. + .....@@. + ........ + ....@@@. + .....@@. + .....@@. + .....@@. + .....@@. + .....@@. + .....@@. + .@@..@@. + .@@..@@. + ..@@@@.. + ........ + +u+006b: +"k": + ........ + ........ + @@...... + @@...... + @@...... + @@...@@. + @@..@@.. + @@.@@... + @@@@.... + @@.@@... + @@..@@.. + @@...@@. + ........ + ........ + ........ + ........ + +u+006c: +"l": + ........ + ........ + ..@@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ..@@@@.. + ........ + ........ + ........ + ........ + +u+006d: +"m": + ........ + ........ + ........ + ........ + ........ + @@@@@@.. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + ........ + ........ + ........ + ........ + +u+006e: +"n": + ........ + ........ + ........ + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+006f: +"o": + ........ + ........ + ........ + ........ + ........ + .@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0070: +"p": + ........ + ........ + ........ + ........ + ........ + @@@@@@.. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@@@@@.. + @@...... + @@...... + @@...... + ........ + +u+0071: +"q": + ........ + ........ + ........ + ........ + ........ + .@@@@@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + .....@@. + .....@@. + .....@@. + ........ + +u+0072: +"r": + ........ + ........ + ........ + ........ + ........ + @@.@@@@. + @@@@.... + @@@..... + @@...... + @@...... + @@...... + @@...... + ........ + ........ + ........ + ........ + +u+0073: +"s": + ........ + ........ + ........ + ........ + ........ + .@@@@@@. + @@...... + @@...... + .@@@@@.. + .....@@. + .....@@. + @@@@@@.. + ........ + ........ + ........ + ........ + +u+0074: +"t": + ........ + ........ + ..@@.... + ..@@.... + ..@@.... + @@@@@@.. + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ...@@@@. + ........ + ........ + ........ + ........ + +u+0075: +"u": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + ........ + ........ + ........ + ........ + +u+0076: +"v": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + .@@.@@.. + .@@.@@.. + ..@@@... + ..@@@... + ........ + ........ + ........ + ........ + +u+0077: +"w": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + @@.@.@@. + .@@@@@.. + ........ + ........ + ........ + ........ + +u+0078: +"x": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + .@@.@@.. + ..@@@... + .@@.@@.. + @@...@@. + @@...@@. + ........ + ........ + ........ + ........ + +u+0079: +"y": + ........ + ........ + ........ + ........ + ........ + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + @@...@@. + .@@@@@@. + .....@@. + .....@@. + .@@@@@.. + ........ + +u+007a: +"z": + ........ + ........ + ........ + ........ + ........ + @@@@@@@. + ....@@.. + ...@@... + ..@@.... + .@@..... + @@...... + @@@@@@@. + ........ + ........ + ........ + ........ + +u+007b: +braceleft: + ........ + ........ + ...@@@.. + ..@@.... + ..@@.... + ..@@.... + .@@..... + ..@@.... + ..@@.... + ..@@.... + ..@@.... + ...@@@.. + ........ + ........ + ........ + ........ + +u+007c: +bar: + ........ + ........ + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ...@@... + ........ + ........ + ........ + ........ + +u+007d: +braceright: + ........ + ........ + .@@@.... + ...@@... + ...@@... + ...@@... + ....@@.. + ...@@... + ...@@... + ...@@... + ...@@... + .@@@.... + ........ + ........ + ........ + ........ + +u+007e: +asciitilde: + ........ + .@@@..@@ + @@.@@.@@ + @@..@@@. + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + ........ + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-18px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-18px.yaff new file mode 100644 index 0000000..d13e3fb --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-18px.yaff @@ -0,0 +1,2040 @@ +name: Terminus ASCII Bold 10x18 +spacing: character-cell +cell-size: 10 18 +family: Terminus ASCII +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 18 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 10 +ascent: 15 +descent: 3 +shift-up: -3 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 10 +converter: monobit v0.32 +source-name: ter-u18b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + .......... + .......... + .......... + .@@@..@@@. + .@@....@@. + .@@....@@. + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .@@....@@. + .@@....@@. + .@@@..@@@. + .......... + .......... + .......... + +u+0020: +space: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+0021: +exclam: + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+0022: +quotedbl: + .......... + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+0023: +numbersign: + .......... + .......... + .......... + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + .@@@@@@@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + .@@@@@@@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + .......... + .......... + .......... + +u+0024: +dollar: + .......... + .......... + ....@@.... + ....@@.... + ..@@@@@@.. + .@@.@@.@@. + .@@.@@.... + .@@.@@.... + .@@.@@.... + ..@@@@@@.. + ....@@.@@. + ....@@.@@. + ....@@.@@. + .@@.@@.@@. + ..@@@@@@.. + ....@@.... + ....@@.... + .......... + +u+0025: +percent: + .......... + .......... + .......... + .@@@..@@.. + .@.@..@@.. + .@@@.@@... + .....@@... + ....@@.... + ....@@.... + ...@@..... + ...@@..... + ..@@...... + ..@@.@@@.. + .@@..@.@.. + .@@..@@@.. + .......... + .......... + .......... + +u+0026: +ampersand: + .......... + .......... + .......... + ..@@@@.... + .@@..@@... + .@@..@@... + .@@..@@... + ..@@@@.... + ..@@@..@@. + .@@.@@.@@. + @@...@@@.. + @@....@@.. + @@....@@.. + .@@..@@@@. + ..@@@@.@@. + .......... + .......... + .......... + +u+0027: +quotesingle: + .......... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+0028: +parenleft: + .......... + .......... + .......... + .....@@... + ....@@.... + ....@@.... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ....@@.... + ....@@.... + .....@@... + .......... + .......... + .......... + +u+0029: +parenright: + .......... + .......... + .......... + ...@@..... + ....@@.... + ....@@.... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + ....@@.... + ....@@.... + ...@@..... + .......... + .......... + .......... + +u+002a: +asterisk: + .......... + .......... + .......... + .......... + .......... + .@@...@@.. + ..@@.@@... + ...@@@.... + @@@@@@@@@. + ...@@@.... + ..@@.@@... + .@@...@@.. + .......... + .......... + .......... + .......... + .......... + .......... + +u+002b: +plus: + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + .@@@@@@@@. + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + .......... + .......... + +u+002c: +comma: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + ...@@..... + .......... + .......... + +u+002d: +hyphen: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@@. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+002e: +period: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+002f: +slash: + .......... + .......... + .......... + ......@@.. + ......@@.. + .....@@... + .....@@... + ....@@.... + ....@@.... + ...@@..... + ...@@..... + ..@@...... + ..@@...... + .@@....... + .@@....... + .......... + .......... + .......... + +u+0030: +zero: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@...@@@. + .@@..@@@@. + .@@.@@.@@. + .@@@@..@@. + .@@@...@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0031: +one: + .......... + .......... + .......... + ....@@.... + ...@@@.... + ..@@@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ..@@@@@@.. + .......... + .......... + .......... + +u+0032: +two: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .......@@. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + +u+0033: +three: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .......@@. + .......@@. + ...@@@@@.. + .......@@. + .......@@. + .......@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0034: +four: + .......... + .......... + .......... + .......@@. + ......@@@. + .....@@@@. + ....@@.@@. + ...@@..@@. + ..@@...@@. + .@@....@@. + .@@....@@. + .@@@@@@@@. + .......@@. + .......@@. + .......@@. + .......... + .......... + .......... + +u+0035: +five: + .......... + .......... + .......... + .@@@@@@@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@@.. + .......@@. + .......@@. + .......@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0036: +six: + .......... + .......... + .......... + ...@@@@@.. + ..@@...... + .@@....... + .@@....... + .@@....... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0037: +seven: + .......... + .......... + .......... + .@@@@@@@@. + .@@....@@. + .@@....@@. + .......@@. + ......@@.. + ......@@.. + .....@@... + .....@@... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+0038: +eight: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0039: +nine: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......@@. + .......@@. + .......@@. + ......@@.. + ..@@@@@... + .......... + .......... + .......... + +u+003a: +colon: + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+003b: +semicolon: + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + ...@@..... + .......... + .......... + +u+003c: +less: + .......... + .......... + .......... + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .@@....... + ..@@...... + ...@@..... + ....@@.... + .....@@... + ......@@.. + .......... + .......... + .......... + +u+003d: +equal: + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@@. + .......... + .......... + .......... + .@@@@@@@@. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+003e: +greater: + .......... + .......... + .......... + .@@....... + ..@@...... + ...@@..... + ....@@.... + .....@@... + ......@@.. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .......... + .......... + .......... + +u+003f: +question: + .......... + .......... + .......... + ...@@@@... + ..@@..@@.. + .@@....@@. + .@@....@@. + .......@@. + ......@@.. + .....@@... + ....@@.... + ....@@.... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+0040: +at: + .......... + .......... + .......... + .@@@@@@@.. + @@.....@@. + @@.....@@. + @@..@@@@@. + @@.@@..@@. + @@.@@..@@. + @@.@@..@@. + @@.@@..@@. + @@..@@@@@. + @@........ + @@........ + .@@@@@@@@. + .......... + .......... + .......... + +u+0041: +"A": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + +u+0042: +"B": + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .......... + .......... + .......... + +u+0043: +"C": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0044: +"D": + .......... + .......... + .......... + .@@@@@@... + .@@...@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@...@@.. + .@@@@@@... + .......... + .......... + .......... + +u+0045: +"E": + .......... + .......... + .......... + .@@@@@@@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + +u+0046: +"F": + .......... + .......... + .......... + .@@@@@@@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .......... + .......... + .......... + +u+0047: +"G": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....... + .@@....... + .@@....... + .@@..@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0048: +"H": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + +u+0049: +"I": + .......... + .......... + .......... + ...@@@@... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ...@@@@... + .......... + .......... + .......... + +u+004a: +"J": + .......... + .......... + .......... + .....@@@@. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + .@@...@@.. + .@@...@@.. + .@@...@@.. + ..@@@@@... + .......... + .......... + .......... + +u+004b: +"K": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@...@@.. + .@@..@@... + .@@.@@.... + .@@@@..... + .@@@@..... + .@@.@@.... + .@@..@@... + .@@...@@.. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + +u+004c: +"L": + .......... + .......... + .......... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + +u+004d: +"M": + .......... + .......... + .......... + @.......@. + @@.....@@. + @@@...@@@. + @@@@.@@@@. + @@.@@@.@@. + @@..@..@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + .......... + .......... + .......... + +u+004e: +"N": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@@...@@. + .@@@@..@@. + .@@.@@.@@. + .@@..@@@@. + .@@...@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + +u+004f: +"O": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0050: +"P": + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .......... + .......... + .......... + +u+0051: +"Q": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@..@@@@. + ..@@@@@@.. + ......@@.. + .......@@. + .......... + +u+0052: +"R": + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .@@@@..... + .@@.@@.... + .@@..@@... + .@@...@@.. + .@@....@@. + .......... + .......... + .......... + +u+0053: +"S": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....... + .@@....... + ..@@@@@@.. + .......@@. + .......@@. + .......@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0054: +"T": + .......... + .......... + .......... + .@@@@@@@@. + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+0055: +"U": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0056: +"V": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + ...@@@@... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+0057: +"W": + .......... + .......... + .......... + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@..@..@@. + @@.@@@.@@. + @@@@.@@@@. + @@@...@@@. + @@.....@@. + @.......@. + .......... + .......... + .......... + +u+0058: +"X": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + ....@@.... + ....@@.... + ...@@@@... + ..@@..@@.. + ..@@..@@.. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + +u+0059: +"Y": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+005a: +"Z": + .......... + .......... + .......... + .@@@@@@@@. + .......@@. + .......@@. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .@@....... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + +u+005b: +bracketleft: + .......... + .......... + .......... + ...@@@@... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@@@... + .......... + .......... + .......... + +u+005c: +backslash: + .......... + .......... + .......... + .@@....... + .@@....... + ..@@...... + ..@@...... + ...@@..... + ...@@..... + ....@@.... + ....@@.... + .....@@... + .....@@... + ......@@.. + ......@@.. + .......... + .......... + .......... + +u+005d: +bracketright: + .......... + .......... + .......... + ...@@@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + ...@@@@... + .......... + .......... + .......... + +u+005e: +asciicircum: + .......... + ....@@.... + ...@@@@... + ..@@..@@.. + .@@....@@. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+005f: +underscore: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@@. + .......... + +u+0060: +grave: + ..@@...... + ...@@..... + ....@@.... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+0061: +"a": + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .......@@. + .......@@. + ..@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......... + .......... + .......... + +u+0062: +"b": + .......... + .......... + .......... + .@@....... + .@@....... + .@@....... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .......... + .......... + .......... + +u+0063: +"c": + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0064: +"d": + .......... + .......... + .......... + .......@@. + .......@@. + .......@@. + ..@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......... + .......... + .......... + +u+0065: +"e": + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@@. + .@@....... + .@@....... + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0066: +"f": + .......... + .......... + .......... + .....@@@@. + ....@@.... + ....@@.... + ..@@@@@@.. + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+0067: +"g": + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......@@. + .......@@. + ..@@@@@@.. + +u+0068: +"h": + .......... + .......... + .......... + .@@....... + .@@....... + .@@....... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + +u+0069: +"i": + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + ...@@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ...@@@@... + .......... + .......... + .......... + +u+006a: +"j": + .......... + .......... + .......... + ......@@.. + ......@@.. + .......... + .....@@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + +u+006b: +"k": + .......... + .......... + .......... + .@@....... + .@@....... + .@@....... + .@@....@@. + .@@...@@.. + .@@..@@... + .@@.@@.... + .@@@@..... + .@@.@@.... + .@@..@@... + .@@...@@.. + .@@....@@. + .......... + .......... + .......... + +u+006c: +"l": + .......... + .......... + .......... + ...@@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ...@@@@... + .......... + .......... + .......... + +u+006d: +"m": + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@.. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .......... + .......... + .......... + +u+006e: +"n": + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + +u+006f: +"o": + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0070: +"p": + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .@@....... + .@@....... + .@@....... + +u+0071: +"q": + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......@@. + .......@@. + .......@@. + +u+0072: +"r": + .......... + .......... + .......... + .......... + .......... + .......... + .@@.@@@@@. + .@@@@..... + .@@@...... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .......... + .......... + .......... + +u+0073: +"s": + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....... + .@@....... + ..@@@@@@.. + .......@@. + .......@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0074: +"t": + .......... + .......... + .......... + ...@@..... + ...@@..... + ...@@..... + .@@@@@@... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ....@@@@.. + .......... + .......... + .......... + +u+0075: +"u": + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......... + .......... + .......... + +u+0076: +"v": + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+0077: +"w": + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + ..@@@@@@.. + .......... + .......... + .......... + +u+0078: +"x": + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + ..@@..@@.. + ...@@@@... + ....@@.... + ...@@@@... + ..@@..@@.. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + +u+0079: +"y": + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......@@. + .......@@. + ..@@@@@@.. + +u+007a: +"z": + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@@. + .......@@. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + +u+007b: +braceleft: + .......... + .......... + .......... + .....@@@.. + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ..@@@..... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .....@@@.. + .......... + .......... + .......... + +u+007c: +bar: + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+007d: +braceright: + .......... + .......... + .......... + ..@@@..... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .....@@@.. + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ..@@@..... + .......... + .......... + .......... + +u+007e: +asciitilde: + .......... + ..@@@..@@. + .@@.@@.@@. + .@@.@@.@@. + .@@..@@@.. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-20px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-20px.yaff new file mode 100644 index 0000000..d6c2401 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-20px.yaff @@ -0,0 +1,2232 @@ +name: Terminus Bold 10x20 +spacing: character-cell +cell-size: 10 20 +family: Terminus +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 20 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 10 +ascent: 16 +descent: 4 +shift-up: -4 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 10 +converter: monobit v0.32 +source-name: ter-u20b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + .......... + .......... + .......... + .@@@..@@@. + .@@....@@. + .@@....@@. + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .@@....@@. + .@@....@@. + .@@@..@@@. + .......... + .......... + .......... + .......... + +u+0020: +space: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+0021: +exclam: + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+0022: +quotedbl: + .......... + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+0023: +numbersign: + .......... + .......... + .......... + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + .@@@@@@@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + .@@@@@@@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + .......... + .......... + .......... + .......... + +u+0024: +dollar: + .......... + .......... + ....@@.... + ....@@.... + ..@@@@@@.. + .@@.@@.@@. + .@@.@@.... + .@@.@@.... + .@@.@@.... + ..@@@@@@.. + ....@@.@@. + ....@@.@@. + ....@@.@@. + .@@.@@.@@. + ..@@@@@@.. + ....@@.... + ....@@.... + .......... + .......... + .......... + +u+0025: +percent: + .......... + .......... + .......... + .......... + .@@@..@@.. + .@.@..@@.. + .@@@.@@... + .....@@... + ....@@.... + ....@@.... + ...@@..... + ...@@..... + ..@@...... + ..@@.@@@.. + .@@..@.@.. + .@@..@@@.. + .......... + .......... + .......... + .......... + +u+0026: +ampersand: + .......... + .......... + .......... + ..@@@@.... + .@@..@@... + .@@..@@... + .@@..@@... + ..@@@@.... + ...@@..... + ..@@@..@@. + .@@.@@.@@. + @@...@@@.. + @@....@@.. + @@....@@.. + .@@..@@@@. + ..@@@@.@@. + .......... + .......... + .......... + .......... + +u+0027: +quotesingle: + .......... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+0028: +parenleft: + .......... + .......... + .......... + .....@@... + ....@@.... + ....@@.... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ....@@.... + ....@@.... + .....@@... + .......... + .......... + .......... + .......... + +u+0029: +parenright: + .......... + .......... + .......... + ...@@..... + ....@@.... + ....@@.... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + ....@@.... + ....@@.... + ...@@..... + .......... + .......... + .......... + .......... + +u+002a: +asterisk: + .......... + .......... + .......... + .......... + .......... + .......... + .@@...@@.. + ..@@.@@... + ...@@@.... + @@@@@@@@@. + ...@@@.... + ..@@.@@... + .@@...@@.. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+002b: +plus: + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + .@@@@@@@@. + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+002c: +comma: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + ...@@..... + .......... + .......... + .......... + +u+002d: +hyphen: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@@. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+002e: +period: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+002f: +slash: + .......... + .......... + .......... + .......... + ......@@.. + ......@@.. + .....@@... + .....@@... + ....@@.... + ....@@.... + ...@@..... + ...@@..... + ..@@...... + ..@@...... + .@@....... + .@@....... + .......... + .......... + .......... + .......... + +u+0030: +zero: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@...@@@. + .@@..@@@@. + .@@.@@.@@. + .@@@@..@@. + .@@@...@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0031: +one: + .......... + .......... + .......... + ....@@.... + ...@@@.... + ..@@@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0032: +two: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .......@@. + .......@@. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + .......... + +u+0033: +three: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .......@@. + .......@@. + .......@@. + ...@@@@@.. + .......@@. + .......@@. + .......@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0034: +four: + .......... + .......... + .......... + .......@@. + ......@@@. + .....@@@@. + ....@@.@@. + ...@@..@@. + ..@@...@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@@. + .......@@. + .......@@. + .......@@. + .......... + .......... + .......... + .......... + +u+0035: +five: + .......... + .......... + .......... + .@@@@@@@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@@.. + .......@@. + .......@@. + .......@@. + .......@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0036: +six: + .......... + .......... + .......... + ...@@@@@.. + ..@@...... + .@@....... + .@@....... + .@@....... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0037: +seven: + .......... + .......... + .......... + .@@@@@@@@. + .@@....@@. + .@@....@@. + .......@@. + ......@@.. + ......@@.. + .....@@... + .....@@... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+0038: +eight: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0039: +nine: + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......@@. + .......@@. + .......@@. + ......@@.. + ..@@@@@... + .......... + .......... + .......... + .......... + +u+003a: +colon: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + .......... + +u+003b: +semicolon: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + ...@@..... + .......... + .......... + .......... + +u+003c: +less: + .......... + .......... + .......... + .......@@. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + ..@@...... + ...@@..... + ....@@.... + .....@@... + ......@@.. + .......@@. + .......... + .......... + .......... + .......... + +u+003d: +equal: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@@. + .......... + .......... + .......... + .@@@@@@@@. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+003e: +greater: + .......... + .......... + .......... + .@@....... + ..@@...... + ...@@..... + ....@@.... + .....@@... + ......@@.. + .......@@. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .......... + .......... + .......... + .......... + +u+003f: +question: + .......... + .......... + .......... + ...@@@@... + ..@@..@@.. + .@@....@@. + .@@....@@. + .......@@. + ......@@.. + .....@@... + ....@@.... + ....@@.... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+0040: +at: + .......... + .......... + .......... + .@@@@@@@.. + @@.....@@. + @@.....@@. + @@..@@@@@. + @@.@@..@@. + @@.@@..@@. + @@.@@..@@. + @@.@@..@@. + @@.@@..@@. + @@..@@@@@. + @@........ + @@........ + .@@@@@@@@. + .......... + .......... + .......... + .......... + +u+0041: +"A": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+0042: +"B": + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .......... + .......... + .......... + .......... + +u+0043: +"C": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0044: +"D": + .......... + .......... + .......... + .@@@@@@... + .@@...@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@...@@.. + .@@@@@@... + .......... + .......... + .......... + .......... + +u+0045: +"E": + .......... + .......... + .......... + .@@@@@@@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + .......... + +u+0046: +"F": + .......... + .......... + .......... + .@@@@@@@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .......... + .......... + .......... + .......... + +u+0047: +"G": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....... + .@@....... + .@@....... + .@@..@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0048: +"H": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+0049: +"I": + .......... + .......... + .......... + ...@@@@... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ...@@@@... + .......... + .......... + .......... + .......... + +u+004a: +"J": + .......... + .......... + .......... + .....@@@@. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + .@@...@@.. + .@@...@@.. + .@@...@@.. + ..@@@@@... + .......... + .......... + .......... + .......... + +u+004b: +"K": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@...@@.. + .@@..@@... + .@@.@@.... + .@@@@..... + .@@@...... + .@@@@..... + .@@.@@.... + .@@..@@... + .@@...@@.. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+004c: +"L": + .......... + .......... + .......... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + .......... + +u+004d: +"M": + .......... + .......... + .......... + @.......@. + @@.....@@. + @@@...@@@. + @@@@.@@@@. + @@.@@@.@@. + @@..@..@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + .......... + .......... + .......... + .......... + +u+004e: +"N": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@...@@. + .@@@@..@@. + .@@.@@.@@. + .@@..@@@@. + .@@...@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+004f: +"O": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0050: +"P": + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .......... + .......... + .......... + .......... + +u+0051: +"Q": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@..@@@@. + ..@@@@@@.. + ......@@.. + .......@@. + .......... + .......... + +u+0052: +"R": + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .@@@@..... + .@@.@@.... + .@@..@@... + .@@...@@.. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+0053: +"S": + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....... + .@@....... + .@@....... + ..@@@@@@.. + .......@@. + .......@@. + .......@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0054: +"T": + .......... + .......... + .......... + .@@@@@@@@. + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+0055: +"U": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0056: +"V": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + ...@@@@... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+0057: +"W": + .......... + .......... + .......... + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@.....@@. + @@..@..@@. + @@.@@@.@@. + @@@@.@@@@. + @@@...@@@. + @@.....@@. + @.......@. + .......... + .......... + .......... + .......... + +u+0058: +"X": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + ....@@.... + ...@@@@... + ..@@..@@.. + ..@@..@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+0059: +"Y": + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+005a: +"Z": + .......... + .......... + .......... + .@@@@@@@@. + .......@@. + .......@@. + .......@@. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .@@....... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + .......... + +u+005b: +bracketleft: + .......... + .......... + .......... + ...@@@@... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@@@... + .......... + .......... + .......... + .......... + +u+005c: +backslash: + .......... + .......... + .......... + .......... + .@@....... + .@@....... + ..@@...... + ..@@...... + ...@@..... + ...@@..... + ....@@.... + ....@@.... + .....@@... + .....@@... + ......@@.. + ......@@.. + .......... + .......... + .......... + .......... + +u+005d: +bracketright: + .......... + .......... + .......... + ...@@@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + .....@@... + ...@@@@... + .......... + .......... + .......... + .......... + +u+005e: +asciicircum: + .......... + ....@@.... + ...@@@@... + ..@@..@@.. + .@@....@@. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+005f: +underscore: + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@@. + .......... + .......... + +u+0060: +grave: + ..@@...... + ...@@..... + ....@@.... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + +u+0061: +"a": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .......@@. + .......@@. + ..@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......... + .......... + .......... + .......... + +u+0062: +"b": + .......... + .......... + .......... + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .......... + .......... + .......... + .......... + +u+0063: +"c": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0064: +"d": + .......... + .......... + .......... + .......@@. + .......@@. + .......@@. + .......@@. + ..@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......... + .......... + .......... + .......... + +u+0065: +"e": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@@. + .@@....... + .@@....... + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0066: +"f": + .......... + .......... + .......... + .....@@@@. + ....@@.... + ....@@.... + ....@@.... + ..@@@@@@.. + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+0067: +"g": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......@@. + .......@@. + ..@@@@@@.. + .......... + +u+0068: +"h": + .......... + .......... + .......... + .@@....... + .@@....... + .@@....... + .@@....... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+0069: +"i": + .......... + .......... + .......... + ....@@.... + ....@@.... + .......... + .......... + ...@@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ...@@@@... + .......... + .......... + .......... + .......... + +u+006a: +"j": + .......... + .......... + .......... + ......@@.. + ......@@.. + .......... + .......... + .....@@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ......@@.. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + .......... + +u+006b: +"k": + .......... + .......... + .......... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....@@. + .@@...@@.. + .@@..@@... + .@@.@@.... + .@@@@..... + .@@.@@.... + .@@..@@... + .@@...@@.. + .@@....@@. + .......... + .......... + .......... + .......... + +u+006c: +"l": + .......... + .......... + .......... + ...@@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ...@@@@... + .......... + .......... + .......... + .......... + +u+006d: +"m": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@.. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .......... + .......... + .......... + .......... + +u+006e: +"n": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+006f: +"o": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0070: +"p": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@.. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@@@@@@.. + .@@....... + .@@....... + .@@....... + .......... + +u+0071: +"q": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......@@. + .......@@. + .......@@. + .......... + +u+0072: +"r": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@.@@@@@. + .@@@@..... + .@@@...... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .@@....... + .......... + .......... + .......... + .......... + +u+0073: +"s": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + ..@@@@@@.. + .@@....@@. + .@@....... + .@@....... + ..@@@@@@.. + .......@@. + .......@@. + .@@....@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0074: +"t": + .......... + .......... + .......... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + .@@@@@@... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ...@@..... + ....@@@@.. + .......... + .......... + .......... + .......... + +u+0075: +"u": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......... + .......... + .......... + .......... + +u+0076: +"v": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + ..@@..@@.. + ..@@..@@.. + ..@@..@@.. + ...@@@@... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+0077: +"w": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + .@@.@@.@@. + ..@@@@@@.. + .......... + .......... + .......... + .......... + +u+0078: +"x": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + ..@@..@@.. + ...@@@@... + ....@@.... + ...@@@@... + ..@@..@@.. + .@@....@@. + .@@....@@. + .......... + .......... + .......... + .......... + +u+0079: +"y": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + .@@....@@. + ..@@@@@@@. + .......@@. + .......@@. + ..@@@@@@.. + .......... + +u+007a: +"z": + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .@@@@@@@@. + .......@@. + ......@@.. + .....@@... + ....@@.... + ...@@..... + ..@@...... + .@@....... + .@@@@@@@@. + .......... + .......... + .......... + .......... + +u+007b: +braceleft: + .......... + .......... + .......... + .....@@@.. + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ..@@@..... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .....@@@.. + .......... + .......... + .......... + .......... + +u+007c: +bar: + .......... + .......... + .......... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .......... + .......... + .......... + .......... + +u+007d: +braceright: + .......... + .......... + .......... + ..@@@..... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + .....@@@.. + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ....@@.... + ..@@@..... + .......... + .......... + .......... + .......... + +u+007e: +asciitilde: + .......... + ..@@@..@@. + .@@.@@.@@. + .@@.@@.@@. + .@@..@@@.. + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + .......... + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-22px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-22px.yaff new file mode 100644 index 0000000..25f6aa5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-22px.yaff @@ -0,0 +1,2424 @@ +name: Terminus Bold 11x22 +spacing: character-cell +cell-size: 11 22 +family: Terminus +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 22 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 11 +ascent: 17 +descent: 5 +shift-up: -5 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 11 +converter: monobit v0.32 +source-name: ter-u22b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + ........... + ........... + ........... + .@@@@.@@@@. + .@@.....@@. + .@@.....@@. + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@@@.@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0020: +space: + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+0021: +exclam: + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + +u+0022: +quotedbl: + ........... + ..@@..@@... + ..@@..@@... + ..@@..@@... + ..@@..@@... + ..@@..@@... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+0023: +numbersign: + ........... + ........... + ........... + ..@@..@@... + ..@@..@@... + ..@@..@@... + ..@@..@@... + @@@@@@@@@@. + ..@@..@@... + ..@@..@@... + ..@@..@@... + ..@@..@@... + @@@@@@@@@@. + ..@@..@@... + ..@@..@@... + ..@@..@@... + ..@@..@@... + ........... + ........... + ........... + ........... + ........... + +u+0024: +dollar: + ........... + ........... + ....@@..... + ....@@..... + ..@@@@@@... + .@@.@@.@@.. + @@..@@..@@. + @@..@@..... + @@..@@..... + .@@.@@..... + ..@@@@@@... + ....@@.@@.. + ....@@..@@. + ....@@..@@. + @@..@@..@@. + .@@.@@.@@.. + ..@@@@@@... + ....@@..... + ....@@..... + ........... + ........... + ........... + +u+0025: +percent: + ........... + ........... + ........... + .@@@...@@.. + @@.@@..@@.. + @@.@@.@@... + .@@@..@@... + .....@@.... + .....@@.... + ....@@..... + ....@@..... + ...@@...... + ...@@...... + ..@@..@@@.. + ..@@.@@.@@. + .@@..@@.@@. + .@@...@@@.. + ........... + ........... + ........... + ........... + ........... + +u+0026: +ampersand: + ........... + ........... + ........... + ..@@@@@.... + .@@...@@... + .@@...@@... + .@@...@@... + ..@@.@@.... + ...@@@..... + ..@@@@..... + .@@..@@.@@. + @@....@@@@. + @@.....@@.. + @@.....@@.. + @@.....@@.. + .@@...@@@@. + ..@@@@@.@@. + ........... + ........... + ........... + ........... + ........... + +u+0027: +quotesingle: + ........... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+0028: +parenleft: + ........... + ........... + ........... + ......@@... + .....@@.... + ....@@..... + ....@@..... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ....@@..... + ....@@..... + .....@@.... + ......@@... + ........... + ........... + ........... + ........... + ........... + +u+0029: +parenright: + ........... + ........... + ........... + ...@@...... + ....@@..... + .....@@.... + .....@@.... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + .....@@.... + .....@@.... + ....@@..... + ...@@...... + ........... + ........... + ........... + ........... + ........... + +u+002a: +asterisk: + ........... + ........... + ........... + ........... + ........... + ........... + .@@....@@.. + ..@@..@@... + ...@@@@.... + ....@@..... + @@@@@@@@@@. + ....@@..... + ...@@@@.... + ..@@..@@... + .@@....@@.. + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+002b: +plus: + ........... + ........... + ........... + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + @@@@@@@@@@. + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+002c: +comma: + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ...@@...... + ........... + ........... + ........... + ........... + +u+002d: +hyphen: + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+002e: +period: + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + +u+002f: +slash: + ........... + ........... + ........... + .......@@.. + .......@@.. + ......@@... + ......@@... + .....@@.... + .....@@.... + ....@@..... + ....@@..... + ...@@...... + ...@@...... + ..@@....... + ..@@....... + .@@........ + .@@........ + ........... + ........... + ........... + ........... + ........... + +u+0030: +zero: + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@....@@@. + .@@...@@@@. + .@@..@@.@@. + .@@.@@..@@. + .@@@@...@@. + .@@@....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0031: +one: + ........... + ........... + ........... + .....@@.... + ....@@@.... + ...@@@@.... + ..@@.@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + ..@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0032: +two: + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ........@@. + .......@@.. + ......@@... + .....@@.... + ....@@..... + ...@@...... + ..@@....... + .@@........ + .@@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0033: +three: + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + ........@@. + ........@@. + .......@@.. + ....@@@@... + .......@@.. + ........@@. + ........@@. + ........@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0034: +four: + ........... + ........... + ........... + ........@@. + .......@@@. + ......@@@@. + .....@@.@@. + ....@@..@@. + ...@@...@@. + ..@@....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@@@@@@@@. + ........@@. + ........@@. + ........@@. + ........... + ........... + ........... + ........... + ........... + +u+0035: +five: + ........... + ........... + ........... + .@@@@@@@@@. + .@@........ + .@@........ + .@@........ + .@@........ + .@@@@@@@... + .......@@.. + ........@@. + ........@@. + ........@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0036: +six: + ........... + ........... + ........... + ...@@@@@@.. + ..@@....... + .@@........ + .@@........ + .@@........ + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0037: +seven: + ........... + ........... + ........... + .@@@@@@@@@. + .@@.....@@. + .@@.....@@. + ........@@. + .......@@.. + .......@@.. + ......@@... + ......@@... + .....@@.... + .....@@.... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + +u+0038: +eight: + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0039: +nine: + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@....@@. + ...@@@@@@@. + ........@@. + ........@@. + ........@@. + .......@@.. + ..@@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+003a: +colon: + ........... + ........... + ........... + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + ........... + +u+003b: +semicolon: + ........... + ........... + ........... + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ...@@...... + ........... + ........... + ........... + ........... + +u+003c: +less: + ........... + ........... + ........... + .......@@.. + ......@@... + .....@@.... + ....@@..... + ...@@...... + ..@@....... + .@@........ + .@@........ + ..@@....... + ...@@...... + ....@@..... + .....@@.... + ......@@... + .......@@.. + ........... + ........... + ........... + ........... + ........... + +u+003d: +equal: + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@@@@@@@@. + ........... + ........... + ........... + ........... + .@@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+003e: +greater: + ........... + ........... + ........... + .@@........ + ..@@....... + ...@@...... + ....@@..... + .....@@.... + ......@@... + .......@@.. + .......@@.. + ......@@... + .....@@.... + ....@@..... + ...@@...... + ..@@....... + .@@........ + ........... + ........... + ........... + ........... + ........... + +u+003f: +question: + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + ........@@. + .......@@.. + ......@@... + .....@@.... + .....@@.... + ........... + ........... + .....@@.... + .....@@.... + .....@@.... + ........... + ........... + ........... + ........... + ........... + +u+0040: +at: + ........... + ........... + ........... + ..@@@@@@... + .@@....@@.. + @@......@@. + @@...@@@@@. + @@..@@..@@. + @@.@@...@@. + @@.@@...@@. + @@.@@...@@. + @@.@@...@@. + @@..@@..@@. + @@...@@@@@. + @@......... + .@@........ + ..@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0041: +"A": + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@@@@@@@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+0042: +"B": + ........... + ........... + ........... + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@....@@.. + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@....@@.. + .@@@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0043: +"C": + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0044: +"D": + ........... + ........... + ........... + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@....@@.. + .@@@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0045: +"E": + ........... + ........... + ........... + .@@@@@@@@@. + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@@@@@@... + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0046: +"F": + ........... + ........... + ........... + .@@@@@@@@@. + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@@@@@@... + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + ........... + ........... + ........... + ........... + ........... + +u+0047: +"G": + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@........ + .@@........ + .@@........ + .@@..@@@@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0048: +"H": + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@@@@@@@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+0049: +"I": + ........... + ........... + ........... + ..@@@@@@... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ..@@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+004a: +"J": + ........... + ........... + ........... + .....@@@@@@ + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .@@....@@.. + .@@....@@.. + .@@....@@.. + ..@@..@@... + ...@@@@.... + ........... + ........... + ........... + ........... + ........... + +u+004b: +"K": + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@....@@.. + .@@...@@... + .@@..@@.... + .@@.@@..... + .@@@@...... + .@@@@...... + .@@.@@..... + .@@..@@.... + .@@...@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+004c: +"L": + ........... + ........... + ........... + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+004d: +"M": + ........... + ........... + ........... + @........@. + @@......@@. + @@@....@@@. + @@@@..@@@@. + @@.@@@@.@@. + @@..@@..@@. + @@......@@. + @@......@@. + @@......@@. + @@......@@. + @@......@@. + @@......@@. + @@......@@. + @@......@@. + ........... + ........... + ........... + ........... + ........... + +u+004e: +"N": + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@@....@@. + .@@@@...@@. + .@@.@@..@@. + .@@..@@.@@. + .@@...@@@@. + .@@....@@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+004f: +"O": + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0050: +"P": + ........... + ........... + ........... + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@....@@.. + .@@@@@@@... + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + ........... + ........... + ........... + ........... + ........... + +u+0051: +"Q": + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@.@@@@.. + ...@@@@@... + .......@@.. + ........@@. + ........... + ........... + ........... + +u+0052: +"R": + ........... + ........... + ........... + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@....@@.. + .@@@@@@@... + .@@@@...... + .@@.@@..... + .@@..@@.... + .@@...@@... + .@@....@@.. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+0053: +"S": + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@........ + .@@........ + ..@@....... + ...@@@@@... + .......@@.. + ........@@. + ........@@. + ........@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0054: +"T": + ........... + ........... + ........... + @@@@@@@@@@. + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + +u+0055: +"U": + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0056: +"V": + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ..@@...@@.. + ..@@...@@.. + ..@@...@@.. + ...@@.@@... + ...@@.@@... + ....@@@.... + ....@@@.... + ....@@@.... + ........... + ........... + ........... + ........... + ........... + +u+0057: +"W": + ........... + ........... + ........... + @@......@@. + @@......@@. + @@......@@. + @@......@@. + @@......@@. + @@......@@. + @@......@@. + @@..@@..@@. + @@..@@..@@. + @@.@@@@.@@. + @@@@..@@@@. + @@@....@@@. + @@......@@. + @........@. + ........... + ........... + ........... + ........... + ........... + +u+0058: +"X": + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ..@@...@@.. + ...@@.@@... + ...@@.@@... + ....@@@.... + ....@@@.... + ...@@.@@... + ...@@.@@... + ..@@...@@.. + ..@@...@@.. + .@@.....@@. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+0059: +"Y": + ........... + ........... + ........... + @@......@@. + @@......@@. + .@@....@@.. + .@@....@@.. + ..@@..@@... + ..@@..@@... + ...@@@@.... + ...@@@@.... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + +u+005a: +"Z": + ........... + ........... + ........... + .@@@@@@@@@. + ........@@. + ........@@. + ........@@. + .......@@.. + ......@@... + .....@@.... + ....@@..... + ...@@...... + ..@@....... + .@@........ + .@@........ + .@@........ + .@@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+005b: +bracketleft: + ........... + ........... + ........... + ...@@@@@... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@...... + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+005c: +backslash: + ........... + ........... + ........... + .@@........ + .@@........ + ..@@....... + ..@@....... + ...@@...... + ...@@...... + ....@@..... + ....@@..... + .....@@.... + .....@@.... + ......@@... + ......@@... + .......@@.. + .......@@.. + ........... + ........... + ........... + ........... + ........... + +u+005d: +bracketright: + ........... + ........... + ........... + ...@@@@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ......@@... + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+005e: +asciicircum: + ........... + .....@..... + ....@@@.... + ...@@.@@... + ..@@...@@.. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+005f: +underscore: + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@@@@@@@@. + ........... + ........... + ........... + +u+0060: +grave: + ...@@...... + ....@@..... + .....@@.... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + +u+0061: +"a": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ..@@@@@@... + .......@@.. + ........@@. + ........@@. + ..@@@@@@@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0062: +"b": + ........... + ........... + ........... + .@@........ + .@@........ + .@@........ + .@@........ + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@....@@.. + .@@@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0063: +"c": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@........ + .@@........ + .@@........ + .@@........ + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0064: +"d": + ........... + ........... + ........... + ........@@. + ........@@. + ........@@. + ........@@. + ...@@@@@@@. + ..@@....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@....@@. + ...@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0065: +"e": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@@@@@@@@. + .@@........ + .@@........ + .@@........ + ..@@....@@. + ...@@@@@@.. + ........... + ........... + ........... + ........... + ........... + +u+0066: +"f": + ........... + ........... + ........... + .....@@@@@. + ....@@..... + ....@@..... + ....@@..... + .@@@@@@@@.. + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + +u+0067: +"g": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ...@@@@@@@. + ..@@....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@....@@. + ...@@@@@@@. + ........@@. + ........@@. + .......@@.. + ..@@@@@@... + ........... + +u+0068: +"h": + ........... + ........... + ........... + .@@........ + .@@........ + .@@........ + .@@........ + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+0069: +"i": + ........... + ........... + ........... + .....@@.... + .....@@.... + .....@@.... + ........... + ...@@@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + ...@@@@@@.. + ........... + ........... + ........... + ........... + ........... + +u+006a: +"j": + ........... + ........... + ........... + .......@@.. + .......@@.. + .......@@.. + ........... + .....@@@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + .......@@.. + ..@@...@@.. + ..@@...@@.. + ..@@...@@.. + ...@@@@@... + ........... + +u+006b: +"k": + ........... + ........... + ........... + ..@@....... + ..@@....... + ..@@....... + ..@@....... + ..@@....@@. + ..@@...@@.. + ..@@..@@... + ..@@.@@.... + ..@@@@..... + ..@@@@..... + ..@@.@@.... + ..@@..@@... + ..@@...@@.. + ..@@....@@. + ........... + ........... + ........... + ........... + ........... + +u+006c: +"l": + ........... + ........... + ........... + ...@@@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + ...@@@@@@.. + ........... + ........... + ........... + ........... + ........... + +u+006d: +"m": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + @@@@@@@@... + @@..@@.@@.. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + ........... + ........... + ........... + ........... + ........... + +u+006e: +"n": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+006f: +"o": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ...@@@@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@@@@... + ........... + ........... + ........... + ........... + ........... + +u+0070: +"p": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@@@@@@... + .@@....@@.. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@....@@.. + .@@@@@@@... + .@@........ + .@@........ + .@@........ + .@@........ + ........... + +u+0071: +"q": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ...@@@@@@@. + ..@@....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@....@@. + ...@@@@@@@. + ........@@. + ........@@. + ........@@. + ........@@. + ........... + +u+0072: +"r": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@..@@@@@. + .@@.@@..... + .@@@@...... + .@@@....... + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + .@@........ + ........... + ........... + ........... + ........... + ........... + +u+0073: +"s": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ..@@@@@@@.. + .@@.....@@. + .@@........ + .@@........ + ..@@@@@@@.. + ........@@. + ........@@. + ........@@. + .@@.....@@. + ..@@@@@@@.. + ........... + ........... + ........... + ........... + ........... + +u+0074: +"t": + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + .@@@@@@@@.. + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + .....@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0075: +"u": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@....@@. + ...@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+0076: +"v": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ..@@...@@.. + ..@@...@@.. + ...@@.@@... + ...@@.@@... + ....@@@.... + ....@@@.... + ........... + ........... + ........... + ........... + ........... + +u+0077: +"w": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + @@......@@. + @@......@@. + @@......@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + @@..@@..@@. + .@@@@@@@@.. + ........... + ........... + ........... + ........... + ........... + +u+0078: +"x": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + ..@@...@@.. + ...@@.@@... + ....@@@.... + ....@@@.... + ...@@.@@... + ..@@...@@.. + .@@.....@@. + .@@.....@@. + ........... + ........... + ........... + ........... + ........... + +u+0079: +"y": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + .@@.....@@. + ..@@....@@. + ...@@@@@@@. + ........@@. + ........@@. + .......@@.. + ..@@@@@@... + ........... + +u+007a: +"z": + ........... + ........... + ........... + ........... + ........... + ........... + ........... + .@@@@@@@@@. + ........@@. + .......@@.. + ......@@... + .....@@.... + ....@@..... + ...@@...... + ..@@....... + .@@........ + .@@@@@@@@@. + ........... + ........... + ........... + ........... + ........... + +u+007b: +braceleft: + ........... + ........... + ........... + ......@@@.. + .....@@.... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ..@@@...... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + .....@@.... + ......@@@.. + ........... + ........... + ........... + ........... + ........... + +u+007c: +bar: + ........... + ........... + ........... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ....@@..... + ........... + ........... + ........... + ........... + ........... + +u+007d: +braceright: + ........... + ........... + ........... + ..@@@...... + ....@@..... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + ......@@@.. + .....@@.... + .....@@.... + .....@@.... + .....@@.... + .....@@.... + ....@@..... + ..@@@...... + ........... + ........... + ........... + ........... + ........... + +u+007e: +asciitilde: + ........... + ..@@@...@@. + .@@.@@..@@. + .@@..@@.@@. + .@@...@@@.. + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + ........... + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-24px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-24px.yaff new file mode 100644 index 0000000..c2822df --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-24px.yaff @@ -0,0 +1,2616 @@ +name: Terminus Bold 12x24 +spacing: character-cell +cell-size: 12 24 +family: Terminus +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 24 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 12 +ascent: 19 +descent: 5 +shift-up: -5 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 12 +converter: monobit v0.32 +source-name: ter-u24b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + ............ + ............ + ............ + ............ + .@@@@..@@@@. + .@@......@@. + .@@......@@. + .@@......@@. + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + .@@@@..@@@@. + ............ + ............ + ............ + ............ + ............ + +u+0020: +space: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+0021: +exclam: + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+0022: +quotedbl: + ............ + ............ + ...@@..@@... + ...@@..@@... + ...@@..@@... + ...@@..@@... + ...@@..@@... + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+0023: +numbersign: + ............ + ............ + ............ + ............ + ...@@..@@... + ...@@..@@... + ...@@..@@... + ...@@..@@... + .@@@@@@@@@@. + ...@@..@@... + ...@@..@@... + ...@@..@@... + ...@@..@@... + ...@@..@@... + .@@@@@@@@@@. + ...@@..@@... + ...@@..@@... + ...@@..@@... + ...@@..@@... + ............ + ............ + ............ + ............ + ............ + +u+0024: +dollar: + ............ + ............ + ............ + .....@@..... + .....@@..... + ...@@@@@@... + ..@@.@@.@@.. + .@@..@@..@@. + .@@..@@..... + .@@..@@..... + ..@@.@@..... + ...@@@@@@... + .....@@.@@.. + .....@@..@@. + .....@@..@@. + .@@..@@..@@. + ..@@.@@.@@.. + ...@@@@@@... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + +u+0025: +percent: + ............ + ............ + ............ + ............ + ............ + ..@@@...@@.. + .@@.@@..@@.. + .@@.@@.@@... + ..@@@..@@... + ......@@.... + ......@@.... + .....@@..... + .....@@..... + ....@@...... + ....@@...... + ...@@..@@@.. + ...@@.@@.@@. + ..@@..@@.@@. + ..@@...@@@.. + ............ + ............ + ............ + ............ + ............ + +u+0026: +ampersand: + ............ + ............ + ............ + ............ + ....@@@..... + ...@@.@@.... + ..@@...@@... + ..@@...@@... + ..@@...@@... + ...@@.@@.... + ....@@@..... + ...@@@@..@@. + ..@@..@@.@@. + .@@....@@@.. + .@@.....@@.. + .@@.....@@.. + .@@....@@@.. + ..@@..@@.@@. + ...@@@@..@@. + ............ + ............ + ............ + ............ + ............ + +u+0027: +quotesingle: + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+0028: +parenleft: + ............ + ............ + ............ + ............ + ......@@.... + .....@@..... + ....@@...... + ....@@...... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ....@@...... + ....@@...... + .....@@..... + ......@@.... + ............ + ............ + ............ + ............ + ............ + +u+0029: +parenright: + ............ + ............ + ............ + ............ + ...@@....... + ....@@...... + .....@@..... + .....@@..... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + .....@@..... + .....@@..... + ....@@...... + ...@@....... + ............ + ............ + ............ + ............ + ............ + +u+002a: +asterisk: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@.....@@.. + ..@@...@@... + ...@@.@@.... + ....@@@..... + @@@@@@@@@@@. + ....@@@..... + ...@@.@@.... + ..@@...@@... + .@@.....@@.. + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+002b: +plus: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .@@@@@@@@@@. + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+002c: +comma: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ....@@...... + ............ + ............ + ............ + ............ + +u+002d: +hyphen: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+002e: +period: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+002f: +slash: + ............ + ............ + ............ + ............ + ............ + ........@@.. + ........@@.. + .......@@... + .......@@... + ......@@.... + ......@@.... + .....@@..... + .....@@..... + ....@@...... + ....@@...... + ...@@....... + ...@@....... + ..@@........ + ..@@........ + ............ + ............ + ............ + ............ + ............ + +u+0030: +zero: + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@.....@@@. + .@@....@@@@. + .@@...@@.@@. + .@@..@@..@@. + .@@.@@...@@. + .@@@@....@@. + .@@@.....@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0031: +one: + ............ + ............ + ............ + ............ + .....@@..... + ....@@@..... + ...@@@@..... + ..@@.@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ..@@@@@@@@.. + ............ + ............ + ............ + ............ + ............ + +u+0032: +two: + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .........@@. + ........@@.. + .......@@... + ......@@.... + .....@@..... + ....@@...... + ...@@....... + ..@@........ + .@@......... + .@@@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+0033: +three: + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .........@@. + .........@@. + .........@@. + ........@@.. + ....@@@@@... + ........@@.. + .........@@. + .........@@. + .........@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0034: +four: + ............ + ............ + ............ + ............ + .........@@. + ........@@@. + .......@@@@. + ......@@.@@. + .....@@..@@. + ....@@...@@. + ...@@....@@. + ..@@.....@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@@@@@@@@@. + .........@@. + .........@@. + .........@@. + ............ + ............ + ............ + ............ + ............ + +u+0035: +five: + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@@@@@@@... + ........@@.. + .........@@. + .........@@. + .........@@. + .........@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0036: +six: + ............ + ............ + ............ + ............ + ...@@@@@@@.. + ..@@........ + .@@......... + .@@......... + .@@......... + .@@......... + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0037: +seven: + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + .@@......@@. + .@@......@@. + .........@@. + ........@@.. + ........@@.. + .......@@... + .......@@... + ......@@.... + ......@@.... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+0038: +eight: + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0039: +nine: + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@.....@@. + ...@@@@@@@@. + .........@@. + .........@@. + .........@@. + .........@@. + ........@@.. + ..@@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+003a: +colon: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + ............ + +u+003b: +semicolon: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ....@@...... + ............ + ............ + ............ + ............ + +u+003c: +less: + ............ + ............ + ............ + ............ + ........@@.. + .......@@... + ......@@.... + .....@@..... + ....@@...... + ...@@....... + ..@@........ + .@@......... + ..@@........ + ...@@....... + ....@@...... + .....@@..... + ......@@.... + .......@@... + ........@@.. + ............ + ............ + ............ + ............ + ............ + +u+003d: +equal: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+003e: +greater: + ............ + ............ + ............ + ............ + .@@......... + ..@@........ + ...@@....... + ....@@...... + .....@@..... + ......@@.... + .......@@... + ........@@.. + .......@@... + ......@@.... + .....@@..... + ....@@...... + ...@@....... + ..@@........ + .@@......... + ............ + ............ + ............ + ............ + ............ + +u+003f: +question: + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + ........@@.. + .......@@... + ......@@.... + .....@@..... + .....@@..... + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+0040: +at: + ............ + ............ + ............ + ............ + ..@@@@@@@... + .@@.....@@.. + @@.......@@. + @@....@@@@@. + @@...@@..@@. + @@..@@...@@. + @@..@@...@@. + @@..@@...@@. + @@..@@...@@. + @@..@@...@@. + @@...@@..@@. + @@....@@@@@. + @@.......... + .@@......... + ..@@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+0041: +"A": + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@@@@@@@@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+0042: +"B": + ............ + ............ + ............ + ............ + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@.....@@.. + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@.....@@.. + .@@@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0043: +"C": + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0044: +"D": + ............ + ............ + ............ + ............ + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@.....@@.. + .@@@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0045: +"E": + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@@@@@@@... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+0046: +"F": + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@@@@@@@... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + ............ + ............ + ............ + ............ + ............ + +u+0047: +"G": + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......... + .@@......... + .@@......... + .@@...@@@@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0048: +"H": + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@@@@@@@@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+0049: +"I": + ............ + ............ + ............ + ............ + ...@@@@@@... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+004a: +"J": + ............ + ............ + ............ + ............ + ......@@@@@@ + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + .@@.....@@.. + .@@.....@@.. + .@@.....@@.. + ..@@...@@... + ...@@@@@.... + ............ + ............ + ............ + ............ + ............ + +u+004b: +"K": + ............ + ............ + ............ + ............ + .@@......@@. + .@@.....@@.. + .@@....@@... + .@@...@@.... + .@@..@@..... + .@@.@@...... + .@@@@....... + .@@@........ + .@@@@....... + .@@.@@...... + .@@..@@..... + .@@...@@.... + .@@....@@... + .@@.....@@.. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+004c: +"L": + ............ + ............ + ............ + ............ + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+004d: +"M": + ............ + ............ + ............ + ............ + @.........@. + @@.......@@. + @@@.....@@@. + @@@@...@@@@. + @@.@@.@@.@@. + @@..@@@..@@. + @@...@...@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + ............ + ............ + ............ + ............ + ............ + +u+004e: +"N": + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@@.....@@. + .@@@@....@@. + .@@.@@...@@. + .@@..@@..@@. + .@@...@@.@@. + .@@....@@@@. + .@@.....@@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+004f: +"O": + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0050: +"P": + ............ + ............ + ............ + ............ + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@.....@@.. + .@@@@@@@@... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + ............ + ............ + ............ + ............ + ............ + +u+0051: +"Q": + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@..@@..@@. + ..@@..@@@@.. + ...@@@@@@... + ........@@.. + .........@@. + ............ + ............ + ............ + +u+0052: +"R": + ............ + ............ + ............ + ............ + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@.....@@.. + .@@@@@@@@... + .@@@@....... + .@@.@@...... + .@@..@@..... + .@@...@@.... + .@@....@@... + .@@.....@@.. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+0053: +"S": + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......... + .@@......... + .@@......... + ..@@........ + ...@@@@@@... + ........@@.. + .........@@. + .........@@. + .........@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0054: +"T": + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+0055: +"U": + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0056: +"V": + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ..@@....@@.. + ..@@....@@.. + ..@@....@@.. + ...@@..@@... + ...@@..@@... + ...@@..@@... + ....@@@@.... + ....@@@@.... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+0057: +"W": + ............ + ............ + ............ + ............ + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@.......@@. + @@...@...@@. + @@..@@@..@@. + @@.@@.@@.@@. + @@@@...@@@@. + @@@.....@@@. + @@.......@@. + @.........@. + ............ + ............ + ............ + ............ + ............ + +u+0058: +"X": + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + ..@@....@@.. + ..@@....@@.. + ...@@..@@... + ...@@..@@... + ....@@@@.... + .....@@..... + ....@@@@.... + ...@@..@@... + ...@@..@@... + ..@@....@@.. + ..@@....@@.. + .@@......@@. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+0059: +"Y": + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + ..@@....@@.. + ..@@....@@.. + ...@@..@@... + ...@@..@@... + ....@@@@.... + ....@@@@.... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+005a: +"Z": + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + .........@@. + .........@@. + .........@@. + ........@@.. + .......@@... + ......@@.... + .....@@..... + ....@@...... + ...@@....... + ..@@........ + .@@......... + .@@......... + .@@......... + .@@@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+005b: +bracketleft: + ............ + ............ + ............ + ............ + ...@@@@@.... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@....... + ...@@@@@.... + ............ + ............ + ............ + ............ + ............ + +u+005c: +backslash: + ............ + ............ + ............ + ............ + ............ + ..@@........ + ..@@........ + ...@@....... + ...@@....... + ....@@...... + ....@@...... + .....@@..... + .....@@..... + ......@@.... + ......@@.... + .......@@... + .......@@... + ........@@.. + ........@@.. + ............ + ............ + ............ + ............ + ............ + +u+005d: +bracketright: + ............ + ............ + ............ + ............ + ...@@@@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ......@@.... + ...@@@@@.... + ............ + ............ + ............ + ............ + ............ + +u+005e: +asciicircum: + ............ + ............ + .....@@..... + ....@@@@.... + ...@@..@@... + ..@@....@@.. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+005f: +underscore: + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + ............ + ............ + ............ + +u+0060: +grave: + ............ + ...@@....... + ....@@...... + .....@@..... + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + +u+0061: +"a": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ..@@@@@@@... + ........@@.. + .........@@. + .........@@. + ...@@@@@@@@. + ..@@.....@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@.....@@. + ...@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+0062: +"b": + ............ + ............ + ............ + ............ + .@@......... + .@@......... + .@@......... + .@@......... + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@.....@@.. + .@@@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0063: +"c": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0064: +"d": + ............ + ............ + ............ + ............ + .........@@. + .........@@. + .........@@. + .........@@. + ...@@@@@@@@. + ..@@.....@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@.....@@. + ...@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+0065: +"e": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@@@@@@@@@. + .@@......... + .@@......... + .@@......... + ..@@.....@@. + ...@@@@@@@.. + ............ + ............ + ............ + ............ + ............ + +u+0066: +"f": + ............ + ............ + ............ + ............ + ......@@@@@. + .....@@..... + .....@@..... + .....@@..... + ..@@@@@@@@.. + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+0067: +"g": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ...@@@@@@@@. + ..@@.....@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@@. + ...@@@@@@@@. + .........@@. + .........@@. + ........@@.. + ..@@@@@@@... + ............ + +u+0068: +"h": + ............ + ............ + ............ + ............ + .@@......... + .@@......... + .@@......... + .@@......... + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+0069: +"i": + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + ............ + ...@@@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+006a: +"j": + ............ + ............ + ............ + ............ + ........@@.. + ........@@.. + ........@@.. + ............ + ......@@@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ........@@.. + ..@@....@@.. + ..@@....@@.. + ...@@..@@... + ....@@@@.... + ............ + +u+006b: +"k": + ............ + ............ + ............ + ............ + ..@@........ + ..@@........ + ..@@........ + ..@@........ + ..@@.....@@. + ..@@....@@.. + ..@@...@@... + ..@@..@@.... + ..@@.@@..... + ..@@@@...... + ..@@.@@..... + ..@@..@@.... + ..@@...@@... + ..@@....@@.. + ..@@.....@@. + ............ + ............ + ............ + ............ + ............ + +u+006c: +"l": + ............ + ............ + ............ + ............ + ...@@@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+006d: +"m": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@@@@@@@... + .@@..@@.@@.. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + ............ + ............ + ............ + ............ + ............ + +u+006e: +"n": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+006f: +"o": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ...@@@@@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@@@@@... + ............ + ............ + ............ + ............ + ............ + +u+0070: +"p": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@@@@@@@... + .@@.....@@.. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@.....@@.. + .@@@@@@@@... + .@@......... + .@@......... + .@@......... + .@@......... + ............ + +u+0071: +"q": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ...@@@@@@@@. + ..@@.....@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@.....@@. + ...@@@@@@@@. + .........@@. + .........@@. + .........@@. + .........@@. + ............ + +u+0072: +"r": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@..@@@@@@. + .@@.@@...... + .@@@@....... + .@@@........ + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + .@@......... + ............ + ............ + ............ + ............ + ............ + +u+0073: +"s": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ..@@@@@@@@.. + .@@......@@. + .@@......... + .@@......... + .@@......... + ..@@@@@@@@.. + .........@@. + .........@@. + .........@@. + .@@......@@. + ..@@@@@@@@.. + ............ + ............ + ............ + ............ + ............ + +u+0074: +"t": + ............ + ............ + ............ + ............ + ....@@...... + ....@@...... + ....@@...... + ....@@...... + .@@@@@@@@... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + .....@@@@@.. + ............ + ............ + ............ + ............ + ............ + +u+0075: +"u": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@.....@@. + ...@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+0076: +"v": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@.. + ..@@....@@.. + ...@@..@@... + ...@@..@@... + ....@@@@.... + ....@@@@.... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+0077: +"w": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + .@@..@@..@@. + ..@@@@@@@@.. + ............ + ............ + ............ + ............ + ............ + +u+0078: +"x": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + ..@@....@@.. + ...@@..@@... + ....@@@@.... + .....@@..... + ....@@@@.... + ...@@..@@... + ..@@....@@.. + .@@......@@. + .@@......@@. + ............ + ............ + ............ + ............ + ............ + +u+0079: +"y": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + .@@......@@. + ..@@....@@@. + ...@@@@@@@@. + .........@@. + .........@@. + ........@@.. + ..@@@@@@@... + ............ + +u+007a: +"z": + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + .@@@@@@@@@@. + .........@@. + ........@@.. + .......@@... + ......@@.... + .....@@..... + ....@@...... + ...@@....... + ..@@........ + .@@......... + .@@@@@@@@@@. + ............ + ............ + ............ + ............ + ............ + +u+007b: +braceleft: + ............ + ............ + ............ + ............ + ......@@@... + .....@@..... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + ..@@@....... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + ....@@...... + .....@@..... + ......@@@... + ............ + ............ + ............ + ............ + ............ + +u+007c: +bar: + ............ + ............ + ............ + ............ + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ............ + ............ + ............ + ............ + ............ + +u+007d: +braceright: + ............ + ............ + ............ + ............ + ..@@@....... + ....@@...... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ......@@@... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + .....@@..... + ....@@...... + ..@@@....... + ............ + ............ + ............ + ............ + ............ + +u+007e: +asciitilde: + ............ + ............ + ..@@@@...@@. + .@@..@@..@@. + .@@..@@..@@. + .@@...@@@@.. + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + ............ + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-28px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-28px.yaff new file mode 100644 index 0000000..da85411 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-28px.yaff @@ -0,0 +1,3000 @@ +name: Terminus Bold 14x28 +spacing: character-cell +cell-size: 14 28 +family: Terminus +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 28 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 14 +ascent: 22 +descent: 6 +shift-up: -6 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 14 +converter: monobit v0.32 +source-name: ter-u28b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + .............. + .............. + .............. + .............. + .@@@@...@@@@.. + .@@@@...@@@@.. + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@@@...@@@@.. + .@@@@...@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0020: +space: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0021: +exclam: + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0022: +quotedbl: + .............. + .............. + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0023: +numbersign: + .............. + .............. + .............. + .............. + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0024: +dollar: + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ...@@@@@@@@... + ..@@@@@@@@@@.. + .@@@..@@..@@@. + .@@...@@...@@. + .@@...@@...... + .@@...@@...... + .@@@..@@...... + ..@@@@@@@@@... + ...@@@@@@@@@.. + ......@@..@@@. + ......@@...@@. + ......@@...@@. + .@@...@@...@@. + .@@@..@@..@@@. + ..@@@@@@@@@@.. + ...@@@@@@@@... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + +u+0025: +percent: + .............. + .............. + .............. + .............. + ..@@@@....@@.. + .@@@@@@...@@.. + .@@..@@..@@... + .@@@@@@..@@... + ..@@@@..@@.... + ........@@.... + .......@@..... + .......@@..... + ......@@...... + ......@@...... + .....@@....... + .....@@....... + ....@@........ + ....@@..@@@@.. + ...@@..@@@@@@. + ...@@..@@..@@. + ..@@...@@@@@@. + ..@@....@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0026: +ampersand: + .............. + .............. + .............. + .............. + ....@@@@...... + ...@@@@@@..... + ..@@@..@@@.... + ..@@....@@.... + ..@@....@@.... + ..@@@..@@@.... + ...@@@@@@..... + ....@@@@...... + ....@@@....... + ...@@@@@...@@. + ..@@@.@@@.@@@. + .@@@...@@@@@.. + .@@.....@@@... + .@@.....@@@... + .@@.....@@@... + .@@@...@@@@@.. + ..@@@@@@@.@@@. + ...@@@@@...@@. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0027: +quotesingle: + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0028: +parenleft: + .............. + .............. + .............. + .............. + .......@@..... + ......@@...... + .....@@....... + .....@@....... + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + .....@@....... + .....@@....... + ......@@...... + .......@@..... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0029: +parenright: + .............. + .............. + .............. + .............. + ....@@........ + .....@@....... + ......@@...... + ......@@...... + .......@@..... + .......@@..... + .......@@..... + .......@@..... + .......@@..... + .......@@..... + .......@@..... + .......@@..... + .......@@..... + .......@@..... + ......@@...... + ......@@...... + .....@@....... + ....@@........ + .............. + .............. + .............. + .............. + .............. + .............. + +u+002a: +asterisk: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ..@@@...@@@... + ...@@@.@@@.... + ....@@@@@..... + .....@@@...... + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .....@@@...... + ....@@@@@..... + ...@@@.@@@.... + ..@@@...@@@... + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+002b: +plus: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .@@@@@@@@@@@@. + .@@@@@@@@@@@@. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+002c: +comma: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .....@@....... + ....@@........ + .............. + .............. + .............. + .............. + +u+002d: +hyphen: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+002e: +period: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+002f: +slash: + .............. + .............. + .............. + .............. + ..........@@.. + ..........@@.. + .........@@... + .........@@... + ........@@.... + ........@@.... + .......@@..... + .......@@..... + ......@@...... + ......@@...... + .....@@....... + .....@@....... + ....@@........ + ....@@........ + ...@@......... + ...@@......... + ..@@.......... + ..@@.......... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0030: +zero: + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@......@@@.. + .@@.....@@@@.. + .@@....@@@@@.. + .@@...@@@.@@.. + .@@..@@@..@@.. + .@@.@@@...@@.. + .@@@@@....@@.. + .@@@@.....@@.. + .@@@......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0031: +one: + .............. + .............. + .............. + .............. + ......@@...... + .....@@@...... + ....@@@@...... + ...@@@@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ...@@@@@@@@... + ...@@@@@@@@... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0032: +two: + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + ..........@@.. + .........@@@.. + ........@@@... + .......@@@.... + ......@@@..... + .....@@@...... + ....@@@....... + ...@@@........ + ..@@@......... + .@@@.......... + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0033: +three: + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + ..........@@.. + ..........@@.. + ..........@@.. + .........@@@.. + ....@@@@@@@... + ....@@@@@@@... + .........@@@.. + ..........@@.. + ..........@@.. + ..........@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0034: +four: + .............. + .............. + .............. + .............. + ..........@@.. + .........@@@.. + ........@@@@.. + .......@@@@@.. + ......@@@.@@.. + .....@@@..@@.. + ....@@@...@@.. + ...@@@....@@.. + ..@@@.....@@.. + .@@@......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + ..........@@.. + ..........@@.. + ..........@@.. + ..........@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0035: +five: + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@@@@@@@.... + .@@@@@@@@@@... + .........@@@.. + ..........@@.. + ..........@@.. + ..........@@.. + ..........@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0036: +six: + .............. + .............. + .............. + .............. + ...@@@@@@@@... + ..@@@@@@@@@... + .@@@.......... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@@@@@@@.... + .@@@@@@@@@@... + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0037: +seven: + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .........@@... + .........@@... + ........@@.... + ........@@.... + .......@@..... + .......@@..... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0038: +eight: + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0039: +nine: + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@......@@.. + ..@@@@@@@@@@.. + ...@@@@@@@@@.. + ..........@@.. + ..........@@.. + ..........@@.. + ..........@@.. + .........@@@.. + ..@@@@@@@@@... + ..@@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+003a: +colon: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+003b: +semicolon: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .....@@....... + ....@@........ + .............. + .............. + .............. + .............. + +u+003c: +less: + .............. + .............. + .............. + .............. + .............. + .........@@@.. + ........@@@... + .......@@@.... + ......@@@..... + .....@@@...... + ....@@@....... + ...@@@........ + ..@@@......... + .@@@.......... + ..@@@......... + ...@@@........ + ....@@@....... + .....@@@...... + ......@@@..... + .......@@@.... + ........@@@... + .........@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+003d: +equal: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+003e: +greater: + .............. + .............. + .............. + .............. + .............. + .@@@.......... + ..@@@......... + ...@@@........ + ....@@@....... + .....@@@...... + ......@@@..... + .......@@@.... + ........@@@... + .........@@@.. + ........@@@... + .......@@@.... + ......@@@..... + .....@@@...... + ....@@@....... + ...@@@........ + ..@@@......... + .@@@.......... + .............. + .............. + .............. + .............. + .............. + .............. + +u+003f: +question: + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .........@@@.. + ........@@@... + .......@@@.... + ......@@@..... + ......@@...... + ......@@...... + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0040: +at: + .............. + .............. + .............. + .............. + ...@@@@@@@@... + ..@@@@@@@@@@.. + .@@@......@@@. + .@@........@@. + .@@....@@@@@@. + .@@...@@@@@@@. + .@@..@@@...@@. + .@@..@@....@@. + .@@..@@....@@. + .@@..@@....@@. + .@@..@@....@@. + .@@..@@@...@@. + .@@...@@@@@@@. + .@@....@@@@.@. + .@@........... + .@@@.......... + ..@@@@@@@@@@@. + ...@@@@@@@@@@. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0041: +"A": + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0042: +"B": + .............. + .............. + .............. + .............. + .@@@@@@@@@.... + .@@@@@@@@@@... + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@......@@... + .@@@@@@@@@.... + .@@@@@@@@@.... + .@@......@@... + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@......@@@.. + .@@@@@@@@@@... + .@@@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0043: +"C": + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0044: +"D": + .............. + .............. + .............. + .............. + .@@@@@@@...... + .@@@@@@@@@.... + .@@.....@@@... + .@@......@@... + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@......@@... + .@@.....@@@... + .@@@@@@@@@.... + .@@@@@@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0045: +"E": + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@@@@@@..... + .@@@@@@@@..... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0046: +"F": + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@@@@@@..... + .@@@@@@@@..... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0047: +"G": + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@........... + .@@........... + .@@........... + .@@...@@@@@@.. + .@@...@@@@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0048: +"H": + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0049: +"I": + .............. + .............. + .............. + .............. + ....@@@@@@.... + ....@@@@@@.... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ....@@@@@@.... + ....@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+004a: +"J": + .............. + .............. + .............. + .............. + .......@@@@@@. + .......@@@@@@. + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .@@......@@... + .@@......@@... + .@@......@@... + .@@@....@@@... + ..@@@@@@@@.... + ...@@@@@@..... + .............. + .............. + .............. + .............. + .............. + .............. + +u+004b: +"K": + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@......@@@.. + .@@.....@@@... + .@@....@@@.... + .@@...@@@..... + .@@..@@@...... + .@@.@@@....... + .@@@@@........ + .@@@@......... + .@@@@......... + .@@@@@........ + .@@.@@@....... + .@@..@@@...... + .@@...@@@..... + .@@....@@@.... + .@@.....@@@... + .@@......@@@.. + .@@.......@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+004c: +"L": + .............. + .............. + .............. + .............. + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+004d: +"M": + .............. + .............. + .............. + .............. + .@@........@@. + .@@........@@. + .@@@......@@@. + .@@@@....@@@@. + .@@@@@..@@@@@. + .@@.@@@@@@.@@. + .@@..@@@@..@@. + .@@...@@...@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .............. + .............. + .............. + .............. + .............. + .............. + +u+004e: +"N": + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@......@@.. + .@@@@.....@@.. + .@@@@@....@@.. + .@@.@@@...@@.. + .@@..@@@..@@.. + .@@...@@@.@@.. + .@@....@@@@@.. + .@@.....@@@@.. + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+004f: +"O": + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0050: +"P": + .............. + .............. + .............. + .............. + .@@@@@@@@@.... + .@@@@@@@@@@... + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@......@@@.. + .@@@@@@@@@@... + .@@@@@@@@@.... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0051: +"Q": + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@...@@@.@@.. + .@@@...@@@@@.. + ..@@@@@@@@@... + ...@@@@@@@@... + .........@@@.. + ..........@@@. + .............. + .............. + .............. + .............. + +u+0052: +"R": + .............. + .............. + .............. + .............. + .@@@@@@@@@.... + .@@@@@@@@@@... + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@......@@@.. + .@@@@@@@@@@... + .@@@@@@@@@.... + .@@@@@........ + .@@.@@@....... + .@@..@@@...... + .@@...@@@..... + .@@....@@@.... + .@@.....@@@... + .@@......@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0053: +"S": + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@........... + .@@........... + .@@........... + .@@@.......... + ..@@@@@@@@.... + ...@@@@@@@@... + .........@@@.. + ..........@@.. + ..........@@.. + ..........@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0054: +"T": + .............. + .............. + .............. + .............. + .@@@@@@@@@@@@. + .@@@@@@@@@@@@. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0055: +"U": + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0056: +"V": + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + ..@@.....@@... + ..@@.....@@... + ..@@.....@@... + ..@@.....@@... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ....@@.@@..... + ....@@.@@..... + ....@@.@@..... + .....@@@...... + .....@@@...... + .....@@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0057: +"W": + .............. + .............. + .............. + .............. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@...@@...@@. + .@@..@@@@..@@. + .@@.@@@@@@.@@. + .@@@@@..@@@@@. + .@@@@....@@@@. + .@@@......@@@. + .@@........@@. + .@@........@@. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0058: +"X": + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + ..@@.....@@... + ..@@.....@@... + ...@@...@@.... + ...@@...@@.... + ....@@.@@..... + ....@@.@@..... + .....@@@...... + .....@@@...... + ....@@.@@..... + ....@@.@@..... + ...@@...@@.... + ...@@...@@.... + ..@@.....@@... + ..@@.....@@... + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0059: +"Y": + .............. + .............. + .............. + .............. + .@@........@@. + .@@........@@. + ..@@......@@.. + ..@@......@@.. + ...@@....@@... + ...@@....@@... + ....@@..@@.... + ....@@..@@.... + .....@@@@..... + .....@@@@..... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+005a: +"Z": + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + ..........@@.. + ..........@@.. + .........@@@.. + ........@@@... + .......@@@.... + ......@@@..... + .....@@@...... + ....@@@....... + ...@@@........ + ..@@@......... + .@@@.......... + .@@........... + .@@........... + .@@........... + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+005b: +bracketleft: + .............. + .............. + .............. + .............. + ....@@@@@@.... + ....@@@@@@.... + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@........ + ....@@@@@@.... + ....@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+005c: +backslash: + .............. + .............. + .............. + .............. + ..@@.......... + ..@@.......... + ...@@......... + ...@@......... + ....@@........ + ....@@........ + .....@@....... + .....@@....... + ......@@...... + ......@@...... + .......@@..... + .......@@..... + ........@@.... + ........@@.... + .........@@... + .........@@... + ..........@@.. + ..........@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+005d: +bracketright: + .............. + .............. + .............. + .............. + ....@@@@@@.... + ....@@@@@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ........@@.... + ....@@@@@@.... + ....@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+005e: +asciicircum: + .............. + .............. + ......@....... + .....@@@...... + ....@@@@@..... + ...@@@.@@@.... + ..@@@...@@@... + .@@@.....@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+005f: +underscore: + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + +u+0060: +grave: + ..@@@......... + ...@@@........ + ....@@@....... + .....@@@...... + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0061: +"a": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ...@@@@@@@@... + .........@@@.. + ..........@@.. + ...@@@@@@@@@.. + ..@@@@@@@@@@.. + .@@@......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@......@@.. + ..@@@@@@@@@@.. + ...@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0062: +"b": + .............. + .............. + .............. + .............. + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@@@@@@@.... + .@@@@@@@@@@... + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@......@@@.. + .@@@@@@@@@@... + .@@@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0063: +"c": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0064: +"d": + .............. + .............. + .............. + .............. + ..........@@.. + ..........@@.. + ..........@@.. + ..........@@.. + ..........@@.. + ...@@@@@@@@@.. + ..@@@@@@@@@@.. + .@@@......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@......@@.. + ..@@@@@@@@@@.. + ...@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0065: +"e": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .@@........... + .@@........... + .@@........... + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0066: +"f": + .............. + .............. + .............. + .............. + ......@@@@@@.. + .....@@@@@@@.. + .....@@....... + .....@@....... + .....@@....... + ..@@@@@@@@.... + ..@@@@@@@@.... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0067: +"g": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ...@@@@@@@@@.. + ..@@@@@@@@@@.. + .@@@......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@......@@.. + ..@@@@@@@@@@.. + ...@@@@@@@@@.. + ..........@@.. + ..........@@.. + .........@@@.. + ..@@@@@@@@@... + ..@@@@@@@@.... + .............. + +u+0068: +"h": + .............. + .............. + .............. + .............. + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@@@@@@@@.... + .@@@@@@@@@@... + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0069: +"i": + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + ....@@@@...... + ....@@@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ....@@@@@@.... + ....@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+006a: +"j": + .............. + .............. + .............. + .............. + .........@@... + .........@@... + .........@@... + .............. + .............. + .......@@@@... + .......@@@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + .........@@... + ..@@.....@@... + ..@@.....@@... + ..@@@...@@@... + ...@@@@@@@.... + ....@@@@@..... + .............. + +u+006b: +"k": + .............. + .............. + .............. + .............. + ..@@.......... + ..@@.......... + ..@@.......... + ..@@.......... + ..@@.......... + ..@@.....@@@.. + ..@@....@@@... + ..@@...@@@.... + ..@@..@@@..... + ..@@.@@@...... + ..@@@@@....... + ..@@@@........ + ..@@@@@....... + ..@@.@@@...... + ..@@..@@@..... + ..@@...@@@.... + ..@@....@@@... + ..@@.....@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+006c: +"l": + .............. + .............. + .............. + .............. + ....@@@@...... + ....@@@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ....@@@@@@.... + ....@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+006d: +"m": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@@@@@@@@@... + .@@@@@@@@@@@.. + .@@...@@..@@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .............. + .............. + .............. + .............. + .............. + .............. + +u+006e: +"n": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@@@@@@@@.... + .@@@@@@@@@@... + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+006f: +"o": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0070: +"p": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@@@@@@@@.... + .@@@@@@@@@@... + .@@......@@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@......@@@.. + .@@@@@@@@@@... + .@@@@@@@@@.... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .............. + +u+0071: +"q": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ...@@@@@@@@@.. + ..@@@@@@@@@@.. + .@@@......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@......@@.. + ..@@@@@@@@@@.. + ...@@@@@@@@@.. + ..........@@.. + ..........@@.. + ..........@@.. + ..........@@.. + ..........@@.. + .............. + +u+0072: +"r": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@..@@@@@@@.. + .@@.@@@@@@@@.. + .@@@@@........ + .@@@@......... + .@@@.......... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .@@........... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0073: +"s": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + ...@@@@@@@.... + ..@@@@@@@@@... + .@@@.....@@@.. + .@@........... + .@@@.......... + ..@@@@@@@@.... + ...@@@@@@@@... + .........@@@.. + ..........@@.. + ..........@@.. + .@@@.....@@@.. + ..@@@@@@@@@... + ...@@@@@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0074: +"t": + .............. + .............. + .............. + .............. + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + ..@@@@@@@@.... + ..@@@@@@@@.... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@@@@@... + ......@@@@@... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0075: +"u": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@......@@.. + ..@@@@@@@@@@.. + ...@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0076: +"v": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + ..@@.....@@... + ..@@.....@@... + ..@@.....@@... + ...@@...@@.... + ...@@...@@.... + ...@@...@@.... + ....@@.@@..... + ....@@.@@..... + .....@@@...... + .....@@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0077: +"w": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@........@@. + .@@........@@. + .@@........@@. + .@@........@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@...@@...@@. + .@@@..@@..@@@. + ..@@@@@@@@@@.. + ...@@@@@@@@... + .............. + .............. + .............. + .............. + .............. + .............. + +u+0078: +"x": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@@.....@@@.. + ..@@@...@@@... + ...@@@.@@@.... + ....@@@@@..... + .....@@@...... + ....@@@@@..... + ...@@@.@@@.... + ..@@@...@@@... + .@@@.....@@@.. + .@@.......@@.. + .@@.......@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+0079: +"y": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@.......@@.. + .@@@......@@.. + ..@@@@@@@@@@.. + ...@@@@@@@@@.. + ..........@@.. + ..........@@.. + .........@@@.. + ..@@@@@@@@@... + ..@@@@@@@@.... + .............. + +u+007a: +"z": + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .........@@@.. + ........@@@... + .......@@@.... + ......@@@..... + .....@@@...... + ....@@@....... + ...@@@........ + ..@@@......... + .@@@.......... + .@@@@@@@@@@@.. + .@@@@@@@@@@@.. + .............. + .............. + .............. + .............. + .............. + .............. + +u+007b: +braceleft: + .............. + .............. + .............. + .............. + .......@@@.... + ......@@@@.... + .....@@@...... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + ...@@@........ + ...@@@........ + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@....... + .....@@@...... + ......@@@@.... + .......@@@.... + .............. + .............. + .............. + .............. + .............. + .............. + +u+007c: +bar: + .............. + .............. + .............. + .............. + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .............. + .............. + .............. + .............. + .............. + .............. + +u+007d: +braceright: + .............. + .............. + .............. + .............. + ...@@@........ + ...@@@@....... + .....@@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .......@@@.... + .......@@@.... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + ......@@...... + .....@@@...... + ...@@@@....... + ...@@@........ + .............. + .............. + .............. + .............. + .............. + .............. + +u+007e: +asciitilde: + .............. + .............. + ..@@@@....@@.. + .@@@@@@...@@.. + .@@..@@@..@@.. + .@@...@@@@@@.. + .@@....@@@@... + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + .............. + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-32px.yaff b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-32px.yaff new file mode 100644 index 0000000..ed83209 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/fonts/terminus-ascii-bold-32px.yaff @@ -0,0 +1,3383 @@ +name: Terminus Bold 16x32 +spacing: character-cell +cell-size: 16 32 +family: Terminus +foundry: xos4 +copyright: Copyright (C) 2020 Dimitar Toshkov Zhekov +notice: Licensed under the SIL Open Font License, Version 1.1 +point-size: 32 +weight: bold +slant: roman +setwidth: normal +dpi: 72 72 +average-width: 16 +ascent: 26 +descent: 6 +shift-up: -6 +encoding: iso10646-1 +default-char: u+fffd +min-word-space: 16 +converter: monobit v0.32 +source-name: ter-u32b.bdf +source-format: BDF v2.1 +history: load --format=bdf + +u+0000: +char0: + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@...@@@@@.. + .@@@@@...@@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@@...@@@@@.. + .@@@@@...@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0020: +space: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+0021: +exclam: + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0022: +quotedbl: + ................ + ................ + ................ + ................ + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+0023: +numbersign: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0024: +dollar: + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.@@@.@@@@.. + .@@@..@@@..@@@.. + .@@@..@@@....... + .@@@..@@@....... + .@@@..@@@....... + .@@@@.@@@....... + ..@@@@@@@@@@.... + ...@@@@@@@@@@... + ......@@@.@@@@.. + ......@@@..@@@.. + ......@@@..@@@.. + ......@@@..@@@.. + .@@@..@@@..@@@.. + .@@@@.@@@.@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + +u+0025: +percent: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@...@@@.. + ..@@@@@@@..@@@.. + ..@@@.@@@.@@@... + ..@@@.@@@.@@@... + ..@@@@@@@@@@.... + ...@@@@@.@@@.... + ........@@@..... + ........@@@..... + .......@@@...... + .......@@@...... + ......@@@....... + ......@@@....... + .....@@@........ + .....@@@........ + ....@@@.@@@@@... + ....@@@@@@@@@@.. + ...@@@.@@@.@@@.. + ...@@@.@@@.@@@.. + ..@@@..@@@@@@@.. + ..@@@...@@@@@... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0026: +ampersand: + ................ + ................ + ................ + ................ + ................ + ................ + ....@@@@@@...... + ...@@@@@@@@..... + ..@@@....@@@.... + ..@@@....@@@.... + ..@@@....@@@.... + ..@@@....@@@.... + ..@@@....@@@.... + ...@@@..@@@..... + ....@@@@@@...... + ....@@@@@....... + ...@@@@@@@..@@@. + ..@@@...@@@.@@@. + .@@@.....@@@@@.. + .@@@......@@@... + .@@@......@@@... + .@@@......@@@... + .@@@......@@@... + .@@@@....@@@@@.. + ..@@@@@@@@@.@@@. + ...@@@@@@@..@@@. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0027: +quotesingle: + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+0028: +parenleft: + ................ + ................ + ................ + ................ + ................ + ................ + ........@@@..... + .......@@@...... + ......@@@....... + .....@@@........ + .....@@@........ + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + .....@@@........ + .....@@@........ + ......@@@....... + .......@@@...... + ........@@@..... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0029: +parenright: + ................ + ................ + ................ + ................ + ................ + ................ + ....@@@......... + .....@@@........ + ......@@@....... + .......@@@...... + .......@@@...... + ........@@@..... + ........@@@..... + ........@@@..... + ........@@@..... + ........@@@..... + ........@@@..... + ........@@@..... + ........@@@..... + ........@@@..... + ........@@@..... + .......@@@...... + .......@@@...... + ......@@@....... + .....@@@........ + ....@@@......... + ................ + ................ + ................ + ................ + ................ + ................ + +u+002a: +asterisk: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ..@@@.....@@@... + ...@@@...@@@.... + ....@@@.@@@..... + .....@@@@@...... + ......@@@....... + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ......@@@....... + .....@@@@@...... + ....@@@.@@@..... + ...@@@...@@@.... + ..@@@.....@@@... + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+002b: +plus: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+002c: +comma: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + .....@@@........ + ....@@@......... + ................ + ................ + ................ + ................ + +u+002d: +hyphen: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+002e: +period: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+002f: +slash: + ................ + ................ + ................ + ................ + ................ + ................ + ...........@@@.. + ...........@@@.. + ..........@@@... + ..........@@@... + .........@@@.... + .........@@@.... + ........@@@..... + ........@@@..... + .......@@@...... + .......@@@...... + ......@@@....... + ......@@@....... + .....@@@........ + .....@@@........ + ....@@@......... + ....@@@......... + ...@@@.......... + ...@@@.......... + ..@@@........... + ..@@@........... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0030: +zero: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@@.. + .@@@.....@@@@@.. + .@@@....@@@@@@.. + .@@@...@@@.@@@.. + .@@@..@@@..@@@.. + .@@@.@@@...@@@.. + .@@@@@@....@@@.. + .@@@@@.....@@@.. + .@@@@......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0031: +one: + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + .....@@@@....... + ....@@@@@....... + ...@@@@@@....... + ...@@@@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ...@@@@@@@@@.... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0032: +two: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ...........@@@.. + ..........@@@... + .........@@@.... + ........@@@..... + .......@@@...... + ......@@@....... + .....@@@........ + ....@@@......... + ...@@@.......... + ..@@@........... + .@@@............ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0033: +three: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ..........@@@@.. + ....@@@@@@@@@... + ....@@@@@@@@@... + ..........@@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0034: +four: + ................ + ................ + ................ + ................ + ................ + ................ + ...........@@@.. + ..........@@@@.. + .........@@@@@.. + ........@@@@@@.. + .......@@@.@@@.. + ......@@@..@@@.. + .....@@@...@@@.. + ....@@@....@@@.. + ...@@@.....@@@.. + ..@@@......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0035: +five: + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + ..........@@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0036: +six: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@@... + ..@@@@@@@@@@@... + .@@@@........... + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0037: +seven: + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@... + ..........@@@... + .........@@@.... + .........@@@.... + ........@@@..... + ........@@@..... + .......@@@...... + .......@@@...... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0038: +eight: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0039: +nine: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + ..@@@@@@@@@@@@.. + ...@@@@@@@@@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ..........@@@@.. + ..@@@@@@@@@@@... + ..@@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+003a: +colon: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+003b: +semicolon: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + .....@@@........ + ....@@@......... + ................ + ................ + ................ + ................ + +u+003c: +less: + ................ + ................ + ................ + ................ + ................ + ................ + ...........@@@.. + ..........@@@... + .........@@@.... + ........@@@..... + .......@@@...... + ......@@@....... + .....@@@........ + ....@@@......... + ...@@@.......... + ..@@@........... + ..@@@........... + ...@@@.......... + ....@@@......... + .....@@@........ + ......@@@....... + .......@@@...... + ........@@@..... + .........@@@.... + ..........@@@... + ...........@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+003d: +equal: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+003e: +greater: + ................ + ................ + ................ + ................ + ................ + ................ + ..@@@........... + ...@@@.......... + ....@@@......... + .....@@@........ + ......@@@....... + .......@@@...... + ........@@@..... + .........@@@.... + ..........@@@... + ...........@@@.. + ...........@@@.. + ..........@@@... + .........@@@.... + ........@@@..... + .......@@@...... + ......@@@....... + .....@@@........ + ....@@@......... + ...@@@.......... + ..@@@........... + ................ + ................ + ................ + ................ + ................ + ................ + +u+003f: +question: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ..........@@@... + .........@@@.... + ........@@@..... + .......@@@...... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0040: +at: + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@@... + ..@@@@@@@@@@@@.. + .@@@@.......@@@. + .@@@.........@@. + .@@@...@@@@@@@@. + .@@@..@@@@@@@@@. + .@@@.@@@@...@@@. + .@@@.@@@....@@@. + .@@@.@@@....@@@. + .@@@.@@@....@@@. + .@@@.@@@....@@@. + .@@@.@@@....@@@. + .@@@.@@@....@@@. + .@@@.@@@@..@@@@. + .@@@..@@@@@@@@@. + .@@@...@@@@@.@@. + .@@@............ + .@@@@........... + ..@@@@@@@@@@@@@. + ...@@@@@@@@@@@@. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0041: +"A": + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0042: +"B": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@... + .@@@@@@@@@@@.... + .@@@@@@@@@@@.... + .@@@......@@@... + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@@.. + .@@@@@@@@@@@@... + .@@@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0043: +"C": + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0044: +"D": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@...... + .@@@@@@@@@@@.... + .@@@.....@@@@... + .@@@......@@@... + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@... + .@@@.....@@@@... + .@@@@@@@@@@@.... + .@@@@@@@@@...... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0045: +"E": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@..... + .@@@@@@@@@@..... + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0046: +"F": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@..... + .@@@@@@@@@@..... + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + ................ + ................ + ................ + ................ + ................ + ................ + +u+0047: +"G": + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@...@@@@@@@.. + .@@@...@@@@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0048: +"H": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0049: +"I": + ................ + ................ + ................ + ................ + ................ + ................ + ....@@@@@@@..... + ....@@@@@@@..... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ....@@@@@@@..... + ....@@@@@@@..... + ................ + ................ + ................ + ................ + ................ + ................ + +u+004a: +"J": + ................ + ................ + ................ + ................ + ................ + ................ + ........@@@@@@@. + ........@@@@@@@. + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + .@@@......@@@... + .@@@......@@@... + .@@@......@@@... + .@@@@....@@@@... + ..@@@@@@@@@@.... + ...@@@@@@@@..... + ................ + ................ + ................ + ................ + ................ + ................ + +u+004b: +"K": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@........@@.. + .@@@.......@@@.. + .@@@......@@@... + .@@@.....@@@.... + .@@@....@@@..... + .@@@...@@@...... + .@@@..@@@....... + .@@@.@@@........ + .@@@@@@......... + .@@@@@.......... + .@@@@@.......... + .@@@@@@......... + .@@@.@@@........ + .@@@..@@@....... + .@@@...@@@...... + .@@@....@@@..... + .@@@.....@@@.... + .@@@......@@@... + .@@@.......@@@.. + .@@@........@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+004c: +"L": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+004d: +"M": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@........@@@. + .@@@........@@@. + .@@@@......@@@@. + .@@@@@....@@@@@. + .@@@@@@..@@@@@@. + .@@@@@@..@@@@@@. + .@@@.@@@@@@.@@@. + .@@@..@@@@..@@@. + .@@@..@@@@..@@@. + .@@@...@@...@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + ................ + ................ + ................ + ................ + ................ + ................ + +u+004e: +"N": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + .@@@@@.....@@@.. + .@@@@@@....@@@.. + .@@@.@@@...@@@.. + .@@@..@@@..@@@.. + .@@@...@@@.@@@.. + .@@@....@@@@@@.. + .@@@.....@@@@@.. + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+004f: +"O": + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0050: +"P": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@@.. + .@@@@@@@@@@@@... + .@@@@@@@@@@@.... + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + ................ + ................ + ................ + ................ + ................ + ................ + +u+0051: +"Q": + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@..@@@..@@@.. + .@@@@..@@@@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ..........@@@... + ...........@@@.. + ................ + ................ + ................ + ................ + +u+0052: +"R": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@@.. + .@@@@@@@@@@@@... + .@@@@@@@@@@@.... + .@@@@@@......... + .@@@.@@@........ + .@@@..@@@....... + .@@@...@@@...... + .@@@....@@@..... + .@@@.....@@@.... + .@@@......@@@... + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0053: +"S": + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@............ + .@@@............ + .@@@............ + .@@@@........... + ..@@@@@@@@@@.... + ...@@@@@@@@@@... + ..........@@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0054: +"T": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0055: +"U": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0056: +"V": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ..@@@.....@@@... + ..@@@.....@@@... + ..@@@.....@@@... + ..@@@.....@@@... + ..@@@.....@@@... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ....@@@.@@@..... + ....@@@.@@@..... + ....@@@.@@@..... + .....@@@@@...... + .....@@@@@...... + .....@@@@@...... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0057: +"W": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@........@@@. + .@@@...@@...@@@. + .@@@..@@@@..@@@. + .@@@..@@@@..@@@. + .@@@.@@@@@@.@@@. + .@@@@@@..@@@@@@. + .@@@@@@..@@@@@@. + .@@@@@....@@@@@. + .@@@@......@@@@. + .@@@........@@@. + .@@@........@@@. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0058: +"X": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + ..@@@.....@@@... + ..@@@.....@@@... + ...@@@...@@@.... + ...@@@...@@@.... + ....@@@.@@@..... + ....@@@.@@@..... + .....@@@@@...... + .....@@@@@...... + .....@@@@@...... + .....@@@@@...... + ....@@@.@@@..... + ....@@@.@@@..... + ...@@@...@@@.... + ...@@@...@@@.... + ..@@@.....@@@... + ..@@@.....@@@... + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0059: +"Y": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ..@@@.....@@@... + ..@@@.....@@@... + ...@@@...@@@.... + ...@@@...@@@.... + ....@@@.@@@..... + ....@@@.@@@..... + .....@@@@@...... + .....@@@@@...... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+005a: +"Z": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ..........@@@... + .........@@@.... + ........@@@..... + .......@@@...... + ......@@@....... + .....@@@........ + ....@@@......... + ...@@@.......... + ..@@@........... + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+005b: +bracketleft: + ................ + ................ + ................ + ................ + ................ + ................ + ....@@@@@@@@.... + ....@@@@@@@@.... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@......... + ....@@@@@@@@.... + ....@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+005c: +backslash: + ................ + ................ + ................ + ................ + ................ + ................ + ..@@@........... + ..@@@........... + ...@@@.......... + ...@@@.......... + ....@@@......... + ....@@@......... + .....@@@........ + .....@@@........ + ......@@@....... + ......@@@....... + .......@@@...... + .......@@@...... + ........@@@..... + ........@@@..... + .........@@@.... + .........@@@.... + ..........@@@... + ..........@@@... + ...........@@@.. + ...........@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+005d: +bracketright: + ................ + ................ + ................ + ................ + ................ + ................ + ....@@@@@@@@.... + ....@@@@@@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + .........@@@.... + ....@@@@@@@@.... + ....@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+005e: +asciicircum: + ................ + ................ + ................ + ................ + ......@@@....... + .....@@@@@...... + ....@@@.@@@..... + ...@@@...@@@.... + ..@@@.....@@@... + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+005f: +underscore: + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + +u+0060: +grave: + ................ + ................ + ...@@@.......... + ....@@@......... + .....@@@........ + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + +u+0061: +"a": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ..@@@@@@@@@@.... + ..@@@@@@@@@@@... + ..........@@@@.. + ...........@@@.. + ...........@@@.. + ...@@@@@@@@@@@.. + ..@@@@@@@@@@@@.. + .@@@@......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + ..@@@@@@@@@@@@.. + ...@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0062: +"b": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@@.. + .@@@@@@@@@@@@... + .@@@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0063: +"c": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0064: +"d": + ................ + ................ + ................ + ................ + ................ + ................ + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...@@@@@@@@@@@.. + ..@@@@@@@@@@@@.. + .@@@@......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + ..@@@@@@@@@@@@.. + ...@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0065: +"e": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + .@@@............ + .@@@............ + .@@@............ + .@@@@......@@@.. + ..@@@@@@@@@@@@.. + ...@@@@@@@@@@... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0066: +"f": + ................ + ................ + ................ + ................ + ................ + ................ + ........@@@@@@@. + .......@@@@@@@@. + ......@@@@...... + ......@@@....... + ......@@@....... + ......@@@....... + ..@@@@@@@@@@@... + ..@@@@@@@@@@@... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0067: +"g": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@@@.. + ..@@@@@@@@@@@@.. + .@@@@......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + ..@@@@@@@@@@@@.. + ...@@@@@@@@@@@.. + ...........@@@.. + ...........@@@.. + ..........@@@@.. + ..@@@@@@@@@@@... + ..@@@@@@@@@@.... + ................ + +u+0068: +"h": + ................ + ................ + ................ + ................ + ................ + ................ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0069: +"i": + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ....@@@@@....... + ....@@@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ....@@@@@@@..... + ....@@@@@@@..... + ................ + ................ + ................ + ................ + ................ + ................ + +u+006a: +"j": + ................ + ................ + ................ + ................ + ................ + ................ + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ................ + ................ + ........@@@@@... + ........@@@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..........@@@... + ..@@@.....@@@... + ..@@@.....@@@... + ..@@@@...@@@@... + ...@@@@@@@@@.... + ....@@@@@@@..... + ................ + +u+006b: +"k": + ................ + ................ + ................ + ................ + ................ + ................ + ..@@@........... + ..@@@........... + ..@@@........... + ..@@@........... + ..@@@........... + ..@@@........... + ..@@@......@@@.. + ..@@@.....@@@... + ..@@@....@@@.... + ..@@@...@@@..... + ..@@@..@@@...... + ..@@@.@@@....... + ..@@@@@@........ + ..@@@@@@........ + ..@@@.@@@....... + ..@@@..@@@...... + ..@@@...@@@..... + ..@@@....@@@.... + ..@@@.....@@@... + ..@@@......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+006c: +"l": + ................ + ................ + ................ + ................ + ................ + ................ + ....@@@@@....... + ....@@@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ....@@@@@@@..... + ....@@@@@@@..... + ................ + ................ + ................ + ................ + ................ + ................ + +u+006d: +"m": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@..@@@.@@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+006e: +"n": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+006f: +"o": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0070: +"p": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@.... + .@@@@@@@@@@@@... + .@@@......@@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@......@@@@.. + .@@@@@@@@@@@@... + .@@@@@@@@@@@.... + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + ................ + +u+0071: +"q": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@@@.. + ..@@@@@@@@@@@@.. + .@@@@......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + ..@@@@@@@@@@@@.. + ...@@@@@@@@@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ...........@@@.. + ................ + +u+0072: +"r": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@..@@@@@@@@.. + .@@@.@@@@@@@@@.. + .@@@@@@......... + .@@@@@.......... + .@@@@........... + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + .@@@............ + ................ + ................ + ................ + ................ + ................ + ................ + +u+0073: +"s": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ...@@@@@@@@@.... + ..@@@@@@@@@@@... + .@@@@.....@@@@.. + .@@@............ + .@@@............ + .@@@@........... + ..@@@@@@@@@@.... + ...@@@@@@@@@@... + ..........@@@@.. + ...........@@@.. + ...........@@@.. + .@@@@.....@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0074: +"t": + ................ + ................ + ................ + ................ + ................ + ................ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .@@@@@@@@@@@.... + .@@@@@@@@@@@.... + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@@....... + ......@@@@@@@@.. + .......@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0075: +"u": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + ..@@@@@@@@@@@@.. + ...@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0076: +"v": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ..@@@.....@@@... + ..@@@.....@@@... + ..@@@.....@@@... + ...@@@...@@@.... + ...@@@...@@@.... + ...@@@...@@@.... + ....@@@.@@@..... + ....@@@.@@@..... + .....@@@@@...... + .....@@@@@...... + .....@@@@@...... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0077: +"w": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@..@@@..@@@.. + .@@@@.@@@.@@@@.. + ..@@@@@@@@@@@... + ...@@@@@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+0078: +"x": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ..@@@.....@@@... + ...@@@...@@@.... + ....@@@.@@@..... + .....@@@@@...... + .....@@@@@...... + ....@@@.@@@..... + ...@@@...@@@.... + ..@@@.....@@@... + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+0079: +"y": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@.......@@@.. + .@@@@......@@@.. + ..@@@@@@@@@@@@.. + ...@@@@@@@@@@@.. + ...........@@@.. + ...........@@@.. + ..........@@@@.. + ..@@@@@@@@@@@... + ..@@@@@@@@@@.... + ................ + +u+007a: +"z": + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ..........@@@... + .........@@@.... + ........@@@..... + .......@@@...... + ......@@@....... + .....@@@........ + ....@@@......... + ...@@@.......... + ..@@@........... + .@@@............ + .@@@@@@@@@@@@@.. + .@@@@@@@@@@@@@.. + ................ + ................ + ................ + ................ + ................ + ................ + +u+007b: +braceleft: + ................ + ................ + ................ + ................ + ................ + ................ + .......@@@@@.... + ......@@@@@@.... + .....@@@@....... + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + ..@@@@@......... + ..@@@@@......... + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@........ + .....@@@@....... + ......@@@@@@.... + .......@@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + +u+007c: +bar: + ................ + ................ + ................ + ................ + ................ + ................ + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ................ + ................ + ................ + ................ + ................ + ................ + +u+007d: +braceright: + ................ + ................ + ................ + ................ + ................ + ................ + ..@@@@@......... + ..@@@@@@........ + .....@@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + .......@@@@@.... + .......@@@@@.... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + ......@@@....... + .....@@@@....... + ..@@@@@@........ + ..@@@@@......... + ................ + ................ + ................ + ................ + ................ + ................ + +u+007e: +asciitilde: + ................ + ................ + ................ + ................ + ...@@@@....@@@.. + ..@@@@@@...@@@.. + .@@@.@@@@..@@@.. + .@@@..@@@@.@@@.. + .@@@...@@@@@@... + .@@@....@@@@.... + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/lodepng.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/lodepng.cpp new file mode 100644 index 0000000..abf4ed4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/lodepng.cpp @@ -0,0 +1,6661 @@ +// stasoid: disabled warnings 4334 and 4267 +/* +LodePNG version 20221108 + +Copyright (c) 2005-2022 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#ifdef LODEPNG_COMPILE_DISK +#include /* LONG_MAX */ +#include /* file handling */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +#include /* allocations */ +#endif /* LODEPNG_COMPILE_ALLOCATORS */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#pragma warning( disable : 4334 ) // result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) +#pragma warning( disable : 4267 ) // conversion from 'size_t' to 'unsigned short', possible loss of data +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20221108"; + +/* +This source file is divided into the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) { +#ifdef LODEPNG_MAX_ALLOC + if(size > LODEPNG_MAX_ALLOC) return 0; +#endif + return malloc(size); +} + +/* NOTE: when realloc returns NULL, it leaves the original memory untouched */ +static void* lodepng_realloc(void* ptr, size_t new_size) { +#ifdef LODEPNG_MAX_ALLOC + if(new_size > LODEPNG_MAX_ALLOC) return 0; +#endif + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) { + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +/* TODO: support giving additional void* payload to the custom allocators */ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) +#define LODEPNG_INLINE inline +#else +#define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) +#define LODEPNG_RESTRICT __restrict +#else +#define LODEPNG_RESTRICT /* not available */ +#endif + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, + const void* LODEPNG_RESTRICT src, size_t size) { + size_t i; + for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; +} + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, + int value, size_t num) { + size_t i; + for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; +} + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) { + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while(*a) a++; + return (size_t)(a - orig); +} + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + +#ifdef LODEPNG_COMPILE_ZLIB +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) { + size_t d; + if(lodepng_addofl(a, b, &d)) return 1; + return d > c; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*dynamic vector of unsigned ints*/ +typedef struct uivector { + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) { + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) { + size_t allocsize = size * sizeof(unsigned); + if(allocsize > p->allocsize) { + size_t newsize = allocsize + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static void uivector_init(uivector* p) { + p->data = NULL; + p->size = p->allocsize = 0; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) { + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector { + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_reserve(ucvector* p, size_t size) { + if(size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; /*success*/ +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) { + p->size = size; + return ucvector_reserve(p, size); +} + +static ucvector ucvector_init(unsigned char* buffer, size_t size) { + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/*free string pointer and set it to NULL*/ +static void string_cleanup(char** out) { + lodepng_free(*out); + *out = NULL; +} + +/*also appends null termination character*/ +static char* alloc_string_sized(const char* in, size_t insize) { + char* out = (char*)lodepng_malloc(insize + 1); + if(out) { + lodepng_memcpy(out, in, insize); + out[insize] = 0; + } + return out; +} + +/* dynamically allocates a new string with a copy of the null terminated input text */ +static char* alloc_string(const char* in) { + return alloc_string_sized(in, lodepng_strlen(in)); +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) +static unsigned lodepng_read32bitInt(const unsigned char* buffer) { + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | + ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} +#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) { + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if(readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { + long size = lodepng_filesize(filename); + if(size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite(buffer, 1, buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct { + ucvector* data; + unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ +} LodePNGBitWriter; + +static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { + writer->data = data; + writer->bp = 0; +} + +/*TODO: this ignores potential out of memory errors*/ +#define WRITEBIT(writer, bit){\ + /* append new byte */\ + if(((writer->bp) & 7u) == 0) {\ + if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ + writer->data->data[writer->data->size - 1] = 0;\ + }\ + (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ + ++writer->bp;\ +} + +/* LSB of value is written first, and LSB of bytes is used first */ +static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ + WRITEBIT(writer, value); + } else { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + size_t i; + for(i = 0; i != nbits; ++i) { + WRITEBIT(writer, (unsigned char)((value >> i) & 1)); + } + } +} + +/* This one is to use for adding huffman symbol, the value bits are written MSB first */ +static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + size_t i; + for(i = 0; i != nbits; ++i) { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct { + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +} LodePNGBitReader; + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ +} + +/* +ensureBits functions: +Ensures the reader can at least read nbits bits in one or more readBits calls, +safely even if not enough bits are available. +The nbits parameter is unused but is given for documentation purposes, error +checking for amount of bits must be done beforehand. +*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static LODEPNG_INLINE void ensureBits9(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer = reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static LODEPNG_INLINE void ensureBits17(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE void ensureBits25(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE void ensureBits32(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static LODEPNG_INLINE unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + +/* Must have enough bits available with ensureBits */ +static LODEPNG_INLINE void advanceBits(LodePNGBitReader* reader, size_t nbits) { + reader->buffer >>= nbits; + reader->bp += nbits; +} + +/* Must have enough bits available with ensureBits */ +static LODEPNG_INLINE unsigned readBits(LodePNGBitReader* reader, size_t nbits) { + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static unsigned reverseBits(unsigned bits, unsigned num) { + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree { + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +} HuffmanTree; + +static void HuffmanTree_init(HuffmanTree* tree) { + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) { + lodepng_free(tree->codes); + lodepng_free(tree->lengths); + lodepng_free(tree->table_len); + lodepng_free(tree->table_value); +} + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); + if(!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for(i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); + if(!tree->table_len || !tree->table_value) { + lodepng_free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for(i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (1u << (l - FIRSTBITS)); + } + lodepng_free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for(i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol, reverse; + if(l == 0) continue; + symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + reverse = reverseBits(symbol, l); + numpresent++; + + if(l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for(j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for(j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if(numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) return 55; + } + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if(!error) { + for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) { + if(tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + lodepng_free(blcount); + lodepng_free(nextcode); + + if(!error) error = HuffmanTree_makeTable(tree); + return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) { + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode { + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists { + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) { + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } else { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } else if(numpresent == 1) { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } else { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if(l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + advanceBits(reader, FIRSTBITS); + value += peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[value] - FIRSTBITS); + return codetree->table_value[value]; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { + unsigned error = generateFixedLitLenTree(tree_ll); + if(error) return error; + return generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + LodePNGBitReader* reader) { + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if(reader->bitsize - reader->bp < 14) return 49; /*error: the bit pointer is or will go past the memory*/ + ensureBits17(reader, 14); + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while(!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for(i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if(code <= 15) /*a length code*/ { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if(code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, + unsigned btype, size_t max_output_size) { + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + const size_t reserved_size = 260; /* must be at least 258 for max length, and a few extra for adding a few extra literals */ + int done = 0; + + if(!ucvector_reserve(out, out->size + reserved_size)) return 83; /*alloc fail*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + + while(!error && !done) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + /* ensure enough bits for 2 huffman code reads (15 bits each): if the first is a literal, a second literal is read at once. This + appears to be slightly faster, than ensuring 20 bits here for 1 huffman symbol and the potential 5 extra bits for the length symbol.*/ + ensureBits32(reader, 30); + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if(code_ll <= 255) { + /*slightly faster code path if multiple literals in a row*/ + out->data[out->size++] = (unsigned char)code_ll; + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + } + if(code_ll <= 255) /*literal symbol*/ { + out->data[out->size++] = (unsigned char)code_ll; + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(numextrabits_l != 0) { + /* bits already ensured above */ + ensureBits25(reader, 5); + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if(code_d > 29) { + if(code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + out->size += length; + if(distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for(forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if(code_ll == 256) { + done = 1; /*end code, finish the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + if(out->allocsize - out->size < reserved_size) { + if(!ucvector_reserve(out, out->size + reserved_size)) ERROR_BREAK(83); /*alloc fail*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + if(max_output_size && out->size > max_output_size) { + ERROR_BREAK(109); /*error, larger than max size*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, + const LodePNGDecompressSettings* settings) { + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + /*out->data can be NULL (when LEN is zero), and arithmetics on NULL ptr is undefined*/ + if (LEN) { + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + } + + reader->bp = bytepos << 3u; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if(error) return error; + + while(!BFINAL) { + unsigned BTYPE; + if(reader.bitsize - reader.bp < 3) return 52; /*error, bit pointer will jump past memory*/ + ensureBits9(&reader, 3); + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/ + if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109; + if(error) break; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + if(error) { + /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && out->size > settings->max_output_size) error = 109; + } + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if(array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) { + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + size_t pos = values->size; + /*TODO: return error when this fails (out of memory)*/ + unsigned ok = uivector_resize(values, values->size + 4); + if(ok) { + values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; + values->data[pos + 1] = extra_length; + values->data[pos + 2] = dist_code; + values->data[pos + 3] = extra_distance; + } +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash { + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) { + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) { + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { + unsigned result = 0; + if(pos + 2 < size) { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= ((unsigned)data[pos + 0] << 0u); + result ^= ((unsigned)data[pos + 1] << 4u); + result ^= ((unsigned)data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = (int)wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = (int)wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) { + if(chainlength++ >= maxchainlength) break; + current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } else { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } else { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else if(length < minmatch || (length == 3 && offset > 4096)) { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + size_t pos = out->size; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + LEN = 65535; + if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); + out->data[pos + 0] = firstbyte; + out->data[pos + 1] = (unsigned char)(LEN & 255); + out->data[pos + 2] = (unsigned char)(LEN >> 8u); + out->data[pos + 3] = (unsigned char)(NLEN & 255); + out->data[pos + 4] = (unsigned char)(NLEN >> 8u); + lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); + datapos += LEN; + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) { + unsigned val = lz77_encoded->data[i]; + writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); + if(val > 256) /*for a length code, 3 more things have to be added*/ { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + writeBits(writer, length_extra_bits, n_length_extra_bits); + writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); + writeBits(writer, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lengths used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ + unsigned* frequencies_d = 0; /*frequency of dist codes*/ + unsigned* frequencies_cl = 0; /*frequency of code length codes*/ + unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ + unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ + size_t datasize = dataend - datapos; + + /* + If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent + tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are + some analogies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t i; + size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + /* could fit on stack, but >1KB is on the larger side so allocate instead */ + frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); + frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); + frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) { + lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); + lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); + lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(settings->use_lz77) { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } else { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll[symbol]; + if(symbol > 256) { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d[dist]; + i += 3; + } + } + frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); + if(error) break; + + numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); + numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); + /*store the code lengths of both generated trees in bitlen_lld*/ + numcodes_lld = numcodes_ll + numcodes_d; + bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); + /*numcodes_lld_e never needs more size than bitlen_lld*/ + bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); + if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ + numcodes_lld_e = 0; + + for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; + for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != numcodes_lld; ++i) { + unsigned j = 0; /*amount of repetitions*/ + while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; + + if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { + bitlen_lld_e[numcodes_lld_e++] = 17; + bitlen_lld_e[numcodes_lld_e++] = j - 3; + } else /*repeat code 18 supports max 138 zeroes*/ { + if(j > 138) j = 138; + bitlen_lld_e[numcodes_lld_e++] = 18; + bitlen_lld_e[numcodes_lld_e++] = j - 11; + } + i += (j - 1); + } else if(j >= 3) /*repeat code for value other than zero*/ { + size_t k; + unsigned num = j / 6u, rest = j % 6u; + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + for(k = 0; k < num; ++k) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = 6 - 3; + } + if(rest >= 3) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = rest - 3; + } + else j -= rest; + i += j; + } else /*too short to benefit from repeat code*/ { + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + for(i = 0; i != numcodes_lld_e; ++i) { + ++frequencies_cl[bitlen_lld_e[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, + NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*compute amount of code-length-code-lengths to output*/ + numcodes_cl = NUM_CODE_LENGTH_CODES; + /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ + while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { + numcodes_cl--; + } + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + writeBits(writer, BFINAL, 1); + writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ + writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies + or in the loop for numcodes_cl above, which saves space. */ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)(numcodes_cl - 4); + writeBits(writer, HLIT, 5); + writeBits(writer, HDIST, 5); + writeBits(writer, HCLEN, 4); + + /*write the code lengths of the code length alphabet ("bitlen_cl")*/ + for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); + + /*write the lengths of the lit/len AND the dist alphabet*/ + for(i = 0; i != numcodes_lld_e; ++i) { + writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); + /*extra bits of repeat codes*/ + if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); + else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); + else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); + + /*write the end code*/ + writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + lodepng_free(frequencies_ll); + lodepng_free(frequencies_d); + lodepng_free(frequencies_cl); + lodepng_free(bitlen_lld); + lodepng_free(bitlen_lld_e); + + return error; +} + +static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + error = generateFixedLitLenTree(&tree_ll); + if(!error) error = generateFixedDistanceTree(&tree_d); + + if(!error) { + writeBits(writer, BFINAL, 1); + writeBits(writer, 1, 1); /*first bit of BTYPE*/ + writeBits(writer, 0, 1); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { + writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); + } + } + /*add END code*/ + if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); + } + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + Hash hash; + LodePNGBitWriter writer; + + LodePNGBitWriter_init(&writer, out); + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8u + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + + if(!error) { + for(i = 0; i != numdeflateblocks && !error; ++i) { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); + } + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { + unsigned error = settings->custom_deflate(out, outsize, in, insize, settings); + /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while(len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for(i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) { + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +static unsigned lodepng_zlib_decompressv(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + unsigned error; + if(settings->custom_zlib) { + error = settings->custom_zlib(out, outsize, in, insize, settings); + if(error) { + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && *outsize > settings->max_output_size) error = 109; + } + } else { + ucvector v = ucvector_init(*out, *outsize); + if(expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + } + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + *out = NULL; + *outsize = 0; + if(!error) { + *outsize = deflatesize + 6; + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!*out) error = 83; /*alloc fail*/ + } + + if(!error) { + unsigned ADLER32 = adler32(in, (unsigned)insize); + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + (*out)[0] = (unsigned char)(CMFFLG >> 8); + (*out)[1] = (unsigned char)(CMFFLG & 255); + for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; + lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); + } + + lodepng_free(deflatedata); + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { + unsigned error = settings->custom_zlib(out, outsize, in, insize, settings); + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + (void)expected_size; + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + settings->max_output_size = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifdef LODEPNG_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#else /* LODEPNG_COMPILE_CRC */ +/*in this case, the function is only declared here, and must be defined externally +so that it will be linked in*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* LODEPNG_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) { + return lodepng_read32bitInt(chunk); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { + if(lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) { + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) { + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + size_t available_size = (size_t)(end - chunk); + if(chunk >= end || available_size < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + if(total_chunk_length > available_size) return end; /*outside of range*/ + return chunk + total_chunk_length; + } +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + size_t available_size = (size_t)(end - chunk); + if(chunk >= end || available_size < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + if(total_chunk_length > available_size) return end; /*outside of range*/ + return chunk + total_chunk_length; + } +} + +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next(chunk, end); + } +} + +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next_const(chunk, end); + } +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { + unsigned i; + size_t total_chunk_length, new_length; + unsigned char *chunk_start, *new_buffer; + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outsize) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +/*Sets length and name and allocates the space for data and crc but does not +set data or crc yet. Returns the start of the chunk in chunk. The start of +the data is at chunk + 8. To finalize chunk, add the data, then use +lodepng_chunk_generate_crc */ +static unsigned lodepng_chunk_init(unsigned char** chunk, + ucvector* out, + unsigned length, const char* type) { + size_t new_length = out->size; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; + if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ + *chunk = out->data + new_length - length - 12u; + + /*1: length*/ + lodepng_set32bitInt(*chunk, length); + + /*2: chunk name (4 letters)*/ + lodepng_memcpy(*chunk + 4, type, 4); + + return 0; +} + +/* like lodepng_chunk_create but with custom allocsize */ +static unsigned lodepng_chunk_createv(ucvector* out, + unsigned length, const char* type, const unsigned char* data) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); + + /*3: the data*/ + lodepng_memcpy(chunk + 8, data, length); + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, + unsigned length, const char* type, const unsigned char* data) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_chunk_createv(&v, length, type, data); + *out = v.data; + *outsize = v.size; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) { + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); + if(!info->palette) return; /*alloc fail*/ + for(i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if(source->palette) { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { + LodePNGColorMode result; + lodepng_color_mode_init(&result); + result.colortype = colortype; + result.bitdepth = bitdepth; + return result; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) { + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(!info->palette) /*allocate palette if empty*/ { + lodepng_color_mode_alloc_palette(info); + if(!info->palette) return 83; /*alloc fail*/ + } + if(info->palettesize >= 256) { + return 108; /*too many palette values*/ + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +/*calculate bits per pixel out of colortype and bitdepth*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) { + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { + size_t i; + for(i = 0; i != info->palettesize; ++i) { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +#ifdef LODEPNG_COMPILE_PNG + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Safely checks whether size_t overflow can be caused due to amount of pixels. +This check is overcautious rather than precise. If this check indicates no overflow, +you can safely compute in a size_t (but not an unsigned): +-(size_t)w * (size_t)h * 8 +-amount of bytes in IDAT (including filter, padding and Adam7 bytes) +-amount of bytes in raw color model +Returns 1 if overflow possible, 0 if not. +*/ +static int lodepng_pixel_overflow(unsigned w, unsigned h, + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { + size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) { + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->text_num; ++i) { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->text_keys = NULL; + dest->text_strings = NULL; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + + if(new_keys) info->text_keys = new_keys; + if(new_strings) info->text_strings = new_strings; + + if(!new_keys || !new_strings) return 83; /*alloc fail*/ + + ++info->text_num; + info->text_keys[info->text_num - 1] = alloc_string(key); + info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); + if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ + + return 0; +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { + return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); +} + +void lodepng_clear_text(LodePNGInfo* info) { + LodePNGText_cleanup(info); +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) { + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->itext_num; ++i) { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->itext_keys = NULL; + dest->itext_langtags = NULL; + dest->itext_transkeys = NULL; + dest->itext_strings = NULL; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) { + LodePNGIText_cleanup(info); +} + +static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + + if(new_keys) info->itext_keys = new_keys; + if(new_langtags) info->itext_langtags = new_langtags; + if(new_transkeys) info->itext_transkeys = new_transkeys; + if(new_strings) info->itext_strings = new_strings; + + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ + + ++info->itext_num; + + info->itext_keys[info->itext_num - 1] = alloc_string(key); + info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); + info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); + info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); + + return 0; +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) { + return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); +} + +/* same as set but does not delete */ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(profile_size == 0) return 100; /*invalid ICC profile size*/ + + info->iccp_name = alloc_string(name); + info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); + + if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ + + lodepng_memcpy(info->iccp_profile, profile, profile_size); + info->iccp_profile_size = profile_size; + + return 0; /*ok*/ +} + +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; + + return lodepng_assign_icc(info, name, profile, profile_size); +} + +void lodepng_clear_icc(LodePNGInfo* info) { + string_cleanup(&info->iccp_name); + lodepng_free(info->iccp_profile); + info->iccp_profile = NULL; + info->iccp_profile_size = 0; + info->iccp_defined = 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) { + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + info->gama_defined = 0; + info->chrm_defined = 0; + info->srgb_defined = 0; + info->iccp_defined = 0; + info->iccp_name = NULL; + info->iccp_profile = NULL; + + info->sbit_defined = 0; + info->sbit_r = info->sbit_g = info->sbit_b = info->sbit_a = 0; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) { + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + lodepng_clear_icc(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + lodepng_info_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + if(source->iccp_defined) { + CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); + } + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree { + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) { + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) { + int i; + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int bit = 0; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") +Returns error code, or 0 if ok*/ +static unsigned color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { + int bit; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + if(!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) out[i] = gray; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if(mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if(mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if(mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if(mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_PALETTE) { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to the common case of RGBA with 8 bit per channel. buffer must be RGBA with +enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + unsigned num_channels = 4; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 3], 3); + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + const unsigned num_channels = 3; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if(mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if(lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if(error) break; + } + } + + if(!error) { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + } + + if(mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + + +/* Converts a single rgb color without alpha from one type to another, color bits truncated to +their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow +function, do not use to process all pixels of an image. Alpha channel not supported on purpose: +this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the +specification it looks like bKGD should ignore the alpha values of the palette since it can use +any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ +unsigned lodepng_convert_rgb( + unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { + unsigned r = 0, g = 0, b = 0; + unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ + unsigned shift = 16 - mode_out->bitdepth; + + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { + r = g = b = r_in * mul; + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { + r = r_in * mul; + g = g_in * mul; + b = b_in * mul; + } else if(mode_in->colortype == LCT_PALETTE) { + if(r_in >= mode_in->palettesize) return 82; + r = mode_in->palette[r_in * 4 + 0] * 257u; + g = mode_in->palette[r_in * 4 + 1] * 257u; + b = mode_in->palette[r_in * 4 + 2] * 257u; + } else { + return 31; + } + + /* now convert to output format */ + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { + *r_out = r >> shift ; + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { + *r_out = r >> shift ; + *g_out = g >> shift ; + *b_out = b >> shift ; + } else if(mode_out->colortype == LCT_PALETTE) { + unsigned i; + /* a 16-bit color cannot be in the palette */ + if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; + for(i = 0; i < mode_out->palettesize; i++) { + unsigned j = i * 4; + if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && + (b >> 8) == mode_out->palette[j + 2]) { + *r_out = i; + return 0; + } + } + return 82; + } else { + return 31; + } + + return 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_stats_init(LodePNGColorStats* stats) { + /*stats*/ + stats->colored = 0; + stats->key = 0; + stats->key_r = stats->key_g = stats->key_b = 0; + stats->alpha = 0; + stats->numcolors = 0; + stats->bits = 1; + stats->numpixels = 0; + /*settings*/ + stats->allow_palette = 1; + stats->allow_greyscale = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorStats(LodePNGColorStats* p) { + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) { + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*stats must already have been inited. */ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + /* mark things as done already if it would be impossible to have a more expensive case */ + unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode_in); + unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; + unsigned sixteen = 0; /* whether the input image is 16 bit */ + unsigned maxnumcolors = 257; + if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); + + stats->numpixels += numpixels; + + /*if palette not allowed, no need to compute numcolors*/ + if(!stats->allow_palette) numcolors_done = 1; + + color_tree_init(&tree); + + /*If the stats was already filled in from previous data, fill its palette in tree + and mark things as done already if we know they are the most expensive case already*/ + if(stats->alpha) alpha_done = 1; + if(stats->colored) colored_done = 1; + if(stats->bits == 16) numcolors_done = 1; + if(stats->bits >= bpp) bits_done = 1; + if(stats->numcolors >= maxnumcolors) numcolors_done = 1; + + if(!numcolors_done) { + for(i = 0; i < stats->numcolors; i++) { + const unsigned char* color = &stats->palette[i * 4]; + error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); + if(error) goto cleanup; + } + } + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode_in->bitdepth == 16 && !sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { + stats->bits = 16; + sixteen = 1; + bits_done = 1; + numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + break; + } + } + } + + if(sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 65535 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + } + } else /* < 16-bit */ { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + + if(!bits_done && stats->bits < 8) { + /*only r is checked, < 8 bits is only relevant for grayscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > stats->bits) stats->bits = bits; + } + bits_done = (stats->bits >= bpp); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 255 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 255 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { + error = color_tree_add(&tree, r, g, b, a, stats->numcolors); + if(error) goto cleanup; + if(stats->numcolors < 256) { + unsigned char* p = stats->palette; + unsigned n = stats->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++stats->numcolors; + numcolors_done = stats->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ + stats->key_r += (stats->key_r << 8); + stats->key_g += (stats->key_g << 8); + stats->key_b += (stats->key_b << 8); + } + +cleanup: + color_tree_cleanup(&tree); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit +(with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for +all pixels of an image but only for a few additional values. */ +static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, + unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned error = 0; + unsigned char image[8]; + LodePNGColorMode mode; + lodepng_color_mode_init(&mode); + image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; + image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; + mode.bitdepth = 16; + mode.colortype = LCT_RGBA; + error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); + lodepng_color_mode_cleanup(&mode); + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Computes a minimal PNG color model that can contain all colors as indicated by the stats. +The stats should be computed with lodepng_compute_color_stats. +mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. +Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, +e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... +This is used if auto_convert is enabled (it is by default). +*/ +static unsigned auto_choose_color(LodePNGColorMode* mode_out, + const LodePNGColorMode* mode_in, + const LodePNGColorStats* stats) { + unsigned error = 0; + unsigned palettebits; + size_t i, n; + size_t numpixels = stats->numpixels; + unsigned palette_ok, gray_ok; + + unsigned alpha = stats->alpha; + unsigned key = stats->key; + unsigned bits = stats->bits; + + mode_out->key_defined = 0; + + if(key && numpixels <= 16) { + alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + key = 0; + if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + + gray_ok = !stats->colored; + if(!stats->allow_greyscale) gray_ok = 0; + if(!gray_ok && bits < 8) bits = 8; + + n = stats->numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ + if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ + if(!stats->allow_palette) palette_ok = 0; + + if(palette_ok) { + const unsigned char* p = stats->palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != stats->numcolors; ++i) { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); /*clears palette, keeps the above set colortype and bitdepth fields as-is*/ + lodepng_color_mode_copy(mode_out, mode_in); + } + } else /*8-bit or 16-bit per channel*/ { + mode_out->bitdepth = bits; + mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) + : (gray_ok ? LCT_GREY : LCT_RGB); + if(key) { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ + mode_out->key_r = stats->key_r & mask; + mode_out->key_g = stats->key_g & mask; + mode_out->key_b = stats->key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predictor, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) { + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if(pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if(w) *w = width; + if(h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if(state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + if(!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) { + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + recon[j]; + break; + } + case 2: + if(precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + /* Unroll independent paths of this predictor. A 6x and 8x version is also possible but that adds + too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + recon[i + 3] = s3 + ((r3 + p3) >> 1u); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + } + } + for(; i != length; ++i, ++j) recon[i] = scanline[i] + ((recon[j] + precon[i]) >> 1u); + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + (recon[j] >> 1u); + } + break; + case 4: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version is also possible but that + adds too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for(; i != length; ++i, ++j) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[j])); + } + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for(i = bytewidth; i != length; ++i, ++j) { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[j]); + } + } + break; + default: return 36; /*error: invalid filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for(y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) { + size_t x; + for(x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) { + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ + lodepng_color_mode_alloc_palette(color); + if(!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + + for(i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned i; + if(color->colortype == LCT_PALETTE) { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 39; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if(color->colortype == LCT_GREY) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if(color->colortype == LCT_RGB) { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ + if(data[0] >= info->color.palettesize) return 103; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 44; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 6 bytes for grayscale image*/ + if(chunkLength != 6) return 45; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + char *key = 0, *str = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(str, data + string2_begin, length); + str[length] = 0; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + char *key = 0; + unsigned char* str = 0; + size_t size = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[string2_begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(error) break; + error = lodepng_add_text_sized(info, key, (char*)str, size); + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(langtag, data + begin, length); + langtag[length] = 0; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(transkey, data + begin, length); + transkey[length] = 0; + + /*read the actual text*/ + begin += length + 1; + + length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; + + if(compressed) { + unsigned char* str = 0; + size_t size = 0; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); + lodepng_free(str); + } else { + error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); + } + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} + +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ + + info->gama_defined = 1; + info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + + return 0; /* OK */ +} + +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ + + info->chrm_defined = 1; + info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; + info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; + info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; + info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; + info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; + info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; + info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; + info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; + + return 0; /* OK */ +} + +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ + + info->srgb_defined = 1; + info->srgb_intent = data[0]; + + return 0; /* OK */ +} + +static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + size_t size = 0; + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + + info->iccp_defined = 1; + if(info->iccp_name) lodepng_clear_icc(info); + + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ + if(length < 1 || length > 79) return 89; /*keyword too short or long*/ + + info->iccp_name = (char*)lodepng_malloc(length + 1); + if(!info->iccp_name) return 83; /*alloc fail*/ + + info->iccp_name[length] = 0; + for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; + + if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_icc_size; + error = zlib_decompress(&info->iccp_profile, &size, 0, + &data[string2_begin], + length, &zlibsettings); + /*error: ICC profile larger than decoder->max_icc_size*/ + if(error && size > zlibsettings.max_output_size) error = 113; + info->iccp_profile_size = size; + if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ + return error; +} + +/*significant bits chunk (sBIT)*/ +static unsigned readChunk_sBIT(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned bitdepth = (info->color.colortype == LCT_PALETTE) ? 8 : info->color.bitdepth; + if(info->color.colortype == LCT_GREY) { + /*error: this chunk must be 1 bytes for grayscale image*/ + if(chunkLength != 1) return 114; + if(data[0] == 0 || data[0] > bitdepth) return 115; + info->sbit_defined = 1; + info->sbit_r = info->sbit_g = info->sbit_b = data[0]; /*setting g and b is not required, but sensible*/ + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 3 bytes for RGB and palette image*/ + if(chunkLength != 3) return 114; + if(data[0] == 0 || data[1] == 0 || data[2] == 0) return 115; + if(data[0] > bitdepth || data[1] > bitdepth || data[2] > bitdepth) return 115; + info->sbit_defined = 1; + info->sbit_r = data[0]; + info->sbit_g = data[1]; + info->sbit_b = data[2]; + } else if(info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 byte for grayscale with alpha image*/ + if(chunkLength != 2) return 114; + if(data[0] == 0 || data[1] == 0) return 115; + if(data[0] > bitdepth || data[1] > bitdepth) return 115; + info->sbit_defined = 1; + info->sbit_r = info->sbit_g = info->sbit_b = data[0]; /*setting g and b is not required, but sensible*/ + info->sbit_a = data[1]; + } else if(info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 4 bytes for grayscale image*/ + if(chunkLength != 4) return 114; + if(data[0] == 0 || data[1] == 0 || data[2] == 0 || data[3] == 0) return 115; + if(data[0] > bitdepth || data[1] > bitdepth || data[2] > bitdepth || data[3] > bitdepth) return 115; + info->sbit_defined = 1; + info->sbit_r = data[0]; + info->sbit_g = data[1]; + info->sbit_b = data[2]; + info->sbit_a = data[3]; + } + + return 0; /* OK */ +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize) { + const unsigned char* chunk = in + pos; + unsigned chunkLength; + const unsigned char* data; + unsigned unhandled = 0; + unsigned error = 0; + + if(pos + 4 > insize) return 30; + chunkLength = lodepng_chunk_length(chunk); + if(chunkLength > 2147483647) return 63; + data = lodepng_chunk_data_const(chunk); + if(chunkLength + 12 > insize - pos) return 30; + + if(lodepng_chunk_type_equals(chunk, "PLTE")) { + error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + error = readChunk_tRNS(&state->info_png.color, data, chunkLength); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + error = readChunk_bKGD(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + error = readChunk_tEXt(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + error = readChunk_tIME(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + error = readChunk_pHYs(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + error = readChunk_gAMA(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + error = readChunk_cHRM(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + error = readChunk_sRGB(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sBIT")) { + error = readChunk_sBIT(&state->info_png, data, chunkLength); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else { + /* unhandled chunk is ok (is not an error) */ + unhandled = 1; + } + + if(!error && !unhandled && !state->decoder.ignore_crc) { + if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned char IEND = 0; + const unsigned char* chunk; /*points to beginning of next chunk*/ + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)lodepng_malloc(insize); + if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + size_t pos = (size_t)(chunk - in); + + /*error: next chunk out of bounds of the in buffer*/ + if(chunk < in || pos + 12 > insize) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the 12 bytes for length, chunk type and CRC*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if(pos + (size_t)chunkLength + 12 > insize || pos + (size_t)chunkLength + 12 < pos) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk (or int overflow)*/ + } + + data = lodepng_chunk_data_const(chunk); + + unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if(newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + state->error = readChunk_gAMA(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + state->error = readChunk_cHRM(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + state->error = readChunk_sRGB(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sBIT")) { + state->error = readChunk_sBIT(&state->info_png, data, chunkLength); + if(state->error) break; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if(!state->error && state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if(!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + lodepng_free(idat); + + if(!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + lodepng_free(scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*disable reading things that this function doesn't output*/ + state.decoder.read_text_chunks = 0; + state.decoder.remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; + settings->max_text_size = 16777216; + settings->max_icc_size = 16777216; /* 16MB is much more than enough for any reasonable ICC profile */ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) { +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) { + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +static unsigned writeSignature(ucvector* out) { + size_t pos = out->size; + const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; + /*8 bytes PNG signature, aka the magic bytes*/ + if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ + lodepng_memcpy(out->data + pos, signature, 8); + return 0; +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { + unsigned char *chunk, *data; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); + data = chunk + 8; + + lodepng_set32bitInt(data + 0, w); /*width*/ + lodepng_set32bitInt(data + 4, h); /*height*/ + data[8] = (unsigned char)bitdepth; /*bit depth*/ + data[9] = (unsigned char)colortype; /*color type*/ + data[10] = 0; /*compression method*/ + data[11] = 0; /*filter method*/ + data[12] = interlace_method; /*interlace method*/ + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +/* only adds the chunk if needed (there is a key or palette with alpha) */ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk; + size_t i, j = 8; + + if(info->palettesize == 0 || info->palettesize > 256) { + return 68; /*invalid palette size, it is only allowed to be 1-256*/ + } + + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); + + for(i = 0; i != info->palettesize; ++i) { + /*add all channels except alpha channel*/ + chunk[j++] = info->palette[i * 4 + 0]; + chunk[j++] = info->palette[i * 4 + 1]; + chunk[j++] = info->palette[i * 4 + 2]; + } + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk = 0; + + if(info->colortype == LCT_PALETTE) { + size_t i, amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) { + if(info->palette[4 * (i - 1) + 3] != 255) break; + --amount; + } + if(amount) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); + /*add the alpha channel values from the palette*/ + for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; + } + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + } + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + chunk[10] = (unsigned char)(info->key_g >> 8); + chunk[11] = (unsigned char)(info->key_g & 255); + chunk[12] = (unsigned char)(info->key_b >> 8); + chunk[13] = (unsigned char)(info->key_b & 255); + } + } + + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* zlib = 0; + size_t zlibsize = 0; + + error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); + if(!error) { + error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); + } + lodepng_free(zlib); + return error; +} + +static unsigned addChunk_IEND(ucvector* out) { + return lodepng_chunk_createv(out, 0, "IEND", 0); +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { + unsigned char* chunk = 0; + size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); + size_t size = keysize + 1 + textsize; + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword); + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "zTXt"); + } + if(!error) { + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + if(compress) { + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + } + if(!error) { + size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); + error = lodepng_chunk_init(&chunk, out, size, "iTXt"); + } + if(!error) { + size_t pos = 8; + lodepng_memcpy(chunk + pos, keyword, keysize); + pos += keysize; + chunk[pos++] = 0; /*null termination char*/ + chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ + chunk[pos++] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + pos, langtag, langsize); + pos += langsize; + chunk[pos++] = 0; /*null termination char*/ + lodepng_memcpy(chunk + pos, transkey, transsize); + pos += transsize; + chunk[pos++] = 0; /*null termination char*/ + if(compress) { + lodepng_memcpy(chunk + pos, compressed, compressedsize); + } else { + lodepng_memcpy(chunk + pos, textstring, textsize); + } + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + chunk[10] = (unsigned char)(info->background_g >> 8); + chunk[11] = (unsigned char)(info->background_g & 255); + chunk[12] = (unsigned char)(info->background_b >> 8); + chunk[13] = (unsigned char)(info->background_b & 255); + } else if(info->color.colortype == LCT_PALETTE) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); + chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); + chunk[8] = (unsigned char)(time->year >> 8); + chunk[9] = (unsigned char)(time->year & 255); + chunk[10] = (unsigned char)time->month; + chunk[11] = (unsigned char)time->day; + chunk[12] = (unsigned char)time->hour; + chunk[13] = (unsigned char)time->minute; + chunk[14] = (unsigned char)time->second; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); + lodepng_set32bitInt(chunk + 8, info->phys_x); + lodepng_set32bitInt(chunk + 12, info->phys_y); + chunk[16] = info->phys_unit; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); + lodepng_set32bitInt(chunk + 8, info->gama_gamma); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); + lodepng_set32bitInt(chunk + 8, info->chrm_white_x); + lodepng_set32bitInt(chunk + 12, info->chrm_white_y); + lodepng_set32bitInt(chunk + 16, info->chrm_red_x); + lodepng_set32bitInt(chunk + 20, info->chrm_red_y); + lodepng_set32bitInt(chunk + 24, info->chrm_green_x); + lodepng_set32bitInt(chunk + 28, info->chrm_green_y); + lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); + lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { + unsigned char data = info->srgb_intent; + return lodepng_chunk_createv(out, 1, "sRGB", &data); +} + +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t keysize = lodepng_strlen(info->iccp_name); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + error = zlib_compress(&compressed, &compressedsize, + info->iccp_profile, info->iccp_profile_size, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "iCCP"); + } + if(!error) { + lodepng_memcpy(chunk + 8, info->iccp_name, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_sBIT(ucvector* out, const LodePNGInfo* info) { + unsigned bitdepth = (info->color.colortype == LCT_PALETTE) ? 8 : info->color.bitdepth; + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY) { + if(info->sbit_r == 0 || info->sbit_r > bitdepth) return 115; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "sBIT")); + chunk[8] = info->sbit_r; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_PALETTE) { + if(info->sbit_r == 0 || info->sbit_g == 0 || info->sbit_b == 0) return 115; + if(info->sbit_r > bitdepth || info->sbit_g > bitdepth || info->sbit_b > bitdepth) return 115; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 3, "sBIT")); + chunk[8] = info->sbit_r; + chunk[9] = info->sbit_g; + chunk[10] = info->sbit_b; + } else if(info->color.colortype == LCT_GREY_ALPHA) { + if(info->sbit_r == 0 || info->sbit_a == 0) return 115; + if(info->sbit_r > bitdepth || info->sbit_a > bitdepth) return 115; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "sBIT")); + chunk[8] = info->sbit_r; + chunk[9] = info->sbit_a; + } else if(info->color.colortype == LCT_RGBA) { + if(info->sbit_r == 0 || info->sbit_g == 0 || info->sbit_b == 0 || info->sbit_a == 0 || + info->sbit_r > bitdepth || info->sbit_g > bitdepth || + info->sbit_b > bitdepth || info->sbit_a > bitdepth) { + return 115; + } + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "sBIT")); + chunk[8] = info->sbit_r; + chunk[9] = info->sbit_g; + chunk[10] = info->sbit_b; + chunk[11] = info->sbit_a; + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) { + size_t i; + switch(filterType) { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } else { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*invalid filter type given*/ + } +} + +/* integer binary logarithm, max return value is 31 */ +static size_t ilog2(size_t i) { + size_t result = 0; + if(i >= 65536) { result += 16; i >>= 16; } + if(i >= 256) { result += 8; i >>= 8; } + if(i >= 16) { result += 4; i >>= 4; } + if(i >= 4) { result += 2; i >>= 2; } + if(i >= 2) { result += 1; /*i >>= 1;*/ } + return result; +} + +/* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ +static size_t ilog2i(size_t i) { + size_t l; + if(i == 0) return 0; + l = ilog2(i); + /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) + linearly approximates the missing fractional part multiplied by i */ + return i * l + ((i - (1u << l)) << 1u); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(color); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { + unsigned char type = (unsigned char)strategy; + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_MINSUM) { + /*adaptive filtering*/ + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + if(type == 0) { + for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); + } else { + for(x = 0; x != linebytes; ++x) { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum < smallest) { + bestType = type; + smallest = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_ENTROPY) { + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t bestSum = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + lodepng_memset(count, 0, 256 * sizeof(*count)); + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + for(x = 0; x != 256; ++x) { + sum += ilog2i(count[x]); + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum > bestSum) { + bestType = type; + bestSum = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_BRUTE_FORCE) { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings; + lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + if(!error) { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { + unsigned testsize = (unsigned)linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) { + size_t x; + for(x = 0; x < ilinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) { + *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) { + addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } else { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) { + if(bpp < 8) { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } else { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk, data + datasize); + } + return 0; +} + +static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { + /* + It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 + are "RGB ". We do not perform any full parsing of the ICC profile here, other + than check those 4 bytes to grayscale profile. Other than that, validity of + the profile is not checked. This is needed only because the PNG specification + requires using a non-gray color model if there is an ICC profile with "RGB " + (sadly limiting compression opportunities if the input data is grayscale RGB + data), and requires using a gray color model if it is "GRAY". + */ + if(size < 20) return 0; + return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; +} + +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { + /* See comment in isGrayICCProfile*/ + if(size < 20) return 0; + return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) { + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + ucvector outv = ucvector_init(NULL, 0); + LodePNGInfo info; + const LodePNGInfo* info_png = &state->info_png; + LodePNGColorMode auto_color; + + lodepng_info_init(&info); + lodepng_color_mode_init(&auto_color); + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { + /*this error is returned even if auto_convert is enabled and thus encoder could + generate the palette by itself: while allowing this could be possible in theory, + it may complicate the code or edge cases, and always requiring to give a palette + when setting this color type is a simpler contract*/ + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + goto cleanup; + } + if(state->encoder.zlibsettings.btype > 2) { + state->error = 61; /*error: invalid btype*/ + goto cleanup; + } + if(info_png->interlace_method > 1) { + state->error = 71; /*error: invalid interlace mode*/ + goto cleanup; + } + state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) { + LodePNGColorStats stats; + unsigned allow_convert = 1; + lodepng_color_stats_init(&stats); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined && + isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use palette with a GRAY ICC profile, even + if the palette has only gray colors, so disallow it.*/ + stats.allow_palette = 0; + } + if(info_png->iccp_defined && + isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ + stats.allow_greyscale = 0; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->background_defined) { + /*the background chunk's color must be taken into account as well*/ + unsigned r = 0, g = 0, b = 0; + LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); + lodepng_convert_rgb(&r, &g, &b, + info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); + state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); + if(state->error) goto cleanup; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = auto_choose_color(&auto_color, &state->info_raw, &stats); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->sbit_defined) { + /*if sbit is defined, due to strict requirements of which sbit values can be present for which color modes, + auto_convert can't be done in many cases. However, do support a few cases here. + TODO: more conversions may be possible, and it may also be possible to get a more appropriate color type out of + auto_choose_color if knowledge about sbit is used beforehand + */ + unsigned sbit_max = LODEPNG_MAX(LODEPNG_MAX(LODEPNG_MAX(info_png->sbit_r, info_png->sbit_g), + info_png->sbit_b), info_png->sbit_a); + unsigned equal = (!info_png->sbit_g || info_png->sbit_g == info_png->sbit_r) + && (!info_png->sbit_b || info_png->sbit_b == info_png->sbit_r) + && (!info_png->sbit_a || info_png->sbit_a == info_png->sbit_r); + allow_convert = 0; + if(info.color.colortype == LCT_PALETTE && + auto_color.colortype == LCT_PALETTE) { + /* input and output are palette, and in this case it may happen that palette data is + expected to be copied from info_raw into the info_png */ + allow_convert = 1; + } + /*going from 8-bit RGB to palette (or 16-bit as long as sbit_max <= 8) is possible + since both are 8-bit RGB for sBIT's purposes*/ + if(info.color.colortype == LCT_RGB && + auto_color.colortype == LCT_PALETTE && sbit_max <= 8) { + allow_convert = 1; + } + /*going from 8-bit RGBA to palette is also ok but only if sbit_a is exactly 8*/ + if(info.color.colortype == LCT_RGBA && auto_color.colortype == LCT_PALETTE && + info_png->sbit_a == 8 && sbit_max <= 8) { + allow_convert = 1; + } + /*going from 16-bit RGB(A) to 8-bit RGB(A) is ok if all sbit values are <= 8*/ + if((info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA) && info.color.bitdepth == 16 && + auto_color.colortype == info.color.colortype && auto_color.bitdepth == 8 && + sbit_max <= 8) { + allow_convert = 1; + } + /*going to less channels is ok if all bit values are equal (all possible values in sbit, + as well as the chosen bitdepth of the result). Due to how auto_convert works, + we already know that auto_color.colortype has less than or equal amount of channels than + info.colortype. Palette is not used here. This conversion is not allowed if + info_png->sbit_r < auto_color.bitdepth, because specifically for alpha, non-presence of + an sbit value heavily implies that alpha's bit depth is equal to the PNG bit depth (rather + than the bit depths set in the r, g and b sbit values, by how the PNG specification describes + handling tRNS chunk case with sBIT), so be conservative here about ignoring user input.*/ + if(info.color.colortype != LCT_PALETTE && auto_color.colortype != LCT_PALETTE && + equal && info_png->sbit_r == auto_color.bitdepth) { + allow_convert = 1; + } + } +#endif + if(state->encoder.force_palette) { + if(info.color.colortype != LCT_GREY && info.color.colortype != LCT_GREY_ALPHA && + (auto_color.colortype == LCT_GREY || auto_color.colortype == LCT_GREY_ALPHA)) { + /*user speficially forced a PLTE palette, so cannot convert to grayscale types because + the PNG specification only allows writing a suggested palette in PLTE for truecolor types*/ + allow_convert = 0; + } + } + if(allow_convert) { + lodepng_color_mode_copy(&info.color, &auto_color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*also convert the background chunk*/ + if(info_png->background_defined) { + if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, + info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { + state->error = 104; + goto cleanup; + } + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + } + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined) { + unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; + if(!gray_icc && !rgb_icc) { + state->error = 100; /* Disallowed profile color type for PNG */ + goto cleanup; + } + if(gray_icc != gray_png) { + /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, + or in case of auto_convert, it wasn't possible to find appropriate model*/ + state->error = state->encoder.auto_convert ? 102 : 101; + goto cleanup; + } + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { + unsigned char* converted; + size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + if(state->error) goto cleanup; + } else { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + if(state->error) goto cleanup; + } + + /* output all PNG chunks */ { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + state->error = writeSignature(&outv); + if(state->error) goto cleanup; + /*IHDR*/ + state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) goto cleanup; + } + /*color profile chunks must come before PLTE */ + if(info.iccp_defined) { + state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + if(info.srgb_defined) { + state->error = addChunk_sRGB(&outv, &info); + if(state->error) goto cleanup; + } + if(info.gama_defined) { + state->error = addChunk_gAMA(&outv, &info); + if(state->error) goto cleanup; + } + if(info.chrm_defined) { + state->error = addChunk_cHRM(&outv, &info); + if(state->error) goto cleanup; + } + if(info_png->sbit_defined) { + state->error = addChunk_sBIT(&outv, &info); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) { + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { + /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + /*tRNS (this will only add if when necessary) */ + state->error = addChunk_tRNS(&outv, &info.color); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) { + state->error = addChunk_bKGD(&outv, &info); + if(state->error) goto cleanup; + } + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) { + state->error = addChunk_pHYs(&outv, &info); + if(state->error) goto cleanup; + } + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) { + state->error = addChunk_tIME(&outv, &info.time); + if(state->error) goto cleanup; + } + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) { + if(lodepng_strlen(info.text_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.text_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + if(state->encoder.text_compression) { + state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } else { + state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + if(state->error) goto cleanup; + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) { + unsigned already_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) { + const char* k = info.text_keys[i]; + /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ + if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && + k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { + already_added_id_text = 1; + break; + } + } + if(already_added_id_text == 0) { + state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + if(state->error) goto cleanup; + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) { + if(lodepng_strlen(info.itext_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.itext_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + state->error = addChunk_iTXt( + &outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + state->error = addChunk_IEND(&outv); + if(state->error) goto cleanup; + } + +cleanup: + lodepng_info_cleanup(&info); + lodepng_free(data); + lodepng_color_mode_cleanup(&auto_color); + + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) { + switch(code) { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ + case 16: return "invalid code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ + case 39: return "tRNS chunk before PLTE or has more entries than palette size"; + case 40: return "tRNS chunk has wrong size for grayscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for grayscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ + case 62: return "conversion from color to grayscale not supported"; + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "integer overflow due to too many pixels"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + case 95: return "integer overflow with combined idat chunk size"; + case 96: return "invalid gAMA chunk size"; + case 97: return "invalid cHRM chunk size"; + case 98: return "invalid sRGB chunk size"; + case 99: return "invalid sRGB rendering intent"; + case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; + case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; + case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; + case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; + case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; + case 105: return "integer overflow of bitsize"; + case 106: return "PNG file must have PLTE chunk if color type is palette"; + case 107: return "color convert from palette mode requested without setting the palette data in it"; + case 108: return "tried to add more than 256 values to a palette"; + /*this limit can be configured in LodePNGDecompressSettings*/ + case 109: return "tried to decompress zlib or deflate data larger than desired max_output_size"; + case 110: return "custom zlib or inflate decompression failed"; + case 111: return "custom zlib or deflate compression failed"; + /*max text size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large text sizes.*/ + case 112: return "compressed text unreasonably large"; + /*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large ICC profile*/ + case 113: return "ICC profile unreasonably large"; + case 114: return "sBIT chunk has wrong size for the color type of the image"; + case 115: return "sBIT value out of range"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector& buffer, const std::string& filename) { + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector& buffer, const std::string& filename) { + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); + if(buffer) { + out.insert(out.end(), buffer, &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) { + out.insert(out.end(), buffer, &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() { + lodepng_state_init(this); +} + +State::State(const State& other) { + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() { + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) { + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), buffer, &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) { + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), buffer, &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + /* safe output values in case error happens */ + w = h = 0; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) { + out.insert(out.end(), buffer, &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) { + out.insert(out.end(), buffer, &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) { + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/lodepng.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/lodepng.h new file mode 100644 index 0000000..7f0190d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/lodepng.h @@ -0,0 +1,2085 @@ +/* +LodePNG version 20221108 + +Copyright (c) 2005-2022 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc or clang. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +/*pass -DLODEPNG_NO_COMPILE_ZLIB to the compiler to disable this, or comment out LODEPNG_COMPILE_ZLIB below*/ +#define LODEPNG_COMPILE_ZLIB +#endif + +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +/*pass -DLODEPNG_NO_COMPILE_PNG to the compiler to disable this, or comment out LODEPNG_COMPILE_PNG below*/ +#define LODEPNG_COMPILE_PNG +#endif + +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +/*pass -DLODEPNG_NO_COMPILE_DECODER to the compiler to disable this, or comment out LODEPNG_COMPILE_DECODER below*/ +#define LODEPNG_COMPILE_DECODER +#endif + +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +/*pass -DLODEPNG_NO_COMPILE_ENCODER to the compiler to disable this, or comment out LODEPNG_COMPILE_ENCODER below*/ +#define LODEPNG_COMPILE_ENCODER +#endif + +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +/*pass -DLODEPNG_NO_COMPILE_DISK to the compiler to disable this, or comment out LODEPNG_COMPILE_DISK below*/ +#define LODEPNG_COMPILE_DISK +#endif + +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +/*pass -DLODEPNG_NO_COMPILE_ANCILLARY_CHUNKS to the compiler to disable this, +or comment out LODEPNG_COMPILE_ANCILLARY_CHUNKS below*/ +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif + +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +/*pass -DLODEPNG_NO_COMPILE_ERROR_TEXT to the compiler to disable this, +or comment out LODEPNG_COMPILE_ERROR_TEXT below*/ +#define LODEPNG_COMPILE_ERROR_TEXT +#endif + +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +/*pass -DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler to disable the built-in ones, +or comment out LODEPNG_COMPILE_ALLOCATORS below*/ +#define LODEPNG_COMPILE_ALLOCATORS +#endif + +/*Disable built-in CRC function, in that case a custom implementation of +lodepng_crc32 must be defined externally so that it can be linked in.*/ +#ifndef LODEPNG_NO_COMPILE_CRC +/*pass -DLODEPNG_NO_COMPILE_CRC to the compiler to disable the built-in one, +or comment out LODEPNG_COMPILE_CRC below*/ +#define LODEPNG_COMPILE_CRC +#endif + +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +/*pass -DLODEPNG_NO_COMPILE_CPP to the compiler to disable C++ (not needed if a C-only compiler), +or comment out LODEPNG_COMPILE_CPP below*/ +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw image).*/ +typedef enum LodePNGColorType { + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings { + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*Maximum decompressed size, beyond this the decoder may (and is encouraged to) stop decoding, + return an error, output a data size > max_output_size and all the data up to that point. This is + not hard limit nor a guarantee, but can prevent excessive memory usage. This setting is + ignored by the PNG decoder, but is used by the deflate/zlib decoder and can be used by custom ones. + Set to 0 to impose no limit (the default).*/ + size_t max_output_size; + + /*use custom zlib decoder instead of built in one (default: null). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ { + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode { + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, with the default settings you can ignore this palette, since + LodePNG already fills the palette colors in the pixels of the raw RGBA output, + but when decoding to the original PNG color mode it is needed to reconstruct + the colors. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); +/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a grayscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime { + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo { + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + Suggested background color chunk (bKGD) + + This uses the same color mode and bit depth as the PNG (except no alpha channel), + with values truncated to the bit depth in the unsigned integer. + + For grayscale and palette PNGs, the value is stored in background_r. The values + in background_g and background_b are then unused. The decoder will set them + equal to background_r, the encoder ignores them in this case. + + When decoding, you may get these in a different color mode than the one you requested + for the raw pixels: the colortype and bitdepth defined by info_png.color, that is the + ones defined in the header of the PNG image, are used. + + When encoding with auto_convert, you must use the color model defined in info_png.color for + these values. The encoder normally ignores info_png.color when auto_convert is on, but will + use it to interpret these values (and convert copies of them to its chosen color model). + + When encoding, avoid setting this to an expensive color, such as a non-gray value + when the image is gray, or the compression will be worse since it will be forced to + write the PNG with a more expensive color mode (when auto_convert is on). + + The decoder does not use this background color to edit the color of pixels. This is a + completely optional metadata feature. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red/gray/palette component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + Non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + All the string fields below including strings, keys, names and language tags are null terminated. + The PNG specification uses null characters for the keys, names and tags, and forbids null + characters to appear in the main text which is why we can use null termination everywhere here. + + A keyword is minimum 1 character and maximum 79 characters long (plus the + additional null terminator). It's discouraged to use a single line length + longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + + Standard text chunk keywords and strings are encoded using Latin-1. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + International text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys", and the following text encodings are used: + keys: Latin-1, langtags: ASCII, transkeys and strings: UTF-8. + keys must be 1-79 characters (plus the additional null terminator), the other + strings are any length. + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + Color profile related chunks: gAMA, cHRM, sRGB, iCPP, sBIT + + LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color + profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please + use these values with a color management library. + + See the PNG, ICC and sRGB specifications for more information about the meaning of these values. + */ + + /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ + unsigned gama_gamma; /* Gamma exponent times 100000 */ + + /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ + unsigned chrm_white_x; /* White Point x times 100000 */ + unsigned chrm_white_y; /* White Point y times 100000 */ + unsigned chrm_red_x; /* Red x times 100000 */ + unsigned chrm_red_y; /* Red y times 100000 */ + unsigned chrm_green_x; /* Green x times 100000 */ + unsigned chrm_green_y; /* Green y times 100000 */ + unsigned chrm_blue_x; /* Blue x times 100000 */ + unsigned chrm_blue_y; /* Blue y times 100000 */ + + /* + sRGB chunk: optional. May not appear at the same time as iCCP. + If gAMA is also present gAMA must contain value 45455. + If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. + */ + unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ + unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ + + /* + iCCP chunk: optional. May not appear at the same time as sRGB. + + LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a + separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color + management and conversions. + + For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC + profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and + enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. + + For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray + PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure + the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is + enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder + error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel + data if the pixels could be encoded as grayscale but the ICC profile is RGB. + + To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so + make sure you compute it carefully to avoid the above problems. + */ + unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ + char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ + /* + The ICC profile in iccp_profile_size bytes. + Don't allocate this buffer yourself. Use the init/cleanup functions + correctly and use lodepng_set_icc and lodepng_clear_icc. + */ + unsigned char* iccp_profile; + unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ + + /* + sBIT chunk: significant bits. Optional metadata, only set this if needed. + + If defined, these values give the bit depth of the original data. Since PNG only stores 1, 2, 4, 8 or 16-bit + per channel data, the significant bits value can be used to indicate the original encoded data has another + sample depth, such as 10 or 12. + + Encoders using this value, when storing the pixel data, should use the most significant bits + of the data to store the original bits, and use a good sample depth scaling method such as + "left bit replication" to fill in the least significant bits, rather than fill zeroes. + + Decoders using this value, if able to work with data that's e.g. 10-bit or 12-bit, should right + shift the data to go back to the original bit depth, but decoders are also allowed to ignore + sbit and work e.g. with the 8-bit or 16-bit data from the PNG directly, since thanks + to the encoder contract, the values encoded in PNG are in valid range for the PNG bit depth. + + For grayscale images, sbit_g and sbit_b are not used, and for images that don't use color + type RGBA or grayscale+alpha, sbit_a is not used (it's not used even for palette images with + translucent palette values, or images with color key). The values that are used must be + greater than zero and smaller than or equal to the PNG bit depth. + + The color type from the header in the PNG image defines these used and unused fields: if + decoding with a color mode conversion, such as always decoding to RGBA, this metadata still + only uses the color type of the original PNG, and may e.g. lack the alpha channel info + if the PNG was RGB. When encoding with auto_convert (as well as without), also always the + color model defined in info_png.color determines this. + + NOTE: enabling sbit can hurt compression, because the encoder can then not always use + auto_convert to choose a more optimal color mode for the data, because the PNG format has + strict requirements for the allowed sbit values in combination with color modes. + For example, setting these fields to 10-bit will force the encoder to keep using a 16-bit per channel + color mode, even if the pixel data would in fact fit in a more efficient 8-bit mode. + */ + unsigned sbit_defined; /*is significant bits given? if not, the values below are unused*/ + unsigned sbit_r; /*red or gray component of significant bits*/ + unsigned sbit_g; /*green component of significant bits*/ + unsigned sbit_b; /*blue component of significant bits*/ + unsigned sbit_a; /*alpha component of significant bits*/ + + /* End of color profile related chunks */ + + + /* + unknown chunks: chunks not known by LodePNG, passed on byte for byte. + + There are 3 buffers, one for each position in the PNG where unknown chunks can appear. + Each buffer contains all unknown chunks for that position consecutively. + The 3 positions are: + 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. + + For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag + above in here, since the encoder will blindly follow this and could then encode an invalid PNG file + (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use + this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), + or any non-standard PNG chunk. + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ + +/*replaces if exists*/ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); +void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings { + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; + + /* maximum size for decompressed text chunks. If a text chunk's text is larger than this, an error is returned, + unless reading text chunks is disabled or this limit is set higher or disabled. Set to 0 to allow any size. + By default it is a value that prevents unreasonably large strings from hogging memory. */ + size_t max_text_size; + + /* maximum size for compressed ICC chunks. If the ICC profile is larger than this, an error will be returned. Set to + 0 to allow any size. By default this is a value that prevents ICC profiles that would be much larger than any + legitimate profile could be to hog memory. */ + size_t max_icc_size; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy { + /*every filter at zero*/ + LFS_ZERO = 0, + /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ + LFS_ONE = 1, + LFS_TWO = 2, + LFS_THREE = 3, + LFS_FOUR = 4, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), +which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorStats { + unsigned colored; /*not grayscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ + size_t numpixels; + + /*user settings for computing/using the stats*/ + unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ + unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ +} LodePNGColorStats; + +void lodepng_color_stats_init(LodePNGColorStats* stats); + +/*Get a LodePNGColorStats of the image. The stats must already have been inited. +Returns error code (e.g. alloc fail) or 0 if ok.*/ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings { + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is always created. If color type is explicitely set + to a grayscale type (1 or 4), this is not done and is ignored. If enabling this, + a palette must be present in the info_png. + NOTE: enabling this may worsen compression if auto_convert is used to choose + optimal color mode, because it cannot use grayscale color modes in this case*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState { +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the IHDR chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* +Reads one metadata chunk (other than IHDR, which is handled by lodepng_inspect) +of the PNG file and outputs what it read in the state. Returns error code on failure. +Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const +to find the desired chunk type, and if non null use lodepng_inspect_chunk (with +chunk_pointer - start_of_file as pos). +Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). +Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). +Requirements: &in[pos] must point to start of a chunk, must use regular +lodepng_inspect first since format of most other chunks depends on IHDR, and if +there is a PLTE chunk, that one must be inspected before tRNS or bKGD. +*/ +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +The chunk pointer always points to the beginning of the chunk itself, that is +the first byte of the 4 length bytes. + +In the PNG file format, chunks have the following format: +-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) +-4 bytes chunk type (ASCII a-z,A-Z only, see below) +-length bytes of data (may be 0 bytes if length was 0) +-4 bytes of CRC, computed on chunk name + data + +The first chunk starts at the 8th byte of the PNG file, the entire rest of the file +exists out of concatenated chunks with the above format. + +PNG standard chunk ASCII naming conventions: +-First byte: uppercase = critical, lowercase = ancillary +-Second byte: uppercase = public, lowercase = private +-Third byte: must be uppercase +-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/* +Iterate to next chunks, allows iterating through all chunks of the PNG file. +Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, +or the 8th byte of a PNG file which always has the first chunk), or alternatively may +point to the first byte of the PNG file (which is not a chunk but the magic header, the +function will then skip over it and return the first real chunk). +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. +In a non-corrupt PNG file, the last chunk should have name "IEND". +*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); + +/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng { +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState { + public: + State(); + State(const State& other); + ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory +*/ +unsigned load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[X] support color profile chunk types (but never let them touch RGB values by default) +[ ] support all public PNG chunk types (almost done except sPLT and hIST) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +[X] provide alternatives for C library functions not present on some platforms (memcpy, ...) +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent + sBIT: significant bits + +1.2. features not supported +--------------------------- + +The following features are not (yet) supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The hIST and sPLT public chunks are not (yet) supported but treated as unknown chunks + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to grayscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, grayscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to grayscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: grayscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: grayscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +Non supported color conversions: +-color to grayscale when non-gray pixels are present: no error is thrown, but +the result will look ugly because only the red channel is taken (it assumes all +three channels are the same in this case so ignores green and blue). The reason +no error is given is to allow converting from three-channel grayscale images to +one-channel even if there are numerical imprecisions. +-anything to palette when the palette does not have an exact match for a from-color +in it: in this case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any gray or gray+alpha, to gray or gray+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + +It is not recommended to use the numerical values to programmatically make +different decisions based on error types as the numbers are not guaranteed to +stay backwards compatible. They are for human consumption only. Programmatically +only 0 or non-0 matter. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outsize. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distinction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards compliant. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +NOTE: these examples do not support wide-character filenames, you can use an +external method to handle such files and encode or decode in-memory + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) { + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) { + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +Not all changes are listed here, the commit history in github lists more: +https://github.com/lvandeve/lodepng + +*) 13 jun 2022: added support for the sBIT chunk. +*) 09 jan 2022: minor decoder speed improvements. +*) 27 jun 2021: added warnings that file reading/writing functions don't support + wide-character filenames (support for this is not planned, opening files is + not the core part of PNG decoding/decoding and is platform dependent). +*) 17 okt 2020: prevent decoding too large text/icc chunks by default. +*) 06 mar 2020: simplified some of the dynamic memory allocations. +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. +*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. +*) 10 sep 2018: added way to inspect metadata chunks without full decoding. +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use + palette index in case of palette. +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This + change is backwards compatible unless you relied on unknown_chunks for those. +*) 11 jun 2018: less restrictive check for pixel size integer overflow +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 24 aug 2014: Moved to github +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011: (!) changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2022 Lode Vandevenne +*/ diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/test_container.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/test_container.cpp new file mode 100644 index 0000000..c3e2a8b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/test_container.cpp @@ -0,0 +1,94 @@ +#include "test_container.h" +#include "Font.h" +string readfile(string filename); + +// note: font is selected only by size, name and style are not used +uint_ptr test_container::create_font(const char* faceName, int size, int weight, font_style italic, unsigned int decoration, font_metrics* fm) +{ + Font* font = new Font(size); + + if (fm) + { + fm->ascent = font->ascent; + fm->descent = font->descent; + fm->height = font->height; + fm->x_height = font->x_height; + } + return (uint_ptr)font; +} + +int test_container::text_width(const char* text, uint_ptr hFont) +{ + Font* font = (Font*)hFont; + return (int)strlen(text) * font->width; +} + +void test_container::draw_text(uint_ptr hdc, const char* text, uint_ptr hFont, web_color color, const position& pos) +{ + Bitmap* bmp = (Bitmap*)hdc; + Font* font = (Font*)hFont; + + int x = pos.x; + for (auto p = text; *p; p++) + { + Bitmap glyph = font->get_glyph(*p, color); + bmp->draw_bitmap(x, pos.y, glyph); + x += glyph.width; + } +} + +int test_container::pt_to_px(int pt) const { return pt * 96 / 72; } +int test_container::get_default_font_size() const { return 16; } +const char* test_container::get_default_font_name() const { return ""; } + +void test_container::draw_background(uint_ptr hdc, const std::vector& bg) +{ + Bitmap* bmp = (Bitmap*)hdc; + bmp->fill_rect(bg.back().border_box, bg.back().color); +} + +void test_container::draw_borders(uint_ptr hdc, const borders& borders, const position& pos, bool root) +{ + Bitmap* bmp = (Bitmap*)hdc; + + // left border + for (int x = 0; x < borders.left.width; x++) + bmp->draw_line( + pos.left() + x, pos.top(), + pos.left() + x, pos.bottom(), borders.left.color); + + // right border + for (int x = 0; x < borders.right.width; x++) + bmp->draw_line( + pos.right() - x - 1, pos.top(), + pos.right() - x - 1, pos.bottom(), borders.right.color); + + // top border + for (int y = 0; y < borders.top.width; y++) + bmp->draw_line( + pos.left(), pos.top() + y, + pos.right(), pos.top() + y, borders.top.color); + + // bottom border + for (int y = 0; y < borders.bottom.width; y++) + bmp->draw_line( + pos.left(), pos.bottom() - y - 1, + pos.right(), pos.bottom() - y - 1, borders.bottom.color); +} + +void test_container::draw_list_marker(uint_ptr hdc, const list_marker& marker) +{ + Bitmap* bmp = (Bitmap*)hdc; + bmp->fill_rect(marker.pos, marker.color); +} + +void test_container::import_css(string& text, const string& url, string& baseurl) +{ + baseurl = basedir + "/" + url; + text = readfile(baseurl); +} + +void test_container::get_client_rect(position& client) const +{ + client = position(0, 0, width, height); +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/test_container.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/test_container.h new file mode 100644 index 0000000..9643126 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/test/test_container.h @@ -0,0 +1,42 @@ +#include +using namespace litehtml; + +class test_container : public document_container +{ +public: + int width; + int height; + string basedir; + + test_container(int width, int height, string basedir) : width(width), height(height), basedir(basedir) {} + + uint_ptr create_font(const char* faceName, int size, int weight, font_style italic, unsigned int decoration, font_metrics* fm) override; + void delete_font(uint_ptr hFont) override {} + int text_width(const char* text, uint_ptr hFont) override; + void draw_text(uint_ptr hdc, const char* text, uint_ptr hFont, web_color color, const position& pos) override; + int pt_to_px(int pt) const override; + int get_default_font_size() const override; + const char* get_default_font_name() const override; + void load_image(const char* src, const char* baseurl, bool redraw_on_ready) override {} + void get_image_size(const char* src, const char* baseurl, size& sz) override {} + void draw_background(uint_ptr hdc, const std::vector& bg) override; + void draw_borders(uint_ptr hdc, const borders& borders, const position& draw_pos, bool root) override; + void draw_list_marker(uint_ptr hdc, const list_marker& marker) override; + element::ptr create_element(const char* tag_name, + const string_map& attributes, + const document::ptr& doc) override { return 0; } + void get_media_features(media_features& media) const override {} + void get_language(string& language, string& culture) const override {} + void link(const document::ptr& doc, const element::ptr& el) override {} + + void transform_text(string& text, text_transform tt) override {} + void set_clip(const position& pos, const border_radiuses& bdr_radius) override {} + void del_clip() override {} + + void set_caption(const char* caption) override {} + void set_base_url(const char* base_url) override {} + void on_anchor_click(const char* url, const element::ptr& el) override {} + void set_cursor(const char* cursor) override {} + void import_css(string& text, const string& url, string& baseurl) override; + void get_client_rect(position& client) const override; +}; \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/win32/win32_container.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/win32/win32_container.cpp new file mode 100644 index 0000000..520b1ed --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/win32/win32_container.cpp @@ -0,0 +1,426 @@ +#include "win32_container.h" + +win32_container::win32_container() +{ + m_hClipRgn = NULL; + m_tmp_hdc = GetDC(NULL); + InitializeCriticalSection(&m_img_sync); + + EnumFonts(m_tmp_hdc, NULL, EnumFontsProc, (LPARAM)this); + m_installed_fonts.insert(L"monospace"); + m_installed_fonts.insert(L"serif"); + m_installed_fonts.insert(L"sans-serif"); + m_installed_fonts.insert(L"fantasy"); + m_installed_fonts.insert(L"cursive"); +} + +win32_container::~win32_container() +{ + DeleteCriticalSection(&m_img_sync); + if(m_hClipRgn) + { + DeleteObject(m_hClipRgn); + } + ReleaseDC(NULL, m_tmp_hdc); +} + +int CALLBACK win32_container::EnumFontsProc(const LOGFONT* lplf, const TEXTMETRIC* lptm, DWORD dwType, LPARAM lpData) +{ + win32_container* container = (win32_container*)lpData; + container->m_installed_fonts.insert(lplf->lfFaceName); + return 1; +} + +static LPCWSTR get_exact_font_name(LPCWSTR facename) +{ + if (!lstrcmpi(facename, L"monospace")) return L"Courier New"; + else if (!lstrcmpi(facename, L"serif")) return L"Times New Roman"; + else if (!lstrcmpi(facename, L"sans-serif")) return L"Arial"; + else if (!lstrcmpi(facename, L"fantasy")) return L"Impact"; + else if (!lstrcmpi(facename, L"cursive")) return L"Comic Sans MS"; + else return facename; +} + +static void trim_quotes(litehtml::string& str) +{ + if (str.front() == '"' || str.front() == '\'') + str.erase(0, 1); + + if (str.back() == '"' || str.back() == '\'') + str.erase(str.length() - 1, 1); +} + +litehtml::uint_ptr win32_container::create_font( const char* font_list, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics* fm ) +{ + std::wstring font_name; + litehtml::string_vector fonts; + litehtml::split_string(font_list, fonts, ","); + bool found = false; + for (auto& name : fonts) + { + litehtml::trim(name); + trim_quotes(name); + std::wstring wname = (const wchar_t*)litehtml_to_wchar(name.c_str()); + if (m_installed_fonts.count(wname)) + { + font_name = wname; + found = true; + break; + } + } + if (!found) font_name = litehtml_to_wchar(get_default_font_name()); + font_name = get_exact_font_name(font_name.c_str()); + + LOGFONT lf = {}; + wcscpy_s(lf.lfFaceName, LF_FACESIZE, font_name.c_str()); + + lf.lfHeight = -size; + lf.lfWeight = weight; + lf.lfItalic = (italic == litehtml::font_style_italic) ? TRUE : FALSE; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfOutPrecision = OUT_DEFAULT_PRECIS; + lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf.lfQuality = DEFAULT_QUALITY; + lf.lfStrikeOut = (decoration & litehtml::font_decoration_linethrough) ? TRUE : FALSE; + lf.lfUnderline = (decoration & litehtml::font_decoration_underline) ? TRUE : FALSE; + HFONT hFont = CreateFontIndirect(&lf); + + if (fm) + { + SelectObject(m_tmp_hdc, hFont); + TEXTMETRIC tm = {}; + GetTextMetrics(m_tmp_hdc, &tm); + fm->ascent = tm.tmAscent; + fm->descent = tm.tmDescent; + fm->height = tm.tmHeight; + fm->x_height = tm.tmHeight / 2; // this is an estimate; call GetGlyphOutline to get the real value + fm->draw_spaces = italic || decoration; + } + + return (uint_ptr) hFont; +} + +void win32_container::delete_font( uint_ptr hFont ) +{ + DeleteObject((HFONT) hFont); +} + +const char* win32_container::get_default_font_name() const +{ + return "Times New Roman"; +} + +int win32_container::get_default_font_size() const +{ + return 16; +} + +int win32_container::text_width( const char* text, uint_ptr hFont ) +{ + SIZE size = {}; + SelectObject(m_tmp_hdc, (HFONT)hFont); + std::wstring wtext = (const wchar_t*)litehtml_to_wchar(text); + GetTextExtentPoint32(m_tmp_hdc, wtext.c_str(), (int)wtext.size(), &size); + return size.cx; +} + +void win32_container::draw_text( uint_ptr hdc, const char* text, uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos ) +{ + apply_clip((HDC) hdc); + + HFONT oldFont = (HFONT) SelectObject((HDC) hdc, (HFONT) hFont); + + SetBkMode((HDC) hdc, TRANSPARENT); + + SetTextColor((HDC) hdc, RGB(color.red, color.green, color.blue)); + + RECT rcText = { pos.left(), pos.top(), pos.right(), pos.bottom() }; + DrawText((HDC) hdc, litehtml_to_wchar(text), -1, &rcText, DT_SINGLELINE | DT_NOPREFIX | DT_BOTTOM | DT_NOCLIP); + + SelectObject((HDC) hdc, oldFont); + + release_clip((HDC) hdc); +} + +int win32_container::pt_to_px( int pt ) const +{ + return MulDiv(pt, GetDeviceCaps(m_tmp_hdc, LOGPIXELSY), 72); +} + +void win32_container::draw_list_marker(uint_ptr hdc, const litehtml::list_marker& marker) +{ + apply_clip((HDC)hdc); + + int top_margin = marker.pos.height / 3; + if (top_margin < 4) + top_margin = 0; + + int draw_x = marker.pos.x; + int draw_y = marker.pos.y + top_margin; + int draw_width = marker.pos.height - top_margin * 2; + int draw_height = marker.pos.height - top_margin * 2; + + switch (marker.marker_type) + { + case litehtml::list_style_type_circle: + { + draw_ellipse((HDC)hdc, draw_x, draw_y, draw_width, draw_height, marker.color, 1); + } + break; + case litehtml::list_style_type_disc: + { + fill_ellipse((HDC)hdc, draw_x, draw_y, draw_width, draw_height, marker.color); + } + break; + case litehtml::list_style_type_square: + { + fill_rect((HDC)hdc, draw_x, draw_y, draw_width, draw_height, marker.color); + } + break; + } + release_clip((HDC)hdc); +} + +void win32_container::make_url_utf8(const char* url, const char* basepath, std::wstring& out) +{ + make_url(litehtml::utf8_to_wchar(url), litehtml::utf8_to_wchar(basepath), out); +} + +void win32_container::load_image( const char* src, const char* baseurl, bool redraw_on_ready ) +{ + std::wstring url; + make_url_utf8(src, baseurl, url); + + lock_images_cache(); + if (m_images.count(url) == 0) + { + unlock_images_cache(); + uint_ptr img = get_image(url.c_str(), redraw_on_ready); + add_image(url.c_str(), img); + } + else + { + unlock_images_cache(); + } +} + +void win32_container::add_image(LPCWSTR url, uint_ptr img) +{ + lock_images_cache(); + m_images[url] = img; + unlock_images_cache(); +} + +void win32_container::get_image_size( const char* src, const char* baseurl, litehtml::size& sz ) +{ + std::wstring url; + make_url_utf8(src, baseurl, url); + + sz.width = 0; + sz.height = 0; + + lock_images_cache(); + images_map::iterator img = m_images.find(url); + if(img != m_images.end() && img->second) + { + get_img_size(img->second, sz); + } + unlock_images_cache(); +} + +void win32_container::clear_images() +{ + lock_images_cache(); + for(auto& img : m_images) + { + if(img.second) + { + free_image(img.second); + } + } + m_images.clear(); + unlock_images_cache(); +} + +void win32_container::lock_images_cache() +{ + EnterCriticalSection(&m_img_sync); +} + +void win32_container::unlock_images_cache() +{ + LeaveCriticalSection(&m_img_sync); +} + +void win32_container::draw_background( uint_ptr _hdc, const std::vector& bg ) +{ + HDC hdc = (HDC)_hdc; + apply_clip(hdc); + + auto border_box = bg.back().border_box; + auto color = bg.back().color; + fill_rect(hdc, border_box.x, border_box.y, border_box.width, border_box.height, color); + + for (int i = (int)bg.size() - 1; i >= 0; i--) + { + std::wstring url; + make_url_utf8(bg[i].image.c_str(), bg[i].baseurl.c_str(), url); + + lock_images_cache(); + images_map::iterator img = m_images.find(url); + if (img != m_images.end() && img->second) + { + draw_img_bg(hdc, img->second, bg[i]); + } + unlock_images_cache(); + } + + release_clip(hdc); +} + +void win32_container::set_clip( const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius ) +{ + m_clips.push_back(pos); +} + +void win32_container::del_clip() +{ + if(!m_clips.empty()) + { + m_clips.pop_back(); + } +} + +void win32_container::apply_clip(HDC hdc) +{ + if(m_hClipRgn) + { + DeleteObject(m_hClipRgn); + m_hClipRgn = NULL; + } + + if(!m_clips.empty()) + { + POINT ptView = {0, 0}; + GetWindowOrgEx(hdc, &ptView); + + litehtml::position clip_pos = m_clips.back(); + m_hClipRgn = CreateRectRgn(clip_pos.left() - ptView.x, clip_pos.top() - ptView.y, clip_pos.right() - ptView.x, clip_pos.bottom() - ptView.y); + SelectClipRgn(hdc, m_hClipRgn); + } +} + +void win32_container::release_clip(HDC hdc) +{ + SelectClipRgn(hdc, NULL); + + if(m_hClipRgn) + { + DeleteObject(m_hClipRgn); + m_hClipRgn = NULL; + } +} + +litehtml::element::ptr win32_container::create_element(const char* tag_name, const litehtml::string_map& attributes, const litehtml::document::ptr& doc) +{ + return 0; +} + +void win32_container::get_media_features(litehtml::media_features& media) const +{ + litehtml::position client; + get_client_rect(client); + + media.type = litehtml::media_type_screen; + media.width = client.width; + media.height = client.height; + media.color = 8; + media.monochrome = 0; + media.color_index = 256; + media.resolution = GetDeviceCaps(m_tmp_hdc, LOGPIXELSX); + media.device_width = GetDeviceCaps(m_tmp_hdc, HORZRES); + media.device_height = GetDeviceCaps(m_tmp_hdc, VERTRES); +} + +void win32_container::get_language(litehtml::string& language, litehtml::string& culture) const +{ + language = "en"; + culture = ""; +} + +void win32_container::transform_text(litehtml::string& text, litehtml::text_transform tt) +{ + if (text.empty()) return; + + LPWSTR txt = _wcsdup(litehtml_to_wchar(text.c_str())); + switch (tt) + { + case litehtml::text_transform_capitalize: + CharUpperBuff(txt, 1); + break; + case litehtml::text_transform_uppercase: + CharUpperBuff(txt, lstrlen(txt)); + break; + case litehtml::text_transform_lowercase: + CharLowerBuff(txt, lstrlen(txt)); + break; + } + text = litehtml_from_wchar(txt); + free(txt); +} + +void win32_container::link(const litehtml::document::ptr& doc, const litehtml::element::ptr& el) +{ +} + +litehtml::string win32_container::resolve_color(const litehtml::string& color) const +{ + struct custom_color + { + const char* name; + int color_index; + }; + + static custom_color colors[] = { + { "ActiveBorder", COLOR_ACTIVEBORDER }, + { "ActiveCaption", COLOR_ACTIVECAPTION }, + { "AppWorkspace", COLOR_APPWORKSPACE }, + { "Background", COLOR_BACKGROUND }, + { "ButtonFace", COLOR_BTNFACE }, + { "ButtonHighlight", COLOR_BTNHIGHLIGHT }, + { "ButtonShadow", COLOR_BTNSHADOW }, + { "ButtonText", COLOR_BTNTEXT }, + { "CaptionText", COLOR_CAPTIONTEXT }, + { "GrayText", COLOR_GRAYTEXT }, + { "Highlight", COLOR_HIGHLIGHT }, + { "HighlightText", COLOR_HIGHLIGHTTEXT }, + { "InactiveBorder", COLOR_INACTIVEBORDER }, + { "InactiveCaption", COLOR_INACTIVECAPTION }, + { "InactiveCaptionText", COLOR_INACTIVECAPTIONTEXT }, + { "InfoBackground", COLOR_INFOBK }, + { "InfoText", COLOR_INFOTEXT }, + { "Menu", COLOR_MENU }, + { "MenuText", COLOR_MENUTEXT }, + { "Scrollbar", COLOR_SCROLLBAR }, + { "ThreeDDarkShadow", COLOR_3DDKSHADOW }, + { "ThreeDFace", COLOR_3DFACE }, + { "ThreeDHighlight", COLOR_3DHILIGHT }, + { "ThreeDLightShadow", COLOR_3DLIGHT }, + { "ThreeDShadow", COLOR_3DSHADOW }, + { "Window", COLOR_WINDOW }, + { "WindowFrame", COLOR_WINDOWFRAME }, + { "WindowText", COLOR_WINDOWTEXT } + }; + + for (auto& clr : colors) + { + if (!litehtml::t_strcasecmp(color.c_str(), clr.name)) + { + char str_clr[20]; + DWORD rgb_color = GetSysColor(clr.color_index); + t_snprintf(str_clr, 20, "#%02X%02X%02X", GetRValue(rgb_color), GetGValue(rgb_color), GetBValue(rgb_color)); + return std::move(litehtml::string(str_clr)); + } + } + return std::move(litehtml::string()); +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/win32/win32_container.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/win32/win32_container.h new file mode 100644 index 0000000..5a85a47 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/containers/win32/win32_container.h @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include + +class win32_container : public litehtml::document_container +{ +public: + typedef litehtml::uint_ptr uint_ptr; + typedef std::map images_map; + +protected: + images_map m_images; + litehtml::position::vector m_clips; + HRGN m_hClipRgn; + std::set m_installed_fonts; + HDC m_tmp_hdc; + CRITICAL_SECTION m_img_sync; + +public: + win32_container(); + virtual ~win32_container(); + + // litehtml::document_container members + uint_ptr create_font(const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics* fm) override; + void delete_font(uint_ptr hFont) override; + const char* get_default_font_name() const override; + int get_default_font_size() const override; + int text_width(const char* text, uint_ptr hFont) override; + void draw_text(uint_ptr hdc, const char* text, uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) override; + void transform_text(litehtml::string& text, litehtml::text_transform tt) override; + + int pt_to_px(int pt) const override; + void draw_list_marker(uint_ptr hdc, const litehtml::list_marker& marker) override; + void load_image(const char* src, const char* baseurl, bool redraw_on_ready) override; + void get_image_size(const char* src, const char* baseurl, litehtml::size& sz) override; + void draw_background(uint_ptr hdc, const std::vector& bg) override; + + void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) override; + void del_clip() override; + litehtml::element::ptr create_element(const char* tag_name, const litehtml::string_map& attributes, const litehtml::document::ptr& doc) override; + void get_media_features(litehtml::media_features& media) const override; + void get_language(litehtml::string& language, litehtml::string& culture) const override; + void link(const litehtml::document::ptr& doc, const litehtml::element::ptr& el) override; + litehtml::string resolve_color(const litehtml::string& color) const override; + +protected: + void apply_clip(HDC hdc); + void release_clip(HDC hdc); + + virtual void make_url(LPCWSTR url, LPCWSTR basepath, std::wstring& out) = 0; + void make_url_utf8(const char* url, const char* basepath, std::wstring& out); + virtual void get_client_rect(litehtml::position& client) const = 0; + + // get_image is called by load_image. + // if url_or_path is URL then get_image may return 0, the image should be added later by add_image when it becomes available + virtual uint_ptr get_image(LPCWSTR url_or_path, bool redraw_on_ready) = 0; + void add_image(LPCWSTR url, uint_ptr img); + void clear_images(); + virtual void free_image(uint_ptr img) = 0; + virtual void get_img_size(uint_ptr img, litehtml::size& sz) = 0; + virtual void draw_img_bg(HDC hdc, uint_ptr img, const litehtml::background_paint& bg) = 0; + + virtual void draw_ellipse(HDC hdc, int x, int y, int width, int height, litehtml::web_color color, int line_width) = 0; + virtual void fill_ellipse(HDC hdc, int x, int y, int width, int height, litehtml::web_color color) = 0; + virtual void fill_rect(HDC hdc, int x, int y, int width, int height, litehtml::web_color color) = 0; + +private: + static int CALLBACK EnumFontsProc(const LOGFONT* lplf, const TEXTMETRIC* lptm, DWORD dwType, LPARAM lpData); + void lock_images_cache(); + void unlock_images_cache(); +}; diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml.h new file mode 100644 index 0000000..2537aee --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml.h @@ -0,0 +1,11 @@ +#ifndef LITEHTML_H +#define LITEHTML_H + +#include +#include +#include +#include +#include +#include + +#endif // LITEHTML_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/background.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/background.h new file mode 100644 index 0000000..c56443d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/background.h @@ -0,0 +1,69 @@ +#ifndef LH_BACKGROUND_H +#define LH_BACKGROUND_H + +#include "types.h" +#include "css_length.h" +#include "css_position.h" +#include "web_color.h" +#include "borders.h" + +namespace litehtml +{ + class background + { + public: + string_vector m_image; + string m_baseurl; + web_color m_color; + int_vector m_attachment; + length_vector m_position_x; + length_vector m_position_y; + size_vector m_size; + int_vector m_repeat; + int_vector m_clip; + int_vector m_origin; + + bool is_empty() const + { + if(m_color.alpha != 0) return false; + if(m_image.empty()) return true; + for(const auto& img : m_image) + { + if(!img.empty()) return false; + } + return true; + } + }; + + class background_paint + { + public: + string image; + string baseurl; + background_attachment attachment; + background_repeat repeat; + web_color color; + position clip_box; + position origin_box; + position border_box; + border_radiuses border_radius; + size image_size; + int position_x; + int position_y; + bool is_root; + + public: + background_paint() + { + attachment = background_attachment_scroll; + repeat = background_repeat_repeat; + color = web_color::transparent; + position_x = 0; + position_y = 0; + is_root = false; + } + }; + +} + +#endif // LH_BACKGROUND_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/borders.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/borders.h new file mode 100644 index 0000000..9c47abc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/borders.h @@ -0,0 +1,345 @@ +#ifndef LH_BORDERS_H +#define LH_BORDERS_H + +#include "css_length.h" +#include "types.h" +#include "web_color.h" + +namespace litehtml +{ + struct css_border + { + css_length width; + border_style style; + web_color color; + + css_border() + { + style = border_style_none; + } + + css_border(const css_border& val) + { + width = val.width; + style = val.style; + color = val.color; + } + + css_border& operator=(const css_border& val) + { + width = val.width; + style = val.style; + color = val.color; + return *this; + } + + string to_string() const; + }; + + struct border + { + int width; + border_style style; + web_color color; + + border() + { + width = 0; + } + border(const border& val) + { + width = val.width; + style = val.style; + color = val.color; + } + border(const css_border& val) + { + width = (int) val.width.val(); + style = val.style; + color = val.color; + } + border& operator=(const border& val) + { + width = val.width; + style = val.style; + color = val.color; + return *this; + } + border& operator=(const css_border& val) + { + width = (int) val.width.val(); + style = val.style; + color = val.color; + return *this; + } + }; + + struct border_radiuses + { + int top_left_x; + int top_left_y; + + int top_right_x; + int top_right_y; + + int bottom_right_x; + int bottom_right_y; + + int bottom_left_x; + int bottom_left_y; + + border_radiuses() + { + top_left_x = 0; + top_left_y = 0; + top_right_x = 0; + top_right_y = 0; + bottom_right_x = 0; + bottom_right_y = 0; + bottom_left_x = 0; + bottom_left_y = 0; + } + border_radiuses(const border_radiuses& val) + { + top_left_x = val.top_left_x; + top_left_y = val.top_left_y; + top_right_x = val.top_right_x; + top_right_y = val.top_right_y; + bottom_right_x = val.bottom_right_x; + bottom_right_y = val.bottom_right_y; + bottom_left_x = val.bottom_left_x; + bottom_left_y = val.bottom_left_y; + } + border_radiuses& operator = (const border_radiuses& val) + { + top_left_x = val.top_left_x; + top_left_y = val.top_left_y; + top_right_x = val.top_right_x; + top_right_y = val.top_right_y; + bottom_right_x = val.bottom_right_x; + bottom_right_y = val.bottom_right_y; + bottom_left_x = val.bottom_left_x; + bottom_left_y = val.bottom_left_y; + return *this; + } + void operator += (const margins& mg) + { + top_left_x += mg.left; + top_left_y += mg.top; + top_right_x += mg.right; + top_right_y += mg.top; + bottom_right_x += mg.right; + bottom_right_y += mg.bottom; + bottom_left_x += mg.left; + bottom_left_y += mg.bottom; + fix_values(); + } + void operator -= (const margins& mg) + { + top_left_x -= mg.left; + top_left_y -= mg.top; + top_right_x -= mg.right; + top_right_y -= mg.top; + bottom_right_x -= mg.right; + bottom_right_y -= mg.bottom; + bottom_left_x -= mg.left; + bottom_left_y -= mg.bottom; + fix_values(); + } + void fix_values() + { + if (top_left_x < 0) top_left_x = 0; + if (top_left_y < 0) top_left_y = 0; + if (top_right_x < 0) top_right_x = 0; + if (top_right_y < 0) top_right_y = 0; + if (bottom_right_x < 0) bottom_right_x = 0; + if (bottom_right_y < 0) bottom_right_y = 0; + if (bottom_left_x < 0) bottom_left_x = 0; + if (bottom_left_y < 0) bottom_left_y = 0; + } + void fix_values(int width, int height) + { + fix_values(); + int half_width = width / 2; + int half_height = height / 2; + auto fix_one = [&](int& radii_x, int& radii_y) + { + double factor = std::min((double) half_width / (double) radii_x, (double) half_height / (double) radii_y); + radii_x = (int) ((double) radii_x * factor); + radii_y = (int) ((double) radii_y * factor); + }; + + if(top_left_x > half_width || top_left_y > half_height) + { + fix_one(top_left_x, top_left_y); + } + if(top_right_x > half_width || top_right_y > half_height) + { + fix_one(top_right_x, top_right_y); + } + if(bottom_right_x > half_width || bottom_right_y > half_height) + { + fix_one(bottom_right_x, bottom_right_y); + } + if(bottom_left_x > half_width || bottom_left_y > half_height) + { + fix_one(bottom_left_x, bottom_left_y); + } + } + }; + + struct css_border_radius + { + css_length top_left_x; + css_length top_left_y; + + css_length top_right_x; + css_length top_right_y; + + css_length bottom_right_x; + css_length bottom_right_y; + + css_length bottom_left_x; + css_length bottom_left_y; + + css_border_radius() + { + + } + + css_border_radius(const css_border_radius& val) + { + top_left_x = val.top_left_x; + top_left_y = val.top_left_y; + top_right_x = val.top_right_x; + top_right_y = val.top_right_y; + bottom_left_x = val.bottom_left_x; + bottom_left_y = val.bottom_left_y; + bottom_right_x = val.bottom_right_x; + bottom_right_y = val.bottom_right_y; + } + + css_border_radius& operator=(const css_border_radius& val) + { + top_left_x = val.top_left_x; + top_left_y = val.top_left_y; + top_right_x = val.top_right_x; + top_right_y = val.top_right_y; + bottom_left_x = val.bottom_left_x; + bottom_left_y = val.bottom_left_y; + bottom_right_x = val.bottom_right_x; + bottom_right_y = val.bottom_right_y; + return *this; + } + border_radiuses calc_percents(int width, int height) const + { + border_radiuses ret; + ret.bottom_left_x = bottom_left_x.calc_percent(width); + ret.bottom_left_y = bottom_left_y.calc_percent(height); + ret.top_left_x = top_left_x.calc_percent(width); + ret.top_left_y = top_left_y.calc_percent(height); + ret.top_right_x = top_right_x.calc_percent(width); + ret.top_right_y = top_right_y.calc_percent(height); + ret.bottom_right_x = bottom_right_x.calc_percent(width); + ret.bottom_right_y = bottom_right_y.calc_percent(height); + ret.fix_values(width, height); + return ret; + } + }; + + struct css_borders + { + css_border left; + css_border top; + css_border right; + css_border bottom; + css_border_radius radius; + + css_borders() = default; + + bool is_visible() const + { + return left.width.val() != 0 || right.width.val() != 0 || top.width.val() != 0 || bottom.width.val() != 0; + } + + css_borders(const css_borders& val) + { + left = val.left; + right = val.right; + top = val.top; + bottom = val.bottom; + radius = val.radius; + } + + css_borders& operator=(const css_borders& val) + { + left = val.left; + right = val.right; + top = val.top; + bottom = val.bottom; + radius = val.radius; + return *this; + } + string to_string() const + { + return "left: " + left.to_string() + + ", top: " + top.to_string() + + ", right: " + top.to_string() + + ", bottom: " + bottom.to_string(); + } + }; + + struct borders + { + border left; + border top; + border right; + border bottom; + border_radiuses radius; + + borders() = default; + + borders(const borders& val) + { + left = val.left; + right = val.right; + top = val.top; + bottom = val.bottom; + radius = val.radius; + } + + borders(const css_borders& val) + { + left = val.left; + right = val.right; + top = val.top; + bottom = val.bottom; + } + + bool is_visible() const + { + return left.width != 0 || right.width != 0 || top.width != 0 || bottom.width != 0; + } + + borders& operator=(const borders& val) + { + left = val.left; + right = val.right; + top = val.top; + bottom = val.bottom; + radius = val.radius; + return *this; + } + + borders& operator=(const css_borders& val) + { + left = val.left; + right = val.right; + top = val.top; + bottom = val.bottom; + return *this; + } + }; +} + +#endif // LH_BORDERS_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/codepoint.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/codepoint.h new file mode 100644 index 0000000..52dd495 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/codepoint.h @@ -0,0 +1,51 @@ +// Copyright (C) 2020-2021 Primate Labs Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the names of the copyright holders nor the names of their +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef LITEHTML_CODEPOINT_H__ +#define LITEHTML_CODEPOINT_H__ + +#include + +#include "os_types.h" + +namespace litehtml { + +bool is_ascii_codepoint(char c); + +// Returns true if the codepoint is a reserved codepoint for URLs. +// https://datatracker.ietf.org/doc/html/rfc3986#section-2.2 +bool is_url_reserved_codepoint(char c); + +// Returns true if the codepoint is a scheme codepoint for URLs. +// https://datatracker.ietf.org/doc/html/rfc3986#section-3.1 +bool is_url_scheme_codepoint(char c); + +} // namespace litehtml + +#endif // LITEHTML_CODEPOINT_H__ diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_length.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_length.h new file mode 100644 index 0000000..ae78710 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_length.h @@ -0,0 +1,119 @@ +#ifndef LH_CSS_LENGTH_H +#define LH_CSS_LENGTH_H + +#include "types.h" + +namespace litehtml +{ + class css_length + { + union + { + float m_value; + int m_predef; + }; + css_units m_units; + bool m_is_predefined; + public: + css_length(); + css_length(float val, css_units units = css_units_px); + css_length& operator=(float val); + + bool is_predefined() const; + void predef(int val); + int predef() const; + static css_length predef_value(int val = 0); + void set_value(float val, css_units units); + float val() const; + css_units units() const; + int calc_percent(int width) const; + void fromString(const string& str, const string& predefs = "", int defValue = 0); + static css_length from_string(const string& str, const string& predefs = "", int defValue = 0); + string to_string() const; + }; + + using length_vector = std::vector; + + // css_length inlines + + inline css_length::css_length() + { + m_value = 0; + m_predef = 0; + m_units = css_units_none; + m_is_predefined = false; + } + + inline css_length::css_length(float val, css_units units) + { + m_value = val; + m_units = units; + m_is_predefined = false; + } + + inline css_length& css_length::operator=(float val) + { + m_value = val; + m_units = css_units_px; + m_is_predefined = false; + return *this; + } + + inline bool css_length::is_predefined() const + { + return m_is_predefined; + } + + inline void css_length::predef(int val) + { + m_predef = val; + m_is_predefined = true; + } + + inline int css_length::predef() const + { + if(m_is_predefined) + { + return m_predef; + } + return 0; + } + + inline void css_length::set_value(float val, css_units units) + { + m_value = val; + m_is_predefined = false; + m_units = units; + } + + inline float css_length::val() const + { + if(!m_is_predefined) + { + return m_value; + } + return 0; + } + + inline css_units css_length::units() const + { + return m_units; + } + + inline int css_length::calc_percent(int width) const + { + if(!is_predefined()) + { + if(units() == css_units_percentage) + { + return (int) ((double) width * (double) m_value / 100.0); + } else + { + return (int) val(); + } + } + return 0; + } +} + +#endif // LH_CSS_LENGTH_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_margins.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_margins.h new file mode 100644 index 0000000..17dc769 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_margins.h @@ -0,0 +1,44 @@ +#ifndef LH_CSS_MARGINS_H +#define LH_CSS_MARGINS_H + +#include "css_length.h" + +namespace litehtml +{ + struct css_margins + { + css_length left; + css_length right; + css_length top; + css_length bottom; + + css_margins() = default; + + css_margins(const css_margins& val) + { + left = val.left; + right = val.right; + top = val.top; + bottom = val.bottom; + } + + css_margins& operator=(const css_margins& val) + { + left = val.left; + right = val.right; + top = val.top; + bottom = val.bottom; + return *this; + } + + string to_string() const + { + return "left: " + left.to_string() + + ", right: " + right.to_string() + + ", top: " + top.to_string() + + ", bottom: " + bottom.to_string(); + } + }; +} + +#endif // LH_CSS_MARGINS_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_offsets.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_offsets.h new file mode 100644 index 0000000..5ab175c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_offsets.h @@ -0,0 +1,44 @@ +#ifndef LH_CSS_OFFSETS_H +#define LH_CSS_OFFSETS_H + +#include "css_length.h" + +namespace litehtml +{ + struct css_offsets + { + css_length left; + css_length top; + css_length right; + css_length bottom; + + css_offsets() = default; + + css_offsets(const css_offsets& val) + { + left = val.left; + top = val.top; + right = val.right; + bottom = val.bottom; + } + + css_offsets& operator=(const css_offsets& val) + { + left = val.left; + top = val.top; + right = val.right; + bottom = val.bottom; + return *this; + } + + string to_string() const + { + return "left: " + left.to_string() + + ", top: " + top.to_string() + + ", right: " + right.to_string() + + ", bottom: " + bottom.to_string(); + } + }; +} + +#endif // LH_CSS_OFFSETS_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_position.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_position.h new file mode 100644 index 0000000..978a81a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_position.h @@ -0,0 +1,28 @@ +#ifndef LH_CSS_POSITION_H +#define LH_CSS_POSITION_H + +#include "css_length.h" + +namespace litehtml +{ + struct css_position + { + css_length x; + css_length y; + css_length width; + css_length height; + }; + + struct css_size + { + css_length width; + css_length height; + + css_size() = default; + css_size(css_length width, css_length height) : width(width), height(height) {} + }; + + using size_vector = std::vector; +} + +#endif // LH_CSS_POSITION_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_properties.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_properties.h new file mode 100644 index 0000000..a5609a9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_properties.h @@ -0,0 +1,673 @@ +#ifndef LITEHTML_CSS_PROPERTIES_H +#define LITEHTML_CSS_PROPERTIES_H + +#include "os_types.h" +#include "types.h" +#include "css_margins.h" +#include "borders.h" +#include "css_offsets.h" +#include "background.h" + +namespace litehtml +{ + class element; + class document; + + class css_properties + { + private: + element_position m_el_position; + text_align m_text_align; + overflow m_overflow; + white_space m_white_space; + style_display m_display; + visibility m_visibility; + box_sizing m_box_sizing; + css_length m_z_index; + vertical_align m_vertical_align; + element_float m_float; + element_clear m_clear; + css_margins m_css_margins; + css_margins m_css_padding; + css_borders m_css_borders; + css_length m_css_width; + css_length m_css_height; + css_length m_css_min_width; + css_length m_css_min_height; + css_length m_css_max_width; + css_length m_css_max_height; + css_offsets m_css_offsets; + css_length m_css_text_indent; + css_length m_css_line_height; + int m_line_height; + list_style_type m_list_style_type; + list_style_position m_list_style_position; + string m_list_style_image; + string m_list_style_image_baseurl; + background m_bg; + uint_ptr m_font; + css_length m_font_size; + string m_font_family; + font_weight m_font_weight; + font_style m_font_style; + string m_text_decoration; + font_metrics m_font_metrics; + text_transform m_text_transform; + web_color m_color; + string m_cursor; + string m_content; + border_collapse m_border_collapse; + css_length m_css_border_spacing_x; + css_length m_css_border_spacing_y; + + float m_flex_grow; + float m_flex_shrink; + css_length m_flex_basis; + flex_direction m_flex_direction; + flex_wrap m_flex_wrap; + flex_justify_content m_flex_justify_content; + flex_align_items m_flex_align_items; + flex_align_items m_flex_align_self; + flex_align_content m_flex_align_content; + + caption_side m_caption_side; + + int m_order; + + private: + void compute_font(const element* el, const std::shared_ptr& doc); + void compute_background(const element* el, const std::shared_ptr& doc); + void compute_flex(const element* el, const std::shared_ptr& doc); + + public: + css_properties() : + m_el_position(element_position_static), + m_text_align(text_align_left), + m_overflow(overflow_visible), + m_white_space(white_space_normal), + m_display(display_inline), + m_visibility(visibility_visible), + m_box_sizing(box_sizing_content_box), + m_z_index(0), + m_vertical_align(va_baseline), + m_float(float_none), + m_clear(clear_none), + m_css_margins(), + m_css_padding(), + m_css_borders(), + m_css_width(), + m_css_height(), + m_css_min_width(), + m_css_min_height(), + m_css_max_width(), + m_css_max_height(), + m_css_offsets(), + m_css_text_indent(), + m_css_line_height(0), + m_line_height(0), + m_list_style_type(list_style_type_none), + m_list_style_position(list_style_position_outside), + m_bg(), + m_font_size(0), + m_font(0), + m_font_metrics(), + m_text_transform(text_transform_none), + m_border_collapse(border_collapse_separate), + m_css_border_spacing_x(), + m_css_border_spacing_y(), + m_flex_grow(0), + m_flex_shrink(1), + m_flex_direction(flex_direction_row), + m_flex_wrap(flex_wrap_nowrap), + m_flex_justify_content(flex_justify_content_flex_start), + m_flex_align_items(flex_align_items_stretch), + m_flex_align_self(flex_align_items_auto), + m_flex_align_content(flex_align_content_stretch), + m_order(0) + {} + + void compute(const element* el, const std::shared_ptr& doc); + std::vector> dump_get_attrs(); + + element_position get_position() const; + void set_position(element_position mElPosition); + + text_align get_text_align() const; + void set_text_align(text_align mTextAlign); + + overflow get_overflow() const; + void set_overflow(overflow mOverflow); + + white_space get_white_space() const; + void set_white_space(white_space mWhiteSpace); + + style_display get_display() const; + void set_display(style_display mDisplay); + + visibility get_visibility() const; + void set_visibility(visibility mVisibility); + + box_sizing get_box_sizing() const; + void set_box_sizing(box_sizing mBoxSizing); + + int get_z_index() const; + void set_z_index(int mZIndex); + + vertical_align get_vertical_align() const; + void set_vertical_align(vertical_align mVerticalAlign); + + element_float get_float() const; + void set_float(element_float mFloat); + + element_clear get_clear() const; + void set_clear(element_clear mClear); + + const css_margins &get_margins() const; + void set_margins(const css_margins &mCssMargins); + + const css_margins &get_padding() const; + void set_padding(const css_margins &mCssPadding); + + const css_borders &get_borders() const; + void set_borders(const css_borders &mCssBorders); + + const css_length &get_width() const; + void set_width(const css_length &mCssWidth); + + const css_length &get_height() const; + void set_height(const css_length &mCssHeight); + + const css_length &get_min_width() const; + void set_min_width(const css_length &mCssMinWidth); + + const css_length &get_min_height() const; + void set_min_height(const css_length &mCssMinHeight); + + const css_length &get_max_width() const; + void set_max_width(const css_length &mCssMaxWidth); + + const css_length &get_max_height() const; + void set_max_height(const css_length &mCssMaxHeight); + + const css_offsets &get_offsets() const; + void set_offsets(const css_offsets &mCssOffsets); + + const css_length &get_text_indent() const; + void set_text_indent(const css_length &mCssTextIndent); + + int get_line_height() const; + void set_line_height(int mLineHeight); + + list_style_type get_list_style_type() const; + void set_list_style_type(list_style_type mListStyleType); + + list_style_position get_list_style_position() const; + void set_list_style_position(list_style_position mListStylePosition); + + string get_list_style_image() const; + void set_list_style_image(const string& url); + + string get_list_style_image_baseurl() const; + void set_list_style_image_baseurl(const string& url); + + const background &get_bg() const; + void set_bg(const background &mBg); + + int get_font_size() const; + void set_font_size(int mFontSize); + + uint_ptr get_font() const; + void set_font(uint_ptr mFont); + + const font_metrics& get_font_metrics() const; + void set_font_metrics(const font_metrics& mFontMetrics); + + text_transform get_text_transform() const; + void set_text_transform(text_transform mTextTransform); + + web_color get_color() const; + void set_color(web_color color); + + string get_cursor() const; + void set_cursor(const string& cursor); + + string get_content() const; + void set_content(const string& content); + + border_collapse get_border_collapse() const; + void set_border_collapse(border_collapse mBorderCollapse); + + const css_length& get_border_spacing_x() const ; + void set_border_spacing_x(const css_length& mBorderSpacingX); + + const css_length& get_border_spacing_y() const; + void set_border_spacing_y(const css_length& mBorderSpacingY); + + caption_side get_caption_side() const; + void set_caption_side(caption_side side); + + float get_flex_grow() const; + float get_flex_shrink() const; + const css_length& get_flex_basis() const; + flex_direction get_flex_direction() const; + flex_wrap get_flex_wrap() const; + flex_justify_content get_flex_justify_content() const; + flex_align_items get_flex_align_items() const; + flex_align_items get_flex_align_self() const; + flex_align_content get_flex_align_content() const; + + int get_order() const; + void set_order(int order); + }; + + inline element_position css_properties::get_position() const + { + return m_el_position; + } + + inline void css_properties::set_position(element_position mElPosition) + { + m_el_position = mElPosition; + } + + inline text_align css_properties::get_text_align() const + { + return m_text_align; + } + + inline void css_properties::set_text_align(text_align mTextAlign) + { + m_text_align = mTextAlign; + } + + inline overflow css_properties::get_overflow() const + { + return m_overflow; + } + + inline void css_properties::set_overflow(overflow mOverflow) + { + m_overflow = mOverflow; + } + + inline white_space css_properties::get_white_space() const + { + return m_white_space; + } + + inline void css_properties::set_white_space(white_space mWhiteSpace) + { + m_white_space = mWhiteSpace; + } + + inline style_display css_properties::get_display() const + { + return m_display; + } + + inline void css_properties::set_display(style_display mDisplay) + { + m_display = mDisplay; + } + + inline visibility css_properties::get_visibility() const + { + return m_visibility; + } + + inline void css_properties::set_visibility(visibility mVisibility) + { + m_visibility = mVisibility; + } + + inline box_sizing css_properties::get_box_sizing() const + { + return m_box_sizing; + } + + inline void css_properties::set_box_sizing(box_sizing mBoxSizing) + { + m_box_sizing = mBoxSizing; + } + + inline int css_properties::get_z_index() const + { + return (int)m_z_index.val(); + } + + inline void css_properties::set_z_index(int mZIndex) + { + m_z_index.set_value((float)mZIndex, css_units_none); + } + + inline vertical_align css_properties::get_vertical_align() const + { + return m_vertical_align; + } + + inline void css_properties::set_vertical_align(vertical_align mVerticalAlign) + { + m_vertical_align = mVerticalAlign; + } + + inline element_float css_properties::get_float() const + { + return m_float; + } + + inline void css_properties::set_float(element_float mFloat) + { + m_float = mFloat; + } + + inline element_clear css_properties::get_clear() const + { + return m_clear; + } + + inline void css_properties::set_clear(element_clear mClear) + { + m_clear = mClear; + } + + inline const css_margins &css_properties::get_margins() const + { + return m_css_margins; + } + + inline void css_properties::set_margins(const css_margins &mCssMargins) + { + m_css_margins = mCssMargins; + } + + inline const css_margins &css_properties::get_padding() const + { + return m_css_padding; + } + + inline void css_properties::set_padding(const css_margins &mCssPadding) + { + m_css_padding = mCssPadding; + } + + inline const css_borders &css_properties::get_borders() const + { + return m_css_borders; + } + + inline void css_properties::set_borders(const css_borders &mCssBorders) + { + m_css_borders = mCssBorders; + } + + inline const css_length &css_properties::get_width() const + { + return m_css_width; + } + + inline void css_properties::set_width(const css_length &mCssWidth) + { + m_css_width = mCssWidth; + } + + inline const css_length &css_properties::get_height() const + { + return m_css_height; + } + + inline void css_properties::set_height(const css_length &mCssHeight) + { + m_css_height = mCssHeight; + } + + inline const css_length &css_properties::get_min_width() const + { + return m_css_min_width; + } + + inline void css_properties::set_min_width(const css_length &mCssMinWidth) + { + m_css_min_width = mCssMinWidth; + } + + inline const css_length &css_properties::get_min_height() const + { + return m_css_min_height; + } + + inline void css_properties::set_min_height(const css_length &mCssMinHeight) + { + m_css_min_height = mCssMinHeight; + } + + inline const css_length &css_properties::get_max_width() const + { + return m_css_max_width; + } + + inline void css_properties::set_max_width(const css_length &mCssMaxWidth) + { + m_css_max_width = mCssMaxWidth; + } + + inline const css_length &css_properties::get_max_height() const + { + return m_css_max_height; + } + + inline void css_properties::set_max_height(const css_length &mCssMaxHeight) + { + m_css_max_height = mCssMaxHeight; + } + + inline const css_offsets &css_properties::get_offsets() const + { + return m_css_offsets; + } + + inline void css_properties::set_offsets(const css_offsets &mCssOffsets) + { + m_css_offsets = mCssOffsets; + } + + inline const css_length &css_properties::get_text_indent() const + { + return m_css_text_indent; + } + + inline void css_properties::set_text_indent(const css_length &mCssTextIndent) + { + m_css_text_indent = mCssTextIndent; + } + + inline int css_properties::get_line_height() const + { + return m_line_height; + } + + inline void css_properties::set_line_height(int mLineHeight) + { + m_line_height = mLineHeight; + } + + inline list_style_type css_properties::get_list_style_type() const + { + return m_list_style_type; + } + + inline void css_properties::set_list_style_type(list_style_type mListStyleType) + { + m_list_style_type = mListStyleType; + } + + inline list_style_position css_properties::get_list_style_position() const + { + return m_list_style_position; + } + + inline void css_properties::set_list_style_position(list_style_position mListStylePosition) + { + m_list_style_position = mListStylePosition; + } + + inline string css_properties::get_list_style_image() const { return m_list_style_image; } + inline void css_properties::set_list_style_image(const string& url) { m_list_style_image = url; } + + inline string css_properties::get_list_style_image_baseurl() const { return m_list_style_image_baseurl; } + inline void css_properties::set_list_style_image_baseurl(const string& url) { m_list_style_image_baseurl = url; } + + inline const background &css_properties::get_bg() const + { + return m_bg; + } + + inline void css_properties::set_bg(const background &mBg) + { + m_bg = mBg; + } + + inline int css_properties::get_font_size() const + { + return (int)m_font_size.val(); + } + + inline void css_properties::set_font_size(int mFontSize) + { + m_font_size = (float)mFontSize; + } + + inline uint_ptr css_properties::get_font() const + { + return m_font; + } + + inline void css_properties::set_font(uint_ptr mFont) + { + m_font = mFont; + } + + inline const font_metrics& css_properties::get_font_metrics() const + { + return m_font_metrics; + } + + inline void css_properties::set_font_metrics(const font_metrics& mFontMetrics) + { + m_font_metrics = mFontMetrics; + } + + inline text_transform css_properties::get_text_transform() const + { + return m_text_transform; + } + + inline void css_properties::set_text_transform(text_transform mTextTransform) + { + m_text_transform = mTextTransform; + } + + inline web_color css_properties::get_color() const { return m_color; } + inline void css_properties::set_color(web_color color) { m_color = color; } + + inline string css_properties::get_cursor() const { return m_cursor; } + inline void css_properties::set_cursor(const string& cursor) { m_cursor = cursor; } + + inline string css_properties::get_content() const { return m_content; } + inline void css_properties::set_content(const string& content) { m_content = content; } + + inline border_collapse css_properties::get_border_collapse() const + { + return m_border_collapse; + } + + inline void css_properties::set_border_collapse(border_collapse mBorderCollapse) + { + m_border_collapse = mBorderCollapse; + } + + inline const css_length& css_properties::get_border_spacing_x() const + { + return m_css_border_spacing_x; + } + + inline void css_properties::set_border_spacing_x(const css_length& mBorderSpacingX) + { + m_css_border_spacing_x = mBorderSpacingX; + } + + inline const css_length& css_properties::get_border_spacing_y() const + { + return m_css_border_spacing_y; + } + + inline void css_properties::set_border_spacing_y(const css_length& mBorderSpacingY) + { + m_css_border_spacing_y = mBorderSpacingY; + } + + inline float css_properties::get_flex_grow() const + { + return m_flex_grow; + } + + inline float css_properties::get_flex_shrink() const + { + return m_flex_shrink; + } + + inline const css_length& css_properties::get_flex_basis() const + { + return m_flex_basis; + } + + inline flex_direction css_properties::get_flex_direction() const + { + return m_flex_direction; + } + + inline flex_wrap css_properties::get_flex_wrap() const + { + return m_flex_wrap; + } + + inline flex_justify_content css_properties::get_flex_justify_content() const + { + return m_flex_justify_content; + } + + inline flex_align_items css_properties::get_flex_align_items() const + { + return m_flex_align_items; + } + + inline flex_align_items css_properties::get_flex_align_self() const + { + return m_flex_align_self; + } + + inline flex_align_content css_properties::get_flex_align_content() const + { + return m_flex_align_content; + } + + inline caption_side css_properties::get_caption_side() const + { + return m_caption_side; + } + inline void css_properties::set_caption_side(caption_side side) + { + m_caption_side = side; + } + + inline int css_properties::get_order() const + { + return m_order; + } + + inline void css_properties::set_order(int order) + { + m_order = order; + } +} + +#endif //LITEHTML_CSS_PROPERTIES_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_selector.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_selector.h new file mode 100644 index 0000000..4fe7db9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/css_selector.h @@ -0,0 +1,300 @@ +#ifndef LH_CSS_SELECTOR_H +#define LH_CSS_SELECTOR_H + +#include "style.h" +#include "media_query.h" + +namespace litehtml +{ + ////////////////////////////////////////////////////////////////////////// + + struct selector_specificity + { + int a; + int b; + int c; + int d; + + explicit selector_specificity(int va = 0, int vb = 0, int vc = 0, int vd = 0) + { + a = va; + b = vb; + c = vc; + d = vd; + } + + void operator += (const selector_specificity& val) + { + a += val.a; + b += val.b; + c += val.c; + d += val.d; + } + + bool operator==(const selector_specificity& val) const + { + if(a == val.a && b == val.b && c == val.c && d == val.d) + { + return true; + } + return false; + } + + bool operator!=(const selector_specificity& val) const + { + if(a != val.a || b != val.b || c != val.c || d != val.d) + { + return true; + } + return false; + } + + bool operator > (const selector_specificity& val) const + { + if(a > val.a) + { + return true; + } else if(a < val.a) + { + return false; + } else + { + if(b > val.b) + { + return true; + } else if(b < val.b) + { + return false; + } else + { + if(c > val.c) + { + return true; + } else if(c < val.c) + { + return false; + } else + { + if(d > val.d) + { + return true; + } else if(d < val.d) + { + return false; + } + } + } + } + return false; + } + + bool operator >= (const selector_specificity& val) const + { + if((*this) == val) return true; + if((*this) > val) return true; + return false; + } + + bool operator <= (const selector_specificity& val) const + { + if((*this) > val) + { + return false; + } + return true; + } + + bool operator < (const selector_specificity& val) const + { + if((*this) <= val && (*this) != val) + { + return true; + } + return false; + } + + }; + + ////////////////////////////////////////////////////////////////////////// + + enum attr_select_type + { + select_class, + select_id, + + select_exists, + select_equal, + select_contain_str, + select_start_str, + select_end_str, + + select_pseudo_class, + select_pseudo_element, + }; + + ////////////////////////////////////////////////////////////////////////// + + class css_element_selector; + + struct css_attribute_selector + { + typedef std::vector vector; + + attr_select_type type; + string_id name; // .name, #name, [name], :name + string val; // [name=val], :lang(val) + + std::shared_ptr sel; // :not(sel) + int a, b; // :nth-child(an+b) + + css_attribute_selector() + { + type = select_class; + name = empty_id; + a = b = 0; + } + }; + + ////////////////////////////////////////////////////////////////////////// + + class css_element_selector + { + public: + string_id m_tag; + css_attribute_selector::vector m_attrs; + public: + + void parse(const string& txt); + static void parse_nth_child_params(const string& param, int& num, int& off); + }; + + ////////////////////////////////////////////////////////////////////////// + + enum css_combinator + { + combinator_descendant, + combinator_child, + combinator_adjacent_sibling, + combinator_general_sibling + }; + + ////////////////////////////////////////////////////////////////////////// + + class css_selector + { + public: + typedef std::shared_ptr ptr; + typedef std::vector vector; + public: + selector_specificity m_specificity; + css_element_selector m_right; + css_selector::ptr m_left; + css_combinator m_combinator; + style::ptr m_style; + int m_order; + media_query_list::ptr m_media_query; + public: + explicit css_selector(const media_query_list::ptr& media = nullptr) + { + m_media_query = media; + m_combinator = combinator_descendant; + m_order = 0; + } + + ~css_selector() = default; + + css_selector(const css_selector& val) + { + m_right = val.m_right; + if(val.m_left) + { + m_left = std::make_shared(*val.m_left); + } else + { + m_left = nullptr; + } + m_combinator = val.m_combinator; + m_specificity = val.m_specificity; + m_order = val.m_order; + m_media_query = val.m_media_query; + } + + bool parse(const string& text); + void calc_specificity(); + bool is_media_valid() const; + void add_media_to_doc(document* doc) const; + }; + + inline bool css_selector::is_media_valid() const + { + if(!m_media_query) + { + return true; + } + return m_media_query->is_used(); + } + + + ////////////////////////////////////////////////////////////////////////// + + inline bool operator > (const css_selector& v1, const css_selector& v2) + { + if(v1.m_specificity == v2.m_specificity) + { + return (v1.m_order > v2.m_order); + } + return (v1.m_specificity > v2.m_specificity); + } + + inline bool operator < (const css_selector& v1, const css_selector& v2) + { + if(v1.m_specificity == v2.m_specificity) + { + return (v1.m_order < v2.m_order); + } + return (v1.m_specificity < v2.m_specificity); + } + + inline bool operator > (const css_selector::ptr& v1, const css_selector::ptr& v2) + { + return (*v1 > *v2); + } + + inline bool operator < (const css_selector::ptr& v1, const css_selector::ptr& v2) + { + return (*v1 < *v2); + } + + ////////////////////////////////////////////////////////////////////////// + + class used_selector + { + public: + typedef std::unique_ptr ptr; + typedef std::vector vector; + + css_selector::ptr m_selector; + bool m_used; + + used_selector(const css_selector::ptr& selector, bool used) + { + m_used = used; + m_selector = selector; + } + + used_selector(const used_selector& val) + { + m_used = val.m_used; + m_selector = val.m_selector; + } + + used_selector& operator=(const used_selector& val) + { + m_used = val.m_used; + m_selector = val.m_selector; + return *this; + } + }; +} + +#endif // LH_CSS_SELECTOR_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/document.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/document.h new file mode 100644 index 0000000..e2623b2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/document.h @@ -0,0 +1,134 @@ +#ifndef LH_DOCUMENT_H +#define LH_DOCUMENT_H + +#include "style.h" +#include "types.h" +#include "master_css.h" + +namespace litehtml +{ + struct css_text + { + typedef std::vector vector; + + string text; + string baseurl; + string media; + + css_text() = default; + + css_text(const char* txt, const char* url, const char* media_str) + { + text = txt ? txt : ""; + baseurl = url ? url : ""; + media = media_str ? media_str : ""; + } + + css_text(const css_text& val) + { + text = val.text; + baseurl = val.baseurl; + media = val.media; + } + }; + + class dumper + { + public: + virtual ~dumper() {} + virtual void begin_node(const litehtml::string& descr) = 0; + virtual void end_node() = 0; + virtual void begin_attrs_group(const litehtml::string& descr) = 0; + virtual void end_attrs_group() = 0; + virtual void add_attr(const litehtml::string& name, const litehtml::string& value) = 0; + }; + + class html_tag; + class render_item; + + class document : public std::enable_shared_from_this + { + public: + typedef std::shared_ptr ptr; + typedef std::weak_ptr weak_ptr; + private: + std::shared_ptr m_root; + std::shared_ptr m_root_render; + document_container* m_container; + fonts_map m_fonts; + css_text::vector m_css; + litehtml::css m_styles; + litehtml::web_color m_def_color; + litehtml::css m_master_css; + litehtml::css m_user_css; + litehtml::size m_size; + litehtml::size m_content_size; + position::vector m_fixed_boxes; + media_query_list::vector m_media_lists; + element::ptr m_over_element; + std::list> m_tabular_elements; + media_features m_media; + string m_lang; + string m_culture; + public: + document(document_container* objContainer); + virtual ~document(); + + document_container* container() { return m_container; } + uint_ptr get_font(const char* name, int size, const char* weight, const char* style, const char* decoration, font_metrics* fm); + int render(int max_width, render_type rt = render_all); + void draw(uint_ptr hdc, int x, int y, const position* clip); + web_color get_def_color() { return m_def_color; } + int to_pixels(const char* str, int fontSize, bool* is_percent = nullptr) const; + void cvt_units(css_length& val, int fontSize, int size = 0) const; + int to_pixels(const css_length& val, int fontSize, int size = 0) const; + int width() const; + int height() const; + int content_width() const; + int content_height() const; + void add_stylesheet(const char* str, const char* baseurl, const char* media); + bool on_mouse_over(int x, int y, int client_x, int client_y, position::vector& redraw_boxes); + bool on_lbutton_down(int x, int y, int client_x, int client_y, position::vector& redraw_boxes); + bool on_lbutton_up(int x, int y, int client_x, int client_y, position::vector& redraw_boxes); + bool on_mouse_leave(position::vector& redraw_boxes); + element::ptr create_element(const char* tag_name, const string_map& attributes); + element::ptr root(); + void get_fixed_boxes(position::vector& fixed_boxes); + void add_fixed_box(const position& pos); + void add_media_list(const media_query_list::ptr& list); + bool media_changed(); + bool lang_changed(); + bool match_lang(const string& lang); + void add_tabular(const std::shared_ptr& el); + element::const_ptr get_over_element() const { return m_over_element; } + + void append_children_from_string(element& parent, const char* str); + void dump(dumper& cout); + + static litehtml::document::ptr createFromString(const char* str, litehtml::document_container* objPainter, const char* master_styles = litehtml::master_css, const char* user_styles = ""); + + private: + uint_ptr add_font(const char* name, int size, const char* weight, const char* style, const char* decoration, font_metrics* fm); + + void create_node(void* gnode, elements_list& elements, bool parseTextNode); + bool update_media_lists(const media_features& features); + void fix_tables_layout(); + void fix_table_children(const std::shared_ptr& el_ptr, style_display disp, const char* disp_str); + void fix_table_parent(const std::shared_ptr & el_ptr, style_display disp, const char* disp_str); + }; + + inline element::ptr document::root() + { + return m_root; + } + inline void document::add_tabular(const std::shared_ptr& el) + { + m_tabular_elements.push_back(el); + } + inline bool document::match_lang(const string& lang) + { + return lang == m_lang || lang == m_culture; + } +} + +#endif // LH_DOCUMENT_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/document_container.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/document_container.h new file mode 100644 index 0000000..391873c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/document_container.h @@ -0,0 +1,71 @@ +#ifndef LH_DOCUMENT_CONTAINER_H +#define LH_DOCUMENT_CONTAINER_H + +#include "os_types.h" +#include "types.h" +#include "web_color.h" +#include "background.h" +#include "borders.h" +#include "element.h" +#include +#include + +namespace litehtml +{ + struct list_marker + { + string image; + const char* baseurl; + list_style_type marker_type; + web_color color; + position pos; + int index; + uint_ptr font; + }; + + // call back interface to draw text, images and other elements + class document_container + { + public: + virtual litehtml::uint_ptr create_font(const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics* fm) = 0; + virtual void delete_font(litehtml::uint_ptr hFont) = 0; + virtual int text_width(const char* text, litehtml::uint_ptr hFont) = 0; + virtual void draw_text(litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) = 0; + virtual int pt_to_px(int pt) const = 0; + virtual int get_default_font_size() const = 0; + virtual const char* get_default_font_name() const = 0; + virtual void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker& marker) = 0; + virtual void load_image(const char* src, const char* baseurl, bool redraw_on_ready) = 0; + virtual void get_image_size(const char* src, const char* baseurl, litehtml::size& sz) = 0; + // Note: regular images are also drawn with draw_background + // bg is guaranteed to have at least one item. + // backgrounds in bg are in CSS order - the last one is the farthest from the user. + // only the last background has valid background-color. + virtual void draw_background(litehtml::uint_ptr hdc, const std::vector& bg) = 0; + virtual void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) = 0; + + virtual void set_caption(const char* caption) = 0; + virtual void set_base_url(const char* base_url) = 0; + virtual void link(const std::shared_ptr& doc, const litehtml::element::ptr& el) = 0; + virtual void on_anchor_click(const char* url, const litehtml::element::ptr& el) = 0; + virtual void set_cursor(const char* cursor) = 0; + virtual void transform_text(litehtml::string& text, litehtml::text_transform tt) = 0; + virtual void import_css(litehtml::string& text, const litehtml::string& url, litehtml::string& baseurl) = 0; + virtual void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) = 0; + virtual void del_clip() = 0; + virtual void get_client_rect(litehtml::position& client) const = 0; + virtual litehtml::element::ptr create_element( const char* tag_name, + const litehtml::string_map& attributes, + const std::shared_ptr& doc) = 0; + + virtual void get_media_features(litehtml::media_features& media) const = 0; + virtual void get_language(litehtml::string& language, litehtml::string& culture) const = 0; + virtual litehtml::string resolve_color(const litehtml::string& /*color*/) const { return litehtml::string(); } + virtual void split_text(const char* text, const std::function& on_word, const std::function& on_space); + + protected: + ~document_container() = default; + }; +} + +#endif // LH_DOCUMENT_CONTAINER_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_anchor.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_anchor.h new file mode 100644 index 0000000..b33794c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_anchor.h @@ -0,0 +1,18 @@ +#ifndef LH_EL_ANCHOR_H +#define LH_EL_ANCHOR_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_anchor : public html_tag + { + public: + explicit el_anchor(const std::shared_ptr& doc); + + void on_click() override; + void apply_stylesheet(const litehtml::css& stylesheet) override; + }; +} + +#endif // LH_EL_ANCHOR_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_base.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_base.h new file mode 100644 index 0000000..d7efb80 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_base.h @@ -0,0 +1,17 @@ +#ifndef LH_EL_BASE_H +#define LH_EL_BASE_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_base : public html_tag + { + public: + explicit el_base(const std::shared_ptr& doc); + + void parse_attributes() override; + }; +} + +#endif // LH_EL_BASE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_before_after.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_before_after.h new file mode 100644 index 0000000..eed5f3d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_before_after.h @@ -0,0 +1,39 @@ +#ifndef LH_EL_BEFORE_AFTER_H +#define LH_EL_BEFORE_AFTER_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_before_after_base : public html_tag + { + public: + el_before_after_base(const std::shared_ptr& doc, bool before); + + void add_style(const style& style) override; + private: + void add_text(const string& txt); + void add_function(const string& fnc, const string& params); + static string convert_escape(const char* txt); + }; + + class el_before : public el_before_after_base + { + public: + explicit el_before(const std::shared_ptr& doc) : el_before_after_base(doc, true) + { + + } + }; + + class el_after : public el_before_after_base + { + public: + explicit el_after(const std::shared_ptr& doc) : el_before_after_base(doc, false) + { + + } + }; +} + +#endif // LH_EL_BEFORE_AFTER_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_body.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_body.h new file mode 100644 index 0000000..fb30e0c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_body.h @@ -0,0 +1,17 @@ +#ifndef LH_EL_BODY_H +#define LH_EL_BODY_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_body : public html_tag + { + public: + explicit el_body(const std::shared_ptr& doc); + + bool is_body() const override; + }; +} + +#endif // LH_EL_BODY_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_break.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_break.h new file mode 100644 index 0000000..2a4d8fe --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_break.h @@ -0,0 +1,17 @@ +#ifndef LH_EL_BREAK_H +#define LH_EL_BREAK_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_break : public html_tag + { + public: + explicit el_break(const std::shared_ptr& doc); + + bool is_break() const override; + }; +} + +#endif // LH_EL_BREAK_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_cdata.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_cdata.h new file mode 100644 index 0000000..838cd92 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_cdata.h @@ -0,0 +1,19 @@ +#ifndef LH_EL_CDATA_H +#define LH_EL_CDATA_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_cdata : public element + { + string m_text; + public: + explicit el_cdata(const std::shared_ptr& doc); + + void get_text(string& text) override; + void set_data(const char* data) override; + }; +} + +#endif // LH_EL_CDATA_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_comment.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_comment.h new file mode 100644 index 0000000..454ee47 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_comment.h @@ -0,0 +1,25 @@ +#ifndef LH_EL_COMMENT_H +#define LH_EL_COMMENT_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_comment : public element + { + string m_text; + public: + explicit el_comment(const std::shared_ptr& doc); + + bool is_comment() const override; + void get_text(string& text) override; + void set_data(const char* data) override; + std::shared_ptr create_render_item(const std::shared_ptr& parent_ri) override + { + // Comments are not rendered + return nullptr; + } + }; +} + +#endif // LH_EL_COMMENT_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_div.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_div.h new file mode 100644 index 0000000..a2d031a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_div.h @@ -0,0 +1,17 @@ +#ifndef LH_EL_DIV_H +#define LH_EL_DIV_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_div : public html_tag + { + public: + explicit el_div(const std::shared_ptr& doc); + + void parse_attributes() override; + }; +} + +#endif // LH_EL_DIV_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_font.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_font.h new file mode 100644 index 0000000..ccb8945 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_font.h @@ -0,0 +1,17 @@ +#ifndef LH_EL_FONT_H +#define LH_EL_FONT_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_font : public html_tag + { + public: + explicit el_font(const std::shared_ptr& doc); + + void parse_attributes() override; + }; +} + +#endif // LH_EL_FONT_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_image.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_image.h new file mode 100644 index 0000000..2b73940 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_image.h @@ -0,0 +1,29 @@ +#ifndef LH_EL_IMAGE_H +#define LH_EL_IMAGE_H + +#include "html_tag.h" + +namespace litehtml +{ + + class el_image : public html_tag + { + string m_src; + public: + el_image(const document::ptr& doc); + + bool is_replaced() const override; + void parse_attributes() override; + void compute_styles(bool recursive = true) override; + void draw(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr &ri) override; + void get_content_size(size& sz, int max_width) override; + string dump_get_name() override; + + std::shared_ptr create_render_item(const std::shared_ptr& parent_ri) override; + + private: +// int calc_max_height(int image_height); + }; +} + +#endif // LH_EL_IMAGE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_link.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_link.h new file mode 100644 index 0000000..0da3513 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_link.h @@ -0,0 +1,18 @@ +#ifndef LH_EL_LINK_H +#define LH_EL_LINK_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_link : public html_tag + { + public: + explicit el_link(const std::shared_ptr& doc); + + protected: + void parse_attributes() override; + }; +} + +#endif // LH_EL_LINK_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_para.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_para.h new file mode 100644 index 0000000..32ad537 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_para.h @@ -0,0 +1,18 @@ +#ifndef LH_EL_PARA_H +#define LH_EL_PARA_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_para : public html_tag + { + public: + explicit el_para(const std::shared_ptr& doc); + + void parse_attributes() override; + + }; +} + +#endif // LH_EL_PARA_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_script.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_script.h new file mode 100644 index 0000000..e0a0374 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_script.h @@ -0,0 +1,21 @@ +#ifndef LH_EL_SCRIPT_H +#define LH_EL_SCRIPT_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_script : public element + { + string m_text; + public: + explicit el_script(const std::shared_ptr& doc); + + void parse_attributes() override; + bool appendChild(const ptr &el) override; + string_id tag() const override; + const char* get_tagName() const override; + }; +} + +#endif // LH_EL_SCRIPT_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_space.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_space.h new file mode 100644 index 0000000..46a292e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_space.h @@ -0,0 +1,21 @@ +#ifndef LH_EL_SPACE_H +#define LH_EL_SPACE_H + +#include "html_tag.h" +#include "el_text.h" + +namespace litehtml +{ + class el_space : public el_text + { + public: + el_space(const char* text, const std::shared_ptr& doc); + + bool is_white_space() const override; + bool is_break() const override; + bool is_space() const override; + string dump_get_name() override; + }; +} + +#endif // LH_EL_SPACE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_style.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_style.h new file mode 100644 index 0000000..62f1546 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_style.h @@ -0,0 +1,21 @@ +#ifndef LH_EL_STYLE_H +#define LH_EL_STYLE_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_style : public element + { + elements_list m_children; + public: + explicit el_style(const std::shared_ptr& doc); + + void parse_attributes() override; + bool appendChild(const ptr &el) override; + string_id tag() const override; + const char* get_tagName() const override; + }; +} + +#endif // LH_EL_STYLE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_table.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_table.h new file mode 100644 index 0000000..3835250 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_table.h @@ -0,0 +1,25 @@ +#ifndef LH_EL_TABLE_H +#define LH_EL_TABLE_H + +#include "html_tag.h" + +namespace litehtml +{ + struct col_info + { + int width; + bool is_auto; + }; + + + class el_table : public html_tag + { + public: + explicit el_table(const std::shared_ptr& doc); + + bool appendChild(const litehtml::element::ptr& el) override; + void parse_attributes() override; + }; +} + +#endif // LH_EL_TABLE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_td.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_td.h new file mode 100644 index 0000000..03d21c1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_td.h @@ -0,0 +1,17 @@ +#ifndef LH_EL_TD_H +#define LH_EL_TD_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_td : public html_tag + { + public: + explicit el_td(const std::shared_ptr& doc); + + void parse_attributes() override; + }; +} + +#endif // LH_EL_TD_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_text.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_text.h new file mode 100644 index 0000000..4b8a442 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_text.h @@ -0,0 +1,31 @@ +#ifndef LH_EL_TEXT_H +#define LH_EL_TEXT_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_text : public element + { + protected: + string m_text; + string m_transformed_text; + size m_size; + bool m_use_transformed; + bool m_draw_spaces; + public: + el_text(const char* text, const document::ptr& doc); + + void get_text(string& text) override; + void compute_styles(bool recursive) override; + bool is_text() const override { return true; } + + void draw(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr &ri) override; + string dump_get_name() override; + std::vector> dump_get_attrs() override; + protected: + void get_content_size(size& sz, int max_width) override; + }; +} + +#endif // LH_EL_TEXT_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_title.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_title.h new file mode 100644 index 0000000..201186a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_title.h @@ -0,0 +1,18 @@ +#ifndef LH_EL_TITLE_H +#define LH_EL_TITLE_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_title : public html_tag + { + public: + explicit el_title(const std::shared_ptr& doc); + + protected: + void parse_attributes() override; + }; +} + +#endif // LH_EL_TITLE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_tr.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_tr.h new file mode 100644 index 0000000..49c0b9a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/el_tr.h @@ -0,0 +1,17 @@ +#ifndef LH_EL_TR_H +#define LH_EL_TR_H + +#include "html_tag.h" + +namespace litehtml +{ + class el_tr : public html_tag + { + public: + explicit el_tr(const std::shared_ptr& doc); + + void parse_attributes() override; + }; +} + +#endif // LH_EL_TR_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/element.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/element.h new file mode 100644 index 0000000..4d24473 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/element.h @@ -0,0 +1,234 @@ +#ifndef LH_ELEMENT_H +#define LH_ELEMENT_H + +#include +#include +#include +#include "stylesheet.h" +#include "css_offsets.h" +#include "css_margins.h" +#include "css_properties.h" + +namespace litehtml +{ + class line_box; + class dumper; + class render_item; + + class element : public std::enable_shared_from_this + { + friend class line_box; + friend class html_tag; + friend class el_table; + friend class document; + public: + typedef std::shared_ptr ptr; + typedef std::shared_ptr const_ptr; + typedef std::weak_ptr weak_ptr; + protected: + std::weak_ptr m_parent; + std::weak_ptr m_doc; + elements_list m_children; + css_properties m_css; + std::list> m_renders; + used_selector::vector m_used_styles; + + virtual void select_all(const css_selector& selector, elements_list& res); + element::ptr _add_before_after(int type, const style& style); + + private: + std::map m_counter_values; + + public: + explicit element(const std::shared_ptr& doc); + virtual ~element() = default; + + const css_properties& css() const; + css_properties& css_w(); + + bool in_normal_flow() const; + bool is_inline() const; // returns true if element is inline + bool is_inline_box() const; // returns true if element is inline box (inline-table, inline-box, inline-flex) + bool is_block_box() const; + position get_placement() const; + bool is_positioned() const; + bool is_float() const; + bool is_block_formatting_context() const; + + bool is_root() const; + element::ptr parent() const; + void parent(const element::ptr& par); + // returns true for elements inside a table (but outside cells) that don't participate in table rendering + bool is_table_skip() const; + + std::shared_ptr get_document() const; + const std::list>& children() const; + + virtual elements_list select_all(const string& selector); + virtual elements_list select_all(const css_selector& selector); + + virtual element::ptr select_one(const string& selector); + virtual element::ptr select_one(const css_selector& selector); + + virtual bool appendChild(const ptr &el); + virtual bool removeChild(const ptr &el); + virtual void clearRecursive(); + + virtual string_id id() const; + virtual string_id tag() const; + virtual const char* get_tagName() const; + virtual void set_tagName(const char* tag); + virtual void set_data(const char* data); + + virtual void set_attr(const char* name, const char* val); + virtual const char* get_attr(const char* name, const char* def = nullptr) const; + virtual void apply_stylesheet(const litehtml::css& stylesheet); + virtual void refresh_styles(); + virtual bool is_white_space() const; + virtual bool is_space() const; + virtual bool is_comment() const; + virtual bool is_body() const; + virtual bool is_break() const; + virtual bool is_text() const; + + virtual bool on_mouse_over(); + virtual bool on_mouse_leave(); + virtual bool on_lbutton_down(); + virtual bool on_lbutton_up(); + virtual void on_click(); + virtual bool set_pseudo_class(string_id cls, bool add); + virtual bool set_class(const char* pclass, bool add); + virtual bool is_replaced() const; + virtual void compute_styles(bool recursive = true); + virtual void draw(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr& ri); + virtual void draw_background(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr &ri); + virtual int get_enum_property (string_id name, bool inherited, int default_value, uint_ptr css_properties_member_offset) const; + virtual int get_int_property (string_id name, bool inherited, int default_value, uint_ptr css_properties_member_offset) const; + virtual css_length get_length_property(string_id name, bool inherited, css_length default_value, uint_ptr css_properties_member_offset) const; + virtual web_color get_color_property (string_id name, bool inherited, web_color default_value, uint_ptr css_properties_member_offset) const; + virtual string get_string_property(string_id name, bool inherited, const string& default_value, uint_ptr css_properties_member_offset) const; + virtual float get_number_property(string_id name, bool inherited, float default_value, uint_ptr css_properties_member_offset) const; + virtual string_vector get_string_vector_property(string_id name, bool inherited, const string_vector& default_value, uint_ptr css_properties_member_offset) const; + virtual int_vector get_int_vector_property (string_id name, bool inherited, const int_vector& default_value, uint_ptr css_properties_member_offset) const; + virtual length_vector get_length_vector_property(string_id name, bool inherited, const length_vector& default_value, uint_ptr css_properties_member_offset) const; + virtual size_vector get_size_vector_property (string_id name, bool inherited, const size_vector& default_value, uint_ptr css_properties_member_offset) const; + virtual string get_custom_property(string_id name, const string& default_value) const; + + virtual void get_text(string& text); + virtual void parse_attributes(); + virtual int select(const string& selector); + virtual int select(const css_selector& selector, bool apply_pseudo = true); + virtual int select(const css_element_selector& selector, bool apply_pseudo = true); + virtual element::ptr find_ancestor(const css_selector& selector, bool apply_pseudo = true, bool* is_pseudo = nullptr); + virtual bool is_ancestor(const ptr &el) const; + virtual element::ptr find_adjacent_sibling(const element::ptr& el, const css_selector& selector, bool apply_pseudo = true, bool* is_pseudo = nullptr); + virtual element::ptr find_sibling(const element::ptr& el, const css_selector& selector, bool apply_pseudo = true, bool* is_pseudo = nullptr); + virtual void get_content_size(size& sz, int max_width); + virtual bool is_nth_child(const element::ptr& el, int num, int off, bool of_type) const; + virtual bool is_nth_last_child(const element::ptr& el, int num, int off, bool of_type) const; + virtual bool is_only_child(const element::ptr& el, bool of_type) const; + virtual void add_style(const style& style); + virtual const background* get_background(bool own_only = false); + + virtual string dump_get_name(); + virtual std::vector> dump_get_attrs(); + void dump(litehtml::dumper& cout); + + std::tuple split_inlines(); + virtual std::shared_ptr create_render_item(const std::shared_ptr& parent_ri); + bool requires_styles_update(); + void add_render(const std::shared_ptr& ri); + bool find_styles_changes( position::vector& redraw_boxes); + element::ptr add_pseudo_before(const style& style) + { + return _add_before_after(0, style); + } + element::ptr add_pseudo_after(const style& style) + { + return _add_before_after(1, style); + } + + string get_counter_value(const string& counter_name); + string get_counters_value(const string_vector& parameters); + void increment_counter(const string_id& counter_name_id, const int increment = 1); + void reset_counter(const string_id& counter_name_id, const int value = 0); + + private: + std::vector get_siblings_before() const; + bool find_counter(const string_id& counter_name_id, std::map::iterator& map_iterator); + void parse_counter_tokens(const string_vector& tokens, const int default_value, std::function handler) const; + }; + + ////////////////////////////////////////////////////////////////////////// + // INLINE FUNCTIONS // + ////////////////////////////////////////////////////////////////////////// + + inline bool litehtml::element::in_normal_flow() const + { + if(css().get_position() != element_position_absolute && css().get_display() != display_none) + { + return true; + } + return false; + } + + inline bool litehtml::element::is_root() const + { + return m_parent.expired(); + } + + inline element::ptr litehtml::element::parent() const + { + return m_parent.lock(); + } + + inline void litehtml::element::parent(const element::ptr& par) + { + m_parent = par; + } + + inline bool litehtml::element::is_positioned() const + { + return (css().get_position() > element_position_static); + } + + inline bool litehtml::element::is_float() const + { + return (css().get_float() != float_none); + } + + inline std::shared_ptr element::get_document() const + { + return m_doc.lock(); + } + + inline const css_properties& element::css() const + { + return m_css; + } + + inline css_properties& element::css_w() + { + return m_css; + } + + inline bool element::is_block_box() const + { + if (css().get_display() == display_block || + css().get_display() == display_flex || + css().get_display() == display_table || + css().get_display() == display_list_item || + css().get_display() == display_flex) + { + return true; + } + return false; + } + + inline const std::list>& element::children() const + { + return m_children; + } +} + +#endif // LH_ELEMENT_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/flex_item.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/flex_item.h new file mode 100644 index 0000000..a2b3426 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/flex_item.h @@ -0,0 +1,137 @@ +#ifndef LITEHTML_FLEX_ITEM_H +#define LITEHTML_FLEX_ITEM_H + +#include +#include "formatting_context.h" + +namespace litehtml +{ + class flex_line; + + /** + * Base class for flex item + */ + class flex_item + { + public: + std::shared_ptr el; + int base_size; + int min_size; + def_value max_size; + int main_size; + int grow; + int shrink; + int scaled_flex_shrink_factor; + bool frozen; + int order; + int src_order; + def_value auto_margin_main_start; + def_value auto_margin_main_end; + bool auto_margin_cross_start; + bool auto_margin_cross_end; + flex_align_items align; + + explicit flex_item(std::shared_ptr &_el) : + el(_el), + align(flex_align_items_auto), + grow(0), + base_size(0), + shrink(0), + min_size(0), + frozen(false), + main_size(0), + max_size(0), + order(0), + src_order(0), + scaled_flex_shrink_factor(0), + auto_margin_main_start(0), + auto_margin_main_end(0), + auto_margin_cross_start(false), + auto_margin_cross_end(false) + {} + + virtual ~flex_item() = default; + + bool operator<(const flex_item& b) const + { + if(order < b.order) return true; + if(order == b.order) return src_order < b.src_order; + return false; + } + void init(const litehtml::containing_block_context &self_size, + litehtml::formatting_context *fmt_ctx, flex_align_items align_items); + virtual void apply_main_auto_margins() = 0; + virtual bool apply_cross_auto_margins(int cross_size) = 0; + virtual void set_main_position(int pos) = 0; + virtual void set_cross_position(int pos) = 0; + virtual int get_el_main_size() = 0; + virtual int get_el_cross_size() = 0; + + void place(flex_line &ln, int main_pos, + const containing_block_context &self_size, + formatting_context *fmt_ctx); + int get_last_baseline(baseline::_baseline_type type) const; + int get_first_baseline(baseline::_baseline_type type) const; + + protected: + virtual void direction_specific_init(const litehtml::containing_block_context &self_size, + litehtml::formatting_context *fmt_ctx) = 0; + virtual void align_stretch(flex_line &ln, const containing_block_context &self_size, + formatting_context *fmt_ctx) = 0; + virtual void align_baseline(flex_line &ln, + const containing_block_context &self_size, + formatting_context *fmt_ctx) = 0; + }; + + /** + * Flex item with "flex-direction: row" or " flex-direction: row-reverse" + */ + class flex_item_row_direction : public flex_item + { + public: + explicit flex_item_row_direction(std::shared_ptr &_el) : flex_item(_el) {} + + void apply_main_auto_margins() override; + bool apply_cross_auto_margins(int cross_size) override; + void set_main_position(int pos) override; + void set_cross_position(int pos) override; + int get_el_main_size() override; + int get_el_cross_size() override; + + protected: + void direction_specific_init(const litehtml::containing_block_context &self_size, + litehtml::formatting_context *fmt_ctx) override; + void align_stretch(flex_line &ln, const containing_block_context &self_size, + formatting_context *fmt_ctx) override; + void align_baseline(flex_line &ln, + const containing_block_context &self_size, + formatting_context *fmt_ctx) override; + }; + + /** + * Flex item with "flex-direction: column" or " flex-direction: column-reverse" + */ + class flex_item_column_direction : public flex_item + { + public: + explicit flex_item_column_direction(std::shared_ptr &_el) : flex_item(_el) {} + + void apply_main_auto_margins() override; + bool apply_cross_auto_margins(int cross_size) override; + void set_main_position(int pos) override; + void set_cross_position(int pos) override; + int get_el_main_size() override; + int get_el_cross_size() override; + + protected: + void direction_specific_init(const litehtml::containing_block_context &self_size, + litehtml::formatting_context *fmt_ctx) override; + void align_stretch(flex_line &ln, const containing_block_context &self_size, + formatting_context *fmt_ctx) override; + void align_baseline(flex_line &ln, + const containing_block_context &self_size, + formatting_context *fmt_ctx) override; + }; +} + +#endif //LITEHTML_FLEX_ITEM_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/flex_line.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/flex_line.h new file mode 100644 index 0000000..4803d23 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/flex_line.h @@ -0,0 +1,56 @@ +#ifndef LITEHTML_FLEX_LINE_H +#define LITEHTML_FLEX_LINE_H + +#include "formatting_context.h" + +namespace litehtml +{ + class flex_item; + + class flex_line + { + public: + std::list> items; + int cross_start; // for row direction: top. for column direction: left + int main_size; // sum of all items main size + int cross_size; // sum of all items cross size + int base_size; + int total_grow; + int total_shrink; + int num_auto_margin_main_start; // number of items with auto margin left/top + int num_auto_margin_main_end; // number of items with auto margin right/bottom + baseline first_baseline; + baseline last_baseline; + bool reverse_main; + bool reverse_cross; + + flex_line(bool _reverse_main, bool _reverse_cross) : + cross_size(0), + cross_start(0), + total_grow(0), + base_size(0), + total_shrink(0), + main_size(0), + num_auto_margin_main_start(0), + num_auto_margin_main_end(0), + first_baseline(), + last_baseline(), + reverse_main(_reverse_main), + reverse_cross(_reverse_cross) + {} + + void init(int container_main_size, bool fit_container, bool is_row_direction, + const litehtml::containing_block_context &self_size, + litehtml::formatting_context *fmt_ctx); + bool distribute_main_auto_margins(int free_main_size); + int calculate_items_position(int container_main_size, + flex_justify_content justify_content, + bool is_row_direction, + const containing_block_context &self_size, + formatting_context *fmt_ctx); + protected: + void distribute_free_space(int container_main_size); + }; +} + +#endif //LITEHTML_FLEX_LINE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/formatting_context.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/formatting_context.h new file mode 100644 index 0000000..eee481d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/formatting_context.h @@ -0,0 +1,54 @@ +#ifndef LITEHTML_FLOATS_HOLDER_H +#define LITEHTML_FLOATS_HOLDER_H + +#include +#include "types.h" + +namespace litehtml +{ + class formatting_context + { + private: + std::list m_floats_left; + std::list m_floats_right; + int_int_cache m_cache_line_left; + int_int_cache m_cache_line_right; + int m_current_top; + int m_current_left; + + public: + formatting_context() : m_current_top(0), m_current_left(0) {} + + void push_position(int x, int y) + { + m_current_left += x; + m_current_top += y; + } + void pop_position(int x, int y) + { + m_current_left -= x; + m_current_top -= y; + } + + void add_float(const std::shared_ptr &el, int min_width, int context); + void clear_floats(int context); + int find_next_line_top( int top, int width, int def_right ); + int get_floats_height(element_float el_float = float_none) const; + int get_left_floats_height() const; + int get_right_floats_height() const; + int get_line_left( int y ); + void get_line_left_right( int y, int def_right, int& ln_left, int& ln_right ) + { + ln_left = get_line_left(y); + ln_right = get_line_right(y, def_right); + } + int get_line_right( int y, int def_right ); + int get_cleared_top(const std::shared_ptr &el, int line_top) const; + void update_floats(int dy, const std::shared_ptr &parent); + void apply_relative_shift(const containing_block_context &containing_block_size); + int find_min_left(int y, int context_idx); + int find_min_right(int y, int right, int context_idx); + }; +} + +#endif //LITEHTML_FLOATS_HOLDER_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/html.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/html.h new file mode 100644 index 0000000..818d5ce --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/html.h @@ -0,0 +1,88 @@ +#ifndef LH_HTML_H +#define LH_HTML_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "os_types.h" +#include "string_id.h" +#include "types.h" +#include "utf8_strings.h" +#include "background.h" +#include "borders.h" +#include "web_color.h" +#include "media_query.h" +#include "html_tag.h" +#include "document_container.h" +#include "document.h" + +namespace litehtml +{ + void trim(string &s, const string& chars_to_trim = " \n\r\t"); + void lcase(string &s); + int value_index(const string& val, const string& strings, int defValue = -1, char delim = ';'); + string index_value(int index, const string& strings, char delim = ';'); + bool value_in_list(const string& val, const string& strings, char delim = ';'); + string::size_type find_close_bracket(const string &s, string::size_type off, char open_b = '(', char close_b = ')'); + void split_string(const string& str, string_vector& tokens, const string& delims, const string& delims_preserve = "", const string& quote = "\""); + void join_string(string& str, const string_vector& tokens, const string& delims); + double t_strtod(const char* string, char** endPtr = nullptr); + string get_escaped_string(const string& in_str); + + int t_strcasecmp(const char *s1, const char *s2); + int t_strncasecmp(const char *s1, const char *s2, size_t n); + + bool is_number(const string& string, const bool allow_dot = 1); + + inline int t_isdigit(int c) + { + return (c >= '0' && c <= '9'); + } + + inline int t_isalpha(int c) + { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + } + + inline int t_tolower(int c) + { + return (c >= 'A' && c <= 'Z' ? c + 'a' - 'A' : c); + } + + inline int round_f(float val) + { + int int_val = (int) val; + if(val - int_val >= 0.5) + { + int_val++; + } + return int_val; + } + + inline int round_d(double val) + { + int int_val = (int) val; + if(val - int_val >= 0.5) + { + int_val++; + } + return int_val; + } + + inline float t_strtof(const string& str, char** endPtr = nullptr) + { + return (float)t_strtod(str.c_str(), endPtr); + } + + inline int baseline_align(int line_height, int line_base_line, int height, int baseline) + { + return (line_height - line_base_line) - (height - baseline); + } +} + +#endif // LH_HTML_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/html_tag.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/html_tag.h new file mode 100644 index 0000000..b085d7d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/html_tag.h @@ -0,0 +1,139 @@ +#ifndef LH_HTML_TAG_H +#define LH_HTML_TAG_H + +#include "element.h" +#include "style.h" +#include "background.h" +#include "css_margins.h" +#include "borders.h" +#include "css_selector.h" +#include "stylesheet.h" +#include "line_box.h" +#include "table.h" + +namespace litehtml +{ + + class html_tag : public element + { + friend class elements_iterator; + friend class el_table; + friend class table_grid; + friend class line_box; + public: + typedef std::shared_ptr ptr; + protected: + string_id m_tag; + string_id m_id; + string_vector m_str_classes; + std::vector m_classes; + litehtml::style m_style; + string_map m_attrs; + std::vector m_pseudo_classes; + + void select_all(const css_selector& selector, elements_list& res) override; + + public: + explicit html_tag(const std::shared_ptr& doc); + // constructor for anonymous wrapper boxes + explicit html_tag(const element::ptr& parent, const string& style = "display: block"); + + bool appendChild(const element::ptr &el) override; + bool removeChild(const element::ptr &el) override; + void clearRecursive() override; + string_id tag() const override; + string_id id() const override; + const char* get_tagName() const override; + void set_tagName(const char* tag) override; + void set_data(const char* data) override; + + void set_attr(const char* name, const char* val) override; + const char* get_attr(const char* name, const char* def = nullptr) const override; + void apply_stylesheet(const litehtml::css& stylesheet) override; + void refresh_styles() override; + + bool is_white_space() const override; + bool is_body() const override; + bool is_break() const override; + + bool on_mouse_over() override; + bool on_mouse_leave() override; + bool on_lbutton_down() override; + bool on_lbutton_up() override; + void on_click() override; + bool set_pseudo_class(string_id cls, bool add) override; + bool set_class(const char* pclass, bool add) override; + bool is_replaced() const override; + void compute_styles(bool recursive = true) override; + void draw(uint_ptr hdc, int x, int y, const position *clip, const std::shared_ptr &ri) override; + void draw_background(uint_ptr hdc, int x, int y, const position *clip, + const std::shared_ptr &ri) override; + + template + const Type& get_property_impl (string_id name, bool inherited, const Type& default_value, uint_ptr css_properties_member_offset) const; + int get_enum_property (string_id name, bool inherited, int default_value, uint_ptr css_properties_member_offset) const override; + int get_int_property (string_id name, bool inherited, int default_value, uint_ptr css_properties_member_offset) const override; + css_length get_length_property(string_id name, bool inherited, css_length default_value, uint_ptr css_properties_member_offset) const override; + web_color get_color_property (string_id name, bool inherited, web_color default_value, uint_ptr css_properties_member_offset) const override; + string get_string_property(string_id name, bool inherited, const string& default_value, uint_ptr css_properties_member_offset) const override; + float get_number_property(string_id name, bool inherited, float default_value, uint_ptr css_properties_member_offset) const override; + string_vector get_string_vector_property(string_id name, bool inherited, const string_vector& default_value, uint_ptr css_properties_member_offset) const override; + int_vector get_int_vector_property (string_id name, bool inherited, const int_vector& default_value, uint_ptr css_properties_member_offset) const override; + length_vector get_length_vector_property(string_id name, bool inherited, const length_vector& default_value, uint_ptr css_properties_member_offset) const override; + size_vector get_size_vector_property (string_id name, bool inherited, const size_vector& default_value, uint_ptr css_properties_member_offset) const override; + string get_custom_property(string_id name, const string& default_value) const override; + + elements_list& children(); + + int select(const string& selector) override; + int select(const css_selector& selector, bool apply_pseudo = true) override; + int select(const css_element_selector& selector, bool apply_pseudo = true) override; + int select_pseudoclass(const css_attribute_selector& sel); + int select_attribute(const css_attribute_selector& sel); + + elements_list select_all(const string& selector) override; + elements_list select_all(const css_selector& selector) override; + + element::ptr select_one(const string& selector) override; + element::ptr select_one(const css_selector& selector) override; + + element::ptr find_ancestor(const css_selector& selector, bool apply_pseudo = true, bool* is_pseudo = nullptr) override; + element::ptr find_adjacent_sibling(const element::ptr& el, const css_selector& selector, bool apply_pseudo = true, bool* is_pseudo = nullptr) override; + element::ptr find_sibling(const element::ptr& el, const css_selector& selector, bool apply_pseudo = true, bool* is_pseudo = nullptr) override; + void get_text(string& text) override; + void parse_attributes() override; + + void get_content_size(size& sz, int max_width) override; + void add_style(const style& style) override; + + bool is_nth_child(const element::ptr& el, int num, int off, bool of_type) const override; + bool is_nth_last_child(const element::ptr& el, int num, int off, bool of_type) const override; + bool is_only_child(const element::ptr& el, bool of_type) const override; + const background* get_background(bool own_only = false) override; + + string dump_get_name() override; + + protected: + void init_background_paint(position pos, std::vector& bg_paint, const background* bg, const std::shared_ptr& ri); + void init_one_background_paint(int i, position pos, background_paint& bg_paint, const background* bg, const std::shared_ptr& ri); + void draw_list_marker( uint_ptr hdc, const position &pos ); + string get_list_marker_text(int index); + element::ptr get_element_before(const style& style, bool create); + element::ptr get_element_after(const style& style, bool create); + + private: + void handle_counter_properties(); + + }; + + /************************************************************************/ + /* Inline Functions */ + /************************************************************************/ + + inline elements_list& litehtml::html_tag::children() + { + return m_children; + } +} + +#endif // LH_HTML_TAG_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/iterators.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/iterators.h new file mode 100644 index 0000000..b1a678d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/iterators.h @@ -0,0 +1,84 @@ +#ifndef LH_ITERATORS_H +#define LH_ITERATORS_H + +#include "types.h" +#include +#include + +namespace litehtml +{ + class render_item; + + class iterator_selector + { + public: + virtual bool select(const std::shared_ptr& el) = 0; + + protected: + ~iterator_selector() = default; + }; + + enum iterator_item_type + { + iterator_item_type_child, + iterator_item_type_start_parent, + iterator_item_type_end_parent + }; + + class elements_iterator + { + private: + iterator_selector* m_go_inside; + iterator_selector* m_select; + bool m_return_parent; + + /** + * Checks if iterator should go inside the element + * + * @param el element to check + * @return true to go inside + */ + bool go_inside(const std::shared_ptr& el); + + public: + elements_iterator(bool return_parents, iterator_selector* go_inside, iterator_selector* select); + ~elements_iterator() = default; + + void process(const std::shared_ptr& container, const std::function&, iterator_item_type)>& func); + + private: + void next_idx(); + }; + + class go_inside_inline final : public iterator_selector + { + public: + bool select(const std::shared_ptr& el) override; + }; + + class inline_selector final : public iterator_selector + { + public: + bool select(const std::shared_ptr& el) override; + }; + + class go_inside_table final : public iterator_selector + { + public: + bool select(const std::shared_ptr& el) override; + }; + + class table_rows_selector final : public iterator_selector + { + public: + bool select(const std::shared_ptr& el) override; + }; + + class table_cells_selector final : public iterator_selector + { + public: + bool select(const std::shared_ptr& el) override; + }; +} + +#endif // LH_ITERATORS_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/line_box.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/line_box.h new file mode 100644 index 0000000..43f5d43 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/line_box.h @@ -0,0 +1,170 @@ +#ifndef LH_LINE_BOX_H +#define LH_LINE_BOX_H + +#include +#include +#include "os_types.h" +#include "types.h" + +namespace litehtml +{ + class render_item; + + struct line_context + { + int calculatedTop; + int top; + int left; + int right; + + int width() const + { + return right - left; + } + void fix_top() + { + calculatedTop = top; + } + }; + + class line_box_item + { + public: + enum element_type + { + type_text_part, + type_inline_start, + type_inline_continue, + type_inline_end + }; + protected: + std::shared_ptr m_element; + int m_rendered_min_width; + public: + explicit line_box_item(const std::shared_ptr& element) : m_element(element), m_rendered_min_width(0) {} + line_box_item() = default; + line_box_item(const line_box_item& el) = default; + line_box_item(line_box_item&&) = default; + + int height() const { return right() - left(); } + const std::shared_ptr& get_el() const { return m_element; } + virtual position& pos(); + virtual void place_to(int x, int y); + virtual int width() const; + virtual int top() const; + virtual int bottom() const; + virtual int right() const; + virtual int left() const; + virtual element_type get_type() const { return type_text_part; } + virtual int get_rendered_min_width() const { return m_rendered_min_width; } + virtual void set_rendered_min_width(int min_width) { m_rendered_min_width = min_width; } + }; + + class lbi_start : public line_box_item + { + protected: + position m_pos; + public: + explicit lbi_start(const std::shared_ptr& element); + + void place_to(int x, int y) override; + int width() const override; + position& pos() override { return m_pos; } + int top() const override; + int bottom() const override; + int right() const override; + int left() const override; + element_type get_type() const override { return type_inline_start; } + int get_rendered_min_width() const override { return width(); } + }; + + class lbi_end : public lbi_start + { + public: + explicit lbi_end(const std::shared_ptr& element); + + void place_to(int x, int y) override; + int right() const override; + int left() const override; + element_type get_type() const override { return type_inline_end; } + }; + + class lbi_continue : public lbi_start + { + public: + explicit lbi_continue(const std::shared_ptr& element); + + void place_to(int x, int y) override; + int right() const override; + int left() const override; + int width() const override; + element_type get_type() const override { return type_inline_continue; } + }; + + class line_box + { + struct va_context + { + int baseline; + font_metrics fm; + + va_context() : baseline(0) {} + }; + + int m_top; + int m_left; + int m_right; + int m_height; + int m_width; + int m_line_height; + int m_default_line_height; + font_metrics m_font_metrics; + int m_baseline; + text_align m_text_align; + int m_min_width; + std::list< std::unique_ptr > m_items; + public: + line_box(int top, int left, int right, int line_height, const font_metrics& fm, text_align align) : + m_top(top), + m_left(left), + m_right(right), + m_height(0), + m_width(0), + m_font_metrics(fm), + m_default_line_height(line_height), + m_baseline(0), + m_line_height(0), + m_text_align(align), + m_min_width(0) + { + } + + int bottom() const { return m_top + height(); } + int top() const { return m_top; } + int right() const { return m_left + width(); } + int left() const { return m_left; } + int height() const { return m_height; } + int width() const { return m_width; } + int line_right() const { return m_right; } + int min_width() const { return m_min_width; } + + void add_item(std::unique_ptr item); + bool can_hold(const std::unique_ptr& item, white_space ws) const; + bool is_empty() const; + int baseline() const; + int top_margin() const; + int bottom_margin() const; + void y_shift(int shift); + std::list< std::unique_ptr > finish(bool last_box, const containing_block_context &containing_block_size); + std::list< std::unique_ptr > new_width(int left, int right); + std::shared_ptr get_last_text_part() const; + std::shared_ptr get_first_text_part() const; + std::list< std::unique_ptr >& items() { return m_items; } + private: + bool have_last_space() const; + bool is_break_only() const; + static int calc_va_baseline(const va_context& current, vertical_align va, const font_metrics& new_font, int top, int bottom); + }; +} + +#endif //LH_LINE_BOX_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/master_css.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/master_css.h new file mode 100644 index 0000000..0e7b0ca --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/master_css.h @@ -0,0 +1,376 @@ +#ifndef LH_MASTER_CSS_H +#define LH_MASTER_CSS_H +namespace litehtml{ const char* const master_css = R"##( + + +html { + display: block; + position: relative; +} + +head { + display: none +} + +meta { + display: none +} + +title { + display: none +} + +link { + display: none +} + +style { + display: none +} + +script { + display: none +} + +body { + display:block; + margin:8px; +} + +p { + display:block; + margin-top:1em; + margin-bottom:1em; +} + +b, strong { + display:inline; + font-weight:bold; +} + +i, em, cite { + display:inline; + font-style:italic; +} + +ins, u { + text-decoration:underline +} + +del, s, strike { + text-decoration:line-through +} + +center +{ + text-align:center; + display:block; +} + +a:link +{ + text-decoration: underline; + color: #00f; + cursor: pointer; +} + +h1, h2, h3, h4, h5, h6, div { + display:block; +} + +h1 { + font-weight:bold; + margin-top:0.67em; + margin-bottom:0.67em; + font-size: 2em; +} + +h2 { + font-weight:bold; + margin-top:0.83em; + margin-bottom:0.83em; + font-size: 1.5em; +} + +h3 { + font-weight:bold; + margin-top:1em; + margin-bottom:1em; + font-size:1.17em; +} + +h4 { + font-weight:bold; + margin-top:1.33em; + margin-bottom:1.33em +} + +h5 { + font-weight:bold; + margin-top:1.67em; + margin-bottom:1.67em; + font-size:.83em; +} + +h6 { + font-weight:bold; + margin-top:2.33em; + margin-bottom:2.33em; + font-size:.67em; +} + +br { + display:inline-block; +} + +br[clear="all"] +{ + clear:both; +} + +br[clear="left"] +{ + clear:left; +} + +br[clear="right"] +{ + clear:right; +} + +span { + display:inline +} + +img { + display: inline-block; +} + +img[align="right"] +{ + float: right; +} + +img[align="left"] +{ + float: left; +} + +hr { + display: block; + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-left: auto; + margin-right: auto; + border-style: inset; + border-width: 1px +} + + +/***************** TABLES ********************/ + +table { + display: table; + border-collapse: separate; + border-spacing: 2px; + border-top-color:gray; + border-left-color:gray; + border-bottom-color:black; + border-right-color:black; + font-size: medium; + font-weight: normal; + font-style: normal; +} + +tbody, tfoot, thead { + display:table-row-group; + vertical-align:middle; +} + +tr { + display: table-row; + vertical-align: inherit; + border-color: inherit; +} + +td, th { + display: table-cell; + vertical-align: inherit; + border-width:1px; + padding:1px; +} + +th { + font-weight: bold; +} + +table[border] { + border-style:solid; +} + +table[border|=0] { + border-style:none; +} + +table[border] td, table[border] th { + border-style:solid; + border-top-color:black; + border-left-color:black; + border-bottom-color:gray; + border-right-color:gray; +} + +table[border|=0] td, table[border|=0] th { + border-style:none; +} + +table[align=left] { + float: left; +} + +table[align=right] { + float: right; +} + +table[align=center] { + margin-left: auto; + margin-right: auto; +} + +caption { + display: table-caption; +} + +td[nowrap], th[nowrap] { + white-space:nowrap; +} + +tt, code, kbd, samp { + font-family: monospace +} + +pre, xmp, plaintext, listing { + display: block; + font-family: monospace; + white-space: pre; + margin: 1em 0 +} + +/***************** LISTS ********************/ + +ul, menu, dir { + display: block; + list-style-type: disc; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; + padding-left: 40px +} + +ol { + display: block; + list-style-type: decimal; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; + padding-left: 40px +} + +li { + display: list-item; +} + +ul ul, ol ul { + list-style-type: circle; +} + +ol ol ul, ol ul ul, ul ol ul, ul ul ul { + list-style-type: square; +} + +dd { + display: block; + margin-left: 40px; +} + +dl { + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; +} + +dt { + display: block; +} + +ol ul, ul ol, ul ul, ol ol { + margin-top: 0; + margin-bottom: 0 +} + +blockquote { + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 40px; + margin-right: 40px; +} + +/*********** FORM ELEMENTS ************/ + +form { + display: block; + margin-top: 0em; +} + +option { + display: none; +} + +input, textarea, keygen, select, button, isindex { + margin: 0em; + color: initial; + line-height: normal; + text-transform: none; + text-indent: 0; + text-shadow: none; + display: inline-block; +} +input[type="hidden"] { + display: none; +} + + +article, aside, footer, header, hgroup, nav, section +{ + display: block; +} + +sub { + vertical-align: sub; + font-size: smaller; +} + +sup { + vertical-align: super; + font-size: smaller; +} + +figure { + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 40px; + margin-right: 40px; +} + +figcaption { + display: block; +} + +)##"; } +#endif // LH_MASTER_CSS_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/media_query.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/media_query.h new file mode 100644 index 0000000..4cb21fc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/media_query.h @@ -0,0 +1,77 @@ +#ifndef LH_MEDIA_QUERY_H +#define LH_MEDIA_QUERY_H + +namespace litehtml +{ + struct media_query_expression + { + typedef std::vector vector; + media_feature feature; + int val; + int val2; + bool check_as_bool; + + media_query_expression() + { + check_as_bool = false; + feature = media_feature_none; + val = 0; + val2 = 0; + } + + bool check(const media_features& features) const; + }; + + class media_query + { + public: + typedef std::shared_ptr ptr; + typedef std::vector vector; + private: + media_query_expression::vector m_expressions; + bool m_not; + media_type m_media_type; + public: + media_query(); + media_query(const media_query& val); + + static media_query::ptr create_from_string(const string& str, const std::shared_ptr& doc); + bool check(const media_features& features) const; + }; + + class media_query_list + { + public: + typedef std::shared_ptr ptr; + typedef std::vector vector; + private: + media_query::vector m_queries; + bool m_is_used; + public: + media_query_list(); + media_query_list(const media_query_list& val); + + static media_query_list::ptr create_from_string(const string& str, const std::shared_ptr& doc); + bool is_used() const; + bool apply_media_features(const media_features& features); // returns true if the m_is_used changed + }; + + inline media_query_list::media_query_list(const media_query_list& val) + { + m_is_used = val.m_is_used; + m_queries = val.m_queries; + } + + inline media_query_list::media_query_list() + { + m_is_used = false; + } + + inline bool media_query_list::is_used() const + { + return m_is_used; + } + +} + +#endif // LH_MEDIA_QUERY_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/num_cvt.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/num_cvt.h new file mode 100644 index 0000000..0eaaa68 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/num_cvt.h @@ -0,0 +1,19 @@ +#ifndef NUM_CVT_H +#define NUM_CVT_H + +#include +#include "os_types.h" + +namespace litehtml +{ + namespace num_cvt + { + string to_latin_lower(int val); + string to_latin_upper(int val); + string to_greek_lower(int val); + string to_roman_lower(int value); + string to_roman_upper(int value); + } +} + +#endif // NUM_CVT_H \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/os_types.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/os_types.h new file mode 100644 index 0000000..bbc2c3c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/os_types.h @@ -0,0 +1,30 @@ +#ifndef LH_OS_TYPES_H +#define LH_OS_TYPES_H + +#include +#include + +namespace litehtml +{ + using std::string; + typedef std::uintptr_t uint_ptr; + +#if defined( WIN32 ) || defined( _WIN32 ) || defined( WINCE ) + +// noexcept appeared since Visual Studio 2015 +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define noexcept +#endif + + #define t_itoa(value, buffer, size, radix) _itoa_s(value, buffer, size, radix) + #define t_snprintf(s, n, format, ...) _snprintf_s(s, _TRUNCATE, n, format, __VA_ARGS__) + +#else + + #define t_itoa(value, buffer, size, radix) snprintf(buffer, size, "%d", value) + #define t_snprintf(s, n, format, ...) snprintf(s, n, format, __VA_ARGS__) + +#endif +} + +#endif // LH_OS_TYPES_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_block.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_block.h new file mode 100644 index 0000000..eb058c6 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_block.h @@ -0,0 +1,40 @@ +#ifndef LITEHTML_RENDER_BLOCK_H +#define LITEHTML_RENDER_BLOCK_H + +#include "render_item.h" + +namespace litehtml +{ + class render_item_block : public render_item + { + protected: + /** + * Render block content. + * + * @param x - horizontal position of the content + * @param y - vertical position of the content + * @param second_pass - true is this is the second pass. + * @param ret_width - input minimal width. + * @param self_size - defines calculated size of block + * @return return value is the minimal width of the content in block. Must be greater or equal to ret_width parameter + */ + virtual int _render_content(int x, int y, bool second_pass, const containing_block_context &self_size, formatting_context* fmt_ctx) {return 0;} + int _render(int x, int y, const containing_block_context &containing_block_size, formatting_context* fmt_ctx, bool second_pass) override; + int place_float(const std::shared_ptr &el, int top, const containing_block_context &self_size, formatting_context* fmt_ctx); + virtual void fix_line_width(element_float flt, + const containing_block_context &containing_block_size, formatting_context* fmt_ctx) + {} + + public: + explicit render_item_block(std::shared_ptr src_el) : render_item(std::move(src_el)) + {} + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + std::shared_ptr init() override; + }; +} + +#endif //LITEHTML_RENDER_BLOCK_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_block_context.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_block_context.h new file mode 100644 index 0000000..c693898 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_block_context.h @@ -0,0 +1,31 @@ +#ifndef LITEHTML_RENDER_BLOCK_CONTEXT_H +#define LITEHTML_RENDER_BLOCK_CONTEXT_H + +#include "render_block.h" + +namespace litehtml +{ + /** + * In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a + * containing block. + * https://www.w3.org/TR/CSS22/visuren.html#block-formatting + */ + class render_item_block_context : public render_item_block + { + protected: + int _render_content(int x, int y, bool second_pass, const containing_block_context &self_size, formatting_context* fmt_ctx) override; + + public: + explicit render_item_block_context(std::shared_ptr src_el) : render_item_block(std::move(src_el)) + {} + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + int get_first_baseline() override; + int get_last_baseline() override; + }; +} + +#endif //LITEHTML_RENDER_BLOCK_CONTEXT_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_flex.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_flex.h new file mode 100644 index 0000000..6a03b98 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_flex.h @@ -0,0 +1,33 @@ +#ifndef LITEHTML_RENDER_FLEX_H +#define LITEHTML_RENDER_FLEX_H + +#include "render_block.h" +#include "flex_item.h" +#include "flex_line.h" + +namespace litehtml +{ + class render_item_flex : public render_item_block + { + std::list m_lines; + + std::list get_lines(const containing_block_context &self_size, formatting_context *fmt_ctx, bool is_row_direction, + int container_main_size, bool single_line); + int _render_content(int x, int y, bool second_pass, const containing_block_context &self_size, formatting_context* fmt_ctx) override; + + public: + explicit render_item_flex(std::shared_ptr src_el) : render_item_block(std::move(src_el)) + {} + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + std::shared_ptr init() override; + + int get_first_baseline() override; + int get_last_baseline() override; + }; +} + +#endif //LITEHTML_RENDER_FLEX_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_image.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_image.h new file mode 100644 index 0000000..e85f487 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_image.h @@ -0,0 +1,25 @@ +#ifndef LITEHTML_RENDER_IMAGE_H +#define LITEHTML_RENDER_IMAGE_H + +#include "render_item.h" + +namespace litehtml +{ + class render_item_image : public render_item + { + protected: + int calc_max_height(int image_height, int containing_block_height); + int _render(int x, int y, const containing_block_context &containing_block_size, formatting_context* fmt_ctx, bool second_pass) override; + + public: + explicit render_item_image(std::shared_ptr src_el) : render_item(std::move(src_el)) + {} + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + }; +} + +#endif //LITEHTML_RENDER_IMAGE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_inline.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_inline.h new file mode 100644 index 0000000..666074b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_inline.h @@ -0,0 +1,38 @@ +#ifndef LITEHTML_RENDER_INLINE_H +#define LITEHTML_RENDER_INLINE_H + +#include "render_item.h" + +namespace litehtml +{ + class render_item_inline : public render_item + { + protected: + position::vector m_boxes; + + public: + explicit render_item_inline(std::shared_ptr src_el) : render_item(std::move(src_el)) + {} + + void get_inline_boxes( position::vector& boxes ) const override { boxes = m_boxes; } + void set_inline_boxes( position::vector& boxes ) override { m_boxes = boxes; } + void add_inline_box( const position& box ) override { m_boxes.emplace_back(box); }; + void clear_inline_boxes() override { m_boxes.clear(); } + int get_first_baseline() override + { + return src_el()->css().get_font_metrics().height - src_el()->css().get_font_metrics().base_line(); + } + int get_last_baseline() override + { + return src_el()->css().get_font_metrics().height - src_el()->css().get_font_metrics().base_line(); + } + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + }; +} + +#endif //LITEHTML_RENDER_INLINE_H + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_inline_context.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_inline_context.h new file mode 100644 index 0000000..7d629bc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_inline_context.h @@ -0,0 +1,56 @@ +#ifndef LITEHTML_RENDER_INLINE_CONTEXT_H +#define LITEHTML_RENDER_INLINE_CONTEXT_H + +#include "render_block.h" + +namespace litehtml +{ + /** + * An inline formatting context is established by a block container box that contains no block-level boxes. + * https://www.w3.org/TR/CSS22/visuren.html#inline-formatting + */ + class render_item_inline_context : public render_item_block + { + /** + * Structure contains elements with display: inline + * members: + * - element: render_item with display: inline + * - boxes: rectangles represented inline element content. There are can be many boxes if content + * is split into some lines + * - start_box: the start position of currently calculated box + */ + struct inlines_item + { + std::shared_ptr element; + position::vector boxes; + position start_box; + + explicit inlines_item(const std::shared_ptr& el) : element(el) {} + }; + protected: + std::vector > m_line_boxes; + int m_max_line_width; + + int _render_content(int x, int y, bool second_pass, const containing_block_context &self_size, formatting_context* fmt_ctx) override; + void fix_line_width(element_float flt, + const containing_block_context &self_size, formatting_context* fmt_ctx) override; + + std::list > finish_last_box(bool end_of_render, const containing_block_context &self_size); + void place_inline(std::unique_ptr item, const containing_block_context &self_size, formatting_context* fmt_ctx); + int new_box(const std::unique_ptr& el, line_context& line_ctx, const containing_block_context &self_size, formatting_context* fmt_ctx); + void apply_vertical_align() override; + public: + explicit render_item_inline_context(std::shared_ptr src_el) : render_item_block(std::move(src_el)), m_max_line_width(0) + {} + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + + int get_first_baseline() override; + int get_last_baseline() override; + }; +} + +#endif //LITEHTML_RENDER_INLINE_CONTEXT_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_item.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_item.h new file mode 100644 index 0000000..ac8a9f4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_item.h @@ -0,0 +1,387 @@ +#ifndef LH_RENDER_ITEM_H +#define LH_RENDER_ITEM_H + +#include +#include +#include +#include +#include "types.h" +#include "line_box.h" +#include "table.h" +#include "formatting_context.h" + +namespace litehtml +{ + class element; + + class render_item : public std::enable_shared_from_this + { + protected: + std::shared_ptr m_element; + std::weak_ptr m_parent; + std::list> m_children; + margins m_margins; + margins m_padding; + margins m_borders; + position m_pos; + bool m_skip; + std::vector> m_positioned; + + containing_block_context calculate_containing_block_context(const containing_block_context& cb_context); + void calc_cb_length(const css_length& len, int percent_base, containing_block_context::typed_int& out_value) const; + virtual int _render(int x, int y, const containing_block_context& containing_block_size, formatting_context* fmt_ctx, bool second_pass = false) + { + return 0; + } + + public: + explicit render_item(std::shared_ptr src_el); + + virtual ~render_item() = default; + + std::list>& children() + { + return m_children; + } + + position& pos() + { + return m_pos; + } + + bool skip() const + { + return m_skip; + } + + void skip(bool val) + { + m_skip = val; + } + + int right() const + { + return left() + width(); + } + + int left() const + { + return m_pos.left() - m_margins.left - m_padding.left - m_borders.left; + } + + int top() const + { + return m_pos.top() - m_margins.top - m_padding.top - m_borders.top; + } + + int bottom() const + { + return top() + height(); + } + + int height() const + { + return m_pos.height + m_margins.height() + m_padding.height() + m_borders.height(); + } + + int width() const + { + return m_pos.width + m_margins.width() + m_padding.width() + m_borders.width(); + } + + int padding_top() const + { + return m_padding.top; + } + + int padding_bottom() const + { + return m_padding.bottom; + } + + int padding_left() const + { + return m_padding.left; + } + + int padding_right() const + { + return m_padding.right; + } + + int border_top() const + { + return m_borders.top; + } + + int border_bottom() const + { + return m_borders.bottom; + } + + int border_left() const + { + return m_borders.left; + } + + int border_right() const + { + return m_borders.right; + } + + int margin_top() const + { + return m_margins.top; + } + + int margin_bottom() const + { + return m_margins.bottom; + } + + int margin_left() const + { + return m_margins.left; + } + + int margin_right() const + { + return m_margins.right; + } + + std::shared_ptr parent() const + { + return m_parent.lock(); + } + + margins& get_margins() + { + return m_margins; + } + + margins& get_paddings() + { + return m_padding; + } + + void set_paddings(const margins& val) + { + m_padding = val; + } + + margins& get_borders() + { + return m_borders; + } + + /** + * Top offset to the element content. Includes paddings, margins and borders. + */ + int content_offset_top() const + { + return m_margins.top + m_padding.top + m_borders.top; + } + + /** + * Bottom offset to the element content. Includes paddings, margins and borders. + */ + inline int content_offset_bottom() const + { + return m_margins.bottom + m_padding.bottom + m_borders.bottom; + } + + /** + * Left offset to the element content. Includes paddings, margins and borders. + */ + int content_offset_left() const + { + return m_margins.left + m_padding.left + m_borders.left; + } + + /** + * Right offset to the element content. Includes paddings, margins and borders. + */ + int content_offset_right() const + { + return m_margins.right + m_padding.right + m_borders.right; + } + + /** + * Sum of left and right offsets to the element content. Includes paddings, margins and borders. + */ + int content_offset_width() const + { + return content_offset_left() + content_offset_right(); + } + + /** + * Sum of top and bottom offsets to the element content. Includes paddings, margins and borders. + */ + int content_offset_height() const + { + return content_offset_top() + content_offset_bottom(); + } + + int box_sizing_left() const + { + if(css().get_box_sizing() == box_sizing_border_box) + { + return m_padding.left + m_borders.left; + } + return 0; + } + + int box_sizing_right() const + { + if(css().get_box_sizing() == box_sizing_border_box) + { + return m_padding.right + m_borders.right; + } + return 0; + } + + int box_sizing_width() const + { + return box_sizing_left() + box_sizing_right(); + } + + int box_sizing_top() const + { + if(css().get_box_sizing() == box_sizing_border_box) + { + return m_padding.top + m_borders.top; + } + return 0; + } + + int box_sizing_bottom() const + { + if(css().get_box_sizing() == box_sizing_border_box) + { + return m_padding.bottom + m_borders.bottom; + } + return 0; + } + + int box_sizing_height() const + { + return box_sizing_top() + box_sizing_bottom(); + } + + void parent(const std::shared_ptr& par) + { + m_parent = par; + } + + const std::shared_ptr& src_el() const + { + return m_element; + } + + const css_properties& css() const + { + return m_element->css(); + } + + void add_child(const std::shared_ptr& ri) + { + m_children.push_back(ri); + ri->parent(shared_from_this()); + } + + bool is_root() const + { + return m_parent.expired(); + } + + bool collapse_top_margin() const + { + return !m_borders.top && + !m_padding.top && + m_element->in_normal_flow() && + m_element->css().get_float() == float_none && + m_margins.top >= 0 && + !is_flex_item() && + !is_root(); + } + + bool collapse_bottom_margin() const + { + return !m_borders.bottom && + !m_padding.bottom && + m_element->in_normal_flow() && + m_element->css().get_float() == float_none && + m_margins.bottom >= 0 && + !is_root(); + } + + bool is_visible() const + { + return !(m_skip || src_el()->css().get_display() == display_none || src_el()->css().get_visibility() != visibility_visible); + } + + bool is_flex_item() const + { + auto par = parent(); + if(par && (par->css().get_display() == display_inline_flex || par->css().get_display() == display_flex)) + { + return true; + } + return false; + } + + int render(int x, int y, const containing_block_context& containing_block_size, formatting_context* fmt_ctx, bool second_pass = false); + void apply_relative_shift(const containing_block_context &containing_block_size); + void calc_outlines( int parent_width ); + int calc_auto_margins(int parent_width); // returns left margin + + virtual std::shared_ptr init(); + virtual void apply_vertical_align() {} + /** + * Get first baseline position. Default position is element bottom without bottom margin. + * @returns offset of the first baseline from element top + */ + virtual int get_first_baseline() { return height() - margin_bottom(); } + /** + * Get last baseline position. Default position is element bottom without bottom margin. + * @returns offset of the last baseline from element top + */ + virtual int get_last_baseline() { return height() - margin_bottom(); } + + virtual std::shared_ptr clone() + { + return std::make_shared(src_el()); + } + std::tuple< + std::shared_ptr, + std::shared_ptr, + std::shared_ptr + > split_inlines(); + bool fetch_positioned(); + void render_positioned(render_type rt = render_all); + void add_positioned(const std::shared_ptr &el); + void get_redraw_box(litehtml::position& pos, int x = 0, int y = 0); + void calc_document_size( litehtml::size& sz, litehtml::size& content_size, int x = 0, int y = 0 ); + virtual void get_inline_boxes( position::vector& boxes ) const {}; + virtual void set_inline_boxes( position::vector& boxes ) {}; + virtual void add_inline_box( const position& box ) {}; + virtual void clear_inline_boxes() {}; + void draw_stacking_context( uint_ptr hdc, int x, int y, const position* clip, bool with_positioned ); + virtual void draw_children( uint_ptr hdc, int x, int y, const position* clip, draw_flag flag, int zindex ); + virtual int get_draw_vertical_offset() { return 0; } + virtual std::shared_ptr get_child_by_point(int x, int y, int client_x, int client_y, draw_flag flag, int zindex); + std::shared_ptr get_element_by_point(int x, int y, int client_x, int client_y); + bool is_point_inside( int x, int y ); + void dump(litehtml::dumper& cout); + position get_placement() const; + /** + * Returns the boxes of rendering element. All coordinates are absolute + * + * @param redraw_boxes [out] resulting rendering boxes + * @return + */ + void get_rendering_boxes( position::vector& redraw_boxes); + }; +} + +#endif //LH_RENDER_ITEM_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_table.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_table.h new file mode 100644 index 0000000..57fe435 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/render_table.h @@ -0,0 +1,56 @@ +#ifndef LITEHTML_RENDER_TABLE_H +#define LITEHTML_RENDER_TABLE_H + +#include "render_item.h" + +namespace litehtml +{ + class render_item_table : public render_item + { + protected: + // data for table rendering + std::unique_ptr m_grid; + int m_border_spacing_x; + int m_border_spacing_y; + + int _render(int x, int y, const containing_block_context &containing_block_size, formatting_context* fmt_ctx, bool second_pass) override; + + public: + explicit render_item_table(std::shared_ptr src_el); + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + void draw_children(uint_ptr hdc, int x, int y, const position* clip, draw_flag flag, int zindex) override; + int get_draw_vertical_offset() override; + std::shared_ptr init() override; + }; + + class render_item_table_part : public render_item + { + public: + explicit render_item_table_part(std::shared_ptr src_el) : render_item(std::move(src_el)) + {} + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + }; + + class render_item_table_row : public render_item + { + public: + explicit render_item_table_row(std::shared_ptr src_el) : render_item(std::move(src_el)) + {} + + std::shared_ptr clone() override + { + return std::make_shared(src_el()); + } + void get_inline_boxes( position::vector& boxes ) const override; + }; +} + +#endif //LITEHTML_RENDER_TABLE_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/string_id.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/string_id.h new file mode 100644 index 0000000..2c90fc6 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/string_id.h @@ -0,0 +1,306 @@ +#ifndef LH_STRING_ID_H +#define LH_STRING_ID_H + +namespace litehtml +{ + +#define STRING_ID(...)\ + enum string_id { __VA_ARGS__ };\ + const auto initial_string_ids = #__VA_ARGS__; + +STRING_ID( + + // HTML tags + _a_, + _abbr_, + _acronym_, + _address_, + _applet_, + _area_, + _article_, + _aside_, + _audio_, + _b_, + _base_, + _basefont_, + _bdi_, + _bdo_, + _big_, + _blockquote_, + _body_, + _br_, + _button_, + _canvas_, + _caption_, + _center_, + _cite_, + _code_, + _col_, + _colgroup_, + _data_, + _datalist_, + _dd_, + _del_, + _details_, + _dfn_, + _dialog_, + _dir_, + _div_, + _dl_, + _dt_, + _em_, + _embed_, + _fieldset_, + _figcaption_, + _figure_, + _footer_, + _form_, + _frame_, + _frameset_, + _h1_, + _h2_, + _h3_, + _h4_, + _h5_, + _h6_, + _head_, + _header_, + _hr_, + _html_, + _i_, + _iframe_, + _img_, + _input_, + _ins_, + _kbd_, + _label_, + _legend_, + _li_, + _link_, + _main_, + _map_, + _mark_, + _meta_, + _meter_, + _nav_, + _noframes_, + _noscript_, + _object_, + _ol_, + _optgroup_, + _option_, + _output_, + _p_, + _param_, + _picture_, + _pre_, + _progress_, + _q_, + _rp_, + _rt_, + _ruby_, + _s_, + _samp_, + _script_, + _section_, + _select_, + _small_, + _source_, + _span_, + _strike_, + _strong_, + _style_, + _sub_, + _summary_, + _sup_, + _svg_, + _table_, + _tbody_, + _td_, + _template_, + _textarea_, + _tfoot_, + _th_, + _thead_, + _time_, + _title_, + _tr_, + _track_, + _tt_, + _u_, + _ul_, + _var_, + _video_, + _wbr_, + + // litehtml internal tags + __tag_before_, // note: real tag cannot start with '-' + __tag_after_, + + // CSS pseudo-elements + _before_, + _after_, + + // CSS pseudo-classes + _root_, + _only_child_, + _only_of_type_, + _first_child_, + _first_of_type_, + _last_child_, + _last_of_type_, + _nth_child_, + _nth_of_type_, + _nth_last_child_, + _nth_last_of_type_, + _not_, + _lang_, + + _active_, + _hover_, + + // CSS property names + _background_, + _background_color_, + _background_image_, + _background_image_baseurl_, + _background_repeat_, + _background_origin_, + _background_clip_, + _background_attachment_, + _background_size_, + _background_position_, + _background_position_x_, + _background_position_y_, + + _border_, + _border_width_, + _border_style_, + _border_color_, + + _border_spacing_, + __litehtml_border_spacing_x_, + __litehtml_border_spacing_y_, + + _border_left_, + _border_right_, + _border_top_, + _border_bottom_, + + _border_left_style_, + _border_right_style_, + _border_top_style_, + _border_bottom_style_, + + _border_left_width_, + _border_right_width_, + _border_top_width_, + _border_bottom_width_, + + _border_left_color_, + _border_right_color_, + _border_top_color_, + _border_bottom_color_, + + _border_radius_, + _border_radius_x_, + _border_radius_y_, + + _border_bottom_left_radius_, + _border_bottom_left_radius_x_, + _border_bottom_left_radius_y_, + + _border_bottom_right_radius_, + _border_bottom_right_radius_x_, + _border_bottom_right_radius_y_, + + _border_top_left_radius_, + _border_top_left_radius_x_, + _border_top_left_radius_y_, + + _border_top_right_radius_, + _border_top_right_radius_x_, + _border_top_right_radius_y_, + + _list_style_, + _list_style_type_, + _list_style_position_, + _list_style_image_, + _list_style_image_baseurl_, + + _margin_, + _margin_left_, + _margin_right_, + _margin_top_, + _margin_bottom_, + _padding_, + _padding_left_, + _padding_right_, + _padding_top_, + _padding_bottom_, + + _font_, + _font_family_, + _font_style_, + _font_variant_, + _font_weight_, + _font_size_, + _line_height_, + _text_decoration_, + + _white_space_, + _text_align_, + _vertical_align_, + _color_, + _width_, + _height_, + _min_width_, + _min_height_, + _max_width_, + _max_height_, + _position_, + _overflow_, + _display_, + _visibility_, + _box_sizing_, + _z_index_, + _float_, + _clear_, + _text_indent_, + _left_, + _right_, + _top_, + _bottom_, + _cursor_, + _content_, + _border_collapse_, + _text_transform_, + + _flex_, + _flex_flow_, + _flex_direction_, + _flex_wrap_, + _justify_content_, + _align_items_, + _align_content_, + _align_self_, + _flex_grow_, + _flex_shrink_, + _flex_basis_, + + _caption_side_, + _order_, + + _counter_reset_, + _counter_increment_, +); +#undef STRING_ID +extern const string_id empty_id; // _id("") +extern const string_id star_id; // _id("*") + +string_id _id(const string& str); +const string& _s(string_id id); + +} // namespace litehtml + +#endif // LH_STRING_ID_H diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/style.h b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/style.h new file mode 100644 index 0000000..2e04059 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/include/litehtml/style.h @@ -0,0 +1,213 @@ +#ifndef LH_STYLE_H +#define LH_STYLE_H + +namespace litehtml +{ + enum property_type + { + prop_type_invalid, // indicates "not found" condition in style::get_property + prop_type_inherit, // "inherit" was specified as the value of this property + + prop_type_enum_item, + prop_type_enum_item_vector, + prop_type_length, + prop_type_length_vector, + prop_type_number, + prop_type_color, + prop_type_string, + prop_type_string_vector, + prop_type_size_vector, + + prop_type_var, // also string, but needs further parsing because of var() + }; + + class property_value + { + public: + property_type m_type; + bool m_important; + + union { + int m_enum_item; + int_vector m_enum_item_vector; + css_length m_length; + length_vector m_length_vector; + float m_number; + web_color m_color; + string m_string; + string_vector m_string_vector; + size_vector m_size_vector; + }; + + property_value() + : m_type(prop_type_invalid) + { + } + property_value(bool important, property_type type) + : m_type(type), m_important(important) + { + } + property_value(const string& str, bool important, property_type type = prop_type_string) + : m_string(str), m_type(type), m_important(important) + { + } + property_value(const string_vector& vec, bool important) + : m_string_vector(vec), m_type(prop_type_string_vector), m_important(important) + { + } + property_value(const css_length& length, bool important) + : m_length(length), m_type(prop_type_length), m_important(important) + { + } + property_value(const length_vector& vec, bool important) + : m_length_vector(vec), m_type(prop_type_length_vector), m_important(important) + { + } + property_value(float number, bool important) + : m_number(number), m_type(prop_type_number), m_important(important) + { + } + property_value(int enum_item, bool important) + : m_enum_item(enum_item), m_type(prop_type_enum_item), m_important(important) + { + } + property_value(const int_vector& vec, bool important) + : m_enum_item_vector(vec), m_type(prop_type_enum_item_vector), m_important(important) + { + } + property_value(web_color color, bool important) + : m_color(color), m_type(prop_type_color), m_important(important) + { + } + property_value(const size_vector& vec, bool important) + : m_size_vector(vec), m_type(prop_type_size_vector), m_important(important) + { + } + ~property_value() + { + switch (m_type) + { + case prop_type_string: + case prop_type_var: + m_string.~string(); + break; + case prop_type_string_vector: + m_string_vector.~string_vector(); + break; + case prop_type_length: + m_length.~css_length(); + break; + case prop_type_length_vector: + m_length_vector.~length_vector(); + break; + case prop_type_enum_item_vector: + m_enum_item_vector.~int_vector(); + break; + case prop_type_color: + m_color.~web_color(); + break; + case prop_type_size_vector: + m_size_vector.~size_vector(); + break; + } + } + property_value& operator=(const property_value& val) + { + this->~property_value(); + + switch (val.m_type) + { + case prop_type_invalid: + new(this) property_value(); + break; + case prop_type_inherit: + new(this) property_value(val.m_important, val.m_type); + break; + case prop_type_string: + case prop_type_var: + new(this) property_value(val.m_string, val.m_important, val.m_type); + break; + case prop_type_string_vector: + new(this) property_value(val.m_string_vector, val.m_important); + break; + case prop_type_enum_item: + new(this) property_value(val.m_enum_item, val.m_important); + break; + case prop_type_enum_item_vector: + new(this) property_value(val.m_enum_item_vector, val.m_important); + break; + case prop_type_length: + new(this) property_value(val.m_length, val.m_important); + break; + case prop_type_length_vector: + new(this) property_value(val.m_length_vector, val.m_important); + break; + case prop_type_number: + new(this) property_value(val.m_number, val.m_important); + break; + case prop_type_color: + new(this) property_value(val.m_color, val.m_important); + break; + case prop_type_size_vector: + new(this) property_value(val.m_size_vector, val.m_important); + break; + } + + return *this; + } + }; + + typedef std::map props_map; + + class style + { + public: + typedef std::shared_ptr + + + + + + + + + + + + + + + +
Month savings
MonthSavings
January$100
MonthlysavingsMonthly savings!!! Monthly savings!!!
February$50
+ +just a table \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/acid1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/acid1.htm new file mode 100644 index 0000000..039a45a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/acid1.htm @@ -0,0 +1,177 @@ + + + + + display/box/float/clear test + + + + +
+
+ toggle +
+
+
    +
  • + the way +
  • +
  • +

    + the world ends +

    +
    +

    + bang + +

    +

    + whimper + +

    +
    +
  • +
  • + i grow old +
  • +
  • + pluot? +
  • +
+
+
+ bar maids, +
+
+

+ sing to me, erbarme dich +

+
+
+

+ This is a nonsensical document, but syntactically valid HTML 4.0. All 100%-conformant CSS1 agents should be able to render the document elements above this paragraph indistinguishably (to the pixel) from this + reference rendering, + (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the parent page. +

+ + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/acid1.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/acid1.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a6e8717a542c969a3bd92c26810f6ec8c4b4f430 GIT binary patch literal 3553 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 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/counter.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/counter.htm new file mode 100644 index 0000000..bd24d15 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/counter.htm @@ -0,0 +1,79 @@ + + + + + css counters + + + + +

HTML/CSS Tutorials

+

HTML

+

CSS

+

Bootstrap

+

W3.CSS

+ +

Scripting Tutorials

+

JavaScript

+

jQuery

+

React

+ +

Programming Tutorials

+

Python

+

Java

+

C++

+
    +
  1. item
  2. +
  3. item +
      +
    1. item
    2. +
    3. item
    4. +
    5. item +
        +
      1. item
      2. +
      3. item
      4. +
      5. item
      6. +
      +
    6. +
    7. item
    8. +
    +
  4. +
  5. item
  6. +
  7. item
  8. +
+ +
    +
  1. item
  2. +
  3. item
  4. +
+ + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/counter.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/counter.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ae151ac44d77d872bbf905a6354b3f92d675f59a GIT binary patch literal 3300 zcmZ{nc|6ox8^FgBL(&W*OTx_9!X-q=`kTG3CKPvEOQa+jOSZ|HJ7XPYK_W)sDlJ5= ztdUE`zIK)Dh9qL7*OGYke%__`{e0d(&hvSmb3UK*obx>A`#hhMboLDHfVhG<2n0Gn zz@zO!AR(2#;U=&*0cu7%1%p5mj|ga+bKwGuugl4!_yN#lOw-+GYCC;R9vW|X&E4du zaq^3Wi_$Ka1>)~C3kRzx3TYftsdl4@io~a1ifq5PPteg!Fx@BicCD6iNk5m}T~2Co zE_M9vPqOh>>9^;*cmI@6MkH$%B>qz_6x;uzdq(Q9{Zx?!$7TWK?0w1w< z9+VxEyy!$Wk4wNuDBpupCUy^iaYO}w9pz<2*>1kN`zNTlhyq2)?;7;EF?#k2wJ-r{ zmQ%;wEXzmGDBX!Zeu*6{JeHrCS-(Qc&RS==vOgy(y&ZUCsARV~k7Od|XqUVin{s8I zd>ghBvTwndt56!|plf=(w#P|^$4xD%0S2pHJtog^6=gP0Bf(R{Mk>E}t}6&NN2Tr1 z7B#6Sm^f(8E$oHiMw~d1e6ir8FOyzcd$C~uhpIN#9Z~3uim6`k&%uZ@zz>;PbMXc7J{-i)^d zNMzspPhK?(xQvu=w0D1lK`;zOhCC8}lpdev$(2EXnrZh71(+sz6v2@fEA$u7XotSDQkW=``EHg;Ov%a8-fX<(1lB?}MyzNTxA z`mZY}%kmR@Jg=6pg3k-q*gTe^qG2_F@T68!cpwSk>E{8&aTjiKz7jb)4}bkJrc=7(f)1wGgbMK?E)A`zk8!i3Tbe>648Y zg)c}_*Gu8VaNFYEbcDb-&0XGm7*kO^FU8#;b`JTUX8GgP^?rEYU9@mBLG@*cPxOdO zUd+R0y81QOp87pZ$BNVdqtKwK{)oGU;8lV0--q`dL3mP)_!e<&yrF1NHy-7EUOV9& zUCvB;zXt$XdH#uT3@SbqyDeU?4hGhDwScBmkp2b za^<^5ZUM5K|H{W6adg0?tMS)se)D2W0lXLAGLKLH)z?^#Q=yrnfs=|FsESp{-ikRI z@HO#e*zB>jXXPr&cewIaI+Wy{XP}Xgmq}3BI+N5Bw-^yEt?&C^6^~^e53QVk((P;Z zaVw?z54a!>K|3lz#Ng6Ol#X`!Vr#Waj*?k@?zjJn2P@LNU<6Wo=bcQ`wxNhm@5c=} zRdwtv(h;WL1VfFXoC*tc``m4gS$|kNJ2@U=wG^OM30VC#q|m6|(+3L|B?Vc185lID zd$l2dQ9{%e)t#MN!yiPBE+cNZ;#5Ku#xPrbkF6LmDva-hj8cyjXrB+M;rGiFzaxei zinois8glHF!-^yE36ma7Ypz07-0($N zm9^`)<9`+Llj^@rI{RwneiC&2YS5_8;HOh=34SzBE%9%d6Vb|V=Mn@|uK+QVMOls_qRNB;-#85s-2;nV@A9)g>^C%u6CfzWij2%1 zC~4;rf#qV9d2CsIFZgCVD{qOV#s37m4lQbxq`Y{*(F?%n5e4}IrhH}h8}$$%NAhOo zZn##0%R%m|PPk0-PlB>;A~z*BKmWe|r0k3Wq&+=Y3=>CwzEVevXeH#2d>P{2-&d># z%LZlF{{Xmw?6i(kBw3ZEHI=N9sw&td;}k8@(i>Z;eQ>$!oi_r8kqypdcFv`CeTK02 zwEg)6_{mNql^WOpVmLMy1`KY+*1IOcGpM$^s+#dGNGFdX;DKBX6Ad)a5J;{!uK0T~ zgT+3u!#vin3)j&kq)ug0U4PMCWD*J87Y(A9Ws&7{!Cfc%J@P9<^H zF}0?|lQaeUoIHfz1LN7#nPS?=*bCOWhs}LjIKn>dK2uIgdi8P@&#Lc1EY;9tTW2%% z+mvNaJDigi>tB^}YK0K)dH0Jo1YLTPXMdk5C#>xzC@fp1P{Qe{;|K;vOL1c{55!kSK}NP&Z5Z z%CeGA8-fzGXPKNylbH43=~jKC?ij=+fIC!lLj%7zuXQd3{jHyJ?yR1>ZAt6koKfC) z{0;^BqIA-%#U120@oASC^=S{RrZ1j~p`P`%7XO?B9UOtAxT6h{??2|zdOLKBf#BEP zqePreZ;aJ$p89=9>#E>cy8HR|bvlr2QnrbGk>}(&_|2bEqpn#tyyo>jCU;lgd?rfOh?L;PPRhm-wecCDAhT z7sL7_ab=;Q6U7Tdlvb zn0x+G`_q-N#24}o%5vPM?=hpsw(PSbQo&FJIuQa_PHVaL_VO{Q)y$i@*Re>S%`n%- zX<1nI!X2vB${i+CAcdMtzt954S2}RW(3A@dW-r$B=>HD{1P*-Pn%DYSHZpzNdF*}3 Q()aHR0dodj3S5Z$I}rZymH+?% literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-1-line-height.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-1-line-height.htm new file mode 100644 index 0000000..06d8773 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-1-line-height.htm @@ -0,0 +1,19 @@ + + + + +
normal
normal
+
1
1
+
100%
100%
+
1em
1em
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-1-line-height.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-1-line-height.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b750a15054aedaeffbcf5e0a158ff24a57a63872 GIT binary patch literal 321 zcmV-H0lxl;P)002?}00000`|jeB00039NklF9;8#ME$49-v5+~7{vR8)Ba`Ailka#k^WcN?6a(RPcc>fk3+1(Obc5B1N zz_se)v}$MDqx_8aOH66G#DeC(#N8;mhWZn$f|d`g*?J+I$B>%W>rhTz=Bue2*kRHgo_x&abnC2qM$}Ig+)owUPrV)W6QS;>M$}J)x}Rvf?k7goPkkiRPgmyouZVxDmN + + + +

text \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-2-invalid-color.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/css-2-invalid-color.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..69dfe83c1665d53883b5dde516ac32617f903d05 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK4H%h$5M4 zba4!+V0?RVBX5HNk4vE1N1iD>M>*uqUG+^>RI%D+vQO!k|I7V)b_@&$&i}Mk>bAQ2 y*<0ms`AajGd8Y+5k54kXqY`8&$H>57UB&1i#uvZO%cCD?E`z75pUXO@geCxH!!a=c literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flex-minimum-height-flex-items-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flex-minimum-height-flex-items-003.htm new file mode 100644 index 0000000..dfa7cb5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flex-minimum-height-flex-items-003.htm @@ -0,0 +1,54 @@ + + + + + + CSS Flexible Box Test: Minimum height of flex items + + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-001a.htm new file mode 100644 index 0000000..44d48db --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-001a.htm @@ -0,0 +1,57 @@ + + + + + CSS Test: Testing that "min-width", "max-width", "min-height", and "max-height" are applied on absolutely positioned children of a horizontal flex container + + + + + + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-001b.htm new file mode 100644 index 0000000..f216dc2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-001b.htm @@ -0,0 +1,58 @@ + + + + + CSS Test: Testing that "min-width", "max-width", "min-height", and "max-height" are applied on absolutely positioned children of a vertical flex container + + + + + + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-002.htm new file mode 100644 index 0000000..e35c21d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-abspos-child-002.htm @@ -0,0 +1,65 @@ + + + + + + + CSS Test: Test that "flex-basis" doesn't affect layout of abspos flex child + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-baseline-single-item-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-baseline-single-item-001a.htm new file mode 100644 index 0000000..6bc2925 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-baseline-single-item-001a.htm @@ -0,0 +1,55 @@ + + + + + CSS Test: Testing the baseline of a horizontal flex container with one flex item + + + + + + + + A +
a
+
a
+
a
+
a
+
a
+
+ +
abs
+
a
+ + +
+ + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-baseline-single-item-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-baseline-single-item-001b.htm new file mode 100644 index 0000000..2944a58 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-baseline-single-item-001b.htm @@ -0,0 +1,56 @@ + + + + + CSS Test: Testing the baseline of a vertical flex container with one flex item + + + + + + + + A +
a
+
a
+
a
+
a
+
a
+
+ +
abs
+
a
+ + +
+ + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-002a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-002a.htm new file mode 100644 index 0000000..10306aa --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-002a.htm @@ -0,0 +1,87 @@ + + + + + + CSS Test: Testing "flex-basis: content" in a column-oriented flex container + + + + + + + + + + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-002b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-002b.htm new file mode 100644 index 0000000..4790d20 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-002b.htm @@ -0,0 +1,87 @@ + + + + + + CSS Test: Testing "flex-basis: content" (set via the "flex" shorthand) + in a column-oriented flex container. + + + + + + + + + + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-003a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-003a.htm new file mode 100644 index 0000000..b0e48f2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-003a.htm @@ -0,0 +1,124 @@ + + + + + + CSS Test: Testing that explicit "flex-basis: content" is treated as + "max-content" when calculating flex base size + + + + + + + + + + + +
+
+
+ + +
+
+ + + +
+
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-003b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-003b.htm new file mode 100644 index 0000000..ad66c82 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-003b.htm @@ -0,0 +1,125 @@ + + + + + + CSS Test: Testing that used "flex-basis: content" is treated as + "max-content" when calculating flex base size + + + + + + + + + + + +
+
+
+ + +
+
+ + + +
+
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-004a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-004a.htm new file mode 100644 index 0000000..f7a36d3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-flex-basis-content-004a.htm @@ -0,0 +1,130 @@ + + + + + + CSS Test: Testing that explicit "flex-basis: content" is treated as + "max-content" when calculating flex base size + + + + + + + + + + + +
+
+
+ + +
+
+ + + +
+
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-gap-position-absolute.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-gap-position-absolute.htm new file mode 100644 index 0000000..5cae707 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-gap-position-absolute.htm @@ -0,0 +1,30 @@ + + + + + CSS Flexible Box Layout Test: Test flexbox intrinsic inline-size, gap, and absolute-positioned children + + + + + + + + + +
+ + + B + C +
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-items-as-stacking-contexts-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-items-as-stacking-contexts-001.htm new file mode 100644 index 0000000..b25d732 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-items-as-stacking-contexts-001.htm @@ -0,0 +1,116 @@ + + + + + + CSS Test: Testing that 'z-index' property makes flex items form stacking contexts + + + + + + + +
+
+
+
+ + +
+
+
+ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-mbp-horiz-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-mbp-horiz-004.htm new file mode 100644 index 0000000..c85ff59 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-mbp-horiz-004.htm @@ -0,0 +1,72 @@ + + + + + + CSS Test: Testing percent-valued padding and margin on flex items + + + + + + + +
+ + +
+ + +
+ + +
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-001.htm new file mode 100644 index 0000000..a1d5d1e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-001.htm @@ -0,0 +1,105 @@ + + + + + + CSS Test: Testing min-height:auto + + + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-003.htm new file mode 100644 index 0000000..fe03fff --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-003.htm @@ -0,0 +1,60 @@ + + + + + + CSS Test: Testing min-height:auto & 'overflow' interaction + + + + + + + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-004.htm new file mode 100644 index 0000000..231da37 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-height-auto-004.htm @@ -0,0 +1,66 @@ + + + + + + CSS Test: Testing min-height:auto & 'overflow' interaction + + + + + + + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-001.htm new file mode 100644 index 0000000..b4024df --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-001.htm @@ -0,0 +1,103 @@ + + + + + + CSS Test: Testing min-width:auto + + + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+ +
+
+
+ + + +
+
+
+ +
+
+
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-003.htm new file mode 100644 index 0000000..d7cf60f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-003.htm @@ -0,0 +1,58 @@ + + + + + + CSS Test: Testing min-width:auto & 'overflow' interaction + + + + + + + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-004.htm new file mode 100644 index 0000000..6fd481b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-min-width-auto-004.htm @@ -0,0 +1,64 @@ + + + + + + CSS Test: Testing min-width:auto & 'overflow' interaction + + + + + + + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-paint-ordering-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-paint-ordering-001.htm new file mode 100644 index 0000000..9657a9f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-paint-ordering-001.htm @@ -0,0 +1,92 @@ + + + + + + CSS Test: Testing the paint-order of overlapping flex items, with varying tweaks on the container + + + + + + + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+ + +
+
+
+
+ + +
+
+
+
+ +
+ + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-paint-ordering-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-paint-ordering-002.htm new file mode 100644 index 0000000..a8ea21a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-paint-ordering-002.htm @@ -0,0 +1,164 @@ + + + + + + CSS Test: Testing the paint-order of overlapping flex items with 'order' and 'z-index' set + + + + + + + +
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-single-line-clamp-3.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-single-line-clamp-3.htm new file mode 100644 index 0000000..064864c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-single-line-clamp-3.htm @@ -0,0 +1,42 @@ + + + + +CSS Test: Single-line flex containers should clamp their line's height to the container's computed min and max cross-size. + + + + + +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-horiz-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-horiz-001.htm new file mode 100644 index 0000000..665656b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-horiz-001.htm @@ -0,0 +1,86 @@ + + + + + + CSS Test: Testing sizing of an auto-sized horizontal flex container with min-width and max-width constraints + + + + + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-horiz-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-horiz-002.htm new file mode 100644 index 0000000..6f6f869 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-horiz-002.htm @@ -0,0 +1,63 @@ + + + + + + CSS Test: Testing sizing of an auto-sized horizontal flex container with min-height and max-height constraints + + + + + + + +
+
text
+
+ + +
+
text
+
+ + +
+
text
+
+ + +
+
text
+
+ + +
+
text
+
+ + +
+
text
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-vert-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-vert-001.htm new file mode 100644 index 0000000..e0a6542 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-vert-001.htm @@ -0,0 +1,100 @@ + + + + + + CSS Test: Testing sizing of an auto-sized vertical flex container with min-height and max-height constraints + + + + + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-vert-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-vert-002.htm new file mode 100644 index 0000000..64a451a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-sizing-vert-002.htm @@ -0,0 +1,64 @@ + + + + + + CSS Test: Testing sizing of an auto-sized vertical flex container with min-width and max-width constraints + + + + + + + +
+
AB
+
+ + +
+
AB
+
+ + +
+
AB
+
+ + +
+
AB
+
+ + +
+
AB
+
+ + +
+
AB
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-with-pseudo-elements-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-with-pseudo-elements-003.htm new file mode 100644 index 0000000..5939f15 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox-with-pseudo-elements-003.htm @@ -0,0 +1,69 @@ + + + + + CSS Test: Testing that generated content nodes with table-part display types are wrapped with an anonymous table, which forms a flex item + + + + + + + +
+ x +
y
+ z +
+
+ x +
y
+ z +
+
+ x +
y
+ z +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_fbfc.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_fbfc.htm new file mode 100644 index 0000000..3168b6f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_fbfc.htm @@ -0,0 +1,35 @@ + + + +flexbox | flex formatting context :: float intrusion + + + + + +
filler
+ +
+
Yellow box should be below the blue box
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_fbfc2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_fbfc2.htm new file mode 100644 index 0000000..389e29a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_fbfc2.htm @@ -0,0 +1,33 @@ + + + +flexbox | flex formatting context :: float intrusion + + + + + +
float
+ +
+
Yellow box yellow box yellow box
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-formatting-interop.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-formatting-interop.htm new file mode 100644 index 0000000..3fdfbd8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-formatting-interop.htm @@ -0,0 +1,41 @@ + + + +flexbox | flex formatting context :: negative margins and + border box + + + + + +
float
+ +
+
xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-natural-mixed-basis.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-natural-mixed-basis.htm new file mode 100644 index 0000000..cc902e1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-natural-mixed-basis.htm @@ -0,0 +1,39 @@ + + + +flexbox | flex: larger integer, mixed basis + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-natural-variable-zero-basis.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-natural-variable-zero-basis.htm new file mode 100644 index 0000000..df74f95 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-natural-variable-zero-basis.htm @@ -0,0 +1,39 @@ + + + +flexbox | flex: larger integer, zero basis + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-none-wrappable-content.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-none-wrappable-content.htm new file mode 100644 index 0000000..057a770 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_flex-none-wrappable-content.htm @@ -0,0 +1,26 @@ + + + +Specifying flex:none on wrappable content should give content its full width + + + + + + + +
+
+ XXX XXX XXX +
+
+ +
You should see three green rectangles above, all on the same line.
+ + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline-abspos.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline-abspos.htm new file mode 100644 index 0000000..abfa917 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline-abspos.htm @@ -0,0 +1,20 @@ + + + +flexbox | absolutely positioned inline + + + + + +
FAIL
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline-float.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline-float.htm new file mode 100644 index 0000000..2631c11 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline-float.htm @@ -0,0 +1,20 @@ + + + +flexbox | floated inline + + + + + +
FAIL
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline.htm new file mode 100644 index 0000000..2fad137 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_inline.htm @@ -0,0 +1,20 @@ + + + +flexbox | inline + + + + + +
HELLOWORLD
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_order-abspos-space-around.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_order-abspos-space-around.htm new file mode 100644 index 0000000..883cd24 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_order-abspos-space-around.htm @@ -0,0 +1,39 @@ + + + +flexbox | order; justify-content: space-around + + + + + +
+ filler + + filler + filler +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_stf-table-singleline-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_stf-table-singleline-2.htm new file mode 100644 index 0000000..fc8065b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_stf-table-singleline-2.htm @@ -0,0 +1,41 @@ + + + +flexbox | singleline flexcontainer versus stf :: table + + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_table-fixed-layout.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_table-fixed-layout.htm new file mode 100644 index 0000000..e369f73 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_table-fixed-layout.htm @@ -0,0 +1,58 @@ + + + +flexbox | flexcontainers in tables + + + + + + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_width-overflow.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_width-overflow.htm new file mode 100644 index 0000000..94175b7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--flexbox_width-overflow.htm @@ -0,0 +1,30 @@ + + + +flexbox | overflow + + + + + +
+

one two three four

+

filler

+

filler

+

filler

+

filler

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--multiline-shrink-to-fit.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--multiline-shrink-to-fit.htm new file mode 100644 index 0000000..f14c44a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--multiline-shrink-to-fit.htm @@ -0,0 +1,77 @@ + + + +CSS Flexbox: multiline column flexboxes and shrink-to-fit. + + + + + + +
+
+
+
+
+
+

The grey background should be 100px wide.

+ +
+
+
+
+
+
+

The grey background should be 100px wide.

+ +
+
+
+
+
+
+
+
+

The grey background should be 100px wide.

+ +
+
+
+
+
+
+
+
+

The grey background should be 100px wide and 5px should +stick out the bottom.

+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--percentage-heights-008.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--percentage-heights-008.htm new file mode 100644 index 0000000..50ca6eb --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--percentage-heights-008.htm @@ -0,0 +1,39 @@ + + + +Fixed indefinite heights + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--percentage-heights-010.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--percentage-heights-010.htm new file mode 100644 index 0000000..2edd869 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/--percentage-heights-010.htm @@ -0,0 +1,18 @@ + + + +A height: 100% descendant should trigger a relayout when stretching. + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-001.htm new file mode 100644 index 0000000..aa73086 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-001.htm @@ -0,0 +1,42 @@ + + + + + CSS Test: A multi-line flex container with the 'align-content' property set to 'center' + + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-002.htm new file mode 100644 index 0000000..f3775be --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-002.htm @@ -0,0 +1,41 @@ + + + + + CSS Test: A multi-line flex container with the 'align-content' property set to 'flex-start' + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-003.htm new file mode 100644 index 0000000..c18c287 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-003.htm @@ -0,0 +1,41 @@ + + + + + CSS Test: A multi-line flex container with the 'align-content' property set to 'flex-end' + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-004.htm new file mode 100644 index 0000000..3aef07d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-004.htm @@ -0,0 +1,43 @@ + + + + + CSS Test: A multi-line flex container with the 'align-content' property set to 'space-between' + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-005.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-005.htm new file mode 100644 index 0000000..dffc1ad --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-content-005.htm @@ -0,0 +1,41 @@ + + + + + CSS Test: A multi-line flex container with the 'align-content' property set to 'space-around' + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-001.htm new file mode 100644 index 0000000..6847c01 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-001.htm @@ -0,0 +1,39 @@ + + + + + CSS Test: A flex container with the 'align-items' property set to 'center' + + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-002.htm new file mode 100644 index 0000000..2027037 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-002.htm @@ -0,0 +1,40 @@ + + + + + CSS Test: A flex container with the 'align-items' property set to 'flex-start' + + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-003.htm new file mode 100644 index 0000000..40f3807 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-003.htm @@ -0,0 +1,40 @@ + + + + + CSS Test: A flex container with the 'align-items' property set to 'flex-end' + + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-004.htm new file mode 100644 index 0000000..a5e90f0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-align-items-004.htm @@ -0,0 +1,53 @@ + + + + + CSS Test: A flex container with the 'align-items' property set to 'baseline' + + + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
d1
+
d2
+
d3
+
d4
+
d5
+
d6
+
d7
+
d8
+
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-contain-layout-baseline-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-contain-layout-baseline-002.htm new file mode 100644 index 0000000..b4774a2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-contain-layout-baseline-002.htm @@ -0,0 +1,39 @@ + + + + +CSS Containment Test: Layout containment supress baseline in flex items + + + + + + +

Test passes if there is a filled green square and no red.

+
+ +
item
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-contain-layout-suppress-baseline-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-contain-layout-suppress-baseline-002.htm new file mode 100644 index 0000000..7e3b597 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-contain-layout-suppress-baseline-002.htm @@ -0,0 +1,75 @@ + + + + + + CSS Test: 'contain: layout' should make elements behave as if they have no baseline + + + + + + + +
+ + + + + + +
+ + +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-content-height-with-scrollbars.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-content-height-with-scrollbars.htm new file mode 100644 index 0000000..505d54a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-content-height-with-scrollbars.htm @@ -0,0 +1,47 @@ + +Ensure flexbox content-size excludes scrollbar. + + + + + + + +

This tests that when setting the height of a flex item to a percentage +height, we use the content height with scrollbars. The content should not be +scrollable in any of the test cases below.

+ +
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-cross-axis-scrollbar.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-cross-axis-scrollbar.htm new file mode 100644 index 0000000..1c3ed5c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-cross-axis-scrollbar.htm @@ -0,0 +1,147 @@ + + + +CSS Flexbox: Scrollbars and flex-direction. + + + + + + + + +This test passes if no red is showing. + +
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+ + + + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-box-justify-content.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-box-justify-content.htm new file mode 100644 index 0000000..5f0a96e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-box-justify-content.htm @@ -0,0 +1,34 @@ + +flexbox |css-box-justify-content + + + + + + +

This test passes if the DIV5's position in the end and the div is Horizontal layout

+
+
DIV1
+   +
DIV2
+   +
DIV3
+   +
DIV4
+   +
DIV5
+
+ \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse-wrap-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse-wrap-reverse.htm new file mode 100644 index 0000000..ad22c18 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse-wrap-reverse.htm @@ -0,0 +1,47 @@ + + + + + + CSS Flexbox Test: flex direction: row, writing mode vertical + + + + + + + + + + + + + +

Pass condition: 4 rectangles, with colors in clockwise order starting from top-left: grey, orange, blue, yellow. + +

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse-wrap.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse-wrap.htm new file mode 100644 index 0000000..b486bc4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse-wrap.htm @@ -0,0 +1,47 @@ + + + + + + CSS Flexbox Test: flex direction: row, writing mode vertical + + + + + + + + + + + + + +

Pass condition: 4 rectangles, with colors in clockwise order starting from top-left: grey, orange, blue, yellow. + +

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse.htm new file mode 100644 index 0000000..9eb2765 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-reverse.htm @@ -0,0 +1,56 @@ + + + + + + CSS Flexbox Test: flex direction: row, writing mode vertical + + + + + + + + + + + + + +

Pass condition: 4 rectangles, with colors in clockwise order starting from top-left: grey, orange, blue, yellow. +

+
+ +
+ +
+
+ +
+ +
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-wrap-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-wrap-reverse.htm new file mode 100644 index 0000000..e0e2529 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-wrap-reverse.htm @@ -0,0 +1,46 @@ + + + + + + CSS Flexbox Test: flex direction: row, writing mode vertical + + + + + + + + + + + + + +

Pass condition: 4 rectangles, with colors in clockwise order starting from top-left: grey, orange, blue, yellow. +

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-wrap.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-wrap.htm new file mode 100644 index 0000000..f2c5a5d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row-wrap.htm @@ -0,0 +1,47 @@ + + + + + + CSS Flexbox Test: flex direction: row, writing mode vertical + + + + + + + + + + + + + +

Pass condition: 4 rectangles, with colors in clockwise order starting from top-left: grey, orange, blue, yellow. + +

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row.htm new file mode 100644 index 0000000..31903c8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-row.htm @@ -0,0 +1,56 @@ + + + + + + CSS Flexbox Test: flex direction: row, writing mode vertical + + + + + + + + + + + + + +

Pass condition: 4 rectangles, with colors in clockwise order starting from top-left: grey, orange, blue, yellow. +

+
+ +
+ +
+
+ +
+ +
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-test1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-test1.htm new file mode 100644 index 0000000..e5d2057 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-css-flexbox-test1.htm @@ -0,0 +1,54 @@ + + + + + + CSS Flexbox Test: flex direction: row, writing mode vertical + + + + + + + + + + + + +

The test passes if you see a tall green box with pairs of the digits 1-9 listed top to bottom in two columns.

+ +
+
+
123
123
+
456
456
+
789
789
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-direction-upright-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-direction-upright-002.htm new file mode 100644 index 0000000..2b5a232 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-direction-upright-002.htm @@ -0,0 +1,142 @@ + + + +'text-orientation: upright' forces used 'direction' to LTR in vertical typographic modes + + + + + + + + +

Test passes if both rows of boxes are identical (coloring, order, orientation, and arrangement of contents). + + + +

+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+ + + +
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+ +
+ + + +
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+ +
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+
+ + + +
ABC +
+
A B
+
A B
+
A Bb CcDd Ee
+
+ + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-002.htm new file mode 100644 index 0000000..401e6cd --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-002.htm @@ -0,0 +1,53 @@ + + + CSS Test: The 'flex' shorthand adjusting the 'flex-shrink' sub-property + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-003.htm new file mode 100644 index 0000000..05f9426 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-003.htm @@ -0,0 +1,54 @@ + + + CSS Test: Comparing two different elements using different values for the 'flex-grow' sub-property on the 'flex' shorthand + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-004.htm new file mode 100644 index 0000000..61c5979 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-004.htm @@ -0,0 +1,54 @@ + + + CSS Test: Comparing two different elements using different values for the 'flex-shrink' sub-property on the 'flex' shorthand + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-019.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-019.htm new file mode 100644 index 0000000..e18be57 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-019.htm @@ -0,0 +1,18 @@ + + + +CSS aspect-ratio: Row flexbox main size with flex-basis:content + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-020.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-020.htm new file mode 100644 index 0000000..7102229 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-020.htm @@ -0,0 +1,18 @@ + + + +CSS aspect-ratio: Flex item main size with flex-basis:content in Column flex container + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-021.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-021.htm new file mode 100644 index 0000000..ba626ec --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-021.htm @@ -0,0 +1,18 @@ + + + +CSS aspect-ratio: Flex item main size with flex-basis:content and width in Row flex container + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-022.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-022.htm new file mode 100644 index 0000000..aff31c8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-aspect-ratio-022.htm @@ -0,0 +1,18 @@ + + + +CSS aspect-ratio: Column flexbox main size with flex-basis:content and height + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-direction-row-002-visual.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-direction-row-002-visual.htm new file mode 100644 index 0000000..83c5969 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-direction-row-002-visual.htm @@ -0,0 +1,45 @@ + + + + + CSS Test: flex-direction:row axis matches that of writing mode inline axis + + + + + + + + +

Test passes if both the lines below are identical.

+
+ ABC +
+
+ CBA +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-direction-row-vertical.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-direction-row-vertical.htm new file mode 100644 index 0000000..c643f81 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-direction-row-vertical.htm @@ -0,0 +1,51 @@ + + + + + CSS Test: flex-direction:row has the same orientation as inline axis + + + + + + + + + +

Test passes if both the two columns below are identical.

+
+
+ ABC +
+
+ CBA +
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-inline.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-inline.htm new file mode 100644 index 0000000..45d22c8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-inline.htm @@ -0,0 +1,33 @@ + + + + + CSS Flexible Box Test: display proprety - inline-flex + + + + + + + +

The test passed if you see a green block which its text is 'Success!'.

+
+ +
Success!
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-height-flex-items-022.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-height-flex-items-022.htm new file mode 100644 index 0000000..b685c30 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-height-flex-items-022.htm @@ -0,0 +1,15 @@ + +Minimum height of a replaced element with borders + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+ +
+
+
+ \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-001.htm new file mode 100644 index 0000000..ed98e4c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-001.htm @@ -0,0 +1,44 @@ + + + + + + CSS Flexible Box Test: Minimum width of flex items + + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
IT E
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-003.htm new file mode 100644 index 0000000..3bac0f0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-003.htm @@ -0,0 +1,45 @@ + + + + + + CSS Flexible Box Test: Minimum width of flex items + + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
IT E
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-009.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-009.htm new file mode 100644 index 0000000..4f2848a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flex-minimum-width-flex-items-009.htm @@ -0,0 +1,32 @@ + +CSS Flexible Box Test: Minimum width of flex items + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+ +
+ \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-abspos-child-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-abspos-child-001b.htm new file mode 100644 index 0000000..80998ab --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-abspos-child-001b.htm @@ -0,0 +1,54 @@ + + + CSS Test: Testing that "min-width", "max-width", "min-height", and "max-height" are applied on absolutely positioned children of a vertical flex container + + + + + + + +
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-abspos-child-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-abspos-child-002.htm new file mode 100644 index 0000000..43e833c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-abspos-child-002.htm @@ -0,0 +1,61 @@ + + + + + CSS Test: Test that "flex-basis" doesn't affect layout of abspos flex child + + + + + + + + + + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-baseline-horiz-006.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-baseline-horiz-006.htm new file mode 100644 index 0000000..464752f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-baseline-horiz-006.htm @@ -0,0 +1,56 @@ + + + + + + CSS Test: Baseline alignment of block flex items with 'baseline' value for 'align-items' / 'align-self' against non-parallel writing-modes. + + + + + + +
+
ortho
+
one line
+
two
lines
+
offset
+
+
+
ortho
+
one line
+
two
lines
+
offset
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-baseline-horiz-008.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-baseline-horiz-008.htm new file mode 100644 index 0000000..0dc1cb1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-baseline-horiz-008.htm @@ -0,0 +1,57 @@ + + + + + + CSS Test: Baseline alignment of block flex items with 'baseline' and 'last-baseline' values for 'align-self' against each other. + + + + + + +
+
one line (first)
+
one line (last)
+
two
lines and offset (last)
+
offset (first)
+
+
+
one line (first)
+
one line (last)
+
two
lines and offset (last)
+
offset (first)
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-horiz-001-table.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-horiz-001-table.htm new file mode 100644 index 0000000..6449e5c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-horiz-001-table.htm @@ -0,0 +1,101 @@ + + + + + + CSS Test: Testing the various 'align-self' property values on flex items that are tables + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
base
+
abc
+
stretch
+
a b c d e f
+
auto
+
unspec
+
initial
+
inherit
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-horiz-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-horiz-002.htm new file mode 100644 index 0000000..647659d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-horiz-002.htm @@ -0,0 +1,100 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' with a horizontal flex container, with margin/padding/border on the items + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
+
+
+
base
+
abc
+
stretch
+
a b c d e f
+
+
+
+
self-start
+
a b c d e f
+
self-end
+
a b c d e f
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-002.htm new file mode 100644 index 0000000..658b01b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-002.htm @@ -0,0 +1,96 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' with a vertical flex container, with margin/padding/border on the items + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
+
+
base
+
abc
+
stretch
+
a b c d e f
+
+
+
self-start
+
a b c d e f
+
self-end
+
a b c d e f
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-001.htm new file mode 100644 index 0000000..25bbe19 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-001.htm @@ -0,0 +1,97 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' property values on flex items that are blocks, in a vertical flex container with 'direction: rtl' + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
base
+
abc
+
stretch
+
a b c d e f
+
auto
+
unspec
+
initial
+
inherit
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-002.htm new file mode 100644 index 0000000..03ca3ef --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-002.htm @@ -0,0 +1,82 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' with a vertical flex container, with margin/padding/border on the items and with 'direction: rtl' + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
+
+
base
+
abc
+
stretch
+
a b c d e f
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-003.htm new file mode 100644 index 0000000..69eea46 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-003.htm @@ -0,0 +1,72 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' with a vertical flex container that's skinnier than its items and with 'direction: rtl' + + + + + + +
+
start
+
a b
+
end
+
a b
+
center
+
a b
+
base
+
abc
+
stretch
+
a b
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-004.htm new file mode 100644 index 0000000..b8c9566 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-004.htm @@ -0,0 +1,90 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' with a vertical flex container that's skinnier than its items, with margin/padding/border on the items and with 'direction: rtl' + + + + + + + +
+
start
+
a b
+
stretch
+
a b
+
center
+
a b
+
+
+
base
+
abc
+
end
+
a b
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-005.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-005.htm new file mode 100644 index 0000000..9d7600e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-align-self-vert-rtl-005.htm @@ -0,0 +1,94 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' with a vertical flex container, with margin/padding/border on the items and with 'direction:rtl' + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
+
+
base
+
abc
+
stretch
+
a b c d e f
+
+
+
self-start
+
a b c d e f
+
self-end
+
a b c d e f
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-anonymous-items-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-anonymous-items-001.htm new file mode 100644 index 0000000..b48a455 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-anonymous-items-001.htm @@ -0,0 +1,25 @@ + + + CSS Test: Testing that we gracefully handle cases where two anonymous flex items become adjacent due to "order" reordering + + + + + + + +
+ a a +
x x
+ b b +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-basic-block-horiz-001v.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-basic-block-horiz-001v.htm new file mode 100644 index 0000000..09e2905 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-basic-block-horiz-001v.htm @@ -0,0 +1,75 @@ + + + + + + + CSS Test: Testing flexbox layout algorithm property on block flex items in a horizontal flex container + (with various writing-modes on the flex items). + + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-basic-block-vert-001v.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-basic-block-vert-001v.htm new file mode 100644 index 0000000..bd3318a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-basic-block-vert-001v.htm @@ -0,0 +1,77 @@ + + + + + + + CSS Test: Testing flexbox layout algorithm property on block flex items in a vertical flex container + (with various writing-modes on the flex items). + + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-baseline-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-baseline-001.htm new file mode 100644 index 0000000..adedd02 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-baseline-001.htm @@ -0,0 +1,57 @@ + + + + + CSS Test: Testing that a collapsed flex item participates in baseline alignment only for the purpose of establishing container's cross size + + + + + + + + +
+
a
+
b
+
+ + +
+
a
+
b
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-001.htm new file mode 100644 index 0000000..6bc91e1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-001.htm @@ -0,0 +1,101 @@ + + + + + CSS Test: Testing that visibility:collapse on a flex item in a single-line flex container maintains the containers's cross size, but doesn't otherwise impact flex layout + + + + + + + + + +
+
+
+ +
+ + + +
+
+
+
+ +
+
+
+
+ +
+ + + +
+
+
+
+ +
+
+
+
+ +
+ + + +
+
+
+
+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-002.htm new file mode 100644 index 0000000..74b24f5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-002.htm @@ -0,0 +1,114 @@ + + + + + CSS Test: Testing that visibility:collapse on a flex item in a multi-line flex container creates struts, and that they can migrate between lines + + + + + + + + + +
+
+
+
+
+ +
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+ +
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-003.htm new file mode 100644 index 0000000..b151b3e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-collapsed-item-horiz-003.htm @@ -0,0 +1,59 @@ + + + + + CSS Test: Testing that strut formation (from visibility:collapse) happens *after* lines have been stretched + + + + + + + + +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-001a.htm new file mode 100644 index 0000000..ffb5c3a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-001a.htm @@ -0,0 +1,86 @@ + + + + + + CSS Test: Testing "flex-basis: content" in a row-oriented flex container + + + + + + + + + + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-001b.htm new file mode 100644 index 0000000..8b49929 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-001b.htm @@ -0,0 +1,86 @@ + + + + + + CSS Test: Testing "flex-basis: content" (set via the "flex" shorthand) + in a row-oriented flex container. + + + + + + + + + + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-004b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-004b.htm new file mode 100644 index 0000000..38b3529 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-basis-content-004b.htm @@ -0,0 +1,131 @@ + + + + + + CSS Test: Testing that used "flex-basis: content" is treated as + "max-content" when calculating flex base size + + + + + + + + + + + +
+
+
+ + +
+
+ + + +
+
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-default.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-default.htm new file mode 100644 index 0000000..170481d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-default.htm @@ -0,0 +1,39 @@ + + + CSS Flexbox Test: Flex-wrap defaults to nowrap + + + + + + + + + +

The test passes if there is a green square and no red.

+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-vert-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-vert-001.htm new file mode 100644 index 0000000..15a76cc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-vert-001.htm @@ -0,0 +1,98 @@ + + + CSS Test: Testing flex-wrap in vertical flex containers + + + + + + + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-vert-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-vert-002.htm new file mode 100644 index 0000000..fcdcba0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-vert-002.htm @@ -0,0 +1,60 @@ + + + CSS Test: Ensure that min-height is honored for vertical multi-line flex containers + + + + + + + + + +
+
+ + +
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-wrap-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-wrap-reverse.htm new file mode 100644 index 0000000..2fa0c07 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-flex-wrap-wrap-reverse.htm @@ -0,0 +1,60 @@ + + + + + CSS Flexbox Test: Flex-wrap = wrap-reverse + + + + + + + + + +

The test passes if there is a 3x3 grid of green squares, numbered 1-9 left-to-right and top-to-bottom, and there is no red.

+
+
7
8
9
+
4
5
6
+
1
2
3
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-justify-content-wmvert-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-justify-content-wmvert-001.htm new file mode 100644 index 0000000..8548613 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-justify-content-wmvert-001.htm @@ -0,0 +1,144 @@ + + + + + + CSS Test: Testing 'justify-content' in a vertical writing mode flex container + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-001-rtl-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-001-rtl-reverse.htm new file mode 100644 index 0000000..70f6d61 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-001-rtl-reverse.htm @@ -0,0 +1,75 @@ + + + + + + CSS Test: Testing borders on flex items in a row-reverse horizontal flex container, with 'direction: rtl' + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-001-rtl.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-001-rtl.htm new file mode 100644 index 0000000..dbee82b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-001-rtl.htm @@ -0,0 +1,73 @@ + + + + + + CSS Test: Testing borders on flex items in a horizontal flex container with 'direction: rtl' + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-002v.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-002v.htm new file mode 100644 index 0000000..5e30e0e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-002v.htm @@ -0,0 +1,87 @@ + + + + + + + CSS Test: Testing margins, borders, and padding on flex items in a horizontal flex container + (with a vertical writing-mode on the flex items). + + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-003v.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-003v.htm new file mode 100644 index 0000000..48eeab2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-mbp-horiz-003v.htm @@ -0,0 +1,81 @@ + + + + + + + CSS Test: Testing borders and padding on a horizontal flex container and its flex items + (with a vertical writing-mode on the flex items). + + + + + + + +
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002a.htm new file mode 100644 index 0000000..0a23667 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002a.htm @@ -0,0 +1,66 @@ + + + + CSS Test: Testing min-width:auto + + + + + + + + + + + +
+ blue square +
+ +
+ blue square +
+ +
+ blue square +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002b.htm new file mode 100644 index 0000000..e622270 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002b.htm @@ -0,0 +1,66 @@ + + + + CSS Test: Testing min-width:auto + + + + + + + + + + + +
+ blue square +
+ +
+ blue square +
+ +
+ blue square +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002c.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002c.htm new file mode 100644 index 0000000..1f19a01 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-002c.htm @@ -0,0 +1,68 @@ + + + + CSS Test: Testing min-width:auto + + + + + + + + + + + +
+ blue square +
+ +
+ blue square +
+ +
+ blue square +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-005.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-005.htm new file mode 100644 index 0000000..f4321ab --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-005.htm @@ -0,0 +1,38 @@ + +CSS Flexible Box Test: Aspect ratio handling of images + + + + + + + +

Test passes if there is no red visible on the page.

+ +
+
+ +
+ +
+ +
+
+
+ +
+
+ \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-006.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-006.htm new file mode 100644 index 0000000..6625437 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-min-width-auto-006.htm @@ -0,0 +1,45 @@ + +CSS Flexible Box Test: Aspect ratio handling of images + + + + + + + +

Test passes if there are a (vertically centered) 20x20 and a 60x100 green boxes enclosed on each 100x100 square.

+ +
+
+ +
+
+ +
+ +
+
+ +
+
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-overflow-horiz-001.htm.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-overflow-horiz-001.htm.htm new file mode 100644 index 0000000..291b337 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-overflow-horiz-001.htm.htm @@ -0,0 +1,54 @@ + + + CSS Test: Testing 'overflow' property on a horizontal flex container, with contents not overflowing + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-whitespace-handling-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-whitespace-handling-001a.htm new file mode 100644 index 0000000..f70b68d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-whitespace-handling-001a.htm @@ -0,0 +1,54 @@ + + + + CSS Test: Test that anonymous flex items aren't created for pure-whitespace inline content + + + + + + + +
+ + +
+ + +
+
+ + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-whitespace-handling-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-whitespace-handling-001b.htm new file mode 100644 index 0000000..59adb90 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-whitespace-handling-001b.htm @@ -0,0 +1,42 @@ + + + + CSS Test: Test that flex items are created correctly + + + + + + +
+ +
+ +
+ + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-001.htm new file mode 100644 index 0000000..466b0b7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-001.htm @@ -0,0 +1,81 @@ + + + + + CSS Test: Try various flex-flow values, with 'direction: ltr' and 'writing-mode: horizontal-tb' + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-002.htm new file mode 100644 index 0000000..34b11ec --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-002.htm @@ -0,0 +1,81 @@ + + + + + CSS Test: Try various flex-flow values, with 'direction: ltr' and 'writing-mode: vertical-rl' + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-003.htm new file mode 100644 index 0000000..af3f20a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-003.htm @@ -0,0 +1,81 @@ + + + + + CSS Test: Try various flex-flow values, with 'direction: ltr' and 'writing-mode: vertical-lr' + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-004.htm new file mode 100644 index 0000000..7332a2a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-004.htm @@ -0,0 +1,81 @@ + + + + + CSS Test: Try various flex-flow values, with 'direction: rtl' and 'writing-mode: horizontal-tb' + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-005.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-005.htm new file mode 100644 index 0000000..65ee1e3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-005.htm @@ -0,0 +1,81 @@ + + + + + CSS Test: Try various flex-flow values, with 'direction: rtl' and 'writing-mode: vertical-rl' + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-006.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-006.htm new file mode 100644 index 0000000..0389a15 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-006.htm @@ -0,0 +1,81 @@ + + + + + CSS Test: Try various flex-flow values, with 'direction: rtl' and 'writing-mode: vertical-lr' + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-007.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-007.htm new file mode 100644 index 0000000..11e44f2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-007.htm @@ -0,0 +1,78 @@ + + + + + CSS Test: Verify that explicit sizes are honored on flex items whose writing-mode may differ from the flex container's writing-mode + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-008.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-008.htm new file mode 100644 index 0000000..f32db5b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-008.htm @@ -0,0 +1,78 @@ + + + + + CSS Test: Verify that explicit sizes are honored on flex items whose writing-mode may differ from the flex container's writing-mode + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-009.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-009.htm new file mode 100644 index 0000000..6145779 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-009.htm @@ -0,0 +1,78 @@ + + + + + CSS Test: Verify that explicit sizes are honored on flex items whose writing-mode may differ from the flex container's writing-mode + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-010.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-010.htm new file mode 100644 index 0000000..ca1f38d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-010.htm @@ -0,0 +1,87 @@ + + + + + + CSS Test: Testing a mix of flex items with various values for + 'writing-mode' / 'direction' in a horizontal row-oriented flex container. + + + + + + + + + + + +
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-011.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-011.htm new file mode 100644 index 0000000..971f54a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-011.htm @@ -0,0 +1,88 @@ + + + + + + CSS Test: Testing a mix of flex items with various values for + 'writing-mode' / 'direction' in a vertical row-oriented flex container. + + + + + + + + + + + +
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-012.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-012.htm new file mode 100644 index 0000000..0487847 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-012.htm @@ -0,0 +1,89 @@ + + + + + + CSS Test: Testing a mix of flex items with various values for + 'writing-mode' / 'direction' in a vertical row-oriented flex container + with 'direction' flipped. + + + + + + + + + + + +
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-013.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-013.htm new file mode 100644 index 0000000..b3b7f99 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-013.htm @@ -0,0 +1,88 @@ + + + + + + CSS Test: Testing a mix of flex items with various values for + 'writing-mode' / 'direction' in a horizontal column-oriented flex container. + + + + + + + + + + + +
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-014.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-014.htm new file mode 100644 index 0000000..3a8755b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-014.htm @@ -0,0 +1,87 @@ + + + + + + CSS Test: Testing a mix of flex items with various values for + 'writing-mode' / 'direction' in a vertical column-oriented flex container. + + + + + + + + + + + +
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-015.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-015.htm new file mode 100644 index 0000000..a01975a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-015.htm @@ -0,0 +1,88 @@ + + + + + + CSS Test: Testing a mix of flex items with various values for + 'writing-mode' / 'direction' in a vertical column-oriented flex container + with 'direction' flipped. + + + + + + + + + + + +
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+
+ p b c + p e + p b c + p e + p b c + p e +
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-016.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-016.htm new file mode 100644 index 0000000..afbcb55 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox-writing-mode-016.htm @@ -0,0 +1,147 @@ + + + + + + CSS Test: Testing auto-sized flex containers + with various 'writing-mode' values + and various padding amounts on flex items. + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_align-items-stretch-writing-modes.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_align-items-stretch-writing-modes.htm new file mode 100644 index 0000000..dd019ad --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_align-items-stretch-writing-modes.htm @@ -0,0 +1,62 @@ + + + + + CSS Test: Flexbox align-items: stretch with writing-mode vertical-lr and vertical-rl + + + + + + + + + +

The test passes if you see a green rectangle and no red.

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_block.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_block.htm new file mode 100644 index 0000000..afa26b4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_block.htm @@ -0,0 +1,19 @@ + + + +flexbox | block + + + + + +
FAIL
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_columns-flexitems-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_columns-flexitems-2.htm new file mode 100644 index 0000000..101188a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_columns-flexitems-2.htm @@ -0,0 +1,32 @@ + +flexbox | multicol on flexbox items + + + + + + + +
+

+ one two three four five + one two three four five + one two three four five +

+
+ \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_columns-flexitems.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_columns-flexitems.htm new file mode 100644 index 0000000..22665e2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_columns-flexitems.htm @@ -0,0 +1,30 @@ + + + +flexbox | multicol on flexbox items + + + + + +
+

one two three four five

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan-overflow-automatic.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan-overflow-automatic.htm new file mode 100644 index 0000000..6209c6a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan-overflow-automatic.htm @@ -0,0 +1,69 @@ + + + +flexbox | flexcontainers in cells with rowspan + + + + + + + + + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan-overflow.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan-overflow.htm new file mode 100644 index 0000000..281ba4e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan-overflow.htm @@ -0,0 +1,68 @@ + + + +flexbox | flexcontainers in cells with rowspan + + + + + + + + + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan.htm new file mode 100644 index 0000000..f99fd37 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rowspan.htm @@ -0,0 +1,68 @@ + + + +flexbox | flexcontainers in cells with rowspan + + + + + + + + + + + + + +
+
+

 

+

 

+

 

+

 

+

 

+
+
+
+

 

+

 

+

 

+

 

+

 

+
+
+
+

 

+

 

+

 

+

 

+

 

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-direction.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-direction.htm new file mode 100644 index 0000000..91c501a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-direction.htm @@ -0,0 +1,38 @@ + + + +flexbox | flex-direction: column-reverse | rtl + + + + + + +
+ filler + filler + filler + filler +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-flow-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-flow-reverse.htm new file mode 100644 index 0000000..67828d5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-flow-reverse.htm @@ -0,0 +1,38 @@ + + + +flexbox | flex-flow: column wrap-reverse | rtl + + + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-flow.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-flow.htm new file mode 100644 index 0000000..d4d122e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-flow.htm @@ -0,0 +1,38 @@ + + + +flexbox | flex-flow: column wrap | rtl + + + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-order.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-order.htm new file mode 100644 index 0000000..c4581db --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_rtl-order.htm @@ -0,0 +1,57 @@ + + + +flexbox | flex-flow: column-reverse wrap-reverse; order | rtl + + + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_stf-table-singleline.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_stf-table-singleline.htm new file mode 100644 index 0000000..8f5db70 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_stf-table-singleline.htm @@ -0,0 +1,38 @@ + + + +flexbox | singleline flexcontainer versus stf :: table + + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_writing_mode_vertical_lays_out_contents_from_top_to_bottom.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_writing_mode_vertical_lays_out_contents_from_top_to_bottom.htm new file mode 100644 index 0000000..1b1fc84 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-flexbox_writing_mode_vertical_lays_out_contents_from_top_to_bottom.htm @@ -0,0 +1,64 @@ + + + + + + CSS Flexbox Test: Align content flex-start with writing mode vertical-rl. + + + + + + + +

The test passes if you see green and red top, blue and yellow bottom.

+ +
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-001.htm new file mode 100644 index 0000000..1115dc7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-001.htm @@ -0,0 +1,41 @@ + + + + + CSS Test: A flex container with 'justify-content' property set to 'center' + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-002.htm new file mode 100644 index 0000000..d6ff8ae --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-002.htm @@ -0,0 +1,38 @@ + + + + + CSS Test: A flex container with the 'justify-content' property set to 'flex-start' + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-003.htm new file mode 100644 index 0000000..b1a83a9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-003.htm @@ -0,0 +1,37 @@ + + + + + CSS Test: A flex container with the 'justify-content' property set to 'flex-end' + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-004.htm new file mode 100644 index 0000000..fcab4fb --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-004.htm @@ -0,0 +1,41 @@ + + + + + CSS Test: A flex container with the 'justify-content' property set to 'space-between' + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-005.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-005.htm new file mode 100644 index 0000000..f9748df --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-justify-content-005.htm @@ -0,0 +1,41 @@ + + + + + CSS Test: A flex container with the 'justify-content' property set to 'space-around' + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-negative-flex-margins-crash.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-negative-flex-margins-crash.htm new file mode 100644 index 0000000..c6cdcfe --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-negative-flex-margins-crash.htm @@ -0,0 +1,26 @@ + + + +CSS Flexbox: Crash caused by negative width in flex box + + + + + + +
+
+
PASS if we don't assert
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-nested-orthogonal-flexbox-relayout.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-nested-orthogonal-flexbox-relayout.htm new file mode 100644 index 0000000..fb5f085 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-nested-orthogonal-flexbox-relayout.htm @@ -0,0 +1,34 @@ + +CSS Flexbox: nested orthogonal children on relayout. + + + + + +
+
+
This text should not overflow its box
+
+
+ + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-percentage-size-subitems-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-percentage-size-subitems-001.htm new file mode 100644 index 0000000..3d610b9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-percentage-size-subitems-001.htm @@ -0,0 +1,98 @@ + + + + +CSS Flexbox Test: Percentage size on child of a flex item with margin, border, padding and scrollbar + + + + + + +

The test passes if in the different examples you see scrollbars but there's no overflow, so you cannot actually scroll.

+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-fixed-min-width-3.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-fixed-min-width-3.htm new file mode 100644 index 0000000..2dc259d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-fixed-min-width-3.htm @@ -0,0 +1,25 @@ + + + +table is flex item + + + + + + +

Test passes if there is a filled green square and no red.

+ + + +
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-flex-cross-size.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-flex-cross-size.htm new file mode 100644 index 0000000..12118e1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-flex-cross-size.htm @@ -0,0 +1,17 @@ + + + + + + +

Test passes if there is a filled green square and no red.

+
+ + + + +
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-column-1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-column-1.htm new file mode 100644 index 0000000..c525aa0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-column-1.htm @@ -0,0 +1,19 @@ + + + +Table as a flex item in column-oriented flex container + + + + + + + + +

Test passes if there is a filled green square.

+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-column-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-column-2.htm new file mode 100644 index 0000000..97f0caa --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-column-2.htm @@ -0,0 +1,22 @@ + + + +Table as a flex item in column-oriented flex container + + + + + + + + +

Test passes if there is a filled green square.

+
+ + +
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-row-1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-row-1.htm new file mode 100644 index 0000000..b1b7574 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-row-1.htm @@ -0,0 +1,19 @@ + + + +Table as a flex item in row-oriented flex container + + + + + + + + +

Test passes if there is a filled green square.

+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-row-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-row-2.htm new file mode 100644 index 0000000..a4e391d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-inflexible-in-row-2.htm @@ -0,0 +1,22 @@ + + + +Table as a flex item in row-oriented flex container + + + + + + + + +

Test passes if there is a filled green square.

+
+ + +
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-narrow-content-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-narrow-content-2.htm new file mode 100644 index 0000000..c6ec740 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-narrow-content-2.htm @@ -0,0 +1,19 @@ + + + +CSS Flexbox Test: Flex item as table with narrow content + + + + + +

Test passes if there is a filled green square.

+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-percent-width-cell-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-percent-width-cell-001.htm new file mode 100644 index 0000000..434aebe --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-percent-width-cell-001.htm @@ -0,0 +1,41 @@ + + + + +Flexbox Test: display:table flex items with percent-width cells and content keywords for used flex-basis + + + + + + +
+ +
12
+
+
+
12
+
+
+
12
+
+
+
12
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-specified-height.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-specified-height.htm new file mode 100644 index 0000000..d90ed78 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-specified-height.htm @@ -0,0 +1,20 @@ + + + +table is flex item + + + + + + + + +

Test passes if there is a filled green square.

+ +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-specified-width.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-specified-width.htm new file mode 100644 index 0000000..df4b62c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-specified-width.htm @@ -0,0 +1,21 @@ + + + +table is flex item + + + + + + +

Test passes if there is a filled green square.

+ + + +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size-2.htm new file mode 100644 index 0000000..303ae80 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size-2.htm @@ -0,0 +1,18 @@ + + + + + + + +

Test passes if there is a filled green square and no red.

+
+ + + + +
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size-5.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size-5.htm new file mode 100644 index 0000000..e153a33 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size-5.htm @@ -0,0 +1,43 @@ + + + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size.htm new file mode 100644 index 0000000..af582df --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/-table-as-item-stretch-cross-size.htm @@ -0,0 +1,17 @@ + + + + + + +

Test passes if there is a filled green square and no red.

+
+ + + + +
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-ltr.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-ltr.htm new file mode 100644 index 0000000..bdfbe2c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-ltr.htm @@ -0,0 +1,39 @@ + + + +Absolutely positioned child with auto position in vertical-rl ltr flexbox + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-ltr.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-ltr.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..21d20656fa6af26397cfb4bc1e73585347999744 GIT binary patch literal 583 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKS2HmK$;XWXwLpq9z$e7@|Ns9$CPM>*>m@@m z1_s8To-U3d6^w7EX7@c-5MVpWf1lyqWMu=Pm?Hn3e>WsotNp%^F}EfvG;))Nd4)#C zR}qkIp#EOVzk6#s@4wsr@MB$vjoah0_%|C9|2~#qvH#~+{gZEPg+JaXo_N&s{m1Bd zm1Osc#clyhzM3t2TV5uAGdchE8{1^LzZ+(_+q_$RbNY^nb8fahpMCaAq4eH7ZJTGt zf25bztcl;}v*fy5`km~J6K%iQO$mEjT6HdcUHY!6)1KGNeSW>L=hUu6ITtIQubJL) zBhyY)l6~Tiwf;(vCwoZmJd~dEHC17gH9qe|-^p`quQABb&bE#Gd{f7xJ_BMa)JetB5HPB1_LpP49g3 z<-K0b)p{W&-#=1+Y5Vcdec_#b OATdu@KbLh*2~7YJZu&d` literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-rtl.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-rtl.htm new file mode 100644 index 0000000..f3a5af1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-rtl.htm @@ -0,0 +1,39 @@ + + + +Absolutely positioned child with auto position in vertical-rl ltr flexbox + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-rtl.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-htb-rtl.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..21d20656fa6af26397cfb4bc1e73585347999744 GIT binary patch literal 583 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKS2HmK$;XWXwLpq9z$e7@|Ns9$CPM>*>m@@m z1_s8To-U3d6^w7EX7@c-5MVpWf1lyqWMu=Pm?Hn3e>WsotNp%^F}EfvG;))Nd4)#C zR}qkIp#EOVzk6#s@4wsr@MB$vjoah0_%|C9|2~#qvH#~+{gZEPg+JaXo_N&s{m1Bd zm1Osc#clyhzM3t2TV5uAGdchE8{1^LzZ+(_+q_$RbNY^nb8fahpMCaAq4eH7ZJTGt zf25bztcl;}v*fy5`km~J6K%iQO$mEjT6HdcUHY!6)1KGNeSW>L=hUu6ITtIQubJL) zBhyY)l6~Tiwf;(vCwoZmJd~dEHC17gH9qe|-^p`quQABb&bE#Gd{f7xJ_BMa)JetB5HPB1_LpP49g3 z<-K0b)p{W&-#=1+Y5Vcdec_#b OATdu@KbLh*2~7YJZu&d` literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-ltr.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-ltr.htm new file mode 100644 index 0000000..bcb71d9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-ltr.htm @@ -0,0 +1,39 @@ + + + +Absolutely positioned child with auto position in vertical-rl ltr flexbox + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-ltr.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-ltr.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..21d20656fa6af26397cfb4bc1e73585347999744 GIT binary patch literal 583 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKS2HmK$;XWXwLpq9z$e7@|Ns9$CPM>*>m@@m z1_s8To-U3d6^w7EX7@c-5MVpWf1lyqWMu=Pm?Hn3e>WsotNp%^F}EfvG;))Nd4)#C zR}qkIp#EOVzk6#s@4wsr@MB$vjoah0_%|C9|2~#qvH#~+{gZEPg+JaXo_N&s{m1Bd zm1Osc#clyhzM3t2TV5uAGdchE8{1^LzZ+(_+q_$RbNY^nb8fahpMCaAq4eH7ZJTGt zf25bztcl;}v*fy5`km~J6K%iQO$mEjT6HdcUHY!6)1KGNeSW>L=hUu6ITtIQubJL) zBhyY)l6~Tiwf;(vCwoZmJd~dEHC17gH9qe|-^p`quQABb&bE#Gd{f7xJ_BMa)JetB5HPB1_LpP49g3 z<-K0b)p{W&-#=1+Y5Vcdec_#b OATdu@KbLh*2~7YJZu&d` literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-rtl.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-rtl.htm new file mode 100644 index 0000000..b369dad --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-rtl.htm @@ -0,0 +1,39 @@ + + + +Absolutely positioned child with auto position in vertical-rl ltr flexbox + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-rtl.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vlr-rtl.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..21d20656fa6af26397cfb4bc1e73585347999744 GIT binary patch literal 583 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKS2HmK$;XWXwLpq9z$e7@|Ns9$CPM>*>m@@m z1_s8To-U3d6^w7EX7@c-5MVpWf1lyqWMu=Pm?Hn3e>WsotNp%^F}EfvG;))Nd4)#C zR}qkIp#EOVzk6#s@4wsr@MB$vjoah0_%|C9|2~#qvH#~+{gZEPg+JaXo_N&s{m1Bd zm1Osc#clyhzM3t2TV5uAGdchE8{1^LzZ+(_+q_$RbNY^nb8fahpMCaAq4eH7ZJTGt zf25bztcl;}v*fy5`km~J6K%iQO$mEjT6HdcUHY!6)1KGNeSW>L=hUu6ITtIQubJL) zBhyY)l6~Tiwf;(vCwoZmJd~dEHC17gH9qe|-^p`quQABb&bE#Gd{f7xJ_BMa)JetB5HPB1_LpP49g3 z<-K0b)p{W&-#=1+Y5Vcdec_#b OATdu@KbLh*2~7YJZu&d` literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-ltr.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-ltr.htm new file mode 100644 index 0000000..8987cea --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-ltr.htm @@ -0,0 +1,39 @@ + + + +Absolutely positioned child with auto position in vertical-rl ltr flexbox + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-ltr.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-ltr.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..21d20656fa6af26397cfb4bc1e73585347999744 GIT binary patch literal 583 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKS2HmK$;XWXwLpq9z$e7@|Ns9$CPM>*>m@@m z1_s8To-U3d6^w7EX7@c-5MVpWf1lyqWMu=Pm?Hn3e>WsotNp%^F}EfvG;))Nd4)#C zR}qkIp#EOVzk6#s@4wsr@MB$vjoah0_%|C9|2~#qvH#~+{gZEPg+JaXo_N&s{m1Bd zm1Osc#clyhzM3t2TV5uAGdchE8{1^LzZ+(_+q_$RbNY^nb8fahpMCaAq4eH7ZJTGt zf25bztcl;}v*fy5`km~J6K%iQO$mEjT6HdcUHY!6)1KGNeSW>L=hUu6ITtIQubJL) zBhyY)l6~Tiwf;(vCwoZmJd~dEHC17gH9qe|-^p`quQABb&bE#Gd{f7xJ_BMa)JetB5HPB1_LpP49g3 z<-K0b)p{W&-#=1+Y5Vcdec_#b OATdu@KbLh*2~7YJZu&d` literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-rtl.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-rtl.htm new file mode 100644 index 0000000..cb46539 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-rtl.htm @@ -0,0 +1,39 @@ + + + +Absolutely positioned child with auto position in vertical-rl ltr flexbox + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-rtl.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/abspos-autopos-vrl-rtl.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..21d20656fa6af26397cfb4bc1e73585347999744 GIT binary patch literal 583 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKS2HmK$;XWXwLpq9z$e7@|Ns9$CPM>*>m@@m z1_s8To-U3d6^w7EX7@c-5MVpWf1lyqWMu=Pm?Hn3e>WsotNp%^F}EfvG;))Nd4)#C zR}qkIp#EOVzk6#s@4wsr@MB$vjoah0_%|C9|2~#qvH#~+{gZEPg+JaXo_N&s{m1Bd zm1Osc#clyhzM3t2TV5uAGdchE8{1^LzZ+(_+q_$RbNY^nb8fahpMCaAq4eH7ZJTGt zf25bztcl;}v*fy5`km~J6K%iQO$mEjT6HdcUHY!6)1KGNeSW>L=hUu6ITtIQubJL) zBhyY)l6~Tiwf;(vCwoZmJd~dEHC17gH9qe|-^p`quQABb&bE#Gd{f7xJ_BMa)JetB5HPB1_LpP49g3 z<-K0b)p{W&-#=1+Y5Vcdec_#b OATdu@KbLh*2~7YJZu&d` literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-baseline.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-baseline.htm new file mode 100644 index 0000000..85db4d7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-baseline.htm @@ -0,0 +1,30 @@ + + + + + + + + + + + +
+

This text

+

should be left aligned.

+
+ +
+

This text

+

should be right aligned.

+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-baseline.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-baseline.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..360c0ad1798ceee58919d4a8e0e19bfecdfe057c GIT binary patch literal 675 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKzc2y`25+DGLKZc z=!pp^Y6D{WV{8hI3N zcMhJgP|(>aCRhHn^H)Qal{1#4$E0y(%J4e`3Ap@{t=!M0#N1xJ%Y-1wki{T&1Zo)1nwdr zBS_rP5l1gfVC4bh0 z?)3Pl^r+wA6+co?z+AB3+DbHIYpQwv^PgKzWbGIFB=g*T*SEao(}EYwy)|K17+dDM zvJ5UGlO&Ih)o+t4SJ<_wh)({}bJ|C-_c5afSChwn<@e!^DG%Pv+IjR0=^=8S85A!t z$3Pe-_BTl^D%3PiH8QtLxu^Ga;-9XR$d-8=SKa4)va$}Ex3Aw%^V3Z6_pg-Pvgev+ z`sQBy<>vjY)!*qvsq-9`N99kP(ws{i^`=ftbw8t>)aO_f>XP~X(X?1KtG{k~pB}}= sKAFD1D)L + + + + CSS Test: A multi-line flex container with the 'align-content' property set to 'stretch' + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d9e99107844d149e504cf70cdb7a0f59ec648c GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*GqL_5I9y?^MS2GzJJ5aM(=M^?)Fd3nCx>bGx%8h|LK`ctA*+q zGnHBdoH!JLL{-++-+w~pUt75%ul>|v)t%wXuU@LLsNRzukyNo>;q&1M_neolc3SkU z%%d_tj%m}4pjGw7rd$6!eQ!6zG;7tpJwAwSrs{X+ zyuH4C&Yq|G_4Ybzb9TOpiFVlK^>$ZnRA1^aJ{fF-!JR|02^gw;CJSNw+>-O0Jn9cs3}9 z`}=*HG(82)eB()VXHJxftUc}g>?_Zc%*X%M$DaN^=SIKR8}nP0X~HR=Shvhrx-EO_ zu0Gb6)w5T96yF_IJ?mEN#l>Z76(`uvk7rtY+_2wj!ev{&U*#U)VBt`Fa{lM9m<9$$ pCKe6>1qZ4!P8 + + + + CSS Flexible Box Test: align-content_center + + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a vertical column in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in middle left of red rectangle.

+
1
2
3
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_center.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_center.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e278bf455a20b38c99bff62ba8a4e35158e23a92 GIT binary patch literal 1828 zcmb_dc~H`M6vvG<#WXxJJ4`6A*2>jNGfNZHAQg$SG(!x@Br-Q|+7-+Ec_a#@nQb0h zTCR=aYF?B|2dU+4x`>jLVumTYUYLk1IkPkEU%S)pAMd@-eCEx3KHquseJ>e{Ij9L) z4*`KdnrM_W4g^{O1A$av;1x=y{wX9>S#+@OM_m*Og%T6&DiAg(q^K{#*|m=ytY@yylQKt?$U(A!!R-a~_Y(kw9S z+Wmm}B7=1z-(2)uu;$rL5i%awetK(#NlCc~H+)Y}Drcu7Xtu>Df2^8;p3^ESdpALV{fNW(VG*UBzI zrPrZSW%hy>o}oH5h5n&&K%3ae#1aQR-BROkL=K-=;5gJDcf;KrSvZo`&XYFR%^#nU zLoxjkV;y<9QJ>z-)8F4k_EJ)-H#}@@B-|oIaf;K$(|sYbf|i~JBc8WAT4;xS#trWr zi@0c8{W`)Yz7o0JkS+9v4k`!nqFVjS}Lo&?xA%WHB<7mwhaztDW6YE`4*j z#87wcVvelqCe4^s?zUp}=R-Zzk+hvmM#w+tm3bz+!L(7a@tD{HWo2|4bMHmW?z}Y8D0q&0@Q7B(u zxrc79;UvChHj`t_rOUIJ;@PFe9U7g{Tk~k{j~n_CGfVfG5gsSitx`yoMLb-L^;0JY z5bTg?z*zf4KEle?ex+fA#Yb9zdt^$CSA1|MUI8Z@6_vbrBbb3-rD=w3ts4b)S@r1=@fhz*?6dwEFRQ zOg#gz^tg&}urp!TSC}ItBFvXtLAfNFq{9>GwK*e<%ZMkxK)*K!uY#yt`8qhn)7P8Q z()&ggxOB=;4X9-to?H!p{B6|$(39)mDp@D^^0)uo@E1&&g(Kjl(-aK|b>&wJ|D)d_ z&USN=;qu#_n?toB)SSS#Le8Kggt~57^n+F?gqr8*(xe#gDIGSEs9kTq_ Q%3B9QyI`DI2>-Z00h3HHkN^Mx literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-end.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-end.htm new file mode 100644 index 0000000..4b55b24 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-end.htm @@ -0,0 +1,31 @@ + + + + + CSS Flexible Box Test: align-content_flex-end + + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a vertical column in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in bottom left of red rectangle.

+
1
2
3
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-end.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-end.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4da94059eef6cbcde6fa6a62a307ff3dc0cd1740 GIT binary patch literal 1810 zcmchXc}$Z@7{)&ik#a}@k1BE$K?GEy$kDAhWDcEw0l*6)sa(qfk zg|Z;Z+JFMeLbX)Ox}Y@Wia7pke|5N+zl*>kjv&26;W{d3pG{C=`lyxgM$Op-`YB#_?n2 zbO2D}xH+SJ6P0FYdAHtMLDkNvd1%;N_ot^uWkK&M$#FeGadZObVUQa9{M}b`>r4DO z(#G=5i}*QP;19}QVcX;}kf`WxjVIX6@NWla2v+7EI>9nKLU$M zw$KZ96%ICx9(uShgxk5pg<`Kb&A|q!C+Q} z`UqTy-gGV>!(*_msmO^lm3apuENKU3>pQEg&LxS2;*-?X%REaGcW6@&XIwxUw1`IVrKICUKA}-ZjA~|M??%CfQ^%L`Sqx0PJuk>9gi--YN^p$?b)Zt1N%4yd`8pyJg zWjCe~S2Os@?_ma((+waCa>#hfK4BYS6oRm5UCnw!6tSg4h5Gf?Sa~&wz}IDZba$15 z7ow-5ZQs^tat)$VCTt+2m}U=NxI8h1p4lfMAC;F<3Klc_#2idNf@I!GyxlEH$eeIb zG7l?W;Tz5e@g&P4tFYL2G34sj+xo*FJST&&L_7ZvD5>nu0G^%a~r{j1NiXju_N**1cMm2$$Nk>SrVmz~snW0&-GiW?RMc zOlwX+S4b|Hfdo63x6 zO%(W}rfgrlUF6`cUd(FKsIokf)rcryU_K^?G7Pm6_r}`=!_BU_@cOql2V@s?dEY3e z*a>jLDX)+HingjicehdH7Av}d5|^XL-a~$0M3J=W)J|q-^4HQ<=t) z;{op2Z|%}00$dV4L)MZxZpIhLOHT^#MXvJs$@lxtX3t(EhDNCue;C&9A!rW}{6jnJ zDL;3NRRrD?HoZkU7+{R#y9Yd7Q>*Hzu`y-2cA9-n$U5YZF708J%$@GOkxA6c+Xp z@|dCiTge4^v_a1#M`F2Hj9cot2c7RSAClJi>5?tt|NI>QAg#PT)j}3q+N?&+PKO?X znyO)1Ph`W?byeVhywv~Clw8!IUi(V%T*ZkeCTXml$_CcbOq>DlqQ3_cW$nbEy2_71 z=XZWPIQJ&l{7LWxvIh;_^ai#oR>^Q*x~;v!cT0fK?)7Ko7j0o)t5fB0*IbiB8LjGi zd;VE0pmf8sHo8XiI&w{>xtrFwC@gqkW7dM>M8=H1*$3S)QAQ+E+Zi-xrmNRa9D$;X zO4Uqxw|h^h29Pxm+uER6FR-dZ-?H1kfc&M@5ogNVf+ncuu+8u6ME9p?uC6R3W$a%^ zAvvfHS!^6;aknkqWO%>XNf3r67};&O9iLiCirqotK#mDgHAnYr>Awmk09e8+lv-MH UdDhNOqK#YU=HlsGi#eb1J8R4zF8}}l literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-start.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-start.htm new file mode 100644 index 0000000..eaf4ff0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-start.htm @@ -0,0 +1,32 @@ + + + + + CSS Flexible Box Test: align-content_flex-start + + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a vertical column in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in upper left of red rectangle.

+
+
1
2
3
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-start.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_flex-start.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f0368ef0a90870588c577dc822bebf4bb22f717e GIT binary patch literal 1828 zcmb_ddo+}J82&_zDeV}u(nfrYSeGn9!$ZbO-$&i^@lo9D-m!v35 zF{LRvLUqKLq|I0pGbxvGsWEc~Yg}iR-LvPkd)hs_XZN4q`<&-J&-*^_AMg9ib$4@C zfqwx906+!rg7X9bc^v?d(@~ILWm=xV6ITP$o#5>#lgUGMPj4&=Kt6WB^cn zfyX&`pO6z=JYC2$L^x(|oG>!@#%)OVq0zWDTkg6Cc@H^)wlKItCmwasi8^sTRTYAP z6vRhQiyiW{v6|T~ZPKuB$I9_zkqJkr%=hK37QWQLN#}S3*L zXuI$G>UNSTG!^bt(V1obbD~G(5w2MS_kts+9j1f6Qfvv1?!|!^Q-9EZ-@c$5S0E`c zN%5xYjvn^(?esuRhYI0CTGQau_Av&Eb)M+|z?-jcZ#EU6M_rCCE__^$_{wnAArsXt zI*e(wA5av(F)wv#Fu7-*H&!=1JK}er(PwLaERRPskOvq;<;y)vqg4j z6N1f)K6X<8=BJtMn$%)EYfL!e4RtLoMb~iIh_I#DB;W8K5R%BQrVn--ejh2K4rxdGlP*~V6GO5ogc)$9N#jL#3}k-OyS8#7EjXWd0qOb74tJ*45X+KX0dK6|T{5T)jy8GlT``^1IP$zuE2^V}^~_RI)y;>VY?_Qk zke_r9JK+Snb+EfJHfI?lon7%SVXt=gw1H~mPTGRcGzB5CgCghMpjZmhldv3JQ+S8H zgL6YF8i~=@KX^7dJMgAxQlq*~JzTwK0m|UgXFHRkOSJCgvV+fTEt=b;TyoyVGZuTC z%c?Hh)>t39B4#{lZD>}rA*J|?#Vl$q7OT~F^tPqy$Gg-tmHQWS7W>p&?=(QRRnJ;B zXH*LfDD!ww8p$iziNRE<1bY&{CYatbAUH&-&g&pec)!mvBlNk@jsUY!bbRma zi}`fLaa__V|0t8HlTqwIOnqb~rO2wQ z`CAV%v9`Ag9U3ulqT(ruA1|2vVM9g1XVA_X<1o0t{3o)X*8FQ$9HT(J zeHvQ3708RvQed=aTo=pet3Exyd0+A-54-yv3n!W%K;PVp^7o8hVUq zqtI`#Cgo^aIv2!kM>`d0IE2Mpt+8E)Ltk{MMsR-@tNxZ$$o)^C#h$V9l7skPYW+hi e3IOn~nIvZ% + + + + CSS Flexible Box Test: align-content_space-around + + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a vertical column in a red rectangle.
+ 2. the rectangle 1, 2, 3 are distributed such that the empty space between any two adjacent rectangle is the same, and the empty space of the column before the first and after the last rectangle are half the size of the other empty spaces.

+
1
2
3
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-around.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-around.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..704ecfefd06b38f401c834079ae704035863a321 GIT binary patch literal 2800 zcmb_ec~sL^7XBqch)5uXRtf?6v6mJ^5ea2WfFNPVK&uomZGu1qWB`MpB!SQ(3Q9^u zwgRFoB3qRu?AR6&DNB(p5Fw*5ARt>p*g{}%dS=eF)A^%k=A7@I_nmv+x$l1Wz4P9? z?&`O$PB>fd7|7cUMEY8V>^D>L0f#Qja3&?oi(XQRommA-`M;c)^%l8$<5o`lfpF%3 zV0+8F~+hkm0in~2J!=6J~3KSVQKT8Gq#=aX{WatAzzi~OEF2mA5@>w(Y zp+2s24^PTsZ7Q0vD@k#93c1*@IQ+GL3E`oZ6GPoN5fBX8IYjCK5qdzvewpqD9im*l zWk(D);*B;E(LNU{cQphZ(J49WzNIGDClH?=a04w-xWKy-x;AUlCJw;I!0|CK*2>2$ zZS}B`zx77RbsTjq>zFRMqv~v_UgWm8mo+<_J|xv{-96UUm)W*caETsS%BL=x_N^E8 z^qQ%r9Q}$nA3h(|tADP0c?_+iTap{dQ}f-LsSo|}aI(|Ai%avR5YkLFr%XLaIUuZi z{HDSIMfteN-hKq@!JinWXPvcI#hul@Pm^Bf!9L5n3*0?Fo8mBt7C#vX3|n zo3xm?Lsv^);vHovPQ0HyrsF%c41MJo3k&cUccUWZ0@C>&XN-K=UWthZXO8#rU%HdO zAjjTX-=I#!oxu!rf0uT^3z`;2&; zscC;kiO$QOe$J%QvG|tip4O*nXTard1t$20Q(l&7TDBkop1wi_OBP38o7&F)GVYLD zSD*IDO;RyxwcoYRMzfa91D6ij^iF?raO@;PZ6WPc0;Fp;;cH8*G@YTqy1Edq=K1mQ ztok^VbW9oX2vM}wCOPKI&EoGBtGj%qxy?| zryhk=Y^gGu@l`U|o*+ojU)~ie1-AUXKgEP(+a!<@FD2_vTn0Uz>87&CR(Ix62Gwx-ou~e1%Yo zmF9di`}80y=5Vq>BFXC_&l=P%Fsr(Ns>kN+P+PxOCtC1F<2}Vjx@3f(+D2*8D6;v; z1%;xEfg;pMdMUMChQMYM9>Kmog~p9Ow~@wv&fqoR0bvVa+zcJ8&}A{Q-6mC3Z6-Q3 z#ICK-kM(*itq}K935+DRZ#_%~qlID2pm^@440b&7MW;jWDNe*o=sn?F_kjWP%L$~5u8EhNvBr_->mVg2Gfo|7tKVg4yyXh*RQ!ORvga>w{-Wn z?AKrQH7l%5F+Nuwr(Cd8SzMBmvFNy3Lo={GNf!mtM&fvWAxjceA-y8+R^}tpV9)4> znET&T-7Wd@xWtCJi7oW~SL|GdH5k4v!miz*rvxL>G?P7Jo(7zGm-E1w7BHzNb{`m4 zHDm_~`z+*4Hyn5yx3?Q7K+$fq-(`)pP#2lq;F$${ZF@Zmyo9vR0tz2t=5O{x3kNip zAPm{A&;fgz3X9^WA#FD|e&F6Ead6=}hB)zZr2H9eYmj><*Ef!9Ge@M|oE^5=by^!c zxA@}|dcQJ5UwKS{TJJ_r7j`N1KhJo;#fb)HN#rvH+Ki@aBcp0?CAWjOUAgvQE|K%x z{Mrj%Ka@tF6`QJtaPop?y0}+X1eYxfLXp};y+Bk-Z}pQeM&~8L&$X=QYS}5Bq&RuW zYWqu@tM>OSK2pk>Mu1k;uos(6kfI)r}k-z-JgNWQjo|qkz zTAeGqp+D#^q`F|D9)RBnDKiI0z!8Y>6dpfAO+1>ov2Lqj-vcdh)C% zUw$$TBWhTCo}`3*jBqoHnkEM-fEv;!xpH9Q4q*$9E4%TF-Tt+Vw&c__5JO}0xq&DX zT3vgOQ=-)o6h^%6rr|`Xk?gS9>5}rwt*50MYbU`tE&FMmSYkJt)96y|G_S;q^c3!q z2J>UG%dcf)%JMiN#Lij|^Ni@T&Q4j}5V>(q&S?mVmecfcu|1H1HJRW!Dsm_17GB2l;M z;;Et=Z`;z&0a|s1< literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-between.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-between.htm new file mode 100644 index 0000000..da23b9e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-between.htm @@ -0,0 +1,31 @@ + + + + + CSS Flexible Box Test: align-content_space-between + + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a vertical column in a red rectangle.
+ 2. No gap between the top of red rectangle and the top of rectangle 1, no gap between the bottom of red rectangle and the bottom of rectangle 3 too, and rectangle 2 is distributed so that the empty space between rectangle 1 and rectangle 3 is the same.

+
1
2
3
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-between.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_space-between.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c1b913418adae5eeab59006fab7b88abaa3fd40d GIT binary patch literal 2561 zcmcImc`)1C9#6y)S`ze5?S5_TT8c`jElO(%s+OuMs)A}QMJcU>aO+YRuXEO73^<{CS+B##^a!RUD5&->oJm}Sl)GAFA z@9$KeYMH(C?Go+ZW zLm_vz7_RXeedl(n;yjx`3kO0$qR2o&!JD>9wpwT@+axJ_k1`PgOoK+K&BhO?gm;7--BiyUP$LJDPm#7!kY0L}+r=4Jc-0)+TXR!maJM9P7c zwqQic5xV>R7?T3clG349Y;&#SqL@RLDrzfVGf>p+MKG|a3M>i(i&_M;HC5)djeU^p z%%OZ4-n@&I&KtGun%#Dsua2GjXXfcC5{2`okf%PgJ{n=diP`k)PP@gG%e?J9Xg)1Z zJ-pQT+Y8nw`8kPxyId~ZE95tveV<&ak+oA;qbpdFGZI?6lq{NawPJl(7@}u_pp=~! zhmUZL_V;X=0j1L}U_F0;&17t!74Y1c&|dol9zv`QZFWnjsH^5RDjaR~bv{2qUCw(H z3>c>&^0HP-@Xpazi;0Atqb{#urR&ka!S)@dFzwc?c2pJSzQDnFx^U$*-IzXrzcz!w zlb2Ia);X9Ai#;l)!`i@eRyGo&JUAOqY&hSf_S6a5pE@wvZbcu9$Pm93+f~o3C|#?} z2`DzpqoRxNZO=f`s&x9F?@qD~`lIK6A!Is6D4Ae;kY(9dqr*IV6?1QJOLMMm#CJxY zeg=y4EqeMq^DJ{F@|aC4 zZp+#ZnguD(AZLyjrlSJ;D`5g~chu-K3UdsT!H`el^)6P zRU7NoRvp%F=5~;rd>q}FXr(Nb*@6u+ry3zG_8;_T7m#_K%2YS1$|56{M7i}3VqoSK zPI`-CiV+&gR$voQ1)PmD>_y`@Pj~e}tY7RPyLHRL7j*<0$1Z#tz^=ACo;Ac#mnH|G zgZc5J5Xts_?}~hU?K%l5GMg+74pgtXzc*K!^tf;y*R89bj($5{4r42DWWXIcS;0W$ zWh5wOUk?;Vr0Sk9zmmL=+O0zG^l2*o*1%t6B)EVwuf`Lgb69{`9B)Br%-VA6f|7kT zB@^v*dr9}o1FL&@uqd^=x;xsfNFR?G3iJ0#FHGcX-1JtQ^7KY4CR>m3Wz+ly)-)T| zVLmX0wL-Y}%xHJE@tmKjW5e<@f;J+IWfPRbSKjOgo-N&iP(qMOBH}d_%O;*NDUL`qgc0{rIa@d24kE|1d#) z1GSy(&&~DzL4ydewJc#xh`UJ>C;PhFPvl?t_!-M;cC>gT$@?(!;i^vt2Ybnk6_*hL z+ktT-1Y#X!*U6DDt9AXmkUI}?sqe|S%TKra5`1(iw6fMQw1>*42fq6i7?5<_IqB)V zhDdfTId)g-SHO@j`lR&tx6NlRh)qus3E-5!{%O zOv~!;yZ74I!4M}jSlJu!&(pdE!6kiLYVcbMwp{^1c=Ag9eZ+Df<>;^IqL!mo0?6pzC~!BfpOf>2)@iD8zbj$y~Td;v53 zQF5oRc|z_X<4`kB;wp^TmnO*ZjL+B?%L_oF5H zI(>Y?-p-!6X)V2L%aK*eKLKck^i+y8&<42J?|b2bmc2gLFN&5vwwY7(?yf1diFs_% z3yzc@yS;68Q_eQOOBzL%nv+PRLC7?rN>AQ4YUiCg8!Zw&KhDbdcH7_~f@D~syy#uc zDE!twbKUDoE&C8>Dp7f{`x1=!$1(pEMy`E7cL!V(AD7qLXXMe(Y<>H}wY!?CoI-Vp zitGhxhleU&rG^g|-dqEVB2VU}ilu%8HFl}VrE%KvZksk=_m~_4{CFPVZ15gHEnme+ zBEs`CmL3Q+wz%7+a*taYmk&AZzVtv|4$o zNn?rty(F@!LO(xVY2g^$vVfK}I+_Jc|BK~k zr~kdwMOFfJ_XX*I?cE + + + + CSS Flexible Box Test: align-content_stretch + + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a vertical column in a red rectangle.
+ 2. No gap between the top of red rectangle and the top of rectangle 1, and rectangle 1 , 2, 3 are distributed so that the empty space in the column between 1 , 2 , 3 is the same. +

1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_stretch.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-content_stretch.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d8487e454551134430a80f18dc17cf7af4de4c79 GIT binary patch literal 2278 zcmbtUdpOi-8~(Z2j2vc~5{jCiSQYV6$(b318Ofm{$Inm_t=7=6ldKFG*)cg2>iZUD zoYDb{F_aw>b|#dO(-f0K<1B_TW0tP(pZ#O6-Mzl|pXa`w>$%_OexCQeZj!5ugW}G8 zI{^StbaJ$F2LNdV07#k1NN;7-({$6f0^HU4oV`RM*@E#BU^_@8$k2}=mY1Rc0Fv%x zhddV}HIps%wB)FQ{clQ4_-1(ED!o4427&l*@8IRprbpBY`x8=R;x}8=Kb(c3z_X!+ zF$sl|S^{C%TE#t#Z~;QMoO;lG9`r1h-W>z-Dsm*I<^JkJAN_?%{DEmyyf%C|CBTb5 ztpC_Cz^WrrbNDX(Z4G8G(W0G9c@lIQ)oNRba=+M%%E)qUjI<`+On72Ty{i}OR;tT6 z42jLV2%6X)cxe!R6th|I>L^a{Jh@Lie9#=^4L#IUlSZXUfm;e)kk!7+A4FHo2cm;u zI6(oM9B6<)=~8c<<6&rC^z(Y0a`B$0&ul8qKJ)h{J!khxqo%aMs6?3X9CMe$-RRgw z1^FJ*``o-0ozi46+T$utJl`*fbfWH{i?cYB%Nx_9LiSP%REJ|)KM!Sl+{DM7W@sLX z@)=VM%2{u~EE5C@i@P)J`)2=D7o#y4x~mTLR%>Z2$0QrJa_jw>=y*t*Fh8#;6+}%T zCT2w4knelRaUU_(@ZR^wvSIt=UkB7!%s;1S$J5N_?orhH&amdA!B{txT9@?1syv$g z!q7~c|D4jCqMwys=tI2z!j>f$C=M*Kr=Os6k{-Xv4#2n4q+51+q8VAR>WRdLK4Af)f&!!W;aS0$#~u^ zAI`gIawdB3$@%Mz{_gZ@K?TL@^H_t{1D^kvfnG7%QKzgR-Q1FXihPg#dHAWNxy#%N z9{rLh3dga31bHn+c#L`lYOdfQ0EnWZXuP&ShC|YW`_-ek_PK?HaPIghrI+Ag3XOb& z;07zu78{zeV%{|!?~w;AQ&!Im?9_2q&%O$cF|E;-=1aN{uh#f@5G$p!pERq6 z5}+zNIV$gTW`#5hwQ|_hohz!Oz$C*&P0+@6Miz z5SAYKhx+IEZyTT4;7mJAm6CkZdVI#wj#c_2%w>WPb2Y6ofNA1jW(Fk|rnpsw(HKD> zKgSK)RZj13aGi7~h@o+N<_wl}qmSnxVgriD;SoJ|Ge_}fd`2_9!Zf_Te;m!ah0tS(i^rpbT|~(Z@4H1J7i^vOys=5PUXH=-(%^Nu zN93DDR;{9wr7cv(wPFG1TnBZ$-OA5saP54hg}<_=pd3Hg@M@OYK0VUUiVIz`GHCZx zf#cs7Ql5Ae>}xTwOUEOKE|B*_hO(%aF?3KX99f=UMR{`Nm$zS3DwW;q;yhE@N%Xbh zY8WY6ODj(G<1IbX2Fh6Zk%ebxNRf4VekZMi$b8hJ?5^>{XTGYO+wz=TPig9kFbbcN zdU6r)gJ*+Aep%EoT5b?y#`)E^9d>p_9|)is~xRg-u{7*+vd zn0%uZwq%8Fi-%zW8WPbC9KOi-5f)^VVu)y0{aUeusQT4s8E>reKwk9bj1)KzfPYr> zC)|IQUL$1GcdUo~Ly{*^#P!#>ZvsmUgjFFwRJ1FrHzdf{NiHZkj4H~oHWT8Y$_Q1f zX=54EF0F|dhnNaTtPIIi+bc}YXwKtr*P(qs$AV$Hb zV94Z|9hywah>eWe)BPz3TNh-%HxyY$yCIVgI;J5EZ?{9>hyGvCy^QG-G%Ie)+O;J_ z^)JE1z^>rr?dFB>$`>+ff+Gup<*!prEJZ+!&TikpGVM2$$-fMKRfsDGw_dO22f6HqCz{%dlt{V04+rI-F2nS^V literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d9e99107844d149e504cf70cdb7a0f59ec648c GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*GqL_5I9y?^MS2GzJJ5aM(=M^?)Fd3nCx>bGx%8h|LK`ctA*+q zGnHBdoH!JLL{-++-+w~pUt75%ul>|v)t%wXuU@LLsNRzukyNo>;q&1M_neolc3SkU z%%d_tj%m}4pjGw7rd$6!eQ!6zG;7tpJwAwSrs{X+ zyuH4C&Yq|G_4Ybzb9TOpiFVlK^>$ZnRA1^aJ{fF-!JR|02^gw;CJSNw+>-O0Jn9cs3}9 z`}=*HG(82)eB()VXHJxftUc}g>?_Zc%*X%M$DaN^=SIKR8}nP0X~HR=Shvhrx-EO_ zu0Gb6)w5T96yF_IJ?mEN#l>Z76(`uvk7rtY+_2wj!ev{&U*#U)VBt`Fa{lM9m<9$$ pCKe6>1qZ4!P8 + + + + CSS Test: A flex container with the 'align-items' property set to 'stretch' + + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d9e99107844d149e504cf70cdb7a0f59ec648c GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*GqL_5I9y?^MS2GzJJ5aM(=M^?)Fd3nCx>bGx%8h|LK`ctA*+q zGnHBdoH!JLL{-++-+w~pUt75%ul>|v)t%wXuU@LLsNRzukyNo>;q&1M_neolc3SkU z%%d_tj%m}4pjGw7rd$6!eQ!6zG;7tpJwAwSrs{X+ zyuH4C&Yq|G_4Ybzb9TOpiFVlK^>$ZnRA1^aJ{fF-!JR|02^gw;CJSNw+>-O0Jn9cs3}9 z`}=*HG(82)eB()VXHJxftUc}g>?_Zc%*X%M$DaN^=SIKR8}nP0X~HR=Shvhrx-EO_ zu0Gb6)w5T96yF_IJ?mEN#l>Z76(`uvk7rtY+_2wj!ev{&U*#U)VBt`Fa{lM9m<9$$ pCKe6>1qZ4!P8 + + + + CSS Test: A flex container with 'column' direction and 'align-items' property set to 'flex-start' + + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+
XXXX
+
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-items-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d9e99107844d149e504cf70cdb7a0f59ec648c GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*GqL_5I9y?^MS2GzJJ5aM(=M^?)Fd3nCx>bGx%8h|LK`ctA*+q zGnHBdoH!JLL{-++-+w~pUt75%ul>|v)t%wXuU@LLsNRzukyNo>;q&1M_neolc3SkU z%%d_tj%m}4pjGw7rd$6!eQ!6zG;7tpJwAwSrs{X+ zyuH4C&Yq|G_4Ybzb9TOpiFVlK^>$ZnRA1^aJ{fF-!JR|02^gw;CJSNw+>-O0Jn9cs3}9 z`}=*HG(82)eB()VXHJxftUc}g>?_Zc%*X%M$DaN^=SIKR8}nP0X~HR=Shvhrx-EO_ zu0Gb6)w5T96yF_IJ?mEN#l>Z76(`uvk7rtY+_2wj!ev{&U*#U)VBt`Fa{lM9m<9$$ pCKe6>1qZ4!P8 + + + +CSS Flexbox Test: align-self - flex-start + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - flex-end + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - center + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - stretch + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - stretch (height: number) + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - baseline + + + + + + + + +

Test passes if the underline of all 'a' characters within black border box is horizontal and no breaking.

+
+ + + + +
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e877c3605ace85dc00aeb40a37d98d9402160e77 GIT binary patch literal 1026 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*D)~z$<02E=YSMvfKQ0)|NsAiOa_Mk!NG19 zfU355x;TbZFutAY-M7R-pp92Z`dZrkSyxy1HKti=FHd=QI51#+(43-2{RbS%0)u03 zstPXo*RR9%hvWC>ZU+v<76B&?#Us3-`Sr^Ud3Kny%dR%Mba|S{)|CCF;;IuaeW{+f z_LyDPRpl_vi)&Ob>wOC=mtVQ1J;OKCTCVTO#*VNW^`N&dvljhHo%qUg;;Yw6r#WOg zY^GI&{EkSGe_cFr@pgaLU00quzT)BZFFGkZIV00!>V&|5-sf~rpZGTIny1`5SC-6a zT}47mZ9KP#1ZI1vuG%&$XiHciTY0r}*5&fkw{^W`Pi~#JK(%-G6T6NjGp>X&otPX` zbYFLE$AKM>rAq#I&+4m)Nq2Gg{Jdn&kx4aIE)@3f+VUjNbSc}TmjaoRd#9Ie2|q0z z#iO)*?ZSC(dbPhCmYkm7`J`MaHZI*|yWOjD%hS?}7O^e5d#_XB|F!5Rd+xmDJ)5r- z-QBybwaiZ7;E|afC5+ro_r5DR>nSyx^}Sxg1qMl3ngG1ZUXSm>11 zgy$7q9-5iH|L33B;Ii?3RwBdO}6pOta&4 zzCT#~@%75uL=C4C-oa&T=U#9A?KIJSVz=><*)`@LpXOB6+&=&A{N*JUC*nIaLunhbbaMSk+z=7N=Ct*kG#)txSW#P`9{%ZiO7>Hla5|Jaq{ZZ(_McKl}^9b z-BmLE{{dlOA_AqP%4rW-11A1^Gx1NcR+mMy(!B*8<{FQBlPCU^yH#nkJO93-QTK_G zvitjzGq)Pae&782o2=5-jz6_!_vZ=xOqaX=?&~(~XO%h6v(KFnxg~IF-3r$yZ$({h zZIpYzY0ERg>o;Tn+`2UD-m|KC@7}Jkzo{(z?ZlGnsswQYskF$$0_;D*U;9Va>H1SE1{fpp5M4>gTe~DWM4fPtEWE literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-007.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-007.htm new file mode 100644 index 0000000..01cd936 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-007.htm @@ -0,0 +1,47 @@ + + + + +CSS Flexbox Test: align-self - auto and align-items - flex-start + + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-007.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-007.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - auto and align-items - flex-end + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-008.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-008.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - auto and align-items - center + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-009.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-009.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - auto and align-items - baseline + + + + + + + + +

Test passes if the underline of all 'a' characters within black border box is horizontal and no breaking.

+
+ + + + +
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-010.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-010.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e877c3605ace85dc00aeb40a37d98d9402160e77 GIT binary patch literal 1026 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*D)~z$<02E=YSMvfKQ0)|NsAiOa_Mk!NG19 zfU355x;TbZFutAY-M7R-pp92Z`dZrkSyxy1HKti=FHd=QI51#+(43-2{RbS%0)u03 zstPXo*RR9%hvWC>ZU+v<76B&?#Us3-`Sr^Ud3Kny%dR%Mba|S{)|CCF;;IuaeW{+f z_LyDPRpl_vi)&Ob>wOC=mtVQ1J;OKCTCVTO#*VNW^`N&dvljhHo%qUg;;Yw6r#WOg zY^GI&{EkSGe_cFr@pgaLU00quzT)BZFFGkZIV00!>V&|5-sf~rpZGTIny1`5SC-6a zT}47mZ9KP#1ZI1vuG%&$XiHciTY0r}*5&fkw{^W`Pi~#JK(%-G6T6NjGp>X&otPX` zbYFLE$AKM>rAq#I&+4m)Nq2Gg{Jdn&kx4aIE)@3f+VUjNbSc}TmjaoRd#9Ie2|q0z z#iO)*?ZSC(dbPhCmYkm7`J`MaHZI*|yWOjD%hS?}7O^e5d#_XB|F!5Rd+xmDJ)5r- z-QBybwaiZ7;E|afC5+ro_r5DR>nSyx^}Sxg1qMl3ngG1ZUXSm>11 zgy$7q9-5iH|L33B;Ii?3RwBdO}6pOta&4 zzCT#~@%75uL=C4C-oa&T=U#9A?KIJSVz=><*)`@LpXOB6+&=&A{N*JUC*nIaLunhbbaMSk+z=7N=Ct*kG#)txSW#P`9{%ZiO7>Hla5|Jaq{ZZ(_McKl}^9b z-BmLE{{dlOA_AqP%4rW-11A1^Gx1NcR+mMy(!B*8<{FQBlPCU^yH#nkJO93-QTK_G zvitjzGq)Pae&782o2=5-jz6_!_vZ=xOqaX=?&~(~XO%h6v(KFnxg~IF-3r$yZ$({h zZIpYzY0ERg>o;Tn+`2UD-m|KC@7}Jkzo{(z?ZlGnsswQYskF$$0_;D*U;9Va>H1SE1{fpp5M4>gTe~DWM4fPtEWE literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-011.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-011.htm new file mode 100644 index 0000000..bf63924 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-011.htm @@ -0,0 +1,39 @@ + + + + +CSS Flexbox Test: align-self - auto and align-items - stretch + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-011.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-011.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - auto (initial value) + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-012.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-012.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: align-self - invalid if applied to flex container + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-013.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/align-self-013.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + +CSS Flexbox: anonymous block + + + + + +

This tests that text nodes that have a flexbox as a parent are wrapped in +anonymous blocks.

+
This text should be visible.
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/anonymous-block.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/anonymous-block.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a004783c36e41f202e914b68c4617c15319b2ff0 GIT binary patch literal 564 zcmV-40?Yl0P)d295Cu>Ra{wP9*lOWA)vxAk#2mp#Qo)jEP{lK_n*F4hwUq1z$$}do02zUqp~ik1 znuag_1!E!;naD&YGLdhDTr|6<{Zh_*=*4@kd*K%SCF~;|;^O&uOV7m@&mTil{tS}e zAU*=|_YsM~#e#4a6Nac$ut#ogE}82F=Q0!fVIt1iQ6nx!rYx^Dd||fw?|^{4BxrqX z6m|k62S?9Q06uBTb&ZiSLAll{&W>wo>zTJ|aH}r_i=XvxNT|sDfiOqlTZD?-fx8D~ zg78!k&VG9eFGx5d`tucHo)$~CH<6aukfrB*=fy!}i9mGj9UmXM)LXjvO>1pK&=G6M z)y@bvLrW?h(Y+HbzY%SSwoXuXL>r&6os@SGyh36}+O~WY!E?z+WEI~`HqHne zLu}lXU_D2ORzH(Dsv@K(hs!X=jg0000 + + +CSS Flexbox: auto-height with border and padding + + + + +Tests that auto-height column flexboxes with border and padding correctly size their height to their content. +
+
+
+
+
+
+ + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-column-with-border-and-padding.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-column-with-border-and-padding.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d06e4fa6d10dff7c495354f06b4c4e484528cd56 GIT binary patch literal 828 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK%b1vfq_3iU50K&s@CkAK|NlRb`KzJm|AEu4 zjS@Nx3{0;*T^vIy7~f8fo%C3Xr}g97M?d#H_*=nQB>E=(n(^w{vS(hfbCg~3RKFWL zG4}bACPy~U`3H*{6gZj`Uc_j*-dgcjV#W8{p*ok9uII(yx}_OC-%M`#(LW*CQh8hJ zxH4bG>2=*L-z{DJr&g_S{|z-;<|0o;KE-|PDZe5GcGrn5`MHz7sMoM5$F@~^<{gHa znde&n*PQ8C|A#+O&Fhl*ZJ)x4l4^5FodG6AwQ)14~ z&emw;>d0L`^Vww6kF?o$&WhcAw7&GIs_^MNW~a_yaT{hYS<@*zN$|hsOPQ_PFP;jo zZc2J-KX;+~>UBC>P5aZf&Rd(aIcMI_$?4~;oy875o2IxeHOcT@!-vZc?#yldHmz)$Vqyln|J!w<|9M(ejG%SAx6KeZjAVyiVU< z*)vRxXP!7~+AqnAKN9Dk40l|2f1<%2#q|wK_$IjtRLQxlX1&hz^T0{Yc}u1h#LoP( z!o##HCf=fCt)WqtLg4Gr)lp&Bm8vK2&Jy+C6#lF4$|}{VI_{e`R!mv4Rc_POXD*wX zS4n<6;c(1HXZQ1|agin7UruEIy6JO{>C-JUJEomazXmROf10JqZuyGkWl?UYm)cJ7 zW81V9SvQ44L*33i7@z|sq2H%>V9a$3_Jgz?at{Po!Ai&Y|?)7(07J+=G4s*#9>&vIl zUjNFz!9v(!&&uXM|G!`O#Vn8xlwv$Go8gf!!$-A-3ZN9H!aax%VueebCKMDv6!Sh| lftYtMHV>Fu7c71)|N7-Z6Pu@8alkyl;OXk;vd$@?2>{Xifsz0K literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-with-flex.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-with-flex.htm new file mode 100644 index 0000000..d2cf590 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-with-flex.htm @@ -0,0 +1,14 @@ + + + + + + +
+
Header
+
Flexible content
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-with-flex.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/auto-height-with-flex.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5f7be4df31ec1f25376106c5f60ea58bb88e3340 GIT binary patch literal 345 zcmV-f0jBLSsBMn!VhP>BvS+!1ObR-*$!#r{+*+^T=5{W7t$*XWdA zbwrruCPK@Kf5n52>eMjhaM^+O_)u)zCsJ81<%cQi|=#(Fe r9MLf#0+DIyB99G-K1l!o0ITx{3U<)5CY}0u00000NkvXXu0mjfSl5tO literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/contain-layout-baseline-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/contain-layout-baseline-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + +flexbox |css-box-justify-content + + + + + + +

This test passes if the DIV5's position in the end and the div is Horizontal layout

+
+
DIV1
+   +
DIV2
+   +
DIV3
+   +
DIV4
+   +
DIV5
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/css-box-justify-content.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/css-box-justify-content.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..12d5e1e6ca7269ce5331bded554fe466a53d34eb GIT binary patch literal 1212 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK6Ihsmr1(4TRX|EHz$e7@|Ns9$=BG;m3=Ir< z9PHQEeE!rL@X07Zv;4=R8Dd`5dCfX%vl$pz!aQ9ZLn;{GPPOfOY$4!wR=VSbw8g6u z1)j8xFSTYeTNLhUcqn~hW3wUug-*T)F3V%YPA>8iS{eF%hpyk_>bp(4UG?jhYrO;7 zg#v#3T9Nfyc1o|>i*BAN(`3VZoRX)m+wnic!t}Q2mbWS4t2Q~Uh*fX#{kAkbGVJ8u z9c4E|Yp#Dvvy$}}s!RHFXRoo}>J8oYE**aCy&*qL*GyQ-^HaE`V0Ts9ggZ~N#ZPg4 zbnI}tc4p&;KigYVmdCAF>M!o(+P$iCwf?%Ei{<6=T(?%}#%#3jo%(l2zMn_dT#NSm z?sM`p^GxrAul(&&zu!+pt!{H}@#D}t-bX(lwQLa(J+(pV)JJ9GmS(m4M+6*n?Tp|p+X`3o(@lY4oVs;eH!f9}ooaCIvIbhl&H`j-=0Nzy4^OM99ZlBlGXU3snkZpZ|9G z-}PPSxtQwd^WwKx@8Fl`w>Ps1|7SV;1O7$$$}E? z3=9v>r_NuUzjSqXzRMSQ-u#5E_OOp zz%euNOzD2}wES=D!+-TVz53OtFn?77=Qb{`|Vc=vj(i`tMdfeHEVnr)O;2JLC4#YqPo+N^@+~@@NrG5^`F@_4*L! z!fdUUkO*;=Bc3z4Hfo({7EelZ@^HhJ|e$ieI*?9GM~A;-a`dynfn~mVkp& zLMduTMAx+PSpI+1Y^$mAgq`neIaep=CquVMDn$!ivYL3r+-fi!$kdp4vV(52b_3oJ*8#$SZmdtH^TrCbK z{&QUf2Id@rwWYc1*Gw?5QCHz + + + + CSS Test: An element with the 'display' property set to 'flex' establishes a new block-level flex container + + + + + + + + +

Test passes if there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/display-flex-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/display-flex-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d9e99107844d149e504cf70cdb7a0f59ec648c GIT binary patch literal 516 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*GqL_5I9y?^MS2GzJJ5aM(=M^?)Fd3nCx>bGx%8h|LK`ctA*+q zGnHBdoH!JLL{-++-+w~pUt75%ul>|v)t%wXuU@LLsNRzukyNo>;q&1M_neolc3SkU z%%d_tj%m}4pjGw7rd$6!eQ!6zG;7tpJwAwSrs{X+ zyuH4C&Yq|G_4Ybzb9TOpiFVlK^>$ZnRA1^aJ{fF-!JR|02^gw;CJSNw+>-O0Jn9cs3}9 z`}=*HG(82)eB()VXHJxftUc}g>?_Zc%*X%M$DaN^=SIKR8}nP0X~HR=Shvhrx-EO_ zu0Gb6)w5T96yF_IJ?mEN#l>Z76(`uvk7rtY+_2wj!ev{&U*#U)VBt`Fa{lM9m<9$$ pCKe6>1qZ4!P8 + + + + CSS Test: The 'flex' shorthand adjusting the 'flex-grow' sub-property + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..156126b1f60065e6fdfaca6e17cf15f6e0d71ec5 GIT binary patch literal 992 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZVifKQ0)|NsAiOa_Mk|Ccg& zCszCgD(v@kaSW+od^%`8lAZjI_6yOPTv zb^Aj9uGH>OG1TMD_Wu9n*3z&^As5;wex2KK-Yf5Ex$)MER{CG}?%378{@aa-vde>> z%k6GmdeoNbIb+JX-wHdK53ZAGOpw`g_9Z_0VkS9g-l_xSbqTQ8b@-9gP;Kc{ND z@(tDhcizmcc)KD$v+7Mp?YAG*db($~{`!$+d*a&4Mw#6Y-+e4^UZSnA!eY9^CB_r? z%#%bFZhxP@ch`bdF5cT0{Iu+jXzdhpPZRHK($|VQdO&}@vBdI|i>AK6xq5T(=SKER z78$&^iZ@NodAD%(y=a-$hbr&Q6G&G-w(ipR>c>BClw8cUuTnqp#Z%$)%a)(4yWid@ ze7EoRpHI7&uekp7dF%EMb4$J+SzTgYWNf;1#azDF{F=Q5HGCdFawnAf+A~XD5#7(v z(8=xhCG*#*!jF?>1LwS}`}AnZ|4o;L|8^|s<*4&?5|K;%xLNXZ?X>>6$0vWcb;?Ry z+Qj1dE8%PI;@tOP?_Jl=bEqiU;=R}V)0R!uS$jWTyj_#Wa#DMO<@f$6>=SAPm;AiL z(sXvZXh(^4&?X<(%O@r;w_D^^;6E*hugUQ5nrBVdVnem(Hgp@*2sJTLPvsy;GW2b5TpEE_qA% z+8qk7{>N>7lf5Z2Y^%|#Yq|>BOW*GNlt1ZgX0h>!^DnPE)ctxf$-E*6mZ$^YU#buP z@_)Xz5{Dy)lR%R|ivo)xZJ9L|9H1Ok8(q(M;zn&kiU=n^D4Tk^`njxgN@xNApbp0@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-002.htm new file mode 100644 index 0000000..dd5406c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-002.htm @@ -0,0 +1,57 @@ + + + + + CSS Test: The 'flex' shorthand adjusting the 'flex-shrink' sub-property + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..156126b1f60065e6fdfaca6e17cf15f6e0d71ec5 GIT binary patch literal 992 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZVifKQ0)|NsAiOa_Mk|Ccg& zCszCgD(v@kaSW+od^%`8lAZjI_6yOPTv zb^Aj9uGH>OG1TMD_Wu9n*3z&^As5;wex2KK-Yf5Ex$)MER{CG}?%378{@aa-vde>> z%k6GmdeoNbIb+JX-wHdK53ZAGOpw`g_9Z_0VkS9g-l_xSbqTQ8b@-9gP;Kc{ND z@(tDhcizmcc)KD$v+7Mp?YAG*db($~{`!$+d*a&4Mw#6Y-+e4^UZSnA!eY9^CB_r? z%#%bFZhxP@ch`bdF5cT0{Iu+jXzdhpPZRHK($|VQdO&}@vBdI|i>AK6xq5T(=SKER z78$&^iZ@NodAD%(y=a-$hbr&Q6G&G-w(ipR>c>BClw8cUuTnqp#Z%$)%a)(4yWid@ ze7EoRpHI7&uekp7dF%EMb4$J+SzTgYWNf;1#azDF{F=Q5HGCdFawnAf+A~XD5#7(v z(8=xhCG*#*!jF?>1LwS}`}AnZ|4o;L|8^|s<*4&?5|K;%xLNXZ?X>>6$0vWcb;?Ry z+Qj1dE8%PI;@tOP?_Jl=bEqiU;=R}V)0R!uS$jWTyj_#Wa#DMO<@f$6>=SAPm;AiL z(sXvZXh(^4&?X<(%O@r;w_D^^;6E*hugUQ5nrBVdVnem(Hgp@*2sJTLPvsy;GW2b5TpEE_qA% z+8qk7{>N>7lf5Z2Y^%|#Yq|>BOW*GNlt1ZgX0h>!^DnPE)ctxf$-E*6mZ$^YU#buP z@_)Xz5{Dy)lR%R|ivo)xZJ9L|9H1Ok8(q(M;zn&kiU=n^D4Tk^`njxgN@xNApbp0@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-003.htm new file mode 100644 index 0000000..7a31f35 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-003.htm @@ -0,0 +1,58 @@ + + + + + CSS Test: Comparing two different elements using different values for the 'flex-grow' sub-property on the 'flex' shorthand + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..156126b1f60065e6fdfaca6e17cf15f6e0d71ec5 GIT binary patch literal 992 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZVifKQ0)|NsAiOa_Mk|Ccg& zCszCgD(v@kaSW+od^%`8lAZjI_6yOPTv zb^Aj9uGH>OG1TMD_Wu9n*3z&^As5;wex2KK-Yf5Ex$)MER{CG}?%378{@aa-vde>> z%k6GmdeoNbIb+JX-wHdK53ZAGOpw`g_9Z_0VkS9g-l_xSbqTQ8b@-9gP;Kc{ND z@(tDhcizmcc)KD$v+7Mp?YAG*db($~{`!$+d*a&4Mw#6Y-+e4^UZSnA!eY9^CB_r? z%#%bFZhxP@ch`bdF5cT0{Iu+jXzdhpPZRHK($|VQdO&}@vBdI|i>AK6xq5T(=SKER z78$&^iZ@NodAD%(y=a-$hbr&Q6G&G-w(ipR>c>BClw8cUuTnqp#Z%$)%a)(4yWid@ ze7EoRpHI7&uekp7dF%EMb4$J+SzTgYWNf;1#azDF{F=Q5HGCdFawnAf+A~XD5#7(v z(8=xhCG*#*!jF?>1LwS}`}AnZ|4o;L|8^|s<*4&?5|K;%xLNXZ?X>>6$0vWcb;?Ry z+Qj1dE8%PI;@tOP?_Jl=bEqiU;=R}V)0R!uS$jWTyj_#Wa#DMO<@f$6>=SAPm;AiL z(sXvZXh(^4&?X<(%O@r;w_D^^;6E*hugUQ5nrBVdVnem(Hgp@*2sJTLPvsy;GW2b5TpEE_qA% z+8qk7{>N>7lf5Z2Y^%|#Yq|>BOW*GNlt1ZgX0h>!^DnPE)ctxf$-E*6mZ$^YU#buP z@_)Xz5{Dy)lR%R|ivo)xZJ9L|9H1Ok8(q(M;zn&kiU=n^D4Tk^`njxgN@xNApbp0@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-004.htm new file mode 100644 index 0000000..a190d70 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-004.htm @@ -0,0 +1,58 @@ + + + + + CSS Test: Comparing two different elements using different values for the 'flex-shrink' sub-property on the 'flex' shorthand + + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..156126b1f60065e6fdfaca6e17cf15f6e0d71ec5 GIT binary patch literal 992 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZVifKQ0)|NsAiOa_Mk|Ccg& zCszCgD(v@kaSW+od^%`8lAZjI_6yOPTv zb^Aj9uGH>OG1TMD_Wu9n*3z&^As5;wex2KK-Yf5Ex$)MER{CG}?%378{@aa-vde>> z%k6GmdeoNbIb+JX-wHdK53ZAGOpw`g_9Z_0VkS9g-l_xSbqTQ8b@-9gP;Kc{ND z@(tDhcizmcc)KD$v+7Mp?YAG*db($~{`!$+d*a&4Mw#6Y-+e4^UZSnA!eY9^CB_r? z%#%bFZhxP@ch`bdF5cT0{Iu+jXzdhpPZRHK($|VQdO&}@vBdI|i>AK6xq5T(=SKER z78$&^iZ@NodAD%(y=a-$hbr&Q6G&G-w(ipR>c>BClw8cUuTnqp#Z%$)%a)(4yWid@ ze7EoRpHI7&uekp7dF%EMb4$J+SzTgYWNf;1#azDF{F=Q5HGCdFawnAf+A~XD5#7(v z(8=xhCG*#*!jF?>1LwS}`}AnZ|4o;L|8^|s<*4&?5|K;%xLNXZ?X>>6$0vWcb;?Ry z+Qj1dE8%PI;@tOP?_Jl=bEqiU;=R}V)0R!uS$jWTyj_#Wa#DMO<@f$6>=SAPm;AiL z(sXvZXh(^4&?X<(%O@r;w_D^^;6E*hugUQ5nrBVdVnem(Hgp@*2sJTLPvsy;GW2b5TpEE_qA% z+8qk7{>N>7lf5Z2Y^%|#Yq|>BOW*GNlt1ZgX0h>!^DnPE)ctxf$-E*6mZ$^YU#buP z@_)Xz5{Dy)lR%R|ivo)xZJ9L|9H1Ok8(q(M;zn&kiU=n^D4Tk^`njxgN@xNApbp0@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-center.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-center.htm new file mode 100644 index 0000000..53aad11 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-center.htm @@ -0,0 +1,45 @@ + + + + + CSS Flexible Box Test: align-content property - center + + + + + + + +

The test passed if you see a centered 2*2 table.

+
+ first + second + third + forth +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-center.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-center.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8e3aa9a6170832c8f615e16feeba19af09675af4 GIT binary patch literal 771 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKH!?8;NyjsW4M2(~z$e7@|Ns9$CI~k$sGl}G z#lXO{$ec;-}^_mJnIv3B@1U9Hn z<1U+WvnOZ$wbv6~$Y}F5>r3**tP=$q2LuN?wyM>Jmw%Vu8npZIy17#EcaO^X{PUO} zc(pKi;@v{OC9`De^^@BC#cobj{d6IGU5Du;z9RcA->!9q?>X*y^tsXA=82li?kP4d zNqt;u@Sk_%s&%~_p7Tw2KUQ3SG0i&Yd1%l5O5JSH=#O51Ew?V2d#(D#+1L8hdp0Mp z?|iN_f8vkre?Coi<$F7)V@-YD=^fw1vZFm$XxY_wbxairGE+NY@p6jU+r=GUyh^4` z;*gvk{Yg5s%dcC=^-ap&-{RJj{#W{R6kgI=e!bblZ&#p;(yBjmt5O1L)i*sA{3$*0 zSNX&*Pr6UEP4#wJvg^&Qkjz^l#kXR5gRUAW8C~0OCHs@)#IQMeHAz=Kl>9id?+15i z&Avj;f2X!{ZhX4YHKp;yvh`+=P^yqBOFgsM<)8ih^Vbc|KDXSg@=jeTyJN+M>78HB zCJDwbIkB`-e&Po)CH1@S=YCps`LdGysqCFrZ$l?4O^S+roL2tCy`XN-DN#;tSEq+c zdKI?npB7qwWl}nLUastB|8_ym-#uS$wMJ~dvgL&4Rh{*7^|xfj?w|kvai30yGmD^$ zfFlZVO*m2*T?|FZ#BhO~ZJBoMo_d|mDZCTCA3NPRKJ$m9plbbBp})73rau#PIV9aN zr)2r>y}@z5-E+2F_bWBr6) + + + + CSS Flexible Box Test: align-content property - flex-end + + + + + + + +

The test passed if you see a 2*2 table and all the cells are at the bottom of container.

+
+ first + second + third + forth +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-end.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-end.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6d023c546f8b0e8dd016cbb5568737417055d496 GIT binary patch literal 1050 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU{qpa28z^p#@z%`JOMr-uK)l42QoppfkFMW z;VGcPlb$Y)Ar*{ouR8Y4HV`>hsC>jq+QPqxrJrT`3r(f3Ot$Xf#SI?KM{?7iGArz? zt8>UOV)bveQu$kd(csBZi+_oaC7d`ETLgeaiqGX6Klaa%T|VVl%JMb;ew;e>YJN{Xb%R@cNqMAkOcU+qGYx#sXY(jYlcwMwt zPPl*Gtl`epeJqnEytxt)sZg}$Rhkv|`hqE}il)(FVQjLi4^J{m%}l*;Y2U`1p09oP ztlDJt=2+JIqNyQYHczk0sm@)v_eJ5KHM6pexOV1Mx_Rwg|8W1kyO&xvf9NWWjVh8$ zPgrqmx7;rCUp+0ez9@hF_&L+<%dQ(qGyZPsG|oJg=(bGENO@aL=lgZ{wf?@=kpArH z5xU;-$+@GurRyJ`cBM6v6=upC<*NFPrl+K21NIkP@r#j;!3LJCjg9ZyA{IAo^dn{sHg)jQkL zJN8+3wI8h$oql^q`_WZb)0as8J2Z7+t>KlYoZYYOL!KDFja@g%>rVF}p^Mu-YE{Ikpsd!lP^ zq}t76+w?wt$$4w}Z#tWKERWlsemeK|%XMGBef;r0U1gyxx}Q?4PfWw(#Y&bFv*XYG zHaIb*%wbzYOyAt^?FK^InG8Rzn|`gIcc+XzZuMdxmVotk>Wvb-|=)`hC&6AYBk z$wyhPJLB>ER`UKEdHe$NSw8M=FY%nf$1%bG&!*YuzE(cJUbgnM;hU&?v;R8%DOwi) zt)&b-xF@_-h-O0x{h-S^GQUGX*$$NVem?)n+BLJcda=)g`@r1I;OXk;vd$@?2>`Cr By@LP% literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-around.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-around.htm new file mode 100644 index 0000000..542fa5c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-around.htm @@ -0,0 +1,45 @@ + + + + + CSS Flexible Box Test: align-content property - space-around + + + + + + + +

The test passed if you see a 2*2 table and all the cells are at the bottom of container.

+
+ first + second + third + forth +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-around.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-around.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f0d2c297db51b6d9c751217cc5f7a7a1f929fac1 GIT binary patch literal 1050 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU{qpa28z^p#@z%`JOMr-uK)l42QoppfkFMW z;VGcPlb$Y)Ar*{ouLkBVwh}p3sC=Y2F2VMsPz{Alq$_@KQT?P zv9oiSv4pv#Wtqs|{VS%-kvMLjBje1W*dpKrBzQ8VX8#kP{CLKt8E+zhp+C-uB1JmL?(74%c7${y+2i! zteUP+l>Rv8n$?XP``S;;{8!&F!+Oc8*DNbvxq8G;z4v5IT$9PB79U~t8i|rwSAIz< zg=Vh(qEx%gS@0T*&CH1*{d<=$ojPsh9%(k~Z8I8Q9E(o1hu zzuHoNWmou;SZ&?yiTvK7tIvB}{(n?E`S+srr-i3ot6y^>{nqXn)u~&=kE$-peR{i1 z>{79qc45Akw?%FC8dsM$`!{6XkNI3(biQ3UqHa<1-X|9(WPP(#hD4=6(G(Nu<8`7> z_Rqilj9oy%p@D&siG>3fqp09S^o;Y>_9sl=HsmlFK2%|Td2Y30-Si2!e_Bi^;5cFW z-tSz++`7|zcgu-OS?BW#-Tv{P<6r;R>5!Ml4a*~}^YtC$K3}V0_W01v zzx&MbM1|jVZoBTZ?ns|m2{bvc_i*7q^ + + + + CSS Flexible Box Test: align-content property - space-between + + + + + + + +

The test passed if you see a 2*2 table and all the cells are spaced equally apart.

+
+ first + second + third + forth +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-between.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-space-between.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..cca50c4be8d79e5d7a375e1c3a070f9316741d1e GIT binary patch literal 1046 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU{qpa28z^p#@z%`JOMr-uK)l42QoppfkFMW z;VGcPqn<8~Ar*{oub$3*Y$bB6P^#(ry%S$=s2VWbJhJxCf!u^wGeWrx1Q=hbX^SN^ z96qe9DP{j<#zmE`kN`TwdbRa2&|y?D<)>fN$G71gm@=W^YYIj+#ZYtzzynkmJsp5_m@7U?EWc)jYV zLjUe4*(1?66+Z1L-&Z|d?q%e(s9QNZw;R2@b1O$Pcv{#pg|GMW`fk1Z99q3v_)}Q* z^wmpT_g5G0Jo|Lwr;MG4PHF^Abw3rpZ%5j5|EP&U@3X6}?v9@IMbEZqcb;zL{O|>t zXTM)qc6ZJGiC2~#p1thcl)GIoN_Q@b{CjEkHvP5v+E0qPC+06HI(KbXsgyQ|c|y4bbED%0||r}W)8bAh`hTql1o zV&C|8>g)WqH4+hjC0~nt<;Yl;wfpN&r4GLWr6n~Sn#UFR&+m%v(D`xf>E`24gHOE7 zob11L&)r+bKIbL>_WpRLA3O8acHiA^aw>hVKaJa3zSApe&$TDP`uz`oEuUty#H)Hj zzVLnDmOIbhRfhjdOfPoRs;^HMfA?>v!ted>O8H#ef2%tSfnpFCjgmd_7T;DY{Qnzc zX5YZT$i%`Spx^*vq_92F<$Jz&=94K_3f&GqcTT$KwV#}@!Ry4&4>l8ih)f7Bym?h^ z#wv-nJ+lH%EY@m@RCU=FR=UDH{K@}__HP^GJmzq8YPVl86iJ?NeUZ_92BE_*`eUy> z&Qy5)uQ#U4YMJqA9%sLwXSiHt!>Y3O-ktydZI{mqW`z9^hv=|J8iAbycNfhWKVwcr z%bcq=KB-yiFs)$@e`(KdlgS25hM!(4UA-?|WLLxPQNwg%<%+J;>&y-U{otyR6}UKLzG&22WQ% Jmvv4FO#saszs&#u literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-start.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-start.htm new file mode 100644 index 0000000..4df16c4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-start.htm @@ -0,0 +1,45 @@ + + + + + CSS Flexible Box Test: align-content property - flex-start + + + + + + + +

The test passed if you see a 2*2 table and all the cells are at the top of the container.

+
+ first + second + third + forth +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-start.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-align-content-start.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..43f61cfd2e0d16329c129e7216fa5f33741a2528 GIT binary patch literal 1057 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU{qpa28z^p#@z%`JOMr-uK)l42QoppfkFMW z;VGcP3!W~HAr*{ouU_nZWF>N}&|UES9+j^Socn?<9a+ot(yZY1jpdpQnMzA%R~9jP z#LX`jI>9u#f_b_JL`BlGdz4PlY z9p8Pn?uK^g_n?~=yMHY;>iO~}I>v3<$F{$3Hs{zgp8OU(;jFrCZDKv!$#n`Z*D8^=0a%sJGjuXmjaLp7w3)`EOzC zw}!vWy|s1wYH$6OH`4!y)ZX^y-hHYx^w-VmneAU>q9-1DmtD1Wcl3@gdbV5gqP_3E z<|+x@`}*ahbNiH6s$HL0_5Ww3>zeu7UWDymQK}ky@$T=_KW-I8Y%NS>nVtStu4&3u z5tny4QKwI-&AwGGP?^3yT%f#hO6bxFn`SXy43|9kEV9=*%U1o>mIr^AlzOg??g&}B zm3QyO^Em;Dya$>^FIMf^m8W}m*{Ayjr_BDD9{DACBrjyv#Uou`;@2;lzh(2=Qs#}m z=>nNSJPLI?iVm+`+TOTbe|PTmcT&%9KX|vUV|Uw+_`Zgt3T794JU-5z@JQ;@OG6#r zl!KeS&d>8Y-+#)F%XVwY?yu=H!uHm>zj~+t)#t5wZ0x?oUH^k!?KXn~<;Bh^W*u9n zUi>9ywr|Uo_+H2TEgs4_d*;o#ykC9#o!EUzW#8>P{x_Z8%e5q?qDQ|G6qBInthD%g z=;M9XpWlyf`YF#Lpy1HJz{tb`V#qb@IsUPxWb&NLd?%Cy#J3wwyqssIvPP)r&R#y1 zeJoEV%-^<_d)>+7iurm^K4pA*5XL!i?bSE8PKy52-?p%Rjkr*{)1y<0xja8bp6KS^ z5q3zL@n_TNsy#d@ckfROzIUu7ZD-*L$Mb6@Y)@Re{MzroKku(Q(Nl)79^#G(PZhk` zP@DyKABgcpQsI4}jr?7e=UbRJu{_z4oqT86%ofg$$^ZQ>{o1K`{I0RXBW?%2%E=(N z=E)SDaK5)oZEKvS%36<(+ivsJ!*$MX+?|gt|8F{Bo{>TZAwAnLPL(aA`|ut2IYsf U9T%0#17>mtPgg&ebxsLQ08Gfsy#N3J literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-base.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-base.htm new file mode 100644 index 0000000..2819dd2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-base.htm @@ -0,0 +1,35 @@ + + + + + CSS Flexible Box Test: display proprety - flex + + + + + + + +

The test passed if you can't find red color.

+
+ Hello +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-base.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-base.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..cb278f1f19203de36303585f0485c141e733b535 GIT binary patch literal 586 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK7cnsd$s>J^TY(g3fKQ0)|NsAiOoj#q*GquRQBq67lFk_tsDQ-`xLcT|f2XuHHwrHR11;e(hXiKRdtpu~hQT zi`Q)N(dD5M%%ZVQ-R+q|Y*FFNBE_r|&ENwITxw?%AL51RJ$ z`WC%it8c$v@BHeHa_bWNwmWvp*B-s;s<-f4d(S*<+mEN>>Vey;%6=B*+$^zZ2NPahV4P2wng zUKzP=`s=5q#dvN-uSZvQ{d)aj z`L*Yt96Fest_Un@6EJOAq3FV^n8gw3+#y9>p(pCsstiF+F3{M|a5$ + + + +CSS Flexbox Test: flex-basis - positive number + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-basis - positive number + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-basis - negative number(width not specified) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-basis - negative number(width specified) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-basis - 0 + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-basis - 0% + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-basis - auto + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-007.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-007.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-basis - 50% + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-008.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-008.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: Indefinite % flex-basis should cause height to be ignored + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-010.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-010.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: % flex-basis should not cause engines to treat items as percentage sized + + + + + + +

Test PASS if there are two boxes with blue borders vertically stretched to fit their contents.

+
+
+
+
AAA
+
+
+
BBB
+
+
+
+ + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-011.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-basis-011.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..618758e92a2e957c58622cc1ec208771c164915e GIT binary patch literal 789 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKTbP)Eq-gc>c|eLYz$e7@|Ns9$CIiF&;9$24 z3=B-iJzX3_Dj45R@t*Wpfu}8Tq4a~lGDqDNLJQ{KJHxx{d*t&ei`gyeROb48Kcw~Q ze@v@zbV99L7}yLDVYT&?$0u$7suqFg5l^D*T!O9h-ygY|RJ@?F*#2$0`(FRz{>7e0_$E{{CI@4n}-U*7#@*R(q0x4pNn zZ@w$|*M{G^C||~``@P4TK$RPDE!&hIp6qK`+cD?m1&=z#MVk9Hojxl~lIvPx?s6$C zyPq+eU8|bT?*>n(|TuiiSY6;rHv6T@1rh7?r^BsXEU#ww|w$byYrI*la@pm zI<;;wpLi^D`sLEptM{0nZs#~L%a(o8-s|g*>N_SYf8Ra#MCOvs!VM>;IbUal#&Lm> z`O~U0sr}ELkK3m&XPnr&zx9WN!;{HE@&X^3UG{toRkvdkkW6z|c+{KdVpr1AA+PX= zx1)kt>0U#JITq#)&Fh;lH=H9%La61fzp45>30QRFkTci207Z()z4*}Q$iB}E*)`F literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-box-wrap.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-box-wrap.htm new file mode 100644 index 0000000..17305b2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-box-wrap.htm @@ -0,0 +1,51 @@ + + + + + CSS Flexbox Test: flex-wrap: wrap + + + + + + + + +

There should be a green block with no red.

+ +
    +
  • width: 120px
  • +
  • width: 120px
  • +
  • +
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-box-wrap.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-box-wrap.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c40d31f869a96c737cf31af64ffd8a9390f1dd41 GIT binary patch literal 886 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU`%0R28t~8*uDZtaR&H=xc>kDAIM~AU~s); zCfPQ~RwC|&6$_YOvQ~s?n}263p5Ae+a;we9)Hykucw5dacJ1zM+fjYu zN?ym&$~kQb2WwJ4y_zxqZc^6ycZ(++4L=yJ66gHJsAj#_+ncoyiug78e;cu#@3>ZT zVh^93QUBR?7yGOl*HgwxmP-z%r);{pEK$;{WB>lPle22F*Xl=f?^kYF5qW==t+f-! z%6ABTUJM|x@OwLtEk<*RJF*$PAd0CmW z&bh}XEZonuCx;*Z^F}PVx=g-RXCK!-rH@ic(;rrc0=>&Tz1pMl#&p99w=K_{pOt=? zP?=+=@+ + + + +CSS Test: flex-container-margin-not-collapse-with-content-margin + + + + + + + +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-container-margin.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-container-margin.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c05913f93ac04e69d8e9fb0e306cf1913c59647e GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKo0yn^mdKI;Vst0C1Q`KL7v# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-001-visual.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-001-visual.htm new file mode 100644 index 0000000..fdbfd84 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-001-visual.htm @@ -0,0 +1,29 @@ + + + + + CSS Flexible Box Test: flex-direction_column + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a vertical column in a red rectangle and no gap between them.
+ 2. They are all appear in upper left of the red rectangle.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-001-visual.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-001-visual.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc9c62075c62beef33109025f481f17cfe084af GIT binary patch literal 1810 zcmcJPdo3K7>b!=T;fL(xt(k! z>lQI~N~1C&G)_|*w@h*!BgCe~Fst^DcF(c9XaCsepXYm?=RD8ndEfKAZ^|JT$1QMG zI0OROg2&mpK_KEN2t*7eA-*1AK7o_gox-7mN9{!-(Yj0!LB2SV$d))9XcZ6+fk-{X z+u0tC7Ms4dIs29#VqcmXC?9sY$Y2_VDVND~mEp!{V7^r}_4Ob0S;@#Xqsm}#2kk|1rC`9yoER{y z1q@37!>-h6uQ4$oGs*iB5{Q(+>z`gG{+w0+>Spe3k!D%Ep>D)N+*(fAVpRg!Y!&&T z(TYw(DH-N2H5&FJZ`TOZXhR) z)lUgnlI)4>+-9I3+z8TPXiYwb4$qpQOe<#A7iG33Sb1ypx+mubG5`FNbh)P%>dQ(UoK+xyf-)nJ^y z4J^OSnv~q6T=$5X2|r;`Yi_>P??p;TSlI-@CE|ubm?pWoOEAu8E!Q{rei=uOe|@oF zb_bx_b1jA+ZL!$Gu42woDp@9i6h|(1sQa@+#r@zcYp*n$1ib}~8hA|RH9h0#p?&PU z4#&N9P7B0`&na!F(Mo=Yc}wh8l0O;qNE{+GIm^sRu`1HZ<*7vd^bE#-BC8QZO`l&l za9)MQRP;kCFI%Y?N5FQ#USjHc59N!R4RuhXd!c>Ddv$bltMspw-xU^elJC%kJm~XJ zpOE#TPG-8rsf`~z3Y6NO-9rbfhLA}Nw(jct(jVI1EUT`Z#!YMwC9Lv0iO%%F5|BYA z9_b&5cBZWw4&<$0@ZFBVMrgmDlZ~e38u{LWr@S$lxFl;f)(YceJO-MT6nIJsj$@oR z?1^IRt?NgQI8B=wmt`EIW0z~Z%tol-V71fL8fyK=PQv&v9y4R60OvI?X=a7EW3;vx z7u$9YivG~(N?Vc*pY<*V*JfrZXSk+-d+6L@mItE&%J+JvK~ouea0^ zg+@6r@Drp|bZg9+W_sVN*jQwKB;7f~Q|cxCe(fR7)r^`j*mi0U>0Ct=jeWnVISWLu zxC3bwldc2g&%)46Si((xKRFGmdcMuK-(daWb&q|fC3KHWnGV4zd&L3vJy{8WdrC=| zgplqPPee$Ab%mD^fhd*=^nw)j&(fDnLyeN+GxxQdZjjx$=D$b1wGpH(U(vowG$__6 z5HrWC;mZNYq_0E0wa)+rwzNGKz&sVKQ27o)A+RPdp+>bFq$L0(+5!b-&|Zq!w%cz5 zwB|Ph?hB@y0Pdv?&$IDt7m$Fc(Y=i>FOG&t1pB;us!kA9ZOib|)Kh#cX_8d&PFcr$ zKe)}-Pa;KL$KmhA_@KOhG76o@A}DmFqXgEH@d;}ss7{HLr3Y$>(~9e*dBGX^+U*!c z1#Mh93hiww(YFKF72x}#OWuEgq1`U~uZVx3|23r{jIu38eW4_>pnu^0X8~PbwAfX# XOuKTcQhJWemj%SzyVzA@eG-2MW~US9 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-001-visual.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-001-visual.htm new file mode 100644 index 0000000..2d77d66 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-001-visual.htm @@ -0,0 +1,29 @@ + + + + + CSS Flexible Box Test: flex-direction_column-reverse + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a vertical column in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in bottom left of red rectangle and from top to bottom of the column: 3, 2, 1.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-001-visual.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-001-visual.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0790dcc73f187595e4e196d478e7fa3c073d33ba GIT binary patch literal 2024 zcmc&!dr*^C9t{hPA-vKAc?6IIErQA`>=Lkvgf9q0l!gFBDj|hJfeMX;svr%|L}GbG zK!HNFN)duZN+^U#GmB9VUFJgQLP*&Hlk{*tbe(~8uBuU^iDNt_1S*Mk>v34SuNF^7ZlMEz~ofDt8`0;kY`1^FUEqc>nE zfJez3X_2F$2#tFr_lapHOw{J=BF1s)H>#TtG^oWyFF@Al7eIq@ob&;m!2_`=&J(lS zNj9=GQg{EyxRxRJue-mRDE%Y~-9F)cLdE(v&I;Y@-dq3dI=>%!F{x<;4^LL%&0rpG zh8}pBhia!#B1Zw86^vX9EX$PTJOp|_%{BEeCH~ zwW_>DlrZBMps92qOm?sgZ`c04^hy+Cjj&je)UI?@IJHESs+K%1R2~wOl>-?Q@x+%U zbJP!Nf3S3L9IibvB;fG2=l=e1F&0+7JSe{Dah&I4wYsqB*p_~@+pGCeowR)Hr_fue z`km2bsZWD_k>frtmk^$o<-I_*>InA4_PM#PKtNOdYHG7tZHHC%pyHO0<@on(+icxC z7}RsEcJ&p)%iP&xKRgXmi=$u>d*hG6hRy1yBpqw_`wb@_vVAR(6KgTNoX%`OKCNlB zIklYMdR}3_5e5t@MTXCQQUCtF3rA}7;z7EcxXb9NnvB)fb)g)*DD&$#fq@1IiZ*{J zz0lUErgb?K+k&d8px?bwSB-yEMSk3ocFUN(o>}|ouuoY9?Q-%abstkepU-mJbK|y^ z<(M>`iR*kRL5^F8>E4Xa$c|YmQ93>X)-p0*Ypzuim*C=nM00fI-kuBS}p|#|DEW$^U#HPuM30T(SI1?6OA$2qY`#MGuP@xwY$Fv`pOEUnN%k#y@z`e7a zl%gW-ejxsfSz0le){GTRI6f3?GufiL^5%EGrn}B$69Ssl^(p^MztYRD8nhK2D^(1B zHZ~YTR1<^PS!u(Yq4!elCz(K2q16b(YxrD-%Vjc=@SEi1j+r#^%ObWC9IuoL=6@3( zt08yJp@I^cgWDq08Zs5)+BR{vd3!MvKXk-YW!R=metZ8xb6xVnt04FGohaTNi>$S} zKl*x{zZ;fbL)oh`9%!yNZOO;8S{{7Jdmfmn?+SadTEdw<{*&NW!C9bl2bbS6ZwdM4wHKSXEpAc6kQ9ln+Xl?1upL{WxGjSa=JNzEq`^-n2FZU)S z@$4axaA2!DWL~)sjOn%MM;4ek-p7pW&U@k-0{1{@j1QS`!)ISjF}=mOhRx|pr`-ue zGXKfhDsDBc%#FHs1`1rs^WND3;0nU@uw$o=@CXNVrvicIqUeLU{6z2D5+7{gfJ9i?dj`L1hj=69o@0iH{)a2f7`rPJKW-G7IIp&D##<0-0ou*O`E6+tF zka#$ZzxDs6T-^FDLSyv+m{2b`@%A7h!rPx# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-002-visual.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-002-visual.htm new file mode 100644 index 0000000..09ce44c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-002-visual.htm @@ -0,0 +1,43 @@ + + + + + CSS Test: flex-direction: column-reverse swaps main start and end directions + + + + + + + + +

Test passes if both the two columns below are identical.

+
+ ABC +
+
+ CBA +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-002-visual.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse-002-visual.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5ce25dfea6310b8f7dd7842f76a3327de22278bf GIT binary patch literal 406 zcmV;H0crk;P)$c_Ihq$<`V3xi23FG{em-e>RpI_L?*cGkjeGsiiq|i$=cFp$#zKEA?8R8fuulEL(q#zZA;fGb%)3fsj>VS ziM>L88o?(>5a~h&1d*pjavp#j=Q0jKcqUJZ3_17#LF8!> zvjNC)ZoCMQM?;QNT|+M8fFMGI2oWMghzv*G08;# + + + + CSS Flexible Box Test: flex-direction proprety - column-reverse + + + + + + + +

The test passed if you see all the cells are arraged vertically and reversed.

+
+ first + second + third + forth +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c6f43f235cbe40635903e2521f0e531fecce7dbc GIT binary patch literal 1041 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU}R!q28vWajeZZLcmjMvT>t<74`hOH1B3c$ z!&5+o2RvOILn;{GPWA13Wg*hqt8~m~($^N%9V*qrd%2dx`F?+)%b+=NkF;AH!=&5S z)f&ScR>d{WN-o~#vF^#UU(dVG>bi0$wg@-@37?47m+yadwZ5#i_13yGYwKRWtNo9)U+31X_E)m}6g%<$rvLmCSKm0SWWH&>a#E>yxN>g# zn;P-;H%~Rs^!wG%koj#;WZtWiTysS7+HFniUJd0V z+b7L5UiQcS{=_i0p8MObzN|jFyh41v$a#0~cTY3BR+ZhGDAS)C74>UY*i)I+IXTbw zZQS}Y<0Q{yk2-n#lbL+g^BxyYJXfQ7X{Nu@E1gS{lU<&y?|i$o=N ziQl$)oI7ExA9CQgQjtQ~$An1T|9_TrJ;|D~D$FV`Sk*IdrH9(F;PrQ+7YR+|Qs2K} zj`HbUYw9=Z9hG}+vMFV`l)Cbg!#9^!E%Oik-0!;X;`XMu%*@;DD?anA5__3suTHq*XqS55ms^lE;?8B?2`{s8{ zdMZ~PzU#!kzwJA(1e9xGgq1U`8hqA9dGjAK>BL`5BE z!LB{KJsu)VV{^T*fI|w+3Yg$xC!m(%imyTU{Zj7l7BDM3I#0<#X(7|ZSYxx}y}@y! zdP3z#+Rtj_iYhG>5_A{rvWZR!I43VH-nBl;IigKTq_TYCp@le&$KkEb9m_df=V^TM z{^_=EFSHYxvrS*IsEl33i=P zYIOz$5X|)`#-j-Ka=Er`IP>tTcdfmksM)3m$?-c*>|5`y5~8>%yH9%g9;w-fT%Tn0 z?$xe$p9Bg+*83q!LN=}iE`7b+8`D-E0_x^*)%f%dn}xlnTyj`7K-mo#nU#|N88`2_ WDm^9U=3HQ=X7F_Nb6Mw<&;$TYr^*xn literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column.htm new file mode 100644 index 0000000..fb10851 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column.htm @@ -0,0 +1,42 @@ + + + + + CSS Flexible Box Test: flex-direction proprety - column + + + + + + + +

The test passed if you see all the cells are arraged vertically.

+
+ first + second + third + forth +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-column.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ed9a5315cbc57ef9e96c90c89947ef58dabd642b GIT binary patch literal 980 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU}R!q28vWajeZZLcmjMvT>t<74`hOH1B3c$ z!&5+o&7LlfAr*{ouLkD5vXD4hsN9_%H|cAO>W-j2osq1U)}%dK$?f2!^1e-7pW)== z^LasSmjbqaDS9)nr9qfAkN_=YFyq|sJvfj-TlW*Vt z8C!F7u@0l8v2V7|93QFP`O%ZrlYI9@xuv?S%Q)*j|9D(grTJaYcOQl750}}`%}u+P zJkLYfyr=&5u_gP%|A^c_8ZLF-+xy<><(VO-pM_68{y1aVw12X%cXDM6>{rY1K&^B_FmYf?%T%-&X>kd-u`NLUZbO!`dDa&nlAtSZ}N&pTIsX9 z)hF2GL|Oe%~^?yd*R(PF|t%;$L~7vT&(PwF9au z_g5Nzl9au4r#lE3ciFXd zZ~fn$0);#<_}^T-$>lX+|3BS*^&E`d0$Vs75yWbz69Ut$ZL3$kZeForGUxo=hZK*m zlk-@?RCM9_+~Bz0T?HLFYd01@wV%ksIn^QN!!99@4=oQI&fRL=k-qYfqH{!_fYUWT zO&g~RN-VmBh*bhcEl>T<&wDjXah`ya97knn-0AEqaiY9Gtu5^Lo@#I5QdnOV66(NK zDRqno=rFl=cer|mqb$IPfVJ8kn<0T)E-Hz}D8Vl~J)y*>2;)nFk-4JGC;S_b? ztGWX6lqg@P|B?2)8ppL177BGJw@h$%W_$W4a(BR=b*3#(ITTagPB^4Y*f&ZnYq@{{ z@b1f-mFxHyrR;isZYrN;S&PCKKFf^tbEUqn;BJ?(etulfPB5NXC4{N4#b&RTV&Qcz zM^S#sLq#nDo60;M3O0!;W*`$`sRGfCU64$3Y~Ho{pDf#L=WKIX56qVgp00i_>zopr E0QY*brvLx| literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-001-visual.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-001-visual.htm new file mode 100644 index 0000000..9347b13 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-001-visual.htm @@ -0,0 +1,29 @@ + + + + + CSS Flexible Box Test: flex-direction_row + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a row in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in upper left of red rectangle.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-001-visual.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-001-visual.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a1cd176291c99491f73e020405712d73b9de7d42 GIT binary patch literal 1733 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU{qjX28!%E$W;WSgaUj*T>t<74`kK@iU0o{ zGamwJnacZW3=C{}o-U3d6^w7DHWt3t5ODBifAc5%K^?pK$_M9{wBE1RX6Lw|q$p`Q zX|LbC*%eGo2M-CK<^`IB0CiSEGqjoYE#&JS#mj#GestwtSy83+e7~%VLrzSr`@rDX z!5yjnr{-jw(bdbF|9C%Ld2#tSHK6hlGCOsQ+8!pJI!;jQuJNOwR7c_&|(oMy=Q@ncNE11&CHfB4Vbp&w9!&6 z{hyV&^Q8no<(%+1=Fnj%z{J4dY+*k=_4&UDvyS(D4_$VpO7=cnXgBxR-U@yno$q1_ z>GyYjS}?)wNsQRl3)9Lg+62C@TZld@FaY&l(;`TzHi zIA71RyY6UBKl<+8`VDN4fK!m##L8zg0T>NyL{Qr)`C& zF7n^we8_F8@8z#1*F*fie$m)}ha=E&>Vo{w7QF&r|JBU0D(3$t*;lSU?PdLP-&>|< zp5m#dLQLcQ+Ahg=J#!Xc5%On z{cyJ6wMgNv33;{l_YOOqs(9J5rTCStwZDgCdhiLW_H`|1KCb+Xj4LTY3K6 z@$Pns$lP5$$(C20qff4VIrmeIVcUu#S@q>Q+bi6QeonhR`?ury+Wr0ZQu(!Z_ocV` z9#?x?QTpd+@~rP~L=@xC7jOT1S220<3GK&wFMqGxXj|KOTj9x?rzb5R+GSsKe{}Vw z6a&Mz%ulP|JUN%z{Nvr+mB*z|-BtdcJM(mFlia133TAA#yKgHk5-)V=h}#{tM9$x> ztlGunrE<4LN`gUU!oKh#V8n?kD*sVYv~uFqRQu<(YT|~=de@i!eg8PyRMoV*WOYo~ zJ?-QrRU0KX2h7y}a{X`Ce4Fsh%gK6=zO+BucJdP+=j=)5K_#}Ovrny@C%1D~;@^*6 zm38@5>Un?OtU0&+#r9W={Z7bSRywC#ID5%XZZYo9OYOEE)qk^Y!nw?g?~lf>`pu|V z7tm2}kT+SUsK`%x-?BGbW`u>s&N!pFbpB>*=~(mUeG`oGUkX3JB^30yqlKCdr zL;_x}esaa)Z^50gRvWi;(@lYKbGB9Ye6!Yl_x|9=R>dbyURqX7tcv|dP%`s}y+8LJ zJuWss`p?}*SF>xh1(JALB%G}$@G2@@5*LyZC^GUoxL?}o-*3_XR!;x^2>%x-()!h) zaD;(bGduXi6BfngMn&y43QsB(jz}asJz1>%r2mAv)45cS4m*W86S5_R7V&d9txI+B zD0UDi(p7ev#r)~w4Tlpqc{nmb60W}mUUGBt?%$=UZBnE3B&II^ + + + + CSS Flexible Box Test: flex-direction_row-reverse + + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a row in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in upper right of red rectangle and from left to right of the row: 3, 2, 1.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-001-visual.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-001-visual.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d60d268263eb7feeb163963193638cb337a23f11 GIT binary patch literal 1948 zcmeHITToL65WNx5OXL|uMG(27Mjqt~qf+D%xI_{}5&}z*G%UG`tF-KnNt* z6sA&26;K#r03U=1h_!(z7*Mf7DcX@FQX&H36$oe={pd{p`q9ts%+Btfk3G9PJG=S& zNCZ>#Iy3+ProlvfH~<(p003&6p@AN0TLX^j4QAio{kwHKo!-@Jz{%HuPM4troHUgx z09dg*7$2}d1vU2FVzFYYNp9%!8k*yPNz<0Bx8xp4^Nnvh=-XSn{d}edT)ghqB*XV#(g8GaV#30_zXH@$lt} zzLuqmE_yY2cF(3}UPEJaOImnwd+KGFJwyA@JL?e_@UZMpFc^D06il%SBa%YGi6xi9 zBWrVcwnQx6n#~KY5?-owP{{18v%D3dtU}pIC%)4=4_R$jM0%8JWDh8|jon1`&vSP$ z1c3V%J#gG%F;0b^lDXT3-P&8}ZkNzkLn$~fmE67q+|~9}XCwTF)qAfZrIF2nd5ad% z_%di*zzg9n?W#{o~RN;L;FLLZBIRaQ`iw=i&ZGS=BN-|n`Vvqqiura z%Iwz3jGEk1b_^x5n<>QU3%;*jA}OuO$jlFCdxcgHf`y z-}1TL_hf{p`Q4wF^wDeNLzlIaiwPzer3Z<0NT>=F`zS9ma1X_z-{;%ZC7SNGFjZ_) zmDEKJZ|J)jUkZ9vj9lw)$?gogf|b8K!Hh~0!42jt${nA(TZURzw_%TdFGK-=pmNIP zhN9;^a+#Vj zw=?aw)}VHR*We^JdNlGp$!*#}X;_0&rcjP*Wog5ma8Fn27a!BmTQ--ZF56ue*jU#* zWq&$KB&g?9-kXmT-k3k2X<7{EFZaRqGDcSpV4kg|%oXTHe=`Tw!|XxKFtNV*wyNG$R9F21nThPRZGc!KN=U@wAABhssAVA6zTpkr!^RFm z`0-OA#PU;$hN=|I#as=b9Jr>Vkz9t_M~-eaco3cqAr8k0EI>Pj|*xKFHx9TZNnG)!ET zAeX1``<`#&x%Kw#%%xHF%sLzzbYIR|OT~khcXC+lB8x0uwh6c}OI}I@7c_*WMB@c> zq-e?>GK2^=ErE0BfUaGSZJ=9Lpm-JN-kHaX!bh!#W^56ma>%%Ng(2eh`Ui^({tgNZ z$vEs|80}ANcxrjo3}jIr*g)D$|A(77RjwPg320+$++_qYV%bs0%oY;Rwex-Q(H-fl zEMiPPm1KZ$P*7*4#i!FHS^FQsoC?p$zq|go6dbbUR0cEIU;jihx7B1MCZ<|BpAIB) ucxxedB!(pc^DQxsn}YraKF&kGWa*|E?*^4MqiVv(jSJpQ!V8gOS$_dzm`F?j literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-002-visual.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-002-visual.htm new file mode 100644 index 0000000..222b398 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-002-visual.htm @@ -0,0 +1,42 @@ + + + + + CSS Test: flow-direction:row-reverse swaps main start and end directions + + + + + + + + +

Test passes if both the lines below are identical.

+
+ ABC +
+
+ CBA +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-002-visual.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse-002-visual.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..301ee0d4435af9c4f3dfe5f70d0847da3fb6c591 GIT binary patch literal 374 zcmV-+0g3*JP)T!KKTZjI>L%f9T88}WL(X$Y4UmZa_ zEl9=#l`t0`r$w-cW1fYT + + + + CSS Flexible Box Test: flex-direction proprety - row-reverse + + + + + + + +

The test passed if you see all the cells are arranged horizontally and the order of cells are reversed.

+
+ first + second + third + forth +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction-row-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a066389b54e0d244ed42d12ffc6c3e117ea7e000 GIT binary patch literal 929 zcmV;S177@zP)B}USF-fKI&bJ;cvU*+Z7 zd@J#E)-wv%ve>MN%FlEBX+3#2+8IoakZtEg@{zpkOvHLF6FJEl70H*3FeftTyIG_^ zy}OD;Dmh!)GQ{qQFK%TyrH_0`#D%q&-%GNhkt|z!rAIPK+6C*|1$p9#rk2F+0$0@D z*yLzkjq2Ci=?r#SFOH0Gz9OFNxA3Iy{KijwX}@hPd6u`f*ZFQmte;z*l>U;@Sxj`1 zTgz>uqHHbxQOM^_6^rf7LL*}nEzj)?1|!UglwnS!3}+(lbC@y&9Y*ef+f$LC8^lDc zip8FbgeJNZ`C^2DNR%z)WWn1>%RzurrwI^J7?>{>o$}(yPLSgv?8PxO@z7? zq27vgxsufr8ICYq5!d{gb&Ke1dksWv)18XwJ73!4TY*y%tJRA5Ns%tMtCNq6@Es!l z5)rya#4iw$<;wIV$?R+ic6#WkrQ5B4?&zr!xL>m@TY9BO&llbh79+#S?r*d8?Bj~E ziCH386+MSEs$WksrIlx$!51UUKqFs)8}K*$@AW*j$byBOU^@G@+5r48-}Q$Fk>4k> z)30nvd~d$L4@z|8<>I?Otv#Qs35`g2bP;kfc#47e3(M^zi;(jqE#KUOi{#r+k!-v6 zjII_bDc|zRy)$jKbu|hvD@?NhfQr}gi zzN^UIRqpj=<6d1gIl;X|yd7T(HzVQ-uE;g{-;~I{skWII_reu9R+~SvBE{^A6o=!V zxww7B^_pvLYe}^`ZY>_TSS(U36FDvuDef&&Ef%R}<9Jgo7O84iB`Z>k#$u7WNLysH zhDhyR`3td`o1jFzapcUYC8waCT2(Q5YVo*S?dYn*c)iO+>S;b!jDrY7QV>ZQh@=KY zQUfBX0g=>zNNPYNH6W52PbLzd00#g7000000ITsA$33yL2$kNU00000NkvXXu0mjf Dpdhjc literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction.htm new file mode 100644 index 0000000..f691740 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction.htm @@ -0,0 +1,58 @@ + + + + +CSS Test: flex flow direction + + + + + + + +

flex-direction:row

+
1
2
3
+ +

flex-direction:row-reverse

+
1
2
3
+ +

flex-direction:column

+
1
2
3
+ +

flex-direction:column-reverse

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-direction.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b5bdb27a0fe8932286b46dbf2f2ed3980ad6a82f GIT binary patch literal 2837 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 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-childmargin.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-childmargin.htm new file mode 100644 index 0000000..5a7b361 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-childmargin.htm @@ -0,0 +1,52 @@ + + + + + flex item child margin + + + + + + + + +
+
+

+ a +

+
+
+

+ b +

+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-childmargin.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-childmargin.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a356877ca0c5b2f917540d8b7f7686baaf743d8a GIT binary patch literal 851 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV6}}?<0jlGj&!TAPl+<#Bqf<~w zS;ftxXM)7206F1tuGOcyH28J`Pn+WeZxJ@Vrf*OCS8l)e_piz93^%rf?5`*0*Rga8 zs<=&%a7>~d<6o1_w!Obzy-&y$eiGJnq(?!w>Bt<8PjdvH6d4!SD4x%ETxa(B_46N2 zEIaRXDkLkK3xHfy?{VhLnr272+i`zylyBI^S)@%%H`31cNG4kfvM?|_kpIi%y)bLn TgAl!ZP?qp?^>bP0l+XkKPiqEE literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-percentage-prescation.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-percentage-prescation.htm new file mode 100644 index 0000000..32881e1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-percentage-prescation.htm @@ -0,0 +1,37 @@ + + + + + flex item size prescation + + + + + + + + +
+

d

+

d

+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-percentage-prescation.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flexitem-percentage-prescation.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4b61e2a8b31bf3747165c42a4d34b8b57e61eee6 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^r3?&=HcZSwR@9JKSlrm literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-001.htm new file mode 100644 index 0000000..909e04b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-001.htm @@ -0,0 +1,39 @@ + + + + +CSS Flexbox Test: flex-flow - row nowrap + + + + + + + + + + +

Test passes if there is a filled green rectangle whose width is greater than height + and the number within rectangle is '1 2 3 4' from left to right.

+
+
1
+
2
+
3
+
4
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..9061032971513b165d7673009e53bab49c75f091 GIT binary patch literal 1069 zcmV+|1k(G7P)00009P)t-s|Ns90 z0001h07cR<761SP-$_J4RCodHmC=>sDh!74106s=DM3C5?!%NY{Y*N@LsI3{FM+W= zPIfZ88{-}QIdQN+f-eH$OeT}bWHOmdCX>lzs>&Hp|H(d_h6Fp!EK@5DC(j>lhOL-2 znlB!-i0gtzZSfX9)95;e+Umk`MT^K9ZVSXO!pWpLnl)l$nC#7Jq^!wPYl)1Nty>`d4&op-&bnqtMnCd&)aT zxYCEXXv5}b@k(^DQmS*~F5q6-b&uq{aOlX#yE% zAmauId}4wvl^Nl^2GNLHAgO|6POXA)2&7duK?BK*my>Qn$=d{y>xA52+T5Zt8I}P* zoepu=AR146*apwK(30uYKxmc`M5-X_r5Taqr(I6A7q-tVBgj&zH%pMZU37$k;L||h zryvq{1>%~w6o54dxor^5WXXv8dmv#a5Yz))ey9qqW_a0d11CKaNv-l)J>-mVFMn#) zLO+iZ5VQuaeX{SRzMw~|654D_h}z#6Q(H3b4c)P_YxzDv zDRi@uL-I3U^wadVGsv(2!D6+(^J{|DFY@E%eMb@jRT8BdX>fnNCF5iCZ=tWniVP_=?+NR8-!d=0HKnRoOIi? zbD$u(oDrHpa+-tC{vh}?5K@3pLC~ObPL?1v9rk`e!hH~W8sudcXCzMpp9oL70936Z z;f%!Or2_d`O`cdYvIAD-8&3~xsSxd_PlA~;HQJtWA-XOPPwL3Hgm z0lDeR`N$w=dFmN?J2J>y%gFWpz#s)jLGpn?^oU&M>DhZ>-vc=xALK)>!jF9T3-NYO zdoRpD45SkoNN+Nb-ee%X$v}FOf%GN==}iXGn+&8k8Axw3klth + + + +CSS Flexbox Test: flex-flow - row wrap + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' + from left to right, top to bottom.

+
+
1
+
2
+
3
+
4
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3f391cdd1b27a75dc325780268eb860a80ee8c GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq4{VMW5vVK?0s-JhBd|(lrX~Xra^kb~jtZQYF&uiLGgw=B=t14M9 z6O_#C>3pp|N&Vjkee=o-CZ2OGe1lYC=L!AXqZC!tGs(WlvrOY|nXD zCja?qX}@!4oA%0|;odv5SI@G0YE%@U`DE?U{o8fV)~@d8_-LNf{XyoAXlH10<<>Bz zLRBNTwTi{NI*za_8Qr}sJMmJGZcte3HKk5*f7cYvO6w;_V_bw@FI_P^V70j5xr+}A z_Sy9>@rr7mB|347>yjtk9bYzxWkz4x7kcRYZ>6>8JJtjV7FJe!Zp?B`DcdsfLf3_w z4_l6GnrZShEJe?Fd(_&F-J)RSDlZHp+9%EzyJ-2hizBzwBS+F?n@g{vkZH}mJm)@{ zDaljkKc2+XQv5S`(X=P0!!DQ=?YWrko%LLIiuuwhr#8k%Uk$b_yfaBI#kf+A{rEa- z-OT&9s%IrXPkT`>b5LGDZqMBZ_P5{t{wXP#9-fk;Z5Pkdu>VxSUin(So_$?9Qd?_< zbHBv*wf^^D`BgXV;ET|$-h#44>PnrPFr#G<)0k4C%jIz?Gc?@=8F~n?d~~y z!&(fM2tTMz*PUm(Azr)a-{zjbbF9QHLzR}_dUccex5n|k9aoa~zj1qV<)dV#OWb#* zPn(1%x`Cv+PON;+{qcvt>LjbaD3E@G4u>Ng0vp*})-VcYI~OQeCd7`)Or~Kne%U|`(`Tsg%N!-NX^X+>DJ!QL1+V?&ez1e#rPw&WSvGZ~g tE(%F39TCh**BUxZ6_R)fGeH?{_rv-I!Tc!QLWvv@*VEO{Wt~$(698{M%CP_d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-003.htm new file mode 100644 index 0000000..7a9066a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-003.htm @@ -0,0 +1,41 @@ + + + + +CSS Flexbox Test: flex-flow - row wrap-reverse + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' + from left to right, top to bottom.

+
+
3
+
4
+
1
+
2
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3f391cdd1b27a75dc325780268eb860a80ee8c GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq4{VMW5vVK?0s-JhBd|(lrX~Xra^kb~jtZQYF&uiLGgw=B=t14M9 z6O_#C>3pp|N&Vjkee=o-CZ2OGe1lYC=L!AXqZC!tGs(WlvrOY|nXD zCja?qX}@!4oA%0|;odv5SI@G0YE%@U`DE?U{o8fV)~@d8_-LNf{XyoAXlH10<<>Bz zLRBNTwTi{NI*za_8Qr}sJMmJGZcte3HKk5*f7cYvO6w;_V_bw@FI_P^V70j5xr+}A z_Sy9>@rr7mB|347>yjtk9bYzxWkz4x7kcRYZ>6>8JJtjV7FJe!Zp?B`DcdsfLf3_w z4_l6GnrZShEJe?Fd(_&F-J)RSDlZHp+9%EzyJ-2hizBzwBS+F?n@g{vkZH}mJm)@{ zDaljkKc2+XQv5S`(X=P0!!DQ=?YWrko%LLIiuuwhr#8k%Uk$b_yfaBI#kf+A{rEa- z-OT&9s%IrXPkT`>b5LGDZqMBZ_P5{t{wXP#9-fk;Z5Pkdu>VxSUin(So_$?9Qd?_< zbHBv*wf^^D`BgXV;ET|$-h#44>PnrPFr#G<)0k4C%jIz?Gc?@=8F~n?d~~y z!&(fM2tTMz*PUm(Azr)a-{zjbbF9QHLzR}_dUccex5n|k9aoa~zj1qV<)dV#OWb#* zPn(1%x`Cv+PON;+{qcvt>LjbaD3E@G4u>Ng0vp*})-VcYI~OQeCd7`)Or~Kne%U|`(`Tsg%N!-NX^X+>DJ!QL1+V?&ez1e#rPw&WSvGZ~g tE(%F39TCh**BUxZ6_R)fGeH?{_rv-I!Tc!QLWvv@*VEO{Wt~$(698{M%CP_d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-004.htm new file mode 100644 index 0000000..de59197 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-004.htm @@ -0,0 +1,40 @@ + + + + +CSS Flexbox Test: flex-flow - row-reverse nowrap + + + + + + + + + + +

Test passes if there is a filled green rectangle whose width is greater than height + and the number within rectangle is '1 2 3 4' from left to right.

+
+
4
+
3
+
2
+
1
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..9061032971513b165d7673009e53bab49c75f091 GIT binary patch literal 1069 zcmV+|1k(G7P)00009P)t-s|Ns90 z0001h07cR<761SP-$_J4RCodHmC=>sDh!74106s=DM3C5?!%NY{Y*N@LsI3{FM+W= zPIfZ88{-}QIdQN+f-eH$OeT}bWHOmdCX>lzs>&Hp|H(d_h6Fp!EK@5DC(j>lhOL-2 znlB!-i0gtzZSfX9)95;e+Umk`MT^K9ZVSXO!pWpLnl)l$nC#7Jq^!wPYl)1Nty>`d4&op-&bnqtMnCd&)aT zxYCEXXv5}b@k(^DQmS*~F5q6-b&uq{aOlX#yE% zAmauId}4wvl^Nl^2GNLHAgO|6POXA)2&7duK?BK*my>Qn$=d{y>xA52+T5Zt8I}P* zoepu=AR146*apwK(30uYKxmc`M5-X_r5Taqr(I6A7q-tVBgj&zH%pMZU37$k;L||h zryvq{1>%~w6o54dxor^5WXXv8dmv#a5Yz))ey9qqW_a0d11CKaNv-l)J>-mVFMn#) zLO+iZ5VQuaeX{SRzMw~|654D_h}z#6Q(H3b4c)P_YxzDv zDRi@uL-I3U^wadVGsv(2!D6+(^J{|DFY@E%eMb@jRT8BdX>fnNCF5iCZ=tWniVP_=?+NR8-!d=0HKnRoOIi? zbD$u(oDrHpa+-tC{vh}?5K@3pLC~ObPL?1v9rk`e!hH~W8sudcXCzMpp9oL70936Z z;f%!Or2_d`O`cdYvIAD-8&3~xsSxd_PlA~;HQJtWA-XOPPwL3Hgm z0lDeR`N$w=dFmN?J2J>y%gFWpz#s)jLGpn?^oU&M>DhZ>-vc=xALK)>!jF9T3-NYO zdoRpD45SkoNN+Nb-ee%X$v}FOf%GN==}iXGn+&8k8Axw3klth + + + +CSS Flexbox Test: flex-flow - row-reverse wrap + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' + from left to right, top to bottom.

+
+
2
+
1
+
4
+
3
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3f391cdd1b27a75dc325780268eb860a80ee8c GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq4{VMW5vVK?0s-JhBd|(lrX~Xra^kb~jtZQYF&uiLGgw=B=t14M9 z6O_#C>3pp|N&Vjkee=o-CZ2OGe1lYC=L!AXqZC!tGs(WlvrOY|nXD zCja?qX}@!4oA%0|;odv5SI@G0YE%@U`DE?U{o8fV)~@d8_-LNf{XyoAXlH10<<>Bz zLRBNTwTi{NI*za_8Qr}sJMmJGZcte3HKk5*f7cYvO6w;_V_bw@FI_P^V70j5xr+}A z_Sy9>@rr7mB|347>yjtk9bYzxWkz4x7kcRYZ>6>8JJtjV7FJe!Zp?B`DcdsfLf3_w z4_l6GnrZShEJe?Fd(_&F-J)RSDlZHp+9%EzyJ-2hizBzwBS+F?n@g{vkZH}mJm)@{ zDaljkKc2+XQv5S`(X=P0!!DQ=?YWrko%LLIiuuwhr#8k%Uk$b_yfaBI#kf+A{rEa- z-OT&9s%IrXPkT`>b5LGDZqMBZ_P5{t{wXP#9-fk;Z5Pkdu>VxSUin(So_$?9Qd?_< zbHBv*wf^^D`BgXV;ET|$-h#44>PnrPFr#G<)0k4C%jIz?Gc?@=8F~n?d~~y z!&(fM2tTMz*PUm(Azr)a-{zjbbF9QHLzR}_dUccex5n|k9aoa~zj1qV<)dV#OWb#* zPn(1%x`Cv+PON;+{qcvt>LjbaD3E@G4u>Ng0vp*})-VcYI~OQeCd7`)Or~Kne%U|`(`Tsg%N!-NX^X+>DJ!QL1+V?&ez1e#rPw&WSvGZ~g tE(%F39TCh**BUxZ6_R)fGeH?{_rv-I!Tc!QLWvv@*VEO{Wt~$(698{M%CP_d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-006.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-006.htm new file mode 100644 index 0000000..8ecbb97 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-006.htm @@ -0,0 +1,41 @@ + + + + +CSS Flexbox Test: flex-flow - row-reverse wrap-reverse + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' + from left to right, top to bottom.

+
+
4
+
3
+
2
+
1
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3f391cdd1b27a75dc325780268eb860a80ee8c GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq4{VMW5vVK?0s-JhBd|(lrX~Xra^kb~jtZQYF&uiLGgw=B=t14M9 z6O_#C>3pp|N&Vjkee=o-CZ2OGe1lYC=L!AXqZC!tGs(WlvrOY|nXD zCja?qX}@!4oA%0|;odv5SI@G0YE%@U`DE?U{o8fV)~@d8_-LNf{XyoAXlH10<<>Bz zLRBNTwTi{NI*za_8Qr}sJMmJGZcte3HKk5*f7cYvO6w;_V_bw@FI_P^V70j5xr+}A z_Sy9>@rr7mB|347>yjtk9bYzxWkz4x7kcRYZ>6>8JJtjV7FJe!Zp?B`DcdsfLf3_w z4_l6GnrZShEJe?Fd(_&F-J)RSDlZHp+9%EzyJ-2hizBzwBS+F?n@g{vkZH}mJm)@{ zDaljkKc2+XQv5S`(X=P0!!DQ=?YWrko%LLIiuuwhr#8k%Uk$b_yfaBI#kf+A{rEa- z-OT&9s%IrXPkT`>b5LGDZqMBZ_P5{t{wXP#9-fk;Z5Pkdu>VxSUin(So_$?9Qd?_< zbHBv*wf^^D`BgXV;ET|$-h#44>PnrPFr#G<)0k4C%jIz?Gc?@=8F~n?d~~y z!&(fM2tTMz*PUm(Azr)a-{zjbbF9QHLzR}_dUccex5n|k9aoa~zj1qV<)dV#OWb#* zPn(1%x`Cv+PON;+{qcvt>LjbaD3E@G4u>Ng0vp*})-VcYI~OQeCd7`)Or~Kne%U|`(`Tsg%N!-NX^X+>DJ!QL1+V?&ez1e#rPw&WSvGZ~g tE(%F39TCh**BUxZ6_R)fGeH?{_rv-I!Tc!QLWvv@*VEO{Wt~$(698{M%CP_d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-007.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-007.htm new file mode 100644 index 0000000..55487de --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-007.htm @@ -0,0 +1,39 @@ + + + + +CSS Flexbox Test: flex-flow - column nowrap + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' from top to bottom.

+
+
1
+
2
+
3
+
4
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-007.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-007.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0b9ed1f42022b105c668f8be4d001dd80b06f6ec GIT binary patch literal 922 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq?iITmEOK>Z9=BbBAjP17)ul*iJ9{lIp(X`HUU4OXkJM1@q*xTrcGBdw3^A zdHaf*-i2omm>(}LdXuX%bGKggg!S*IXILB&I4w0q(;CY> z=aVuUpJ>H@7P`-|_0sZG-Mr_E6%4)hO}sAFujjr(#_L^Z(SDby%ZnE6IcoeN@l%QX zl-+qQlXj+liz$Ad_iF$AgYO02{ki>Mzts1_eeOqIic0=&JTK1pV7^h!KKa`6r_JyG zs~oqH-mmHSWakqL)ynDH%dO&X?+^RVR53l4NBC)u|MiIV>!wt?FL}%QK`&CUQy}oD zveMy)H_Lx6I3W?4vf^g<`bk^cjyx3?d|BD+!z~C&H4F^)jj!d-@7pM^{pn+#%abJ6 zCn>H+&RlHjFjH8>Ypk@~Tv^FlN}!R$MPZRhM+URftp=FPV!?@zC6#vVm?gOJQ%}tv zX~CTlp8M0amE5(JexB_(arIQ6gNwAlMK+f$jDoo^W}%gopyfQpMcu1KCw5OwKXRsT zhM?i+4YPrS)18YAKx>!;m%u?> literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-008.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-008.htm new file mode 100644 index 0000000..c1e8c7d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-008.htm @@ -0,0 +1,41 @@ + + + + +CSS Flexbox Test: flex-flow - column wrap + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' + from left to right, top to bottom.

+
+
1
+
3
+
2
+
4
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-008.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-008.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3f391cdd1b27a75dc325780268eb860a80ee8c GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq4{VMW5vVK?0s-JhBd|(lrX~Xra^kb~jtZQYF&uiLGgw=B=t14M9 z6O_#C>3pp|N&Vjkee=o-CZ2OGe1lYC=L!AXqZC!tGs(WlvrOY|nXD zCja?qX}@!4oA%0|;odv5SI@G0YE%@U`DE?U{o8fV)~@d8_-LNf{XyoAXlH10<<>Bz zLRBNTwTi{NI*za_8Qr}sJMmJGZcte3HKk5*f7cYvO6w;_V_bw@FI_P^V70j5xr+}A z_Sy9>@rr7mB|347>yjtk9bYzxWkz4x7kcRYZ>6>8JJtjV7FJe!Zp?B`DcdsfLf3_w z4_l6GnrZShEJe?Fd(_&F-J)RSDlZHp+9%EzyJ-2hizBzwBS+F?n@g{vkZH}mJm)@{ zDaljkKc2+XQv5S`(X=P0!!DQ=?YWrko%LLIiuuwhr#8k%Uk$b_yfaBI#kf+A{rEa- z-OT&9s%IrXPkT`>b5LGDZqMBZ_P5{t{wXP#9-fk;Z5Pkdu>VxSUin(So_$?9Qd?_< zbHBv*wf^^D`BgXV;ET|$-h#44>PnrPFr#G<)0k4C%jIz?Gc?@=8F~n?d~~y z!&(fM2tTMz*PUm(Azr)a-{zjbbF9QHLzR}_dUccex5n|k9aoa~zj1qV<)dV#OWb#* zPn(1%x`Cv+PON;+{qcvt>LjbaD3E@G4u>Ng0vp*})-VcYI~OQeCd7`)Or~Kne%U|`(`Tsg%N!-NX^X+>DJ!QL1+V?&ez1e#rPw&WSvGZ~g tE(%F39TCh**BUxZ6_R)fGeH?{_rv-I!Tc!QLWvv@*VEO{Wt~$(698{M%CP_d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-009.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-009.htm new file mode 100644 index 0000000..5aa71c0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-009.htm @@ -0,0 +1,41 @@ + + + + +CSS Flexbox Test: flex-flow - column wrap-reverse + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' + from left to right, top to bottom.

+
+
2
+
4
+
1
+
3
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-009.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-009.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3f391cdd1b27a75dc325780268eb860a80ee8c GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq4{VMW5vVK?0s-JhBd|(lrX~Xra^kb~jtZQYF&uiLGgw=B=t14M9 z6O_#C>3pp|N&Vjkee=o-CZ2OGe1lYC=L!AXqZC!tGs(WlvrOY|nXD zCja?qX}@!4oA%0|;odv5SI@G0YE%@U`DE?U{o8fV)~@d8_-LNf{XyoAXlH10<<>Bz zLRBNTwTi{NI*za_8Qr}sJMmJGZcte3HKk5*f7cYvO6w;_V_bw@FI_P^V70j5xr+}A z_Sy9>@rr7mB|347>yjtk9bYzxWkz4x7kcRYZ>6>8JJtjV7FJe!Zp?B`DcdsfLf3_w z4_l6GnrZShEJe?Fd(_&F-J)RSDlZHp+9%EzyJ-2hizBzwBS+F?n@g{vkZH}mJm)@{ zDaljkKc2+XQv5S`(X=P0!!DQ=?YWrko%LLIiuuwhr#8k%Uk$b_yfaBI#kf+A{rEa- z-OT&9s%IrXPkT`>b5LGDZqMBZ_P5{t{wXP#9-fk;Z5Pkdu>VxSUin(So_$?9Qd?_< zbHBv*wf^^D`BgXV;ET|$-h#44>PnrPFr#G<)0k4C%jIz?Gc?@=8F~n?d~~y z!&(fM2tTMz*PUm(Azr)a-{zjbbF9QHLzR}_dUccex5n|k9aoa~zj1qV<)dV#OWb#* zPn(1%x`Cv+PON;+{qcvt>LjbaD3E@G4u>Ng0vp*})-VcYI~OQeCd7`)Or~Kne%U|`(`Tsg%N!-NX^X+>DJ!QL1+V?&ez1e#rPw&WSvGZ~g tE(%F39TCh**BUxZ6_R)fGeH?{_rv-I!Tc!QLWvv@*VEO{Wt~$(698{M%CP_d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-010.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-010.htm new file mode 100644 index 0000000..3d46f86 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-010.htm @@ -0,0 +1,39 @@ + + + + +CSS Flexbox Test: flex-flow - column-reverse nowrap + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' from top to bottom.

+
+
4
+
3
+
2
+
1
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-010.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-010.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0b9ed1f42022b105c668f8be4d001dd80b06f6ec GIT binary patch literal 922 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq?iITmEOK>Z9=BbBAjP17)ul*iJ9{lIp(X`HUU4OXkJM1@q*xTrcGBdw3^A zdHaf*-i2omm>(}LdXuX%bGKggg!S*IXILB&I4w0q(;CY> z=aVuUpJ>H@7P`-|_0sZG-Mr_E6%4)hO}sAFujjr(#_L^Z(SDby%ZnE6IcoeN@l%QX zl-+qQlXj+liz$Ad_iF$AgYO02{ki>Mzts1_eeOqIic0=&JTK1pV7^h!KKa`6r_JyG zs~oqH-mmHSWakqL)ynDH%dO&X?+^RVR53l4NBC)u|MiIV>!wt?FL}%QK`&CUQy}oD zveMy)H_Lx6I3W?4vf^g<`bk^cjyx3?d|BD+!z~C&H4F^)jj!d-@7pM^{pn+#%abJ6 zCn>H+&RlHjFjH8>Ypk@~Tv^FlN}!R$MPZRhM+URftp=FPV!?@zC6#vVm?gOJQ%}tv zX~CTlp8M0amE5(JexB_(arIQ6gNwAlMK+f$jDoo^W}%gopyfQpMcu1KCw5OwKXRsT zhM?i+4YPrS)18YAKx>!;m%u?> literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-011.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-011.htm new file mode 100644 index 0000000..d8491c9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-011.htm @@ -0,0 +1,41 @@ + + + + +CSS Flexbox Test: flex-flow - column-reverse wrap + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' + from left to right, top to bottom.

+
+
3
+
1
+
4
+
2
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-011.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-011.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3f391cdd1b27a75dc325780268eb860a80ee8c GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq4{VMW5vVK?0s-JhBd|(lrX~Xra^kb~jtZQYF&uiLGgw=B=t14M9 z6O_#C>3pp|N&Vjkee=o-CZ2OGe1lYC=L!AXqZC!tGs(WlvrOY|nXD zCja?qX}@!4oA%0|;odv5SI@G0YE%@U`DE?U{o8fV)~@d8_-LNf{XyoAXlH10<<>Bz zLRBNTwTi{NI*za_8Qr}sJMmJGZcte3HKk5*f7cYvO6w;_V_bw@FI_P^V70j5xr+}A z_Sy9>@rr7mB|347>yjtk9bYzxWkz4x7kcRYZ>6>8JJtjV7FJe!Zp?B`DcdsfLf3_w z4_l6GnrZShEJe?Fd(_&F-J)RSDlZHp+9%EzyJ-2hizBzwBS+F?n@g{vkZH}mJm)@{ zDaljkKc2+XQv5S`(X=P0!!DQ=?YWrko%LLIiuuwhr#8k%Uk$b_yfaBI#kf+A{rEa- z-OT&9s%IrXPkT`>b5LGDZqMBZ_P5{t{wXP#9-fk;Z5Pkdu>VxSUin(So_$?9Qd?_< zbHBv*wf^^D`BgXV;ET|$-h#44>PnrPFr#G<)0k4C%jIz?Gc?@=8F~n?d~~y z!&(fM2tTMz*PUm(Azr)a-{zjbbF9QHLzR}_dUccex5n|k9aoa~zj1qV<)dV#OWb#* zPn(1%x`Cv+PON;+{qcvt>LjbaD3E@G4u>Ng0vp*})-VcYI~OQeCd7`)Or~Kne%U|`(`Tsg%N!-NX^X+>DJ!QL1+V?&ez1e#rPw&WSvGZ~g tE(%F39TCh**BUxZ6_R)fGeH?{_rv-I!Tc!QLWvv@*VEO{Wt~$(698{M%CP_d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-012.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-012.htm new file mode 100644 index 0000000..412310a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-012.htm @@ -0,0 +1,41 @@ + + + + +CSS Flexbox Test: flex-flow - column-reverse wrap-reverse + + + + + + + + + + +

Test passes if there is a filled green square and no red, the number within square is '1 2 3 4' + from left to right, top to bottom.

+
+
4
+
2
+
3
+
1
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-012.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-flow-012.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3f391cdd1b27a75dc325780268eb860a80ee8c GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZV)fKQ0)|NsAiOoj#q*Gq4{VMW5vVK?0s-JhBd|(lrX~Xra^kb~jtZQYF&uiLGgw=B=t14M9 z6O_#C>3pp|N&Vjkee=o-CZ2OGe1lYC=L!AXqZC!tGs(WlvrOY|nXD zCja?qX}@!4oA%0|;odv5SI@G0YE%@U`DE?U{o8fV)~@d8_-LNf{XyoAXlH10<<>Bz zLRBNTwTi{NI*za_8Qr}sJMmJGZcte3HKk5*f7cYvO6w;_V_bw@FI_P^V70j5xr+}A z_Sy9>@rr7mB|347>yjtk9bYzxWkz4x7kcRYZ>6>8JJtjV7FJe!Zp?B`DcdsfLf3_w z4_l6GnrZShEJe?Fd(_&F-J)RSDlZHp+9%EzyJ-2hizBzwBS+F?n@g{vkZH}mJm)@{ zDaljkKc2+XQv5S`(X=P0!!DQ=?YWrko%LLIiuuwhr#8k%Uk$b_yfaBI#kf+A{rEa- z-OT&9s%IrXPkT`>b5LGDZqMBZ_P5{t{wXP#9-fk;Z5Pkdu>VxSUin(So_$?9Qd?_< zbHBv*wf^^D`BgXV;ET|$-h#44>PnrPFr#G<)0k4C%jIz?Gc?@=8F~n?d~~y z!&(fM2tTMz*PUm(Azr)a-{zjbbF9QHLzR}_dUccex5n|k9aoa~zj1qV<)dV#OWb#* zPn(1%x`Cv+PON;+{qcvt>LjbaD3E@G4u>Ng0vp*})-VcYI~OQeCd7`)Or~Kne%U|`(`Tsg%N!-NX^X+>DJ!QL1+V?&ez1e#rPw&WSvGZ~g tE(%F39TCh**BUxZ6_R)fGeH?{_rv-I!Tc!QLWvv@*VEO{Wt~$(698{M%CP_d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-001.htm new file mode 100644 index 0000000..37013da --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-001.htm @@ -0,0 +1,46 @@ + + + + + + CSS Test: Flex-grow Property of Block-level Flex Items + + + + + + + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..03d0ab88982a24d5c01c59a09341b3fa4e55787d GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0y~yU}OTa{aBcRq-7850U*U6;1lBd|Nnm=lc52K|2H%= zyykvs0Az-Gx;TbZFupzK$l2f^z;Iyg2Db|wQ4SmG)#^9><9oNU;Ec))h83Gmo8S00 q|FKN5?c_XWJfK;X?WQV2m-vLX6VDNPHb6Mw<&;$SiN-n(s literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-002.htm new file mode 100644 index 0000000..a5ebea4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-002.htm @@ -0,0 +1,50 @@ + + + + +CSS Flexbox Test: flex-grow - 0(initial value) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-grow - negative number + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-grow - (invalid when no space distributed) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-grow - (invalid when applied to flex container) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-grow - positive number(fill all space) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-grow - less than one + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-007.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-grow-007.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + + CSS Flexible Box Test: flex item margins + + + + + + + + +

The test passes if there are two green boxes and no red.

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-margin-no-collapse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-margin-no-collapse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..7748b717ced9102d08b87d9c443eeca1e618be12 GIT binary patch literal 754 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU}9ln28zrHS(Fc?I0Jk_T>t<74`eblFt}ba z6a%VQ;_2cTQo;D-jt5Mn+ zi$7-$umH8U=W5sfE~&EJ9iyKutzSO>j=c3(UGtI z=dVA#Yww=b^bHD zw9=gU%bx#rxi0VY%qZ_Bm`|}BftnuqS=Km}{VeyE$ zbxQ5CY{luXot=LPtva2kJ^ka9$m7AQY}@B~aqqm68aUtma)noK?(D+b`TnbK2WLw^ z{+jptdrWl3+ZkUQ6~CW~R<|iS$nmp#!qc-)K|zlN+^PBbjXVDT_nDl6lN>#y6f;|v z2u^aNIfDt+DK%R6Z_iIwW>8>gXx#gkJz(}FmaM{m>K-6f4>Xfy&)iOR>oJj7X&98e q + + + + + CSS Flexible Box Test: Minimum height of flex items + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + + + CSS Flexible Box Test: Minimum height of flex items + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6b9fa9aa31302d67e580cbd9f577ad15a5ce177f GIT binary patch literal 610 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU}Ruo28x*J{(1tWI0Jk_T>t<74`eblFt}ba z6k}ju67h6#45?szdo@4rk%i3hhaZ}xRlYyukkj1xWM0wV%?llF|6QDwx!<}&=e+crw}tN;IZ=G8NkO@FQKzv|I@{o=)Qk9*G;Z{PcD zdtI7resy$y_?3vcf%`Y=>|eM4&!!36wteimz3a>3e=qOzduQjq4lFKx_I2jHX<=ut zmYUYv%DJEZ+V#}TYTxt7`$CuIKU=Q&+xbLA^n0go<%+lM*00!J%zrapw&+W^opV;; zm!}5Pj@mwXQF+`>{9#3YzG7|Ky|nrBk4Mk;{M_~Ji(A&4t@%sz8>3GcO$*nZ-) + + + + + CSS Flexible Box Test: Minimum height of flex items + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-011.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-height-flex-items-011.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + + + CSS Flexible Box Test: Minimum width of flex items + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-width-flex-items-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-minimum-width-flex-items-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + + CSS Test: flex order + + + + + + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-order.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-order.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ea4d0fc25535afcae374bd2d8bbdebdf7ef367fc GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKOPH8}B*V*n?Ld+zz$e7@|Ns9CK)4i0FxbR& z=YSM>x;TbZFupx+$jG3;!Q$W@b!pP12jUSD@1*W9Hq4y8=ZU%!HNk0Rh6`a{>ZQ}% SuKb<^a-OHFpUXO@geCy-ATUJ$ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-001.htm new file mode 100644 index 0000000..1e67553 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-001.htm @@ -0,0 +1,47 @@ + + + + +CSS Flexbox Test: flex-shrink - number(positive) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-shrink - number(negative) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-shrink - 1(initial value) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-shrink - number(flex container has enough space) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-shrink - 0 + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-shrink - 0(one of flex-shrinks sets 0, another not) + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-shrink - applied to flex container + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-007.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-007.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + +CSS Flexbox Test: flex-shrink - less than one + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-008.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flex-shrink-008.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$pzopr0L02AMF0Q* literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-abspos-child-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-abspos-child-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..31c053b0caf93bd31c9edc13f3e0ef18e151691d GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^DnMMu$P6TBbu3l~QfvV}A+G=b|7U1uX#ROZ6r{w{ z#WAFU@$ET7K?Vkn!w!E|A9cLoF#X=wS&E;}TSuH;eJbpwY@Y6`L^s`=eL(dLp00i_ I>zopr0L02AMF0Q* literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-items-center-nested-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-items-center-nested-001.htm new file mode 100644 index 0000000..7e27baa --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-items-center-nested-001.htm @@ -0,0 +1,52 @@ + + + + +CSS Test: Flexbox nested containers with align-items: center and flexible items + + + + + +
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-items-center-nested-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-items-center-nested-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..3f110032deb8b81ea7abbe8b9e173fe23356d846 GIT binary patch literal 121 zcmeAS@N?(olHy`uVBq!ia0vp^Cm0wQConPtSr={geg;x(0X`wF|NsAIVEA8gzy>6u z=jq}YQo;E4q@f@K0|$%3@9Ed>{?2F625LHKc1$9<#}G< + + + + + CSS Test: Baseline alignment of block flex items with 'baseline' value for 'align-items' / 'align-self' + + + + + + +
+
blk_1line
+
blk
2lines
+
super
+
sub
+
big
text
3lines
+ ital
ic
+
+
+
blk_1line
+
blk
2lines
+
super
+
sub
+
big
text
3lines
+ ital
ic
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a570651b0e9b1762fc8535a8e45acd3b73195b GIT binary patch literal 2293 zcmbtWdo+~$8vf>H#xVJi%KaN=myME3tCPrFlxgoxq#A{UAzg56+il#&EtJbgWsB@e zBzs71qao((VjCLCJO6y9 zV!*e58I}KWuV`Lyx+=EJWHEHUg%7H3RMFj8QjeZ^m?w8|{%c@W7g9qQY=L~f7OrD> z`5z-<-#gsa_?x7mP`%JgfD>W~f?!SsUowW!)P;QZKn%%R+<}IrUaY9ctLbrk_bUCl z`#I1|2gY4(0Yt}9b8jo{qz>+Oqy5=q<4dv|ERWAG#|G#9qtHH!4_T293$KsJ9KxxS zSFCEDIGUur3mhpJXzCsmpI7hqGs9M-$w#dN6Zh z>|GJg!Ov8%3BHE{YlqQ3%Q@G7@As8i#xK9MNN{VRO;4z1b<#n7SNGZ`&q!2m5y&A= zTZ%F&*qg^x4cOwdVw||$ecJC&fd%;#rQ+oD4oVGbJbHA1*H#*&uV9WU(P&-#o`jWR zx!CHMbel@5inQ@p%IWq5nG&3{gBd%W49xju{y~(y+)t9E4oQY3oDnSi$$%p3(t%qx zk%eckcXK{`Fj3)5GFA)ayzEN9d`2NIR836O$!5ei%lN7)hhhU!L#I5TkW`s1RL%)= z9o#C(+Yt50TK1avlA>5P)$j{!Lq5s~MCzl{wb(Hw3gTfle|32WW&8l5gku2x94?=X z9{HJducwg;bZ9Fc-5Ivy;d}L5);UC^iVxc=6UFmTP$c6QUEt?A^|L-8&QNl@XMYJ& zT3xR=!C?)Tk-2eH;d>xG6EkEg$$1lzH+sv4$Q>Tt6RHIF?uJ?M%9tBwS%|Fo!%#-W zuQ|}`Dg8v*BfPH?Y<7V}|MTsTV!GXi6zd;tL3yVh%S0<0p7(wBVH4!-5!iSrPaZp; zs#hiT&x_j!3>Ab9u4MqZTHM}Y$v$G7lz|cbNSy-?YoN=GjJJ>*wXqglWs;DSr~BiE z2m7u;q3(cazzX^pbPFLGVD4c4uBh324Vr-wj=1rL*V<5#BT{V_*Xteor{h|!TUjq~ z1FXt+FJPZb2s{dOAni@bt^Wna1Rwvk7ZYuEz*OaKL5%r4!x2G`PobEa0~U3P;b}$1 zKuPJh6p?kgoqsLQYb%@S$fAlo>)lo~_^90Ddg6d`#_VLLpwon4k&VK`zlBg+t=l&# zZVqXv?H!BQ1)tRY-c|*y#wTrq3{8~BwVT7rQ}th-$C5Y;0(?_+c%-9|cTsJxd%>C1 z1vLGC26AZ$^&uL<{^WdOeiRaJ@ke*; zcfgUy{YPs6i_PR~7|?J15pe16g+A2~Uc@0`qc0n`D-g518hHhULR*9&#s4HR=JnIW zGbsyumC{NdngUb7hrpq#-Ly*u>ZG!7$d?MRt=)^t5Uq<#wWxqyx)c*NEs8ZMvJ)+c zk_Icdtfnbji9bsgQ@a9!6HKQcc`e0#vlMt9)cdQL$gu10QfGS@5yT;F!Tj4bZkY<2 z7hPOE85cM%s^plvlukv;dA3!=5~>`b_8ppdY9H9Bi3@hs=xNMThdNA>P3eyh1=abf zk{>AC8SVxR*d7F`6y#xW`an+|74Q*R(g2@z;wUGQZ>%ib2I?uVh`LhjtSQD8s8%7^ zf=#=iEN~qf3DN%eQocrO5z0Pn9)ZGi$R1bOurDAOL<;8P9?^{HTN0Rn=0zuPuhWV{ z-kTUtB*~b)BJT{2aabPV#IG`23XiDxq*|-nTQ=LM1#$}s%@?Oq>~>Xx5ofVV`jgAW z+u6ydpk+%~!H_+3!a%B9G8lwu)iaag&k@3+FI`Z8@9Tk=&3)khyf&q$=LJXiVLQ4M z`cx8kN-8YDVg{DlmG_2QS+tVToz(LxsiBg}HH#KipVX^4rpBPRnIs1gK-W5|AW^nQ zjcZEDop;`1!uONv@@g+8JdWob*nc+7Ie+N=CF{OE>lt?-1Owt}tJ-O|^v&tSiJL;_ zvJmJ-whoSK>W;bB50*THDKh2#%7xo_1#5Y`Sw=HPpHG5HQXfO%=Dz?zg@+65ATTpwF6z zp6;Cf2m8@4P3Q+`b1i^(515;Mw-%r#$d2YCJspvA^k6TWfvEc^QhoY~G$p8G-P>2$ zR<;0X&lgt%#isvHi-+Vyjge(LBrzGJ;)n|oi`p~Zzok)T2080z3vl?Lll=qgnZ$ns DC++Pm literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001b.htm new file mode 100644 index 0000000..5893736 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001b.htm @@ -0,0 +1,75 @@ + + + + + + CSS Test: Baseline alignment of block flex items with 'baseline' value for 'align-items' / 'align-self' in a wrap-reverse flex container + + + + + + +
+
blk_1line
+
blk
2lines
+
super
+
sub
+
big
text
3lines
+ ital
ic
+
+
+
blk_1line
+
blk
2lines
+
super
+
sub
+
big
text
3lines
+ ital
ic
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a570651b0e9b1762fc8535a8e45acd3b73195b GIT binary patch literal 2293 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 DC++Pm literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-002.htm new file mode 100644 index 0000000..d235a48 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-002.htm @@ -0,0 +1,92 @@ + + + + + + CSS Test: Baseline alignment of flex items in fixed-size single-line flex container + + + + + + + + +
+
a
+
+ + + +
+ + +
+
a
+
+ + +
+
a
+
+ + +
+
a
+
+ + +
+
a
+
+ + + +
+ + +
+
a
+
+ + +
+
a
+
+ + +
+
a
+
+ + +
+
a
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..da39c4295a7e4272512a190d653ab54fcb4e6a27 GIT binary patch literal 401 zcmV;C0dD?@P)b>!3`IS3N|r8K+-phq%h1V-kD#k$=sjAX ztpxq5j3}Z-tmwZfg!4eqq{)Sl8E_%t`^0riy!MGN?};~xmAFM~#@(9HD6VlB(Olz5 zlS5I)ysVz{uP2HWDNZN4knjt65Kj3XT>8XwzChRi6Jg}w=E0MCGGWL;geUd4@JHen ztr>S~#zKT6-w@YWh;qS45TV8{vHY)H6r0-2xLY$CMR>)>7JF)hxo-4P{stPN{Fvg6 zZ=jh`YBMwng71u~jR@(#1|SY2nrl=ZDCk<3Saz*1qfvx+cx=&J<1dpVE~|#*XR0ekv%{7iR vITU5lysYHwEe8}SQhXHAg@jiJgb + + + + + CSS Test: Baseline alignment of flex items in fixed-size single-line flex container, with cross axis reversed + + + + + + + + +
+
a
+
+ + + +
+ + +
+
a
+
+ + +
+
a
+
+ + +
+
a
+
+ + +
+
a
+
+ + + +
+ + +
+
a
+
+ + +
+
a
+
+ + +
+
a
+
+ + +
+
a
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b9801e05b0bef614519dfc150c7ca15341d41d83 GIT binary patch literal 398 zcmV;90df9`P)(VT3`Ms{WjASPoNK}TT%@AeBXAWLxkoay zSr`;ni_l~eF#lDSnP)5{&;q~=7#Fc^;%oZ!0uds_>BO6lytj#OpOFuUh3JEJ$5p$d zQoMy>M0JiMbq+)x^ z>0W+%AB=$fzMP0&DIPq?y^04p=I4BNs^w#~Dn+jpN!ERf>KvtYY!E9WfEW1{h$r`^ z@>ucrW`i#s-&3VX{x{#n-HGZPD|O^c*CghpYuiyNl7sA9ROd)LhrKryk$)>k6OD`5 sA0k;HLCH0VkEIE + + + + + CSS Test: Baseline alignment of block flex items with 'baseline' value for 'align-items' / 'align-self' in a multi-line flex container + + + + + + +
+ +
a
+
b
+
c
+ + +
d
+
e
+
f
+ + +
g
+
h
+
i
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..79bcc64cfa80d88842140f2b7592ed538cc99e99 GIT binary patch literal 335 zcmV-V0kHmwP) zjdSm;SRzw?PU{U1u<$)gd@rn15bN|CD(t;#;jFnkYQA?ab~9^^i9H5o)27cr96u*z z;T|Xp_t$OULw#;vnASJE7TH!ih@7?;NX*4_qxkd$40 zoSkO!Iiq4C7?mr+{j%sBliO?SN + + + + + CSS Test: Baseline alignment of block flex items with 'baseline' value for 'align-items' / 'align-self' in a multi-line flex container + + + + + + +
+ +
a
+
b
+
c
+ + +
d
+
e
+
f
+ + +
g
+
h
+
i
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1a7afa48d743f320ee3bde2fdd291a7b1c0cdae4 GIT binary patch literal 336 zcmV-W0k8gvP)dVrP!7NwF%HP ziq!mD_=++1Z~AxXlBj + + + + + CSS Test: Baseline alignment of block flex items with 'baseline' and 'last-baseline' values for 'align-self' against each other. + + + + + + +
+
one line (first)
+
one line (last)
+
two
lines and offset (last)
+
offset (first)
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-007.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-baseline-horiz-007.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f36a198e086b538ca2fd1bff43978e492b422d9f GIT binary patch literal 913 zcmV;C18)3@P)X- zAf@|z!0a0&x&IMHQj{#qZq|H3%=|v6BI#99w0c7N3jhEB00000)ZiJoAPIS@{fz*^ z*R+BV7eI(JAjBCE;tU9J281{RLYx61&VUeSj6zL}A|Ael5QSlE-m-R&NgrLg%E9Dd2mPCj@3ZTB;(81z2sYNT|}gN&q9daHa56BQ5)BG`MTZ^ zFZGPVnFAp|_}X7>yF*a=MV_KNHeA6~bjZ z6B4srh)!df+=CEnSIa_l8q36wkZt}@PZiQE3Q3F0gnS=vN1qGfD<7+1K}cHMi;yPv zsZt0vcPXTeeX0~fZFM0p+~A#F&jLE2G~$?a>EXwH-5q*5*t(swr#@+Y<-d4|YZ z2EU{d(KBvkI@uX->I-Qu!>y3^JRk%@rb3?T`~k`XQO8bb;Q87@epmdz&cKHdsSpvO zQXnKyUq}Zb5aJF9aR!7q!-p{&(ff|aK}Hn9?P-!lAt56@i$b{F^hDhdMz~pC_Y3=! z#dR*k&O*LoArE}TI~#d>nq*^zkQGN(AuNQi(nhL`nAx3EtPq=Mp+dsv*Ipqf36bjf zj1c*zpDHAL211e$zL1%an3)h2B<*TRh_#|9;3FZ{u4Y2?J@R=7NfmNfsF1@ zBt68B3YoN~LWnIO + + + + + CSS Test: Testing the behavior of 'align-self' property values on flex items that are blocks, in a horizontal flex container + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
base
+
abc
+
stretch
+
a b c d e f
+
auto
+
unspec
+
initial
+
inherit
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-001-block.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-001-block.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dfeb7b76eab78a52b8d12dee62ab0e44f60ffddb GIT binary patch literal 1438 zcmZvcc~DbV6vhK$Q7SbFFu_zpjA;-?!XoHEN+o!M1ZXfAR45@;iG;1xvS^VJ!G(k< z@RZ6f@PwLxff@o7WNC+rP%MzJi9i{b0ulnUDk`K8YWr8`&YU@O&dhhuJ-=_R=n(6m zp#k0ig+duJJ%BF?rF#I$pXhBs;?bvHtu@Gpyg3Z5R*OPuk%PK$LHkPwQkFd)Luwh* z87QPzikeQI?lo=Yqfi?inSjnYtE1_&iyCq_ZJn)=`VzUhsF9C#-s`2k{)AFrBPWzX zSya_N+k6|M>tMa$RX@W_!S61y$)a69$R6{D__JqW?bgZ^+_faV{mnDyMlo)ic3jGc z{1cm417_Z){8fu;t~iQYY-o1g-CX!)bj}6%kUjGcU@h2_+f{R^LXW4S1!K}>;`O)q z>>Ff%j2RSDdOY_DZNIecB6cOf!RPuGcna>g8>K34oU@TFv9qc8W7{k@(*#Y8(N8sS z;JB5eCKLCo=&IL}p9otA-6&5+b3F%eW0eh^s+YKJdX$?S93g8qLn51-X*f zg00eq2JoOrIz@795=Uf7Ock0yhU*J_M~uim^J>dobvKv^Q(KrTT18VHgVOq4lrmFA z3YaPPNmzc9R~Vk-v7>%MmGM{`ac`toA7d%Ntvp+PZmx^rTL(Mq!w-d2?oN6P8G@JTJzKFlWJHp1aFLu_1B}vSklqLX8*TaAY!6?PEgw6^s z+a&T0J$6KcUQ~-x+l#7->0v(xFYke4_#n^ zT}#+BU=*79F57@)4h7D5cNoBUrSU&|h$cQ`4EZ=f?oN=DIEVRCowWWMB-FFWjWHzN zL(ZOUjy;UT;lBCMpsw+E@vD~wDFR5!nOPE2BGDrRmf~BGnj|1yBXDGxAD&n55_f|u zuD6+SLnltuR>^xJa4%bAIx78hsl%NidsbYsilygq7h4woL z4bIfw+@-YwOOHYrb{!vCYln-h(@4$^QAZYcR~@&y5HYJq;+_BeRg#t&f2qwOebKIw zGXk`P@W#3x8i+lR_N3qLgP_N8YR+c|5kd$5z>U1ATv@;?|t9aG9_Y>2?N6Z=2F! zK*gJ``RGcQusamzSQbN(*}4%bCzi!Rzf+Mln>h*48Vf9UZTAdUj{ktll%&0_wOt3X z24P&Zj-~e#r7wQ`iyxEQ4w46G{SyW)d|whPZHmyE1SPqTupgbAQL{)4v=ml`n< z@qo0K2(_>)0!tHn{zfGg8tjI}L<6^l{CpQ^GZ>wO5wqSC<0%22CxuH`+2nsP{}oz? z{{JC}0HvSo!@!06788;X#El(Q=Lt%A6;N9#@uaRIjY7o+cpJwcCYP)_CHpjb?^say z?LXWTT?!<%0o1A<23#Fl9DDkkG=k#Rg08S+arV9>WZ=-aXzrRG`!I--)76vpP3@w+ z%SAbjjmp{d)iN>2i*IsW(1UA#D>TyLk#7y{)=A^(xV6dicm$p?^0T6t3>K(x`#Sk= DPRf^Q literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-003.htm new file mode 100644 index 0000000..247a6e1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-003.htm @@ -0,0 +1,99 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' with a horizontal flex container that's shorter than its items + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
base
+
abc
+
stretch
+
a b c d e f
+
auto
+
unspec
+
initial
+
inherit
+
normal
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2d7feff597c79bc56745b4cf6b747727b72f57d0 GIT binary patch literal 1182 zcmeAS@N?(olHy`uVBq!ia0y~yU@8EzD_EF;WW&cxSAdjyfKQ0)e;`=Ou=d6?pa28I ze;{ONX!w8N^#A`16W&c=`2YW1)4NMsdPMB6u4G_fG4gbA45?szd-eRpS8f7q1)&dF zR-9!Jy0A*HNZ0Pi0-g|;4WTLr8b#(bo!>X{kX&tPz`L1wy)D}!**>}-Uj5$L-z4eY zg1@2tYP)TVm9On+ooM~%&ug{elqw5*M-Jkb9bNOt( zi)H%`flJ?isR}>%*P~W9fB)%1^&Kmt(?aT`8Qn{CV;C*O*`)d1T@GhuHP3#yZDMC@ zw2=M(o9T+K55F~~KI}U!yWO~`<4nBUKeHl}PpMDrl@~v__a|uY9!rsKC%($czE@aS_8qUcW68VRew*ja zzrT;Ib}W-&e9m8avN`Xh|F(=B=f8>;{C<_SV?uvGN$s_pyu05@$2g}tN85SD&uQ(h zt@~AK)uR2VER;LX?Pt%XJM*6^YlC|<_Wa@#B4F-$YpZ5ox zNU?J3`?u#^!H%6iYV(WcCU898wdP5m_UYdTbbBJs@3_0O=C#G%+(jSQqYLhYr$1OC z{&WBHw@2mf_@sxbOI|tnQ){}TdO_*iEq_+?AMagNuyB=f^1+XKj)(h;*A>-bGED0( zYwcE?_VicG{=(xgPsX39;=L5T>Q>gDWubLtE7SI}{$Bg|)4`y#Yx=BbZs~o!V%e3Y zx7MC{`Oryt7rWK{ZPEJ1k>z4@?z@z@NJs8_TO7ZSb9M*M*Xd7Z?bq4=+K4krnVX3?IfEpm7L?uILC zFU@~wwT|=Q8XHc(TfMX2JS;eWYVrf8%Z%^l6(ok&D(<-V?7-D;g|iPwom)Ped47%P zR?C7?4~}yu_C73|X!x+lCzox5C`Alu?VEW5kQsbir{ z1vTM7%BkQ^%B}R+sX{X5GrvWK`{rIdRQlS)cSqh_CcXlY_6@7m_O8zB^oZ_T{pA5) zP57;%n58V~Dw-C`!Lu15N`N-BA2bb7|1?p z?b>B#pEFP8Dn~oblHI + + + + + CSS Test: Testing the behavior of 'align-self' with a horizontal flex container that's shorter than its items, with margin/padding/border on the items + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
+
+
+
base
+
abc
+
stretch
+
a b c d e f
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..56487add5a701956451ad731193effa50a77bfe1 GIT binary patch literal 1113 zcmeAS@N?(olHy`uVBq!ia0y~yV4MNO4lK+-k>ZMS6(A)M;1lBd|Nnm=^Z!zYwKtvt zX&_{1X!w8N^qZI5+ZY&_|9HAMhEy=Vx$0ZA*g&8yQU4i}t!DrAbG#by3f%Dxu6z%A zzpp9!E6Bt>hiPHU{=A=moldU0q_X(9?v?hnN}s;hs-%1V`n&zK2iHv>Mz)^Jo`y=x zo+C_Y9KkG;XRqz-%rBGdX{kD~YwAsoVn${)=QEN$+iv^(n|F8liKrR6zt8;qd&ed_ zk!`b>n~L(8#TNQ~6C=tF?)jbkZ8f)pqT05bE5H6oJ9y*Zn!D-Sj)>$-h8`1G_QUkq zE{8?4eWUsl4pj;Mi!{%^#{4XK;whi6om1={u~lmSiSXz887BGhyhG~07l%crFRGk1 zzifY+;n$CunqQAtJ^Qr6yQ1)y#LHdYEXL)=pZE71H|$P2mS@~5@&DfM?g$t6~+ zXRK4XB$_UG<_>q#gSji&Gy89TPJ5fERI4JhxyE2paO#1Eo8OoscAS6HAQ>OtHuKei zgaZvfZ^|<#vn}49*~GY*4QR~l$m1R>+Hb7y`Ep)D!XP1zZ}R!vX`%Bad4QY)4HuhE zt~@{Gc#jsdo9Hs0WqE00%KvUIH=5XRl1DkVd}II3w`S2lW}g(?eAzIX@$!4UXNJv; z%xpXo&Wwvq6SlDRTx+-}We~;emVF?DS7I$;!O49oEr_i>$xwOnTB z^3`(JcmBJd4QIC&9?v;^YR9cRd2dq~lowB(`S15~^+RS7@nYePG6oAR^xFiVO8oz3 zp8SMK;+1jcjC-sbs^;%hk2$OSI&;Rs22jAw*!W*(QmH| + + + + + CSS Test: Testing the behavior of 'align-self' with auto margins in play, in a horizontal flex container + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
base
+
a b c d e f
+
stretch
+
a b c d e f
+
+ +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
base
+
a b c d e f
+
stretch
+
a b c d e f
+
+ +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
base
+
a b c d e f
+
stretch
+
a b c d e f
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-horiz-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e547e5e60d4279e5baff04bfe334b7ddadaf156b GIT binary patch literal 2876 zcmb`JeK?c*AIG=6 zMMX(V2?m3yI69ErU@$lcou>*bp$h6rNIDeOyE;?JGMNkplPzDjU}Zfcu$UNGQycWq zcvd+E22&bzB-v5V!o}UT*RxyIRt3>yi486{d2eEb9q>bwXVcb*U_vCkfm zRXA{i1gyGQk9P)(;ho;-&43V|yc{fG8m3BJp%f+t+lTs@(h?figQ~<}L*q_BrC|n3 zRN|iS{i$dri(}!#6$PCV-r{7Ty%s>_pvVj@ zKnR5>EfyYR;#j%NU@3k7Xs_H7bDB;u_N(=gqwY)@Z`4LL%JF_z*@J!b*$Dsf>w{Uzm`pfb4%{Bh?{```9E8##&H?VW+J8X6lQZ&k4*TX|Te zHKij)wMPBI+4dK;al5KtYri%(SP_2Y(y_?Ssrxn|pea8KwDPkyY%R?i&<0Bbv&L;2 zv!*-Pyz0q^ji+mK0t0+&lalARGmXoviELzkOCE29D@)X&E;y2g{;rw>j721ddJHWt_TY zVjJLf{&(KOIAgvp1VHga?--W~!Ocbi5~RxT@iiYl3?}BIzfM~0|MhDazvCRZ)b{xX z?Ez2!PT^|=oV8G51iRI;C>^$WeHFET6@xkGEulZ0xoC8JV>OArwc$B#?$hGD{SDR! zgEPjZL#nT~D0SgW?d^vjy`l4s;vOgPT-}qp=5?87iyyk3lQsntqdy`!DgrmvW<6GV zFp@Ja^h$){W}qtdqM1G}`W@1``O_3#3|kT!U*3X>%69PzH$Rfd$v#G8NN!iSc;T|j z;{*8{wt<0S*!5#yW+i?-$OP@@IXsfyVz{WpiS+8@>AX5Md8tYlD-re+VxJ1GNcZUP z?G;pLA9m2+TVD9@Q_t$xk!bNx8rG5qA!?9pOef)g zZ>~yo0lg#YphN8zQcBPvCT_SANpAMFgA4#53Ebt*|Fdhb8l#W9$fVtuc zdabArMgEDrY6uIxWu2TVa$@9M39%Om={<`r@`dfENVdgrbi*|FbV0TdM5CY^V1JYU zvLW!eg%>Le-ChD^;$n|bp?g)$ASC&Zps&tba@+3K#Pq0C4P&W;(T_WqxBm^m2A22F zO9d^F_(~m?_%cpf&~pBtr?Xqadz^Bmv{t$1R<}L4CaXFqt1s(wMK?*8IxD;?6(#xG zW0dF}`FoD5dxU8CXJGOwXl^({AFrtV0;eD+M64ygm`+n=Or4CrIQD`fbCeZE7T~3> zsnPgWTl8BU*2gvE`ODyfJ+M@OmwjIzy_>+7JU@rdl^!Uc*4s7L;5~Zitb|h6dHcQS z$@!dUJWuD_@u^(5u4#<4q{lKmI<*|MhK7~{ai}9B>x0a*18v{+N%#@g^eE92Xiw)KpeiwBnvuB3y zd?K8-c}Iik+U$XhuakA;o3#t>BCj&*+z_&lX?WB&q@92bhg`x4ZvKKcD*hoKkmti`qSY@M*p zze;bbLutkVrrE@jBwp{A9K0GN$ZyX+iLQ}htdGVO4G&*pVsVMPzo*x)bi}o#8ChrW z3tY(U_H_^bgYLo_HuJX~RBkh4GTRrM3o_477TA$|BpHj~CkVn*u?4T9+?=M3#|&P-n@Bprg2WFg(t$b=v0m- zYF|o}H0RMqHLxVV^GXbzV*aob7S^Mt`Ecf?+RK9ugwiPcgp9{bBF+7ajn=Lr86KGC zgB^$Gj&k9mvgwFVC;E6%7X7?GeBMi2RO7~R%}mrV_(~vU_};15F~_r# zx=rEgxl%0CJAU-Mx9PURaAo_5q(s9eyJkmp=2pq3VLev6Uf9(jHL~9|YX_^7%-`CX zJ`}(8kkU1w-S2Y=TKoIWUzM`)JUB`OZG5blTZIT*eKG-=?(V5j6JI_~=ZcG|p;RO5 zfHce}bbnt2wC$^)ZSN0B{~09xTafghu|g-9U@!)tR_L;N86==lb=fNAe)%2xZ;z=@ x$AjV@N#}2h%8O86Ja0NbW=oJS(3 + + + + CSS Test: Testing the sizing of a stretched horizontal flex container in a vertical flex container + + + + + + + + + +
+
+
+
+
A B C
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-stretch-vert-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-stretch-vert-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..45101bf3acad26b0ae8b4f5bde8e210eb4d1eb82 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^H-K1%i5W=Nna{ifq&Ne7LR|m<|IhFr$YI!Gns*UM zIeEG`hEy=Vy?Bt5L4n6PaQYwblKFZUHAUwryj*#q|GK-#c{9@|Ryqq5LZ+Rx7c;(g qPc=2{TU7JXuVz1)C%oQ}n;?Gg*M*+6Nx@e^rh2;ixvX + + + + CSS Test: Testing the sizing of stretched flex items in a vertical multi-line flex container + + + + + + + + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-stretch-vert-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-stretch-vert-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6467f563a89ecad900986c700ff98708546edf88 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^6+o=P#0(@oTCQyaQk(%kA+G=b{|7P|82$$byIla% zE}kxqAr*{oFKy&xP~dU7s9@3c`v%7&6TRfeQ<|=twl|p-{nOi{ + + + + + CSS Test: Testing the behavior of 'align-self' property values on flex items that are blocks, in a vertical flex container + + + + + + +
+
start
+
a b c d e f
+
end
+
a b c d e f
+
center
+
a b c d e f
+
base
+
abc
+
stretch
+
a b c d e f
+
auto
+
unspec
+
initial
+
inherit
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4acd0dc8b3cbe0235e379905cef4f0f12c63654a GIT binary patch literal 1145 zcmV-<1cv*GP)m8W z5QXoc3y?#ay9h|y!2Ab(c6`xQf&2pk2A{22kZ%ox4CK|6FF5)N&;- z(Z$ev443=3UJnR}AKvWj%$uyNEUi8&6lmXG9q9*#qqhUniUX@ij0Rlc+E!7xfJ~dW z-iqQ{;_ceefI@R_Me`&`n_DKY&}Q-q$=&Y3XcX?`Xvnk?(F(Y3;%Omr?`Yl-yhV|e z#>=FT2C26R(nvKKRf*=lO<>|=TBeAJ(Q9%*8aD zqX$G5>D%$pR_W1jTjF-*cNeU{q|M6J%CF{G-BP{HEQLVJasi!I9-R4@o=F+a zSBcMLiISelWP3^ZPs+4c)2xSN+IVOHT3U$wJC?WBl>Lh%C5@O#9qlB4nRaO!t0ucD z(cHHQOq@*1wDU6{)(Y&26AMyriO@oyECqeCZwBzZO@!8+pvD`$CI_U^&#m{qF?&hM z6K909?>3!&eZ$dEw0X)j{J3!owky1?H=DKl+pIM-Z)^=nXzPQ$M`)_zazb`M88 z(go2|q9N19Mze10`iN~l;(N0Z5H=-Ekx3y9Qg4%?(QC4)65)MId*Y-?`~HEi-v^{s zhu2lu(x3qkxT`9+H>VonVh?tfcw5> z^1)XO)f->`JKAs3uACMPnKmMtb>nuzAVlsR%^QNZD3a26nH16>^)^8osV1W;(cHHQ zOq@*1wDU6{7U&ZvQgx^FmI$rula-)P_RRpEw~5fg35vYYYjQvuJ(GR+joM36o;az} zKKzGYef`wwk2GnINz(A?(OCFoOX2O&(fp(E1|e-G^`@m+CZB9QK~yD)_LBF{r}{I~ zMnIcAZ@2jA8SuMXY?@oo+cwzpX=cfx%ppIMM(Z4EY1B-K}lOky^(2F7WREt z96PsLCT&lpmq=+}sBh{X{Y9hgk3o9@Z6DM2L|T1JtGP7PdwjN4+rCGQ+5%=K?P=F% zdL{!6*z?b=^HwKEv*Pfyy2+%KOAYc3koA&(r+rlDvW~K{vd-ym-S8mbSQjbE00000 LNkvXXu0mjfAXP!Z literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-003.htm new file mode 100644 index 0000000..6853880 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-003.htm @@ -0,0 +1,70 @@ + + + + + + CSS Test: Testing the behavior of 'align-self' with a vertical flex container that's skinnier than its items + + + + + + +
+
start
+
a b
+
end
+
a b
+
center
+
a b
+
base
+
abc
+
stretch
+
a b
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6246abda97be1f5f690ca86031a394648fcd2767 GIT binary patch literal 616 zcmV-u0+;=XP)KV$^${%++J@D!OrxRlqEqbzzNLC4UZ~RNdYU20V^n4(Z`@TQ1vL}iE{8u z8c?xIcZKK*T2K^F3I#b=*`sD-7uYM+H$yUNbr|((xOxDp)`h~Z#_j;{QT9;R;Rv7r zF%*qSA8Hi>ii};yw#Sd&Dnk_VMAe{+U_iw#-4&uMXhBgxDHP;fWsjPTU0|v2Q~4O@m~VZJQ>X;c52it}G=3Mv!lKsCM!cdR{;oR2X{**rMeH zRJiT!J>`d@Ezc4~-#H^HcImE0JM?RA?71k4C4{;F7StEUF0fasZw5AMra%2fN23K* z>O|px?+>W5U3Gom`JcWMKm|)3z(;kHF-52_^Db09EBYYRm5y1VkTHthJ-?n*LdSGP zZ*weSD0V<86y#j}02BnqF11%$Zw3+6`ut&pB#g4tA2ya}qX2`AwZ0C=QcY4BT-l8; z!(&r@t?@4qj3s+RlZ-l_CW4Vhr3Wl2pn?A|1vKGbknT$7w%^fvekkOLIsq>uDt76v z5M4nFiULZZAm=K3)NJemd!_nj;G&A6FpCy_uRa0(({hhs`<*8M0000 + + + + + CSS Test: Testing the behavior of 'align-self' with a vertical flex container that's skinnier than its items, with margin/padding/border on the items + + + + + + +
+
start
+
a b
+
end
+
a b
+
center
+
a b
+
+
+
base
+
abc
+
stretch
+
a b
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-align-self-vert-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c1a180ca0453dc7bc25a50ffe8f130a3d4aefc46 GIT binary patch literal 805 zcmV+=1KRwFP)|^Tj;8iOAuP2kfzm* z!5WBxc_orHg0cWaCIw=AmhD{d?nG7aPXq!8%nGjBf`-7FW@myIz4`S;dV-JG z5duamqM$tapNfL(`&yn_f}5-XAC%PxQ>a2IAo`1d*Pt9z?2Yn9U2v_-@P~CE2M#H>duBnTilwV)S8LG_;?*tcNhv7v>o5O^jSt~?_v zar$X~3f{BP+Lb2*0R+1dysjn%pBn@U0;(Y|>XCE=R6{=0BhA&IdU<~7mO-xu)ywls zw+ucDc(n`)_|sGc?YJfc?YI!^OYpN24ebXv_5Hw`FB1e1K;TfY5Jkb#(;)Ctu;Q_y zg{}}-6U1?BYShT+O4_a9J01uia4E=6D=0{uBwK!Flx;1@zFB#e2W?*i@yb*Q(jM36Za$OR~8p~wm+v*6Q?1OWsm7EA?#6A6BE3Bv6Mg}}Xl7IOwc_MJ-s0R$%% z5CqNyMFXK8X~P;&bD?tz_$)XUknq4p{&)XY7400000NkvXXu0mjfaF|iA literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-anonymous-items-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-anonymous-items-001.htm new file mode 100644 index 0000000..614d751 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-anonymous-items-001.htm @@ -0,0 +1,29 @@ + + + + + CSS Test: Testing that we gracefully handle cases where two anonymous flex items become adjacent due to "order" reordering + + + + + + + +
+ a a +
x x
+ b b +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-anonymous-items-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-anonymous-items-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ab14f71de428adc047fb9803bba648cd516b8626 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK6&Qg8!=cBD5kM-!)5S5Qg7NL8gPaEx1Y9mQ zgjRaxCUJ_T`c}`0C^VjFy}`KR4DmFG~x4{mM^8M03W(g%b~5 uDpGpiv-YjK*R&u1kJQJqHa0Xg{JFy%I3q+dH+x$d$SzM;KbLh*2~7ZsoiptK literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-horiz-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-horiz-001.htm new file mode 100644 index 0000000..b3b2923 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-horiz-001.htm @@ -0,0 +1,64 @@ + + + + + CSS Test: Testing the baseline of a horizontal flex container with baseline-aligned flex items + + + + + + + + a +
+
b
+
c
+
d
+
+
+
e
+
f
+
g
+
+
+
h
+
i
+
j
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-horiz-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-horiz-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc471302420239d4ab31127725d13f168c6e5a0 GIT binary patch literal 281 zcmV+!0p|XRP)=KRJvJl=9oDz|{$=&pp$A*N-JamS5=9!?BlL_;hj6)*KaUr2!;z}zp z%nvmYxWpX@R|7!J(hz8|O1$)wY6QEF1Pa0MMp){w#7_D+q4LMPjJ#ThRZ<2}k6XqV ftK=IW?C+8%!>>oC_3s>;00000NkvXXu0mjfA`x}7 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-vert-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-vert-001.htm new file mode 100644 index 0000000..973aa3f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-vert-001.htm @@ -0,0 +1,68 @@ + + + + + CSS Test: Testing the baseline of a vertical flex container with baseline-aligned flex items + + + + + + + + + a +
+
b
+
c
+
d
+
+
+
e
+
f
+
g
+
+
+
h
+
i
+
j
+
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-vert-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-align-self-baseline-vert-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..9c4d99ea229aeb469b7353e29450c972b65d8a48 GIT binary patch literal 305 zcmV-10nYx3P)@R4oKy(dT#b_F{!!ul(Y-RS|m zctDfY&qFC2*)7M}<^h4R;y;PD!ah}!ahGkGvxz;aIU8}dj8ACINa_{r}iZ(brk{17t=P|!o18K{| zEZoKDaOD;@Rtj`gtLi@-br-fxj)z|@-RAmJbA5gSv4S)O3@5hZ00000NkvXXu0mjf DaO!~| literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001a.htm new file mode 100644 index 0000000..1260239 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001a.htm @@ -0,0 +1,49 @@ + + + + + CSS Test: Testing the baseline of an empty horizontal flex container + + + + + + + + + A +
+
+
+
+
+
+ + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8fd3b78035c1e22588b76c903c122ba6d84d2fd1 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^SAf`pi5W=#FtaNHQk(%kA+G=b|8HPu0P=cwaPI?B zX`U{QAr*{oFK^^*aNucusBq(s$|DoAe9q+soYk*=R<;Kod}YDuR#Tt+@b?a_Lv}IT z2?7TNMX&x3xpb%Y!Rj~uD`#y#FX8xh_5!WU-dD3aUxnmdKI;Vst0K;BK3;+NC literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001b.htm new file mode 100644 index 0000000..325751b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001b.htm @@ -0,0 +1,50 @@ + + + + + CSS Test: Testing the baseline of an empty vertical flex container + + + + + + + + + A +
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-empty-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8fd3b78035c1e22588b76c903c122ba6d84d2fd1 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^SAf`pi5W=#FtaNHQk(%kA+G=b|8HPu0P=cwaPI?B zX`U{QAr*{oFK^^*aNucusBq(s$|DoAe9q+soYk*=R<;Kod}YDuR#Tt+@b?a_Lv}IT z2?7TNMX&x3xpb%Y!Rj~uD`#y#FX8xh_5!WU-dD3aUxnmdKI;Vst0K;BK3;+NC literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001a.htm new file mode 100644 index 0000000..2a21a4e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001a.htm @@ -0,0 +1,47 @@ + + + + + CSS Test: Testing the baseline of a horizontal flex container whose flex items are not baseline-aligned + + + + + + + + a +
+
b
c
+
+
+
d
e
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..10b053cec08c56c6cb8c531c17bfd10abfbff0eb GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^9zd+c#0(^J_kV2#Qk(%kA+G=b|6hCK8IU*8sZkI} z6?wWihEy + + + + CSS Test: Testing the baseline of a horizontal flex container whose flex items are not baseline-aligned + + + + + + + + a +
+
c
b
+
+
+
e
d
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-horiz-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..10b053cec08c56c6cb8c531c17bfd10abfbff0eb GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^9zd+c#0(^J_kV2#Qk(%kA+G=b|6hCK8IU*8sZkI} z6?wWihEy + + + + CSS Test: Testing the baseline of a vertical flex container whose flex items are not baseline-aligned + + + + + + + + + a +
+
b
c
+
+
+
d
e
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-vert-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-vert-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0e1c1fc4fc2a11996b99cedc8e1e5ca919467d03 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_#0(^Viz%!JQk(%kA+G=b|6hCK8IU*8sZkI} z<$Ag}hEy=_y?C0p!GMG9!LelZ!Z~rboXs}`TQrGJ`>}k>1f_#BepsA%%e}>cL(Fpd za;@8YpKxkBgdmJ + + + + CSS Test: Testing the baseline of a vertical flex container whose flex items are not baseline-aligned + + + + + + + + + a +
+
c
b
+
+
+
e
d
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-vert-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-item-vert-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0e1c1fc4fc2a11996b99cedc8e1e5ca919467d03 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^MnJ5_#0(^Viz%!JQk(%kA+G=b|6hCK8IU*8sZkI} z<$Ag}hEy=_y?C0p!GMG9!LelZ!Z~rboXs}`TQrGJ`>}k>1f_#BepsA%%e}>cL(Fpd za;@8YpKxkBgdmJ + + + + CSS Test: Testing the baseline of a horizontal flex container with multiple flex lines + + + + + + + + a + +
+
b
c
d
e
+
+ +
+
f
g
h
i
+
+ + +
+
j
k
l
m
+
+ n + + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e16213246c0bc5b40a7a8d656cfb2410bde228ff GIT binary patch literal 242 zcmViI&AmmNRmJ?Hv0QCmhiXYte8_UX!J zNY9g`-`4axUW(ze%msP-5pPMhTZ$C>XR`S s))?m{@cQ7vg9rZ){=)oC9`)sV0Ha;0`a;3VO8@`>07*qoM6N<$g8k@g5C8xG literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-002.htm new file mode 100644 index 0000000..6799269 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-002.htm @@ -0,0 +1,76 @@ + + + + + CSS Test: Testing the baseline of a horizontal flex container with multiple flex lines + + + + + + + + a + +
+
b
c
d
e
+
+ +
+
f
g
h
i
+
+ + +
+
j
k
l
m
+
+ n + + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..fc5689f18aa1c4a60df61ca2def14f03528f2d01 GIT binary patch literal 256 zcmV+b0ssDqP)Mxbx zAzp~0hx6-H>S0vGqF2S`R+s&`weG@?*~cYUKlyl`YWF+DGuHjL9Lyf-R{zKu=Q9?@ z&re|Y!mlmouY891jax1{@1x=JmYtXFR1ZCGcW3oTg{Mj`Ms_tYS$@mL`qvMWQM{0q z8}E#$+jDSIhkCi5Oxot8!Vtz2dAYUTpBoj5_xrGq>QGN4uc`W?i3N550000 + + + + CSS Test: Testing the baseline of a horizontal multi-line (wrap) flex container with baseline-aligned items on first line + + + + + + + + a + +
+
b
+
c
+
d
+
e
+
+ + +
+
f
+
g
+
h
+
i
+
+ + +
+
j
+
k
+
l
+
m
+
+ n + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-horiz-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6ce6ffa24fcd897306423d9dbf5d6c78f4e3804a GIT binary patch literal 319 zcmV-F0l@x=P)6lV2)(k&^p(V9OE-a!QC3TJv!0 zlvX@sPZ4}}o!E`WJHrgIH5a5RujNdta^=dEE9W_I+^)eRA^&if+TZX$f2p2`3;1(K ztDi<5efDkPA$_@NT>96+*i4_#zRlH-S8m4YK=|HJ?uY6Ow+|ezD(85!()&y^sdwb` zW%#Q;UxG}mmF*0^rkt1aF35N%Uf{5RPw%@STZr6>c?`47;8e=v;c%xp&b$m>%3~b| z(u}L)MsQV-a&Bjo|01__yj#Fy9Y4TVa#zPcY6+ApS1w9jhjXdQU*H-W#TnJR2YRT9 Rm#_c;002ovPDHLkV1mqqlb8Si literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-001.htm new file mode 100644 index 0000000..55e9acf --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-001.htm @@ -0,0 +1,77 @@ + + + + + CSS Test: Testing the baseline of a vertical flex container with multiple flex lines + + + + + + + + a + +
+
b
c
d
e
+
+ +
+
f
g
h
i
+
+ + +
+
j
k
l
m
+
+ n + + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..38a3400fa23e60f094a0020156680c03a2ed71df GIT binary patch literal 251 zcmVhrJ{XGH#YRIna`E$Xs(H!!FeR;im{ZbygzjlryWi*T zVh^+N<)<0G%w8M^x$)06VSW{Jt8Bv#9y + + + + CSS Test: Testing the baseline of a vertical flex container with multiple flex lines + + + + + + + + a + +
+
b
c
d
e
+
+ +
+
f
g
h
i
+
+ + +
+
j
k
l
m
+
+ n + + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-baseline-multi-line-vert-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..31fda30f40e68c27ac08fe7d255aab315be2b121 GIT binary patch literal 255 zcmVHNLbw7u9clnU(GE5A~4`n5t73qGpBJ>_{hZH1Sm$~knzhj^DN z9(nfK?w)faJKoIfsQ&l8OVtpJN1e!n^OB$De|WDQ>IK)7srs1-CGP+L002ovPDHLk FV1k)BbOQhY literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-horiz-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-horiz-001.htm new file mode 100644 index 0000000..86a2404 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-horiz-001.htm @@ -0,0 +1,67 @@ + + + + + + CSS Test: Testing flexbox layout algorithm property on block flex items in a horizontal flex container + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-horiz-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-horiz-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..55a0a1afe68187d2a3b8ddcb4a9ffacc6b4da132 GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0vp^w-^|hN?4eIthQYW@jyxTqrOc x2|5vP*8O`6=b5z4H*?HppZ%6~`w+uQ=5yEPDsj2)C;~ + + + + + CSS Test: Testing flexbox layout algorithm property on block flex items in a vertical flex container + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-vert-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-basic-block-vert-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e5625a407ec3577d3a438c7d44d78c43c219277d GIT binary patch literal 328 zcmeAS@N?(olHy`uVBq!ia0y~yVDtd8Z?P}~Nwdet{s1Y70G|-o|Ns9pF#MnJZUT`0 zzm%Z?2pAgvA2|K?!UtQRn%AB#jv*C{Z_inC9deLpy_lo8?^g=@BQ6&6gok2p*529I znxGi7sQCm>@xE`*&d+h2wB=4jq5hLBK3~<-zXWbQSN+c`gd1!!Oo>xagoEdq#ayy5 z-v5p><`X8&SNg0z0S4|WZ=Zfa63Mttwki7>TL^(S5<7R>?cQJB3{&_=anln=4=6aX heq!=b-bqW2$|YQWxrxhsODWLX44$rjF6*2UngFI4fA;_Y literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001a.htm new file mode 100644 index 0000000..3f198d9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001a.htm @@ -0,0 +1,111 @@ + + + + + CSS Test: Testing page-break-before in horizontal multi-line flex containers + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ae52642a0a51bc48644de53deba0cc9d06368611 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK8s%?MZj%3Y~ng z=lTNv{X5hX=K~FJh~E*Q6#DI(lXTv0qimU!OM8yJU%dB%$CI^B4ByS_w^_ckfkUZ7 zp$S2pFm)|c?KjMf`#k5FlXQ&6M6K#-Muu}9=Iyqz^Cs%vUDtUuE@QWV{I7~-wcoqm zi&rxWx(GP3Ac%?CU2j-t`#kLis%z#}4NA?|XE-4F^4W&+;=kWE3kZVMCMy1ONV#UQ z#Pa%&+AZR@{zX;x{CF-?=~oOfZDPI9%T&c(V5c(yy>TGChM~(z#L9Z>EJ;wfc)I$z JtaD0e0svswg&F_= literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001b.htm new file mode 100644 index 0000000..e921f4a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001b.htm @@ -0,0 +1,111 @@ + + + + + CSS Test: Testing page-break-after in horizontal multi-line flex containers + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ae52642a0a51bc48644de53deba0cc9d06368611 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK8s%?MZj%3Y~ng z=lTNv{X5hX=K~FJh~E*Q6#DI(lXTv0qimU!OM8yJU%dB%$CI^B4ByS_w^_ckfkUZ7 zp$S2pFm)|c?KjMf`#k5FlXQ&6M6K#-Muu}9=Iyqz^Cs%vUDtUuE@QWV{I7~-wcoqm zi&rxWx(GP3Ac%?CU2j-t`#kLis%z#}4NA?|XE-4F^4W&+;=kWE3kZVMCMy1ONV#UQ z#Pa%&+AZR@{zX;x{CF-?=~oOfZDPI9%T&c(V5c(yy>TGChM~(z#L9Z>EJ;wfc)I$z JtaD0e0svswg&F_= literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002a.htm new file mode 100644 index 0000000..b272d35 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002a.htm @@ -0,0 +1,110 @@ + + + + + CSS Test: Testing page-break-before in horizontal single-line flex containers (should have no effect) + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..32f06e7fd0cfc585f3a6cf00eb9533d45388b1c5 GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK)*P$N3=%Cv7@6SW3AJslLqep56y`S{e9)5@1}FNzy97`pD6Ui{q^x57k;jue|awh z3x|M$LjwaN6PPiNb7hph+qKHq&r9^rXsXQSDgL4_a8Wk;S!%xg`PF-CW22AmH~8*k z^W}c|?)T?jSANuo>4X?DA>CA6BUcg@>oBpUXO@geCxc CfQs<| literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002b.htm new file mode 100644 index 0000000..480b23f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002b.htm @@ -0,0 +1,110 @@ + + + + + CSS Test: Testing page-break-after in horizontal single-line flex containers (should have no effect) + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-horiz-002b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..32f06e7fd0cfc585f3a6cf00eb9533d45388b1c5 GIT binary patch literal 329 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK)*P$N3=%Cv7@6SW3AJslLqep56y`S{e9)5@1}FNzy97`pD6Ui{q^x57k;jue|awh z3x|M$LjwaN6PPiNb7hph+qKHq&r9^rXsXQSDgL4_a8Wk;S!%xg`PF-CW22AmH~8*k z^W}c|?)T?jSANuo>4X?DA>CA6BUcg@>oBpUXO@geCxc CfQs<| literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001a.htm new file mode 100644 index 0000000..0970a7f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001a.htm @@ -0,0 +1,112 @@ + + + + + CSS Test: Testing page-break-before in vertical multi-line flex containers + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..00d088a40a304045e369d3466f0e8e956458e8d2 GIT binary patch literal 508 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKuQ4$L$q-w@VR8+-2<2)I48Jh1wQR}rVzm6l1l2UR!p2n%n2Ub^YoiG}KB za-H8dVWuoX<8yjucArY1qr8Q zER3^OinTi)omm~4EO!6XkB;}1wPDF(xD-Rp+3a%yVwuX?#F_HHueEER1KG22Z+p1S z=d7zUT5($kG~sp27pM!@2yZ$wf108J9_tW_ZTdvM?+kaoBsJ~lM*Yw4%WJ;X-MfF_ ze*ON&|2>h|{RVTcpNIT93y{N(R=n%~A(8*T>&Kd7I>+P#?7si}H#J}Xm=4cE!9--Y@V16osCyelF{r5}E*&soN+3 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001b.htm new file mode 100644 index 0000000..ca87ef6 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001b.htm @@ -0,0 +1,112 @@ + + + + + CSS Test: Testing page-break-after in vertical multi-line flex containers + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..00d088a40a304045e369d3466f0e8e956458e8d2 GIT binary patch literal 508 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKuQ4$L$q-w@VR8+-2<2)I48Jh1wQR}rVzm6l1l2UR!p2n%n2Ub^YoiG}KB za-H8dVWuoX<8yjucArY1qr8Q zER3^OinTi)omm~4EO!6XkB;}1wPDF(xD-Rp+3a%yVwuX?#F_HHueEER1KG22Z+p1S z=d7zUT5($kG~sp27pM!@2yZ$wf108J9_tW_ZTdvM?+kaoBsJ~lM*Yw4%WJ;X-MfF_ ze*ON&|2>h|{RVTcpNIT93y{N(R=n%~A(8*T>&Kd7I>+P#?7si}H#J}Xm=4cE!9--Y@V16osCyelF{r5}E*&soN+3 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002a.htm new file mode 100644 index 0000000..4ad2516 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002a.htm @@ -0,0 +1,111 @@ + + + + + CSS Test: Testing page-break-before in vertical single-line flex containers (should have no effect) + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c2139c89a3a1e6d27bdce1bf92a264423a83ae7a GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKuQ4$L$q-w$ZY*h)S`3S`Lr+XV#cM z4;j8MnV{miwPv2k`|9_{G}rCy$*BC5H|No>$bFBs{qOe%OE@KS^eURPUBJc&R!`AW zj+(bmdijsJ=hy$3{rr61oI?Uq&KW!l4O@<3XPjz*Sk|^g>eQbb>&dlng>cZb%SJ3X zECJcS%|LA_(408SO0ih-;-@cnEnMeL zFq%J2(E!cMzH=u)-MYxnW2XOU0SPp3V%n$T`TwSUq-?u>f{utaD3&~3{an^LB{Ts5 Dm{-88 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002b.htm new file mode 100644 index 0000000..012b80a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002b.htm @@ -0,0 +1,111 @@ + + + + + CSS Test: Testing page-break-after in vertical single-line flex containers (should have no effect) + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-break-request-vert-002b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c2139c89a3a1e6d27bdce1bf92a264423a83ae7a GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKuQ4$L$q-w$ZY*h)S`3S`Lr+XV#cM z4;j8MnV{miwPv2k`|9_{G}rCy$*BC5H|No>$bFBs{qOe%OE@KS^eURPUBJc&R!`AW zj+(bmdijsJ=hy$3{rr61oI?Uq&KW!l4O@<3XPjz*Sk|^g>eQbb>&dlng>cZb%SJ3X zECJcS%|LA_(408SO0ih-;-@cnEnMeL zFq%J2(E!cMzH=u)-MYxnW2XOU0SPp3V%n$T`TwSUq-?u>f{utaD3&~3{an^LB{Ts5 Dm{-88 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001a.htm new file mode 100644 index 0000000..e66abb4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001a.htm @@ -0,0 +1,83 @@ + + + + + + CSS Test: Testing "flex-basis: content" in a row-oriented flex container + + + + + + + + + + +
+
a b
+
c
+
+
+
+ + +
+
a b
+
c
+
+
+
+ + +
+
a b
+
c
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a5f351d663aedaa4a4d86b9f34903100d97ffa01 GIT binary patch literal 530 zcmeAS@N?(olHy`uVBq!ia0vp^4;UC2#aNhutls@?NkB>{z$e7@|Ns9D3_#ehR7;ED z|9_zHqCMtTK&87qT^vIy7~fvL*m>B1ry)`J1H;_|OQg4GmoV(D_#djbj=!eBV{dyz zQT6}-0$f=?CUgo5uDy74rNf1n3d#p-m;AXsC$Bc*O?dJ_!>bP2yJFwFoAcIexyJCc z`C!cL{wP`VDbL#;ZC_}tvEF>muSS2y%}aFBBbgsSp(<%!guUY5R)bBL zL9jr7hyCRP_oAEYxdp(WpmxLegAX_vnV6y$1Q#j@g2CV4`xtm!Udu*1ew+Y|O$JX_ KKbLh*2~7av^4wei literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001b.htm new file mode 100644 index 0000000..8b49929 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001b.htm @@ -0,0 +1,86 @@ + + + + + + CSS Test: Testing "flex-basis: content" (set via the "flex" shorthand) + in a row-oriented flex container. + + + + + + + + + + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + +
+
a b
+
c
+
+
+ +
+ + + + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-basis-content-nocanvas-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e9be0e8522fc1d4d0b34d008dd3e3ba4d133b9cc GIT binary patch literal 537 zcmeAS@N?(olHy`uVBq!ia0vp^4;UC2#aNhutls@?NkB>{z$e7@|Ns9D3_#ehR7;ED z|9_zHqCMtTK&6K~T^vIy7~fvK*!kE%puJGIfbr#l86^|=oVaF8f4P6^=10u2I;Yn> z{38GG?>=_nEzNZvoi`ghe!bAs5)g?^QL%WwEqQxs<*jw|<@nh?%Xi5x`}614!J9U%0oJSYP&M7D{*W6LF_udPy@AW3@>ON{Gq{?gvzOXdm zscjBB-z#la76f=Bc1F~C&1q}vIWqmMs;*6o9yut1!J;d%AqpD1PIwfY%RQd=`qP$J zds{i4XZ9l2K0kkMJ!~A>XMfLoeVpTN1Bs`THpRz$uG_W#_Fd`tvcas+9fT*|trq8x}9;e|JcD+2xz( zBxlbJ;JI~>)4hCh(`oV7z3WQ;yFY##*S&Yu1N-yx4fg-@8E>$aXhu}>-)?OF<;58K zAj)u`UOc0XyaE^`9DnfV<)H`vxdjA1aEOSq3kib2#r5y`f4XwKnGrUv6&R@up00i_ I>zopr0LfeB?EnA( literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column-reverse.htm new file mode 100644 index 0000000..c7bff4e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column-reverse.htm @@ -0,0 +1,61 @@ + + + + + CSS Flexbox Test: Flex-direction = column-reverse + + + + + + + + + +

The test passes if there is a 3x3 grid of green squares, numbered 1-9 left-to-right and top-to-bottom, and there is no red.

+
+
7
4
1
+
8
5
2
+
9
6
3
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a23260e43b77514b178d88ecc1f3458c90a0b179 GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK4=^zU$;M6Zp8zSI0G|-o|Ns93nMP-g7#bK3 zN+~>GU|>n~ba4!+V0=3@FmJV$K%1lB`8_kfns^teY(AOy$n8|yriNF~&b&X^Ho-)F zi{)2`|Le01`evSKe_QCvq1Yne1SC{`-8-Ib{Busw`_~P1_Afoxo_qcO!s|z-Hs5z! z&b!>varwknzOyR)HII+4xzpV6^^Jxt|V8V|n>tu63zf^iB?g1|6!fS zjE*DQAE$Wu*q?DZQK11H9r?a5&4U1eyvzFzcYa_pppxXTBECamn? z&J=m+%CEeUp>6NOSv!tSe6*Fr?No)1^?I)t7rCapn8eKzTpJf4k$%MAbH$Rs=c>C;E&&y?T>$fXeEzk7K=+C=mqg;7LD`em0 z5juI-(vOdV9lbXknecof^QO+$@O|I;Y&U+ht-Qha!*#LpyN7?DtovD)Bds(wcVgbR z6L0=SE8P{pr>(et$i{i0)&|6laey1CcO_2y?JyX~=lQC;?U`@OG)K6~32fBSy;DuZ(u<8`|; zuDc9GIn9+0&N`89{7BtcX;!HI=@s{q12gTb!qgwB=ALN{(KYY-@na67ow(!7TE8dD z6s9lWi&|K4H7bOCODIbRW^!&7&i(h2Q)7g7t zx1T7SerbK#YC8e_$LD5j^-6aa?3&W9bZtS0sYViSS46YYwTgO=mv8D8pGcb~2D26B zfE@29Q-HcxmgZg8m~=V+bI+V>7RvJFF1Jj(=J-nsTJ}~i*$Z?*-a?PruE%oM*Mb~r zbZynt?wD;x%AdcOD-=Oot~IIq-JVb@>#l$$HZZfn{yg^E*(?3Kw5!?XEimW6T$UWH zbX5=Lbt~oNQ|hdDvRIJbqi|*0;O2BL?WK=clK+UaRQqQJT#B zF}GD|x{gu))N_4GBAWatzWVrY!lj#nz`(aZD1R_Y;`XyAn%jXT4}+(xpUXO@geCxD Ch(hH6 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column.htm new file mode 100644 index 0000000..189c04e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column.htm @@ -0,0 +1,60 @@ + + + + + CSS Flexbox Test: Flex-direction = column + + + + + + + + + +

The test passes if there is a 3x3 grid of green squares, numbered 1-9 left-to-right and top-to-bottom, and there is no red.

+
+
1
4
7
+
2
5
8
+
3
6
9
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-column.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a23260e43b77514b178d88ecc1f3458c90a0b179 GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK4=^zU$;M6Zp8zSI0G|-o|Ns93nMP-g7#bK3 zN+~>GU|>n~ba4!+V0=3@FmJV$K%1lB`8_kfns^teY(AOy$n8|yriNF~&b&X^Ho-)F zi{)2`|Le01`evSKe_QCvq1Yne1SC{`-8-Ib{Busw`_~P1_Afoxo_qcO!s|z-Hs5z! z&b!>varwknzOyR)HII+4xzpV6^^Jxt|V8V|n>tu63zf^iB?g1|6!fS zjE*DQAE$Wu*q?DZQK11H9r?a5&4U1eyvzFzcYa_pppxXTBECamn? z&J=m+%CEeUp>6NOSv!tSe6*Fr?No)1^?I)t7rCapn8eKzTpJf4k$%MAbH$Rs=c>C;E&&y?T>$fXeEzk7K=+C=mqg;7LD`em0 z5juI-(vOdV9lbXknecof^QO+$@O|I;Y&U+ht-Qha!*#LpyN7?DtovD)Bds(wcVgbR z6L0=SE8P{pr>(et$i{i0)&|6laey1CcO_2y?JyX~=lQC;?U`@OG)K6~32fBSy;DuZ(u<8`|; zuDc9GIn9+0&N`89{7BtcX;!HI=@s{q12gTb!qgwB=ALN{(KYY-@na67ow(!7TE8dD z6s9lWi&|K4H7bOCODIbRW^!&7&i(h2Q)7g7t zx1T7SerbK#YC8e_$LD5j^-6aa?3&W9bZtS0sYViSS46YYwTgO=mv8D8pGcb~2D26B zfE@29Q-HcxmgZg8m~=V+bI+V>7RvJFF1Jj(=J-nsTJ}~i*$Z?*-a?PruE%oM*Mb~r zbZynt?wD;x%AdcOD-=Oot~IIq-JVb@>#l$$HZZfn{yg^E*(?3Kw5!?XEimW6T$UWH zbX5=Lbt~oNQ|hdDvRIJbqi|*0;O2BL?WK=clK+UaRQqQJT#B zF}GD|x{gu))N_4GBAWatzWVrY!lj#nz`(aZD1R_Y;`XyAn%jXT4}+(xpUXO@geCxD Ch(hH6 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-default.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-default.htm new file mode 100644 index 0000000..e4e2274 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-default.htm @@ -0,0 +1,59 @@ + + + + + CSS Flexbox Test: Flex-direction = row by default + + + + + + + + + +

The test passes if there is a 3x3 grid of green squares, numbered 1-9 left-to-right and top-to-bottom, and there is no red.

+
+
1
2
3
+
4
5
6
+
7
8
9
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-default.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-default.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a23260e43b77514b178d88ecc1f3458c90a0b179 GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK4=^zU$;M6Zp8zSI0G|-o|Ns93nMP-g7#bK3 zN+~>GU|>n~ba4!+V0=3@FmJV$K%1lB`8_kfns^teY(AOy$n8|yriNF~&b&X^Ho-)F zi{)2`|Le01`evSKe_QCvq1Yne1SC{`-8-Ib{Busw`_~P1_Afoxo_qcO!s|z-Hs5z! z&b!>varwknzOyR)HII+4xzpV6^^Jxt|V8V|n>tu63zf^iB?g1|6!fS zjE*DQAE$Wu*q?DZQK11H9r?a5&4U1eyvzFzcYa_pppxXTBECamn? z&J=m+%CEeUp>6NOSv!tSe6*Fr?No)1^?I)t7rCapn8eKzTpJf4k$%MAbH$Rs=c>C;E&&y?T>$fXeEzk7K=+C=mqg;7LD`em0 z5juI-(vOdV9lbXknecof^QO+$@O|I;Y&U+ht-Qha!*#LpyN7?DtovD)Bds(wcVgbR z6L0=SE8P{pr>(et$i{i0)&|6laey1CcO_2y?JyX~=lQC;?U`@OG)K6~32fBSy;DuZ(u<8`|; zuDc9GIn9+0&N`89{7BtcX;!HI=@s{q12gTb!qgwB=ALN{(KYY-@na67ow(!7TE8dD z6s9lWi&|K4H7bOCODIbRW^!&7&i(h2Q)7g7t zx1T7SerbK#YC8e_$LD5j^-6aa?3&W9bZtS0sYViSS46YYwTgO=mv8D8pGcb~2D26B zfE@29Q-HcxmgZg8m~=V+bI+V>7RvJFF1Jj(=J-nsTJ}~i*$Z?*-a?PruE%oM*Mb~r zbZynt?wD;x%AdcOD-=Oot~IIq-JVb@>#l$$HZZfn{yg^E*(?3Kw5!?XEimW6T$UWH zbX5=Lbt~oNQ|hdDvRIJbqi|*0;O2BL?WK=clK+UaRQqQJT#B zF}GD|x{gu))N_4GBAWatzWVrY!lj#nz`(aZD1R_Y;`XyAn%jXT4}+(xpUXO@geCxD Ch(hH6 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row-reverse.htm new file mode 100644 index 0000000..aff8d16 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row-reverse.htm @@ -0,0 +1,61 @@ + + + + + CSS Flexbox Test: Flex-direction = row-reverse + + + + + + + + + +

The test passes if there is a 3x3 grid of green squares, numbered 1-9 left-to-right and top-to-bottom, and there is no red.

+
+
3
2
1
+
6
5
4
+
9
8
7
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a23260e43b77514b178d88ecc1f3458c90a0b179 GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK4=^zU$;M6Zp8zSI0G|-o|Ns93nMP-g7#bK3 zN+~>GU|>n~ba4!+V0=3@FmJV$K%1lB`8_kfns^teY(AOy$n8|yriNF~&b&X^Ho-)F zi{)2`|Le01`evSKe_QCvq1Yne1SC{`-8-Ib{Busw`_~P1_Afoxo_qcO!s|z-Hs5z! z&b!>varwknzOyR)HII+4xzpV6^^Jxt|V8V|n>tu63zf^iB?g1|6!fS zjE*DQAE$Wu*q?DZQK11H9r?a5&4U1eyvzFzcYa_pppxXTBECamn? z&J=m+%CEeUp>6NOSv!tSe6*Fr?No)1^?I)t7rCapn8eKzTpJf4k$%MAbH$Rs=c>C;E&&y?T>$fXeEzk7K=+C=mqg;7LD`em0 z5juI-(vOdV9lbXknecof^QO+$@O|I;Y&U+ht-Qha!*#LpyN7?DtovD)Bds(wcVgbR z6L0=SE8P{pr>(et$i{i0)&|6laey1CcO_2y?JyX~=lQC;?U`@OG)K6~32fBSy;DuZ(u<8`|; zuDc9GIn9+0&N`89{7BtcX;!HI=@s{q12gTb!qgwB=ALN{(KYY-@na67ow(!7TE8dD z6s9lWi&|K4H7bOCODIbRW^!&7&i(h2Q)7g7t zx1T7SerbK#YC8e_$LD5j^-6aa?3&W9bZtS0sYViSS46YYwTgO=mv8D8pGcb~2D26B zfE@29Q-HcxmgZg8m~=V+bI+V>7RvJFF1Jj(=J-nsTJ}~i*$Z?*-a?PruE%oM*Mb~r zbZynt?wD;x%AdcOD-=Oot~IIq-JVb@>#l$$HZZfn{yg^E*(?3Kw5!?XEimW6T$UWH zbX5=Lbt~oNQ|hdDvRIJbqi|*0;O2BL?WK=clK+UaRQqQJT#B zF}GD|x{gu))N_4GBAWatzWVrY!lj#nz`(aZD1R_Y;`XyAn%jXT4}+(xpUXO@geCxD Ch(hH6 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row.htm new file mode 100644 index 0000000..fa53bd5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row.htm @@ -0,0 +1,60 @@ + + + + + CSS Flexbox Test: Flex-direction = row + + + + + + + + + +

The test passes if there is a 3x3 grid of green squares, numbered 1-9 left-to-right and top-to-bottom, and there is no red.

+
+
1
2
3
+
4
5
6
+
7
8
9
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-direction-row.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a23260e43b77514b178d88ecc1f3458c90a0b179 GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK4=^zU$;M6Zp8zSI0G|-o|Ns93nMP-g7#bK3 zN+~>GU|>n~ba4!+V0=3@FmJV$K%1lB`8_kfns^teY(AOy$n8|yriNF~&b&X^Ho-)F zi{)2`|Le01`evSKe_QCvq1Yne1SC{`-8-Ib{Busw`_~P1_Afoxo_qcO!s|z-Hs5z! z&b!>varwknzOyR)HII+4xzpV6^^Jxt|V8V|n>tu63zf^iB?g1|6!fS zjE*DQAE$Wu*q?DZQK11H9r?a5&4U1eyvzFzcYa_pppxXTBECamn? z&J=m+%CEeUp>6NOSv!tSe6*Fr?No)1^?I)t7rCapn8eKzTpJf4k$%MAbH$Rs=c>C;E&&y?T>$fXeEzk7K=+C=mqg;7LD`em0 z5juI-(vOdV9lbXknecof^QO+$@O|I;Y&U+ht-Qha!*#LpyN7?DtovD)Bds(wcVgbR z6L0=SE8P{pr>(et$i{i0)&|6laey1CcO_2y?JyX~=lQC;?U`@OG)K6~32fBSy;DuZ(u<8`|; zuDc9GIn9+0&N`89{7BtcX;!HI=@s{q12gTb!qgwB=ALN{(KYY-@na67ow(!7TE8dD z6s9lWi&|K4H7bOCODIbRW^!&7&i(h2Q)7g7t zx1T7SerbK#YC8e_$LD5j^-6aa?3&W9bZtS0sYViSS46YYwTgO=mv8D8pGcb~2D26B zfE@29Q-HcxmgZg8m~=V+bI+V>7RvJFF1Jj(=J-nsTJ}~i*$Z?*-a?PruE%oM*Mb~r zbZynt?wD;x%AdcOD-=Oot~IIq-JVb@>#l$$HZZfn{yg^E*(?3Kw5!?XEimW6T$UWH zbX5=Lbt~oNQ|hdDvRIJbqi|*0;O2BL?WK=clK+UaRQqQJT#B zF}GD|x{gu))N_4GBAWatzWVrY!lj#nz`(aZD1R_Y;`XyAn%jXT4}+(xpUXO@geCxD Ch(hH6 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-001.htm new file mode 100644 index 0000000..1d8996d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-001.htm @@ -0,0 +1,129 @@ + + + + + CSS Test: Testing all the values of the "flex-flow" shorthand property, with 4 flex items in each container + + + + + + + + +
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+ +
+ + +
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+ +
+ + +
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+ +
+ + +
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+ +
+ + +
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+
+
1
2
3
4
+
+ +
+ + +
+
1
2
3
4
+
+
+
1
2
3
4
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d210b04d3a15a94d3e8956d9c6263a585bb68131 GIT binary patch literal 2059 zcmZXVeLT~98^^bZHOjb6o@&3@oI(%uAj}z?VblGZ+ReyP;Z!frQlk^0&~{WaZgCvU zd1<{Cd1#&%HEt<})J?Z-dRQkWv7zNT#yxe;>vTH*T-W#Y{p0$+uj~3;*Y~GGWbcho zb0`P|+DIZ2C?Jp)00MzC)@f;O>hvpZHBSG~Km9ycS64O1w6rvhFK`>Y2?FUhlL$D! z^I+wG+f_w}xz2|r>&$}zSK9!;7?2|vPx1YonC%B5x%CaM*>=h)SKs>9+BP3 zyI;hHRVqvsiiJCUnEB?KqeE`4+9m8%Sea5#nkVFV|K|hv@SNi5i-g!#^+)hwFyR)| z##^-7AgnO_fW_;aOhw>oPR(3r?@80|!rQuIsY3$8(EmlB!~$pSLUXSqn<0lIlGRg? zK9qb9CkF#7aU{H^%EJy0bXO61qe$Cu(a#GyHeN(9^B0CO$s?t4^6s$sQ(&PuW>RcE zHMlyl8n13q#t7owSO?a1jiwVtjX>u+YV6jbtrqxu4G=JTnN=)+I-71dOj83_*b^y( zTB0SYfO_lf5yKlBxb=mWbG+d1B(aVN0)A!I#jz}6@%+PuUXKr09P@rAR$+x;L;)`c zgWRCpSFt0QT976bX}+hWzM=M#HmxVd#HdnqcVMp({s|TNHbg?p%#g(L2ls}ix3>(y zk?8#*e$zk^<6h?#SjqaAyrjs9;Qn=_KH7Zqd|0YFv=~5_8dxDSV0g$^MlmjBR*VdX zhgf1Rve;3LR7ZAvHWx|?j(x4bT3Xnx!H)k{7Oac*Or_T>#U=AYEir1YEqedT?nI#; z;8jg50JM|5Q8pJC!r>B^G_If_nUcx%sV3eUae4~}bKU!`(zZL3HxC)$;vyHHAAK=M zvukT_iBn@IEg_4X`{roU8==t#3GYUdabnz*OYo>Q+B zD>5ls*CxlW>DWy{+AFif0y!{H!fnfvo>(s4@n~DaG&|9*Bl-fZXWJI`xu4xovY%9u z&9H(e;&%?(!v(>$u#HR(V*$l|E^L_F6@H2PzAEYU62CRYfeg!i_ID)upGa8mH5dzG zleZ?(YQZEO!MYCDv~e3fuCH1vx2&~Ua?z5Za9_3_h6>;wgk2Wi%rO?W z%T(ko?)@)=u;qA)bpW?NzW75HhysKgy60K)^i&MsUc++ut_fw;jo(6!-%BY?h)*WZ zed>LePW-xcnof$kzlc4^v{N(aq5hA_r6_b!)s5beXF8P096w~a;&WE~x-WQ!HCn*? zno0Up=VlD%cRct1V$fy-t* z&!!>YvvHA`jPD;bI3}q%*7B1Dmgu6t%gRpijjXV+kz9@@_p>!i-L95vv_`Oz3)f{A zL?dsV5|5|qZRIZ7W_WOxr_+?W`4Pq^DGM4#`$TKSMgrO?(vw4|klr#UfrTQyp zobk1TL4%faQNYMn5fj ziZO@HY)axm8}sv@O28){nB!s6VX*?$#{YZ{^hdt(;cXY^kcHwXrVIBm%=*WL(;=>#TKIR#=7e zanDoizmPa+&V>Rd?9nPb==Ph};)nx+2GC-i(8VhR1}U>o_Tks-Lvm5doewTC@uG0N zPc|Q5+MN2=HBTb^P0@qax}xkeiIV6<3$t+KpvRS8;I+z?yU(M!k@^(tkBS3N)3-wc zFaK%{3HsW=b^4!H%Tmdwv*#IX`&OzcQDRKoLv(Db3Q0d2^^tDJ=HWV?=roxS=PZyJ zdB-DFYz%@Mh28?_*>PRmjV$CBrP%@#_3V;;+o3w#Ls#b4(f@cX1ej2ct4GhxoqrQ> jaZ;(uqxh5~X4aK#$aFZ{b9{&9O9zoW$%JbBiOc^7j_A$Q literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-002.htm new file mode 100644 index 0000000..17465ae --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-002.htm @@ -0,0 +1,129 @@ + + + + + CSS Test: Testing all the values of the "flex-flow" shorthand property, with 3 flex items in each container + + + + + + + + +
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+ +
+ + +
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+ +
+ + +
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+ +
+ + +
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+ +
+ + +
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+
+
1
2
3
+
+ +
+ + +
+
1
2
3
+
+
+
1
2
3
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-flow-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ec95fc23b3ce11ce7b44e35d4d585f95e7ff7707 GIT binary patch literal 1987 zcmeHI`#aQW6d#Oy8@I1ZDeW+mYP%4Xv^%4iufZtIh)fw_im=(&RZSwdM?+_I;nY$ zR|6^Y=v=L{W^C?6iQG_`0u^|y7gz1J^B$h&;ixXl1_Ry3J8zDz;|2-kf!xA`V}rHwCCz3}-xE6|%`Mn(cF3ZT zf(dM9Fwi`~4NiCx%{k#3Jiu*evn*uzW~7)z1mbC^j%@tar>uU1!;sV;J9Erz_L=q{ ztmlH(%t@um)cjs#|NRFY0}V5)TD1&7M}!&@Oy&j(#codGKuy3!8%zu;T}pq4`j%w0 z8i`40=~i%kP_(0)mZgG`BzYbtq)lyc>Ya9_*sbPahD z$OucfT3amKSFT0L#jYtVg7}*J?F9RY@}Gi-9wbf=hK$Vi=2W+h1hRuljpSwi`qcN9U9=JzMr(v! zUD|V|YnPYuUXWIEKX*W*08L*3^%Zt?%A{sMT|4uZ#i={F=u-#-R)YOEvR)b6#$Kjh z_VT#(x}xt{3ocUxB=VD7i<-eEC=ksnCNG!X@Xw;YKeW<2nOt`x$Sa9#ZXpfP+jID7 zM#+NHUWqYo;l{A-xkvytX3LVq$6$%qi~cX!ze{$vy<;X+=u{HWbs{CUsHdEYkDOnD z^%-SX^LjLdu5>68c6g(UjO5q+^~Gk^>xg&YVDGod(-$vFW;1?d|K`do>22;W?Apfu z(3vsqX9?cdlS>cZInM9*uj4OODC+eLpMD;>&l)1R=?u8reYiZPpQmpRuS+lQBZX5| zA7hz>`q?&S{`L+`HY zIEfMbF0wG)dM|UJ6%Zaj=DNpe9S)iKK5f`ei9UaEzX%(#gPU<7Dx4Fpt?GI%TThM8 z2!$eZ%L4jUaFa2h8oU$!CJ5hpsf~}X!<4pvLTSE+%G>1vq<5c9RzhPx2RG(aAKfu-?d7JHz6700$%YCC$n z5!mQ?zbG{Uv2kk$+Bw||M@g@#tQvFnG#NUQBj{X0FJvF;w092UDi2n3 zAd;dTHII3d%5FdfwJo8yaD5hr-d!4;q^x@Q1cvzs!XlswX5yOeE9b!Rnc1G0vRP)@ emPgM`m} + + + + CSS Flexbox Test: Flex-wrap defaults to nowrap + + + + + + + + + +

The test passes if there is a green square and no red.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-default.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-default.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5c432eedfaa20b21279d53d49e522493162dcbc6 GIT binary patch literal 578 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKH!v{+$*u{HRDl#{fKQ0)|NsAiOoj#q*Gq-B`Df|QQ)7`^YBiia-K&j z$BY?4+Bdx3d+h)6hlM8Vi$7P&s`>BxTJ(JRee?DGPBq_H6)mNm=arRstUqeE)PCBv z|F)a!zh!V{pR1|bHq*c6-FdIio*VD~{Cw%vru83Zu0QqNF8}@4ch;M(sIL1N^!U|n z|8HxKoj-hSQ|R6L?f0Flr%kS@U9&y>*{sa@$CB6ma_ES?{l9IC;*s?Qe#a(UD(byI z*W1HdYsvAQS8|zCI`Rc?%WU-2Q@s1B=INeaHNR(=ZQNP^WtQj8WpQ@}=gStme_Q`~ zQ@hW`y*0mi%8#tCniq9iRptHNExF6)+-b|n-lzQJW%BRmiW^&Vl)qX%_q_aR%BRaO zLTpp|gjXJmEWPEo@`u`uBj%y|wEWh+wfX$syXJn4^siOlo=u3>|8vyhn?9=||J2t@ zvlPK$JpIqk*O%w_2{_sH3z)X7P;}u{%;E@i?vQf2BCx1!K!g|=KA8SvnxcF3`MlQ` Q-hf=`>FVdQ&MBb@02}iMe*gdg literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-flexing.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-flexing.htm new file mode 100644 index 0000000..72f823b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-flexing.htm @@ -0,0 +1,40 @@ + + + + + CSS Flexbox Test: flex-wrap flexes widths after line breaking + + + + + + + + + + + +

Test passes if there is a green rectangle and no red.

+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-flexing.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-flexing.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1b9a92e7b6fe17e7bac035594e880fa5417f45bd GIT binary patch literal 568 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq8UNHe9T}Gu`~>&+B)T^4rh64c&HMzuI@FhK9cQ$+xk>op&}XZJy`xtWW9t z1c8-d(ck};b)V4R?>;$2Z_|ET@tG&wL!whw)tmc9oBWGfBW5c2yer$)+VMzNuX=Id*M>o^dwwo)h|9tZ6=bie`E*Kqd)s^xzTtM@E_Z{hb@@89iowZDk@>@B>t-JNa z8{fdQ$JRu-)%obq;oEBf}SlJ&QW%u1trJ^O$q_cYe} z+i*idD&X;Z + + + + CSS Test: Testing flex-wrap in horizontal flex containers + + + + + + + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..703987050e60f52ccd856d65684e90dd32721a55 GIT binary patch literal 335 zcmV-V0kHmwP)aNRifDQ+ite_$hlaJbX!Am8^b1iWF(%HKnHw zd%CJGmVzsV^tv=;P)vUiX~dxDDLtK6N>68&($ngM9RKn1au}p%Rw1=Kb z_JS)VL5dV<%$m~E`o~XMu&OT>gLD%hy)KOylz&YRgVfy(q(2!HJ*B7fO8e-kWG@Jc h1Zm`;ChsXs$_I=5rl)RE&v*a;002ovPDHLkV1o3xkp%z% literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-002.htm new file mode 100644 index 0000000..019b53a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-002.htm @@ -0,0 +1,64 @@ + + + + + CSS Test: Ensure that min-width is honored for horizontal multi-line flex containers + + + + + + + + + +
+
+ + +
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-horiz-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..062333d6ebac93cdc8a7ac2bbd4e7b09b2d38951 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^n}9fgi5W;9$T(gEq<8{+LR|m<{|{s`F#KP8tk+hVA5_zvy}`!IQl9DUAG2?tIDf6>3k$AVvt9bmT>ZEI za^F1N7wi literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-nowrap.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-nowrap.htm new file mode 100644 index 0000000..9a287b9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-nowrap.htm @@ -0,0 +1,44 @@ + + + + + CSS Flexbox Test: Flex-wrap = nowrap + + + + + + + + + +

The test passes if there is a green square and no red.

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-nowrap.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-nowrap.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5c432eedfaa20b21279d53d49e522493162dcbc6 GIT binary patch literal 578 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKH!v{+$*u{HRDl#{fKQ0)|NsAiOoj#q*Gq-B`Df|QQ)7`^YBiia-K&j z$BY?4+Bdx3d+h)6hlM8Vi$7P&s`>BxTJ(JRee?DGPBq_H6)mNm=arRstUqeE)PCBv z|F)a!zh!V{pR1|bHq*c6-FdIio*VD~{Cw%vru83Zu0QqNF8}@4ch;M(sIL1N^!U|n z|8HxKoj-hSQ|R6L?f0Flr%kS@U9&y>*{sa@$CB6ma_ES?{l9IC;*s?Qe#a(UD(byI z*W1HdYsvAQS8|zCI`Rc?%WU-2Q@s1B=INeaHNR(=ZQNP^WtQj8WpQ@}=gStme_Q`~ zQ@hW`y*0mi%8#tCniq9iRptHNExF6)+-b|n-lzQJW%BRmiW^&Vl)qX%_q_aR%BRaO zLTpp|gjXJmEWPEo@`u`uBj%y|wEWh+wfX$syXJn4^siOlo=u3>|8vyhn?9=||J2t@ zvlPK$JpIqk*O%w_2{_sH3z)X7P;}u{%;E@i?vQf2BCx1!K!g|=KA8SvnxcF3`MlQ` Q-hf=`>FVdQ&MBb@02}iMe*gdg literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-001.htm new file mode 100644 index 0000000..069dfcb --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-001.htm @@ -0,0 +1,102 @@ + + + + + CSS Test: Testing flex-wrap in vertical flex containers + + + + + + + + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c4c60aa5d17ff54267880d0c547cb4aeab009182 GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^8-RE@3p0>(_B!ncq=W)|LR|m<{|{s`F#K;|XjpsW z*?)%r;(~ngK!K;8E{-7;jBl?V>}@s>a7!%9RB&KU%wTZg&Q$!nU5Nk2F&-C%Ru|5y z_n%G||KN)`(Qz)wuA6mx*uin4EW}No-p=_wMR>@67%@5oTnDg8349uB(^Z@_d^Rn*ah2=I&p{ z@cTQ{yRDXM-m}K^9Od1x_cm`q(fYIvZ|fV@-wgZ7u>IK;CYbhmA+fi-AMQ0Yg1~-W gpst*IF7|hspWS-5p>pEJSD+y9boFyt=akR{0LC1E_W%F@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-002.htm new file mode 100644 index 0000000..6499437 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-002.htm @@ -0,0 +1,64 @@ + + + + + CSS Test: Ensure that min-height is honored for vertical multi-line flex containers + + + + + + + + + +
+
+ + +
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-vert-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6c9077f18792793477398c9659005e18ca4079 GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^0YJQoi5W<)_`F~{km3pO332`Z|38q)!0>gJV!GMSLLeGVXG7G2cJUkj|)mGy4G05{^kgtn?PUye(fC`}x zA2@5(&%Akk(n4UT;js<=2Q`;YNcVL}P&Qc|QE#yzik&I``0KiJPrm$OV%g@hxvS~Z zs;}%P&cwL|#H~{Hc+SwM`n2p`QoB9>AKO`>A?53S=gg>%Z@4(K#1-T!Pgg&ebxsLQ E09zVV + + + + CSS Flexbox Test: Flex-wrap = wrap + + + + + + + + + +

The test passes if there is a 3x3 grid of green squares, numbered 1-9 left-to-right and top-to-bottom, and there is no red.

+
+
1
2
3
+
4
5
6
+
7
8
9
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-wrap.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-flex-wrap-wrap.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a23260e43b77514b178d88ecc1f3458c90a0b179 GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK4=^zU$;M6Zp8zSI0G|-o|Ns93nMP-g7#bK3 zN+~>GU|>n~ba4!+V0=3@FmJV$K%1lB`8_kfns^teY(AOy$n8|yriNF~&b&X^Ho-)F zi{)2`|Le01`evSKe_QCvq1Yne1SC{`-8-Ib{Busw`_~P1_Afoxo_qcO!s|z-Hs5z! z&b!>varwknzOyR)HII+4xzpV6^^Jxt|V8V|n>tu63zf^iB?g1|6!fS zjE*DQAE$Wu*q?DZQK11H9r?a5&4U1eyvzFzcYa_pppxXTBECamn? z&J=m+%CEeUp>6NOSv!tSe6*Fr?No)1^?I)t7rCapn8eKzTpJf4k$%MAbH$Rs=c>C;E&&y?T>$fXeEzk7K=+C=mqg;7LD`em0 z5juI-(vOdV9lbXknecof^QO+$@O|I;Y&U+ht-Qha!*#LpyN7?DtovD)Bds(wcVgbR z6L0=SE8P{pr>(et$i{i0)&|6laey1CcO_2y?JyX~=lQC;?U`@OG)K6~32fBSy;DuZ(u<8`|; zuDc9GIn9+0&N`89{7BtcX;!HI=@s{q12gTb!qgwB=ALN{(KYY-@na67ow(!7TE8dD z6s9lWi&|K4H7bOCODIbRW^!&7&i(h2Q)7g7t zx1T7SerbK#YC8e_$LD5j^-6aa?3&W9bZtS0sYViSS46YYwTgO=mv8D8pGcb~2D26B zfE@29Q-HcxmgZg8m~=V+bI+V>7RvJFF1Jj(=J-nsTJ}~i*$Z?*-a?PruE%oM*Mb~r zbZynt?wD;x%AdcOD-=Oot~IIq-JVb@>#l$$HZZfn{yg^E*(?3Kw5!?XEimW6T$UWH zbX5=Lbt~oNQ|hdDvRIJbqi|*0;O2BL?WK=clK+UaRQqQJT#B zF}GD|x{gu))N_4GBAWatzWVrY!lj#nz`(aZD1R_Y;`XyAn%jXT4}+(xpUXO@geCxD Ch(hH6 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-002.htm new file mode 100644 index 0000000..593dfb5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-002.htm @@ -0,0 +1,73 @@ + + + + + CSS Test: Testing that flex items paint as pseudo-stacking contexts (like inline-blocks): atomically, in the absence of 'z-index' on descendants + + + + + + + +
ThisIsALongUnbrokenString
HereIsSomeMoreLongText
+ + +
ThisIsALongUnbrokenString
HereIsSomeMoreLongText
+ + +
ThisIsALongUnbrokenString
HereIsSomeMoreLongText
+ + +
ThisIsALongUnbrokenString
HereIsSomeMoreLongText
+ + +
ThisIsALongUnbrokenString
HereIsSomeMoreLongText
+ + +
ThisIsALongUnbrokenString
HereIsSomeMoreLongText
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0972738babfbb7c5c73a55a3ed03893d28aeb72d GIT binary patch literal 724 zcmeAS@N?(olHy`uVBq!ia0vp^7l62mg&9cN@E$l1r1%4TLR|j?!BU2`H=h0f4-{g^ zJpJnd0|Qfor;B4q1>@VPuQx6>;A#E&kZp!1>&y}dE`f-@*SA=?n-$7Q-|#Ja{%GO} ziTJ!l&2rbwTUuLdj~}xw(^>d=@5PTL`SGGrb^?=PomQO=YSrUfIQiL}@Tsf)J{zd4 zwfA^7ecnfpC4xay%}*`%ypo}i?)3Qvm#?Ig(%0je7eelM3s!Z#DR)}+{B+Vohfhn4 zTi30M6wRJ@7IU$c3bCfK?3=%46_ zq__&<;@*xIJEcM|?Y{DG6O*CR^oK@Qzdk$NydYfU=-l;A8k2K49obAjU77j+tFd=- z=GRw3{DQpqg}rhY&1*FaO0RVGPH?@x@!dVPEekW5r*NM1xhHo|=nba{^VLh-OV6I1 z@ZFECvOdqe<;{+F!kc!q$s2|IpB_+uExxyDgX0Z_904LV}j&i@*|7|fAw;XNc* zSoJ;q@6{KaMH`M!`+wc(G`o}k^71X2*W~<#zPZI7%2M&WJ-MGTZcd`-SId>Volk62 zd9x`fTz{MV>jioDwx4;)uDEhe{X{F_O%6RpGt4TL63@?BB3?7i_3M#~CuhE#z*<^! zqv*^mffZf$wq?JqB00_0InB8=Q)x%f2KA#h?Ndwj9_;?OAjj~*k(FBe5?^gOcCD?w zV+HFhBPH*+i3hB2KYKHyLL@2XsQt~T2T_-QIcW&9?Yxo^*Ls1c=Y?KR+HDTeUVEWg z<=jh!M1sR@_Y3YaDh>bEEO;%ebt_;h literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-003.htm new file mode 100644 index 0000000..8eb99bc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-003.htm @@ -0,0 +1,73 @@ + + + + + CSS Test: Testing that flex items paint as pseudo-stacking contexts (like inline-blocks), instead of full stacking contexts: 'z-index' should let descendants interleave + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-items-as-stacking-contexts-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..05fd667bc0a498dcef60a5d9424e56a0bb8d690a GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^DL|~j!VDyTg*g=fDbWC*5ZC`eu#{o#jc5P=Gc*9< ze+GtzhK(Vy`alJKo-U3d6^w7Mcyl@^inKh8ei*VtbWH-Q_6_?cZFw0%wi<)(pVJQQ zpLo%~LNdwzr!DurP8Gi(<9S*uJ=ruP&u@x1+LLnY$)VLXpAK`(W%_OZc + + + + + CSS Test: Testing 'justify-content' in a horizontal flex container + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..fb552056b188e92583b4ce43bee59c99e0587c6b GIT binary patch literal 324 zcmV-K0lWT*P)5QSlICJl{O;s`Xa;WQqF+S7RhEVPIq z4mQbd_W%2DW}D=741gW?mtTs z3|2iC>jGL literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001b.htm new file mode 100644 index 0000000..283664a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001b.htm @@ -0,0 +1,147 @@ + + + + + + CSS Test: Testing 'justify-content' in a horizontal flex container with "min-width" + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1ecc9135f4300a63d4a75c15cf3c73d25a4fe18c GIT binary patch literal 324 zcmV-K0lWT*P)5QSlICJl{O;s`Xa;WQqF+S7RhEVPIq z4mQbd_W%2DW}D=741gW?mtTs z3|2iC>jG + + + + + CSS Test: Testing 'justify-content' in a horizontal flex container, with margins/border/padding on flex items + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..76db4f6a2e8ae8af7c7b9a907e09bbe74b0e31ea GIT binary patch literal 1268 zcmeAS@N?(olHy`uVBq!ia0vp^w-^|h?yxWeS;b#B-vLsh0X`wF|Ns97G8-5gCcK;Q z|G;U6hKB!38IJ#O%wb?)ndRx?7*fIb=BjVrZ36-K!lNGF7x_=HGWXD%(8_nAx2pNo zKX;8=3TBa0el1A*CwBJxwYGWzgE=odJfZ_iuAZB|KJL!G7n+yi?*ISR1_IaSE!%6+ zznaxG@4n-#Ey0WG^2={ecD6GCfnKp#nJuzW_kXsht!H&@(pNgaC3sSf-f|;x_tmm5 zXZ5j$F7}=6?ChNF-mQ18DQ)9UzEzSjbM}4aKO4v0#S-r_FLSxd%^1H_Jw7QQh)SOI z?UBtoF_E3M?3tJRI)ClGo9oZ_^8yH%M3od?D9fmQE-4x7tiT^8K4jy|(mkycRn>zutwwXTLxjbP=nxkCHJ3wFIb@?Ple z>uC!?VC(Ws{d0S_E;#YF+hcN>SJ|G1+a3GQ{Qq#$?RS)KQSOO$koT8eT-4pX%{f{Y zVeiTL;uF>=v|su@Z4TdBv+Z$LZ$$){-r8bmVzQ-d%Zo=k8Dg_vFPL@k=tn+;H&ZTi zdxYKj#dSZrXcpK*QkS=gFW1u5c(vl)mSCg*OzVW}Z+>>ozJL4ar=2VBJ-zjL!dqru zAn;v$SC8+O+*vh*M<(sFj@^E_`?C1a_isxo|L)PsFVfv#$-dJJdm#LA-h91D>7e+; z^jn&i$E#1Tyv=QK{5u=S@U@1%)rE55++BC}HJfBEclmoS>kc$-xA4w7d(`o@_5TZR zwl47a;QC}{nb#V#>oMQNmwU{PURRZ?vqCEOmJBe~q@#)o#kTI9{rbSIbKRHA|1G?k z?Kts4_X*>-Q*x>pmzSJfyY|E0xtBh-fK$^HU}_4@bPRMaT|ReGeVoI?p7Yad^ILbH zjIEa2RJ#BaFOiwq`h2(cJQG3$x##}6a8dES+rOG0i@Uuc`|sBL$KUF%Ec!cV+c$2Y zHw1O}S^S&*dc&=E-ItNm;-;6ZOQQ2iTi@&M^~(T(+{|0u!BfRVN<+$TY5u&=ts(Oz z=kxou1&)@P=bg9lgA;Xi?!QM1)?QtM6l^c2Uz_51u{zpHe_mAGeLf`5Ye&@-PTTSq zi`_+jV$=P%ix>FuK@xW~*2Eq5|Lgm2uO3`;l;~Kb@jtx5+M~*odyB(=RhP&KH@FoZ zRO-9!EAK5y$(-Q`%ty|vFKy~>4qMpUY`3LC*V1TD$CS$DOK!w03e)Qg&we?}PkmR9 znLH>P$m{O5u#P(NEGvY2hsEOs{JL@8eLVU~srNK5{$>3Ces0C~jMKUx6P;iCe~z0I zpM7%T8`mc%tyM+q52PQ-zb*goXvXQRP1*fl!Ku|Is;uyW)|U0o-Pb26AMW;;S?(2A z@366nW$ORvLaRNuu2q$0U+&afe6}5AqWfvRbA4hPcS>Dl((AK=y0l}{y!*_JjcM77 R*8$5e22WQ%mvv4FO#t_@WLN+I literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-003.htm new file mode 100644 index 0000000..a95237b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-003.htm @@ -0,0 +1,149 @@ + + + + + + CSS Test: Testing 'justify-content' in a horizontal flex container, and its effects on flex items that overflow + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ddc562505b836fd1b11e379c51f0438809bdf146 GIT binary patch literal 429 zcmeAS@N?(olHy`uVBq!ia0vp^PZ$`O5}25Qtmp0OKY z8M^+3`!X;v#(26ohEy=Vy|Qub?Es#(iz`0F$vqV9;_h9bY18GcsAba>{)vGtZ&u8z zfLq>dmCs-Ef7twxzjkunzf3jHNjj56zi! zeUm_Lc#`x&>i;i4kQ<6;|2n5yW*7ZM(Q}fF-s;zf(}7MP zFLMDpAnuv@*NyQ&7nmBYzp>*;bsf;qv{PI3Y9C1n$Lm&223hf|{}=DF!U!&Zm9>|F P;m_dd>gTe~DWM4f + + + + + CSS Test: Testing 'justify-content' in a horizontal flex container, and its effects on flex items that overflow, with margins/border/padding on flex items + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0e949ed0af1b54749beba727dfb8c92fbcb2c022 GIT binary patch literal 914 zcmeAS@N?(olHy`uVBq!ia0y~yU}RxnU|Pn)3>1l!`s)a!gaUj*T>t<7-@wo?;oXG) z2Tn6IH2h!6Fz-=rI|Bo=wWo_?NCo4Ys|O1oCGZ@-nCijU!so$yW(OmmAN!8~Vc$;Z zdF)J?kXrvf{GfHlm;cJ1``;VC3Gx0>^|q-23@&7>5nSTVdv4>=Y%h*r2__J5(p8kq zZi#AAv7NqenU|AMFfYg4<5hmUdR-$9z2g9a-%2XUu|F4>drVle9jXK*3{Ti%##b9IRIYixYF~GBR1$e8C{#o zU)9XwSry&>L;b4Ixr25PtqLYm*(|-`&5N@?HV7Rm?AoOSHgO8fMEm#8w+KBnbe%5O z@`2OemZSJmuFfH$GIs|DhZ~v?|8IX_fI;8|;3(*`s(em#<@ym8IkIUjqmnaY2D z#iR*5!OTw2EC0=yd`ziEKM~|NBwugsmcPDonn2GDRz@&bcrilRqhD%Ha`*Kq0?Q0o zK%gZG=q-+|Ku_t_`c7?0Stcc5)&F%)iFEgdmb-!i0s`M$J&x`A4IGA0~sJk!F$-*x%9AaV+)Wly8B}0WKC1yFiusJVZd?}*ky($mT z9f|QRDQmRlzp!ym+LHG2Ixwuc+n}+Wx<=5Xf>&?DQFAYj=5>PE+t) zfBr>&b!kqpp-6LL2nSn5QuB7kATbX?Gmmfat$hreyldv(`RrT3b^8BzW1y*ruK;6k z-u-P$c5=*j2PK(9dpS-fIrR!Htns6or7}Oi@O}e&|!~F&*MHE0%#IGh6&tKc)9?cK&uHgU1 zi3sY9eNr;9-Rx5Y;-;_QM+=&db#l!d&qZvj1rDk2`J#~H`8uXWIR}&?c4$8Q9>HIE h;ku&7B$EaDpUPb>Ec-S26fl!9c)I$ztaD0e0svu5Y%Tx* literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-005.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-005.htm new file mode 100644 index 0000000..4c57dfd --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-005.htm @@ -0,0 +1,140 @@ + + + + + + CSS Test: Testing 'justify-content' in an auto-sized horizontal flex container + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d4ca3d1273324f6ee5fc040714e296918d0167c5 GIT binary patch literal 866 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYVA5e?28xugRy6@qJOMr-uK)l4pYU$N{{yH0 zFJ#>Ho!tzRbV( z{#Et7RDAYMxIY7f>|IBz}#K_#k4s3FVKP_bRB;8jeoDS zU;VwI0ZBO|07PcpJomVNb=Z3rMj*&!Lkfeq!e9HM_?ag0p@tW0&}(PK{ojKX8X@5a zvmRvLyd57l^{ocE3<2ttp4_cHd*|P2MS%(YyeMY%RHZqQgOd*xP%;3Ah+(@d{|0n^bh*9oULWq!jJUs3NBUdL@NZvvTP!VHhDD21w`cEdDm zX;RbVSn5raYCaARJw!T%gmd!yUDw~tRRDprNa5e1;wk@%_eJ?}>BleLw*WH@gQu&X J%Q~loCIB;+QfdGI literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-006.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-006.htm new file mode 100644 index 0000000..91151e3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-006.htm @@ -0,0 +1,142 @@ + + + + + + CSS Test: Testing 'justify-content' in an auto-sized reversed horizontal flex container + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-horiz-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0d19f686bd6d63e338675b90a28ec3869aac7fa5 GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^Hy9Y05}25Qtf?NZ{y>T+z$e7@|Ns9J-c9&_;Pn5c z3|;@ieHj=S9XwqeLn;{GUUAG~G88$Uxb@7k+4mMN|G<%VuhynZdSS&bxdjt;X)Z}P zG~uO+^4~gZPWgZ7pUZx!`wB|5C3fHZb$8Rc&+bVQZENGTLghOXCANKCwrwI%YHqg8 zdLZ?By4O{ZbgBNM7NF#euWLV+0lB<7Q&+7&ZoqRp?stcBTjGtNYpbvSS?4y#fah@3 zjbBTHem=-%X-kag&E2{(R}&~7KTZC^=|3QInf1f9zWxxda|4;zr} literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001a.htm new file mode 100644 index 0000000..a8e1335 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001a.htm @@ -0,0 +1,143 @@ + + + + + + CSS Test: Testing 'justify-content' in a vertical flex container + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..86cdcec976feed3e6ce1b6330d1d7673c00f5389 GIT binary patch literal 450 zcmeAS@N?(olHy`uVBq!ia0y~yU`hhAZ!j?fNz-rX{y>T+z$e7@|Ns9J-c9&_;Pn5c z3|;@ieHj=S3q4&NLn;{GUNtOQZ6M+hXl|1C?^?zd`|ELc?>idm2z!Vtm}C_eYd}c-G;5Xg-nu?k?otl3ZLMc?0a|qdby8}E$aM^wZbKs= z*rb-PJ$PU=n4$QDJXm8IOeI@1B(Q2OQFQiwU W`mj~5P&pkKDGZ*jelF{r5}E*l*}*da literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001b.htm new file mode 100644 index 0000000..4f68232 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001b.htm @@ -0,0 +1,144 @@ + + + + + + CSS Test: Testing 'justify-content' in a vertical flex container with "min-height" + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..86cdcec976feed3e6ce1b6330d1d7673c00f5389 GIT binary patch literal 450 zcmeAS@N?(olHy`uVBq!ia0y~yU`hhAZ!j?fNz-rX{y>T+z$e7@|Ns9J-c9&_;Pn5c z3|;@ieHj=S3q4&NLn;{GUNtOQZ6M+hXl|1C?^?zd`|ELc?>idm2z!Vtm}C_eYd}c-G;5Xg-nu?k?otl3ZLMc?0a|qdby8}E$aM^wZbKs= z*rb-PJ$PU=n4$QDJXm8IOeI@1B(Q2OQFQiwU W`mj~5P&pkKDGZ*jelF{r5}E*l*}*da literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-002.htm new file mode 100644 index 0000000..3416228 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-002.htm @@ -0,0 +1,156 @@ + + + + + + CSS Test: Testing 'justify-content' in a vertical flex container, with margins/border/padding on flex items + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c2605500a536e5866b84a5ac97fdaf391d360793 GIT binary patch literal 901 zcmeAS@N?(olHy`uVBq!ia0y~yU|I!a-(q0~l5@mrHv%cq0G|-o|Ns93nGFmL6W&eu zf8aDjL&N{2499;s<}ffYYkRslhEy=Vxq7j1wu1<3K)M>M$|{FjVpkmOE;&Vc{8vrM zxn?HEc(aw&_Q4%}v*Psm_fIOsxq!f;=g-}%`X93|U3tk}PxJY=pB1$~&5CAd{jJ!) z>AOYU3@ulg@Q_7bThn4{J72|xypN5${k}i{?ZLw#nG;`K-(PZahvcfW94ms>2CdZ# zTpIP&Yn!zDRISie?cuTMzNJ(}Tz$WP_n#_@E7QtW@9IhLzG}Po zPKf_nDb_bVt6a_3+V{OWk$+~3#1DsLne07TR)U2(qrtfTSk6fyd@O5bx6Htxa-KJNJ$6SMD|9sz;eJuFz<&Por9HR}Tj zEMB#2&eK(UXPZg5e=S+{_Yzxno=uOp{sC?H$^%}&FZGpa) zAuHL!@9uJ$n#EuM^x8Xr(?`$eB3lX9mmZM2T^1#XL7I@mY--m25>HEWps3iL zFYEX2@?;cfS){S2ZQBL0TmSD#NnAZJ-$d^2y5~JYa&7V*D;C`fyBoA(QP-;0tW_JI znm$=^%d_E?`>LI;xhDd)PG`948S;5MZ)nKV5bpil!L^?R7Fzu)`XBoBC?{JP7vtUi$X)pPCll5)cG}iB;Cugc} + + + + + CSS Test: Testing 'justify-content' in a vertical flex container, and its effects on flex items that overflow + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..3f2fe7914097d9a9ab73bda976a3c01b79b5b577 GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0y~yU z8M^+3`vS$Ed%8G=R4~51>X>!dfrs^i%tq<^>tD}VXYRr3+@gH-XzQ`GGrubmzqqfL z_AeHmxOlUUe2CUmucbjNL$pezzSjL;614Ka@3B(ft?_r~{P`Fb3>2U0Rl8YAKh;!y z@1mgc75&G>OZiOg8bUtb_xW$*zV|hIXHAZiUD&tJ#`W#nu&;X#12z8I z4b!(W + + + + + CSS Test: Testing 'justify-content' in a vertical flex container, and its effects on flex items that overflow, with margins/border/padding on flex items + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b162fab687cec4002f2da29a19c6b64a933b17c5 GIT binary patch literal 982 zcmeAS@N?(olHy`uVBq!ia0y~yU|Pk%z{tYF3=|Ox<&FYT0X`wF|NsAQU}%`|Zo>Zq zrx_X={x4;i_b9iWfq}W+)5S5Qg7MAOi@tXpMA|OeTxfX75Ve3&ZW`Z$|6xr!W>VgT zCg<+dzm1*$UUS-+y~gTH|9APGUtg?$x%b;#Y!hyanTbI4O^~Yta8pMznKTF@WW~GJ*=Z44rJ1em=JLv8%X*W|r zkYbRK%iVWB-~vG15E)_^r7Qiex2?Zvx-w5TbXCYoxscM2moAv@R=c{^y6sg5Yv`(* zAm3zs+`BYN!d^0DlhPws&%#gO>Fgxs?31W7e2Q`UvsPo zS_%{mT_wBf^45phWxhbOzm}{z?Y0i?n;_fCuO2N`ygCnTE<|<6O1l}aQv0;7Dz94S zR{HqFOR3OR?BQ>_K+gDIbatX~Daat8ZT~~(uH%j03=C?kRm+2OrFW-YU$U$8`^sM> zGppspbJee|UF`Msys7=ZeZSWPTL0?Gyg2h^QVI)psXH#v z>)r))0iP@+ZcBu&9=&(;@U`hqVzKE`LA(M1FqeTnbai)dtVszpEKp=X5d`%!f>9o_ z_e|&taL9hWv&uPr*GDT*JieNhyZ`pWX6e>O1+GQaHxASoNILvm*)XS3vU%5$)t@~s z{|{VhRrxF<)N|63{r~@FMAv_LI@?V+;BUvPw;x~aTWi0o;Msxq=kB_ILKtM*tXZB@ zvr^SUS52K6vN9|!Xlc|;iUjKYOYLu6laIA1eo+2AZ;{m`he;|xx0qKmJ-8e^{mqIU RUw|2v!PC{xWt~$(697 + + + + + CSS Test: Testing 'justify-content' in an auto-sized vertical flex container + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0b6fd66bf6c53cc2a1e09c1b7f760bef58abdb85 GIT binary patch literal 696 zcmeAS@N?(olHy`uVBq!ia0y~yV44ABZ(?Bvk|Ns|H3KRB0G|-o|Ns93nG@bk_djp|_#d=ib(R+V}SO{MU8w>ptBx{_gV`gNXll=JB1U&-cInSGObG&b<6P z$Gn@y-_I;cJlxgBt1V@=CgaA2LBpAKyRUe9eE#*n^M5w&-0lA1%)FiHV!YB}COHuo wHzc|qZWA5l5FG|*bC2(wseTrkT58MRe~BqjH!x~y0H!|%Pgg&ebxsLQ06#7+i2wiq literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-006.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-006.htm new file mode 100644 index 0000000..9cb36b1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-006.htm @@ -0,0 +1,143 @@ + + + + + + CSS Test: Testing 'justify-content' in a vertical flex container + + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-justify-content-vert-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..88aae81b326f1c61bd8b83603702cf5d08f2cd3f GIT binary patch literal 464 zcmeAS@N?(olHy`uVBq!ia0y~yU`hhAZ!j?fNz-rX{y>T+z$e7@|Ns9J-c9&_;Pn5c z3|;@ieHj=S>pfi@Ln;{GUNy{XHV|QZpr;dCWh!=meX-wl=`9jsE=oD4mkIp&8!Yv= z^8Mf1HeVbaT7bm=oz!CL4Lr@YIyG zT-uQzJyn%~z&du?9B(%WxHdOu_l%x5pEk`}?G6;V77-SwVC`krqWRcw;U9^kor*h~ zdVt_fNo1ws`;VUM6hH>-o(Iwd1#h0R7y-d1=@V@Rsdpyowq9-dQLdmpeX6{~rsP!P zbbqtuF5Bkpd&_0CiTm`oxaGN%!uxe(H(8(DrU){t+Bvf0OE2S=og+^kd6@+WJbII(Ko3CT@6z1_;RZ<>AG zlyc(4y^v|)3d+^#H!t6wv_$;5Mr7>?Ah4P~RsElKW6zqkZ(epym^kt2sb}wu9)3Ok sh;!{ceWOi2k)bC}{F>I;aYw2C;=T`CAI>_>0*oRCPgg&ebxsLQ04wa%djJ3c literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-001.htm new file mode 100644 index 0000000..5bf136e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-001.htm @@ -0,0 +1,85 @@ + + + + + + CSS Test: Testing horizontal auto margins on flex items in a horizontal flex container + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5b484565615ca47464317fbbe18f120b246bccc6 GIT binary patch literal 345 zcmeAS@N?(olHy`uVBq!ia0vp^Hy9Wg#aNhutZ%bj)&VL00G|-o|NsAAzI>UXf#Lsw z(+vy_t5-6V0oAd3x;TbZFuuKb(D$%|KwDsx!9qqIsg7+-D&OOUm-Ae5iL`M$m-uPl z=XLYG-8c64^JYr+ZeE@Uk_71ay*XZ!dbf1uWHk(CFIsldrG8 z_&D?Qj?%k^$~<5-5x=L+{hPPTwfg-bh_ci*`IE2r16B3k`0~5H9%9g&tsC~;-TSk4 z@7GAlvTf>6t;c?u&HR|X=}heOh#yri&O`L?6}z_mk{r8vJHkcA$3kx3=hol&feY*^ zo@pER$;K?(Vh1-SY?k3NsikKVL0$m5Q|;xIItJDE>=l90Cl3L`iow&>&t;ucLK6UB C`jvSA literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-002.htm new file mode 100644 index 0000000..0ac66b0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-002.htm @@ -0,0 +1,70 @@ + + + + + + CSS Test: Testing vertical auto margins on flex items in a horizontal flex container + + + + + + + + +
+
+
+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-margin-auto-horiz-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ce44257d5af812826e6bf5d9c9acf6e2f3dfebc8 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^6$}iFK`hKb)@d#_Rv;x5;1lBd|Nnm=^Z$XF5P-^+=mVRIMkR+v^`9JY}=G55y$^up6Bp~;tzrc4S3eK3txRu zdG_|6R|}iPkLZG>@AI}LN&wNi1MVz`4fe?0YioWeIA;ah`Rseo>|aMu`ElN0&Y}DJ z4~AFtKZt(L*7h1`Qs$jz_K%SUHcb%Y=0xxxw!E`wvobS~I?v0qU+nvNgP8X14-+a_ g)K?v>`d!Dc`*fglbpAs}pwAdQUHx3vIVCg!0GgA0_5c6? literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001-reverse.htm new file mode 100644 index 0000000..389c222 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001-reverse.htm @@ -0,0 +1,73 @@ + + + + + + CSS Test: Testing borders on flex items in a row-reverse horizontal flex container + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f23db3150360a271a5508d49e240b89c18311a GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^Hy9Y0GFX^_tZ&gqO+ZQ_z$e7@|Ns9$CXkr$Zo>bi z3=Iqo{|}sQXlOX|>pmv~17obGi(^Oyct20esRuh1Xm%w5N1u+^zvJ-J zdq2Nj+PvBGuB{keFM`bXI==4hSwlf!I5RE$x7PRJCwA5eCqGFRn63)V_`t3bvl1x7 N;OXk;vd$@?2>@O0pGN=y literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001.htm new file mode 100644 index 0000000..ea948a1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001.htm @@ -0,0 +1,71 @@ + + + + + + CSS Test: Testing borders on flex items in a horizontal flex container + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..cec56939e77935d57775274227ae77c02599e9e8 GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^Hy9Y0GFX^_tZ&gqO+ZQ_z$e7@|Ns9$=7e_>fb{>R z3=Iqo{|}sQXlUSLeRYO`ficR{#WAFU@y)r5E1L=!jy~Ld=wP(%Yqm#R>mG3V32dor z{i47c>BaH?z4?Kk=jJcDKPAm`XLxD+RW`Qfg9!!_JourO3jY-Jr==hFxzeJX(4xK-}r;UzGf4CUruFx zSSf!i)?I3Ieb0-xATQ4`7(7GoLY>Ic4_T#9LqYw*Z5m!PC{x JWt~$(6990Zoz?&V literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002a.htm new file mode 100644 index 0000000..02ab2ec --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002a.htm @@ -0,0 +1,75 @@ + + + + + + CSS Test: Testing margins and borders on flex items in a horizontal flex container + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8f94167fad74c9d8384e0ef35ae0c1d9107c7cb3 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^Hy9Y0GFX^_tZ&gqO+ZQ_z$e7@|Ns9$=7e_>fb{>R z3=Iqo{|}sQXlUSLeRYO`fic_D#WAFU@y)rrnJozdZ5IVU6~E44-N?L=HF^W9Hph~` z5-Q?q8+I?!IaEIHb>cbcN%MlQmX!ZKVWnhzJF&j@P=bL34_os=e3aKQqsOT>iUidq z7)0#6v2fbb^_Qi|F5^jH5LT@!F7D!?b*pnz))v#`Wrp_&{KXc eg~?BiA7ov*uW)N#*GvVV41=eupUXO@geCwLx27)u literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002b.htm new file mode 100644 index 0000000..c1f7cfb --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002b.htm @@ -0,0 +1,82 @@ + + + + + + CSS Test: Testing margins, borders, and padding on flex items in a horizontal flex container + + + + + + +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-002b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8f94167fad74c9d8384e0ef35ae0c1d9107c7cb3 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^Hy9Y0GFX^_tZ&gqO+ZQ_z$e7@|Ns9$=7e_>fb{>R z3=Iqo{|}sQXlUSLeRYO`fic_D#WAFU@y)rrnJozdZ5IVU6~E44-N?L=HF^W9Hph~` z5-Q?q8+I?!IaEIHb>cbcN%MlQmX!ZKVWnhzJF&j@P=bL34_os=e3aKQqsOT>iUidq z7)0#6v2fbb^_Qi|F5^jH5LT@!F7D!?b*pnz))v#`Wrp_&{KXc eg~?BiA7ov*uW)N#*GvVV41=eupUXO@geCwLx27)u literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003-reverse.htm new file mode 100644 index 0000000..0417ff1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003-reverse.htm @@ -0,0 +1,77 @@ + + + + + + CSS Test: Testing borders and padding on a row-reverse horizontal flex container and its flex items + + + + + + +
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8f27a88005382865df8222f0cac8dc5e5f7848c0 GIT binary patch literal 372 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K54Op0g)GD=2KY$c}fKQ0)|NsAiOdv7g-Gl~) z2G)YIB|vowo-U3d6^w7M9?WDm6mbodj%k>$&f8-pv8<8hQoXOISeuTJK$M4kO&%-z zyd(QPF8O=P<*u-`to8yL1_4>jDzW?jKfGa{bMo3}-u^ov`@z7AS;ezz+aC?MfDXII zq$Sti7Dq9(GRk}2ia1$TR`P@wrhI`(0?_rLjI+!S+AA}JFzoTK%rS2`)u7UEc4gii zTUD4khOLa#B}3VKxEU6xIT)+|DCTS6YOp;1qTHN;p + + + + + CSS Test: Testing borders and padding on a horizontal flex container and its flex items + + + + + + +
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-mbp-horiz-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bd332091831bede56f1b9ade915e69dc9eb2c778 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K54Op0g)GD=2KY$c}fKQ0)|NsAi%n9!%0OGF)Xr7MhqW6% zRARTk&tz0#tI1uuSUIY64ajv6;K4gXgux-K;hFD;G?oUQhVz>JZy6^@3Se57XMBoh zh8Tl`UqhPThxalJk_^Rt7w^jfjfrFotm?WJ*KP&!I~XXKCqzM5HyON{7)~%xD7o+D z?!M(!#77pW5(W?62`VqIep(C{*dU|e33TZud5t!g#ix%)JOiopboFyt=akR{0B + + + + CSS Test: flex container layout starts with lowest order item + + + + + + + + +

Test passes if the paragraph below reads 'First,Second,Third'.

+
+

Third

+

Second,

+

First,

+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-from-lowest.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-from-lowest.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..91f98b3a92e67d6a414a2adf7a633733ca829dc4 GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKGZ=vcgW`*okqius+dW+zLn;{GUb5ZvSb>NA zgMrBUFM>9D-#J#5nDzEOQ&LSYSZW-Q`QKsF`_%o%DushQ6AhRw_j6u6dS9TCMaV;; zk>yj`5x?BOvW&rXdrK-Fo_9X>+-jO=JLmn#8275g&s9I&2Pn@;{ppBheRwHQv2-#5vmCD&DRkMt(HMio}4Ri5mV zCKSp$1UXwg+@siYY1TUq`^JJ?kF_!EhvEg_aa`M~ymrcSg_m^VgpJiGL~52$6AXIa=)ojl2#EKd%L9jkZJ zy>mir)<^Fg)}YS(6ISertdrhx_&aIs>IOP1zdJ{2Q?VV-*`+EDW%Dk^+MYU`EM#Pk dY}u2YKRER#yDfbnbkQCZm7cDCF6*2UngIW@&Ts$# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-only-flexitems.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-only-flexitems.htm new file mode 100644 index 0000000..4211ac1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-only-flexitems.htm @@ -0,0 +1,40 @@ + + + + + CSS Test: order only affects flex items + + + + + + + + + +

Test passes if the paragraph below reads 'First, Second, Third'.

+
+ First, + Second, + Third +
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-only-flexitems.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-order-only-flexitems.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1c26ed8b0126b4e0a0f8e1294669a53da9205eb2 GIT binary patch literal 492 zcmVWZN_1++NwwX#^$*Zv1cTGL+lgby_KHw zpCJA>1W9e>6WPm6h)VRRA}apCE-JD`WzksbrsRv_736TpTvk=yf{PLNkAZ&9%0Dd5IGZGXCsRrGV zfQ;(`5(>fr=LKQ3dJZST%?Rg$Rngbv z8y-v)7OY5^u2mKDBz3G-j+;4#OGyf{vNR$!_);uc{#raDeYtuMbGfv9Q}AQAcP$h%EkeiJGI zglwYwzlqHS$;2PX+yh}PNH`Jhg5)w?+#NBmPm8P + + + + CSS Test: Testing 'overflow' property on a horizontal flex container, with contents not overflowing + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..84a85b522620cde4ebbfe7c87647611c77bde04a GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0y~yV6*|UZ!j?fN$!)|w*o1i0G|-o|Ns9pF#Km|XlP() zuEakt!T9#fUQQ-Qk=Bcb8(4mCWMQs9aO95K77^!}S~@40T0Wj-Gg{9O z8F7x+UT^t~v%3%P$X@$(qnD=F-u1_e-mJ_yR?_$B$;S#Cx&GsiVbs4jccZf3zj4i6 z|732}se7uY+K)%?*#1_v`m$!_Z%x(w@H)6sRE@|yMv0fsSSHboFyt I=akR{0L7MUtpET3 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-002.htm new file mode 100644 index 0000000..5eec845 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-002.htm @@ -0,0 +1,54 @@ + + + + + CSS Test: Testing 'overflow' property on a horizontal flex container, with 'align-items: center' + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a052a42cab9cf65952d122615e06eca589910dd8 GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^%YZnGg&9a5=`rsHQv3lvA+G;{pgy310SFry82;-B zSrh`LsytmBLn;{GUOmm} + + + + CSS Test: Testing 'overflow' property on a horizontal flex container, with 'justify-content: space-around' + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..18efa69cd22279c3d19ccc69daaeac7d85758eea GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^%YgU>6El#MIad%0q<8{+LR|m<|KGsSz`*dIp`k(U zE)ps!T{ZtZOSK2r V{jQF~hk#Zxc)I$ztaD0e0szM{MXUe- literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-004.htm new file mode 100644 index 0000000..9194505 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-004.htm @@ -0,0 +1,51 @@ + + + + + CSS Test: Testing 'overflow' property on a horizontal flex container, with 'flex-wrap: wrap' + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..12842f7ddfa1739017550230a64ce3509ae2e3a0 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^%YgU|6El!(RyBPGq<8{+LR|m<|Ig6S(7@2Z!0`XC zmjX9XJlWI5F{Fa=?WwiA4F(bn7lqP}7%&AJaLv-3!QOi8L-65tl_kq6)%EpPhQzLz zzkZq8x`%PfU5noLt@i!GYnoGm P)-iax`njxgN@xNATy{X~ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-005.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-005.htm new file mode 100644 index 0000000..83d57fb --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-005.htm @@ -0,0 +1,53 @@ + + + + + CSS Test: Testing 'overflow' property on a horizontal flex container, with 'align-content: space-around' + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-horiz-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4bdd01bf5e97b7022d770f5f6b74d2b82e03d021 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^%YZnHi5W;*l(7BbV=E&TVe4E%jL~YH2-dGo42bbK%=CmEBB%OgegG|tS?+Pe>UF28Ur+&!PC{x JWt~$(6985VI*0%O literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-001.htm new file mode 100644 index 0000000..5aaf5fd --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-001.htm @@ -0,0 +1,58 @@ + + + + + CSS Test: Testing 'overflow' property on a vertical flex container + + + + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5ecaedfdc29286e2473dd19220c9a4214f2cf472 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^Hy9Wg&6${itgYJVbAc33fKQ0)|Ns9P82&RfG&C?Y zSan=00gBgnx;TbZFuuLAk(1eghc!@lrmF2+^MI^m4nO4|AJ)~sof`D}k8E3_1Q^-h zuzG#(t592Fu}`>Q^-%+!!$7n_X4|=4<$GHaZ^&E@JrZXK7BtXlU%UBTtbU?|+3^z5 sjo}bMp3{l1*HmBkmS{U`$oh>nUqe?Svi-v!pgR~mUHx3vIVCg!0GU!t>;M1& literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-002.htm new file mode 100644 index 0000000..5085f7c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-002.htm @@ -0,0 +1,54 @@ + + + + + CSS Test: Testing 'overflow' property on a vertical flex container, with 'align-items: center' + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f7308d5c1c91eaa59fcdb4d93b6721c76306a56a GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^SwOsyg&9cRRWQE{r1%4TLR|j?L45!N!~X^bAZ%da zI057^WO}+dhEy=Vy>gJ#Sy7_JG1`MW^Wn|LZ}Gt&6#gi5IC=^^v(`IPI{DqJues@0 zce@6PfKh$GXS0ykoKJ6Q7y2cOxUSXsTK6;O_`d0jG`c|O+toe1SF4 + + + + CSS Test: Testing 'overflow' property on a vertical flex container, with 'justify-content: space-around' + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1102be21fadbb214782110199deb5b43f2fbb42c GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^H-LB{6EldFJhL1~@dWsUxc>kDzk#8Ff#E+xLxbGM zuUSCx7*7|+kP61PR}}dUD2TWml-sy$`jbf}5&M)Rrk(k`*zBhJ%GzT6X$Nio~gM(KGu^{ URF3=tZPgg&ebxsLQ08a)&PXGV_ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-004.htm new file mode 100644 index 0000000..db61fa1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-004.htm @@ -0,0 +1,51 @@ + + + + + CSS Test: Testing 'overflow' property on a vertical flex container, with 'flex-wrap: wrap' + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f22c4ed8b82bb3dd4992cbed6e9ec90b08935fa3 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^Z-96q6El$9S-sm7Nbv;tgt-3y|DU0ufq~(F14F|{ z&Ce!4@jy=($B+ufw^ujvHW=_QIO?CAzopr0JsA`mH+?% literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-005.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-005.htm new file mode 100644 index 0000000..06026a2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-005.htm @@ -0,0 +1,53 @@ + + + + + CSS Test: Testing 'overflow' property on a vertical flex container, with 'align-content: space-around' + + + + + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-005.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-overflow-vert-005.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dff1df57e87919e92f13fa3890305f073f68de56 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^VL-f)i5W;LtY2^xNbv;tgt-3y|G$Bufq~&aLqmhy z$FEsHaeq%2$B+ufw^tAHHYfeRZEF2qr|Jm%%hR!e<}Hsy~kpR?YxGqmm5`^)f78qjD4Pgg&e IbxsLQ0A7PT?*IS* literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-paint-ordering-003.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-paint-ordering-003.htm new file mode 100644 index 0000000..c3b67db --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-paint-ordering-003.htm @@ -0,0 +1,55 @@ + + + + + CSS Test: Testing that paint order isn't influenced + by "order" for absolutely positioned flex children + + + + + + + +
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-paint-ordering-003.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-paint-ordering-003.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ec539b62aa3527343e78572eceeaacd0668477b4 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKOBk7fq`T*la3IAN;1lBd|Nno6{|v{T>G1$X zoIG6|Ln;{Go>dfNVBlae__cf~V}#20D(wwSa?dh4U6dwzs0iVq9C8@z>o{YC!v2&2 PO=IwM^>bP0l+XkK2&X54 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001a.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001a.htm new file mode 100644 index 0000000..c97d252 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001a.htm @@ -0,0 +1,26 @@ + + + + + CSS Test: Testing 'display:flex' on root node + + + + + + + centered + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001a.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001a.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..339ccae7bbb6aa6618857148fc73e3b7393f38c3 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0y~yVB7&@D=-2H29M7HEkG*L)5S5Qg7NKzjl2yGJkEiB zk`00a$vaNvoM294JGP+dLcn8@4yAaj^7T7>($|)~y(jv!Z@=}W9&Pihj2vegUi~Ta wKFE9I#KgJEHtzr8FHXGi@|W%WTU&o0iq)~$bT{3X=QPMNPgg&ebxsLQ0ICu=s{jB1 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001b.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001b.htm new file mode 100644 index 0000000..c3048b1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001b.htm @@ -0,0 +1,24 @@ + + + + + CSS Test: Testing 'display:flex' on root node + + + + + +centered + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001b.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-root-node-001b.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..339ccae7bbb6aa6618857148fc73e3b7393f38c3 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0y~yVB7&@D=-2H29M7HEkG*L)5S5Qg7NKzjl2yGJkEiB zk`00a$vaNvoM294JGP+dLcn8@4yAaj^7T7>($|)~y(jv!Z@=}W9&Pihj2vegUi~Ta wKFE9I#KgJEHtzr8FHXGi@|W%WTU&o0iq)~$bT{3X=QPMNPgg&ebxsLQ0ICu=s{jB1 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-1.htm new file mode 100644 index 0000000..39adff3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-1.htm @@ -0,0 +1,36 @@ + + + + +CSS Test: Single-line flex containers should clamp their line's height to the container's computed min and max cross-size. + + + + +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-1.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-1.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..58541d2462b2e86741163e2faf40fdfc4e3c2f0d GIT binary patch literal 404 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU|h(=3>0~iYq1_k@dWsUxc>kDzk#6v2-e>9Zj=W3;A_oF|-WeJHa1=^YnAtSHAVT + + + +CSS Test: Single-line flex containers should clamp their line's height to the container's computed min and max cross-size. + + + + +
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-single-line-clamp-2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bc335cb09657dcef51936bbb2f24ff45aecce989 GIT binary patch literal 415 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Pg7A9t($d`3nt$`FzfKQ0)|Ns9R7#h~zc-GL+ zaPHE9a0UiOKTj9OkP5~(7Y%tE0t8qcRpN@(0-E`*JY-;*aAA*j-HA0#o@JAll*@(+ zxBT_75+E5&Q!EMj+p^&R2Mej7MbOFpD$B#ZT_oE{a}E2dZ_RCH1O_Xc)4yxg{SUho fSXoPlw$C7^>bP0l+XkK=_pA> literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-table-fixup-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-table-fixup-001.htm new file mode 100644 index 0000000..b6dbb12 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-table-fixup-001.htm @@ -0,0 +1,64 @@ + + + + + + CSS Test: Testing that table cells in a flex container get blockified and each form their own flex item + + + + + + + + +
cell1cell2
+ + +
cell1t
+ + +
cell1
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-table-fixup-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-table-fixup-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..842f3a923c53dd555464d53a4da208abe2c92d1e GIT binary patch literal 297 zcmV+^0oMMBP)}d~XixEukS6!ZQ7BiOGW^6iQi^!2 zBmxOX-)#nP&rE&jo)8D7LkV2v)iBEH1p+e(oii~g!a(tF`)m5gEOkCvh z|Hh3JO&kqwBpY{R;d%-;V;_yaenMp2pKy;9OVl@o)S7n{&}e9_-Uc9_AS2RCtV2 v;X#hPoCpv1k#m18?n(G*{9DEMJqynr4qVr%{s6aA00000NkvXXu0mjf!%c`M literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-whitespace-handling-002.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-whitespace-handling-002.htm new file mode 100644 index 0000000..b63cbb3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-whitespace-handling-002.htm @@ -0,0 +1,55 @@ + + + + + + CSS Test: Test that whitespace is preserved at the edges of anonymous flex items if 'white-space: pre' is set + + + + + + + + +
abc
+
abc
+
abc
+ + +
abc
+
abc
+
abc
+ + +
abc
+
abc
+
abc
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-whitespace-handling-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-whitespace-handling-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2f985adb35561467236d9545994759d9b96894e9 GIT binary patch literal 902 zcmeAS@N?(olHy`uVBq!ia0vp^w-^|hm$5JdS@qV_&I2j_0G|-o|Ns9pF#MnJZURtX z?Tu%bf-CL-)fsuZIEGX(zPWTU_qKxoOMvF}hMSHLxHxkD?|rKAfytsxSKW$V`4R7i z2kmzA(!T2{O*%X2ME={C$I#YPds;$6~o0xZX_g%G1xfzYVeR1m7gUY@4o%AQ@*WX~* zo!I?t=*QtB>HRK_RbRNs03iho!olm?xg%rhjGNimijAoTfN!U z*D*T^IDD04Pe@gI!BSK3&#?Ap#hck;M*== lrcp7-u85;DzgKy0N265>ZF8%NG zZeP2*FCv>ew;^onmQuO<9^0dK+p1q$o89R94d#zt95%zlDtEe>deMY$H=oQ1-S@rX z|DWIAtG6uTy%2RZY`hZfk V`A;8aC + + + + CSS Test: Testing that generated content nodes are treated as a flex items + + + + + + + +
+ x +
y
+ z +
+
+ x +
y
+ z +
+
+ x +
y
+ z +
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-with-pseudo-elements-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-with-pseudo-elements-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0c02696228dd7b138eb0ffe2ec50fdd2fd04a0c5 GIT binary patch literal 524 zcmV+n0`vWeP)=)kT(UUvZf*Ay{%vB#O9 z6QDcff1d?>taLCCLdZ8}3>kRv;K73zD)8XJgLfY9mg9}NE>G|}=1HM>xBJ5z0}o!D zTxD{D|J2}h%=3o8gBL5^@-$A#vOcet?Ez8>%v&E(&zq9%X?9+|V#nE2I+>?EXYcg( z*0U4%?5(TY@zzPSu1`(Bd)CQq{8Ii z>y)?Tz=H?xBeN8lw=DBBFfX?knWxz8!P}GIXD|474_?Q-HSplU`=6(AN~SBV7fg}@ z^G5UfSKgG2?`G%O%X>L{N+zF46joMtKb=lU9@-H+nZ!B74<&7%1YZa^-Y2eb-<)`dW*~%+FU${;y$aRyxBfQ( O0000 + + + + CSS Test: Testing that generated content nodes are treated as a flex items, and honor 'order' + + + + + + + + +
+
I
+
+ + +
+
I
+
+ + +
+
I
+
+ + +
+
I
+
+ + +
+
I
+
+ + +
+
I
+
+ + +
+
I
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-with-pseudo-elements-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox-with-pseudo-elements-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4f5a43050f7c98eed6a94e7e454ce3b17c4ddb37 GIT binary patch literal 685 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKdsvu(WLd_AU?9aG;1lBd|NsBXmoNYS59Bkf zz47eXy5Bb#7?>hGT^vIy7~ft!$h+7;!X+`vq2xh`gPN?vf77)$GjiwbEtoy=kKNNL zmp%OVM}o8-NL=f+^-z~WVwFIqsND^YZu?DO0IFrNRB4*=H|E60LjGBI-?s`J$>C7aI|BB_GWLl- z+E`vVuG(D0KT8(sR2_zSKfcFIJypBSSlMHSVaJv(g)pGk)H@^{4{cFs|H7*zc;rQy z;_>IzitD#mUpezm+-U-XZpV%Z9N^G;Z0@qkUiABlnLyW-{teUuvIHR^0P^hXlaF?O z0vfB>u})!9_2n!7_&2?M=Nk+Z5qmPh&y&S)ckGFeft7nXAC>MGXky85Jksi-0P>#X ze#M|j)$hwz0*wvT`o7>MvY{~*+x~C}0e$n2P0(=-JUW|XE@^fuxY>&r)r7?510C`= zj>GXxo5CZfH{cko5_tKWcVZXN4^gTe~DWM4ft{WJa literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_absolute-atomic.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_absolute-atomic.htm new file mode 100644 index 0000000..a489064 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_absolute-atomic.htm @@ -0,0 +1,33 @@ + + + +flexbox | abspos atomic flexitems + + + + + +
+
filler
+
filler
+
filler
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_absolute-atomic.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_absolute-atomic.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0b54584b8a6c024b01d9420d7d0d742e1327c707 GIT binary patch literal 257 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKEtr^rWJPhw1R%v3;1lBd{|tk1+8H3vUrBH= zklO6&;uuoF`1Xn+U$X&^+rw=^)90;YJ~u&n!sQY}=HliS|H$T$gFbA*Uu*x%J@stl z2I^s8_+eRo(`K<=;oaCpzkg3ywD-)u3lm~Ci^O|-Os$K}@X)V}jqaGE=n~~D_w4*B z=SfdLb#d*SInDG)+sE1uJ?}4{%3Sl%U;Cv=`CG-Rwh6anl9K-%pOqVu4N@nsNcTN!&-?W=Wv1N`93)t!-KN-RvP4YGqJa7WU_jL7hS?83{1ORN@ BV%h)z literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-center.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-center.htm new file mode 100644 index 0000000..273d729 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-center.htm @@ -0,0 +1,41 @@ + + + +flexbox | align-content: center + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-center.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-center.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e949f70c7a6d86e8d0d7fcbdae3ed6bfd53848e4 GIT binary patch literal 353 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CKhHO`D|0HJCG6z@CkAK|NlRb$-n@j{~tKL z_Qtd6Ss$Z-%DFsU978G?-(EfF%WNp%_HfIG$#Ru!cmC8Vj{K5fugP2FQo}TFM;>3g z`y{nfd?# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexend.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexend.htm new file mode 100644 index 0000000..34a770c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexend.htm @@ -0,0 +1,41 @@ + + + +flexbox | align-content: flex-end + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexend.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexend.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0013c5d519cd7825c5a4d4305e8ba82585352dfb GIT binary patch literal 352 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CKhHO`D|0HJCG6z@CkAK|NlRb$-n@j{~tKL z_Qtd6Ss$Z-$~iq<978G?-(C&$I$|Ku{&DZ2``g$(*0Qg#ZM?)Ca^mm$M=CCYwZ@u0 zC%$iVmiz8*cG#{11oo)Dul1fEcAs^L$3&%4*&rbGi%%0s)wYWOseR5}UzUIQx#IQL z>*hJ>vF|ZOkugmWRA@wc2`n z#l@wS-!Dt&Zuvc1xAxxeXS?k`><1YPx# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexstart.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexstart.htm new file mode 100644 index 0000000..6cf2fae --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexstart.htm @@ -0,0 +1,41 @@ + + + +flexbox | align-content: flex-start + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexstart.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-flexstart.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..04c663e330060dff9c1c358c4bec1cacf77ee21e GIT binary patch literal 352 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CKhHO`D|0HJCG6z@CkAK|NlRb$-n@j{~tKL z_Qtd6Ss$Z-$~iq<978G?-(J1Q+hicnkQips$k=7UVAR;gYVh*k>Wa0Yk5}uSH2wR( z;^pk0O|~yPKwypPwcj58msfwDb-nag?!8aDmcK9b*njDE!xp)f9{XRcN#ydFdtyPv z%`ZCIrCBkTrc^C+l-erl(XPDoS9)~k7X9skYrP-8YqyTd_?{m7YsdGa<@+CQ*9=!|(J36c8du<$&F-oqtW?jhw31nG=jNd8?vgH;Z$d250kxY*@*9Ad9 a@-?T$WX1onH|@WIg2~g>&t;ucLK6Thg`JlG literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacearound.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacearound.htm new file mode 100644 index 0000000..29c765a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacearound.htm @@ -0,0 +1,41 @@ + + + +flexbox | align-content: space-around + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacearound.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacearound.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..9c4fae5dc95d479427d93ae0013730fb02552adb GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CKhHO`D|0HJCG6z@CkAK|NlRb$-n@j{~tKL z_Qtd6Ss$Z-%DFvV978G?-(C&$I$|Ku{xR-h{%vNL*Nj>I3%)Rk7X4ps!5MgP-%K5! z6W=#F%YAn@JM8BI0{13;uUq1Oejm5TL?zc(yLNwhy5jZQ^X56}vGN}8FMVgtv=7R> zm)mE;yTmbyXZ7WC+b$&>+v~I1+V|3$w+_2(g0#LW&Apv_<;A5vzhCy6-THfWn%(=_ z&v)g2+!xUdS^_e(3(WYh`c(?V|HTIquD!nbi|MtmonQ3!UdxbQ;_TJDB`z~e^VZDc zOSjxvsWzwRsEAC9jpbzqeoc`R@3x2{02uMwghZybcn$QocW^ fGG7S + + +flexbox | align-content: space-between + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacebetween.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-spacebetween.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b305e476b65b7c296ad3e7cf32f072afb380c656 GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CKhHO`D|0HJCG6z@CkAK|NlRb$-n@j{~tKL z_Qtd6Ss$Z-%DFvV978G?-(HPf*JL2zkhsl2Ji=k;4u%v~DYFL8@B2lUZ~b@FJbH3w zmEW8%XKN1jmAHVwHKEtGOX@GL{cpPd@t1pho|gLEe>}l%>2k(Q+n|Yc7b6dgOt6~h zxN+0V$go#eb}Z5S>eFnJr9FXf|CCqrb5$?xyW_T1uXnfnyNrc*=a;^Gd{6xT-?sb9 zJSHl+c8O>PEdeopO}&x@qO+_)+^w=8(OY~V$=r64m#e=#_`FNu@053ouk&A0Tk4l` z=Wvfw+<6PNr7vfm(tCMjqu$r89$(F`M=wZP={Somb6Lprzj~(oe%tAT4Tss1>#B9_ ebq5Ial(HUttoT3Zz&aC9KzX|QxvX + + +flexbox | align-content: stretch + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch-2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch-2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..cfb61e9697f233259512f71c28fed76b82950f76 GIT binary patch literal 349 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CKhHO`D|0HJCG6z@CkAK|NlRb$-n@j{~tKL z_Qtd6Ss$Z-%Go?!978G?-(C&$YjzN5co>(cBg3$UeT8k~GVuc~zvGWG20k{9-YmV( ze(sy-&#mvKOjhxn6cQa+ZGUO`$JNho?~6OWKla`4s{8xW75+}$SGwe_hw{1Wb8Z{4 zE^&65eq+b;$S|uVA9wk#OyA`Zp38jc-NZ|OHs`uj>1_{mpT2JQ{yP_)cGsU=8vlLY zk9YDva=oyETYJ8w-n)MM$BbFi{Ql07zvS%I-cc8|tbfMmnB`tCzxgD0y*;xs)@r-b z+50y6j$MmcUz#nkUb^$m{Y$6H>+gwcVg)b%doTXxH%Y}aD7BV}rF|El#3r5TpfK`u L^>bP0l+XkK%?z97 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch.htm new file mode 100644 index 0000000..86b4f07 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch.htm @@ -0,0 +1,41 @@ + + + +flexbox | align-content: stretch + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-content-stretch.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb58461de2ad4add03b90232086b7f2be9af1fe GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CKhHO`D|0HJCG6z@CkAK|NlRb$-n@j{~tKL z_Qtd6Ss$Z-%DFvV978G?-(J1Q+hicnkQips$k=7UVAR;gYVh*k>Wa0Y$u*lzcP{%c zuU~5%zd>DA5C|r2JyNCrGO?;8=ChUm{Ik(<*T2~Ey^OAsoN!H9^riS*Qy<2a!jdA| zv*)EQ_3QPX6?ERjWAQeommmCB{<|5b^!9Xa$m>fxen0FJyY)A6y4}0lk9YHbHgE9+8vos3amRJzKF`dmWt+UM zO4oST>IVHi`&Dd3MAn35j + + +flexbox | align-items: baseline + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-baseline.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-baseline.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b55682407527b4404b7e95d875ac0a6ec1b59c9 GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRBq#!pI&Ku>ba{QI@rfnS^5MO)a$SB zn*#?me=ylIYVHd@v!##dRoH6MIg{h~j KpUXO@geCwf(amT8 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center-2.htm new file mode 100644 index 0000000..0881d30 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center-2.htm @@ -0,0 +1,46 @@ + + + +flexbox | align-items: center + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center-2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center-2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a4998fd8f5bf3b2dcf132cc7490cd43928a15a69 GIT binary patch literal 393 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa_MkYi~UJ zf8aEbw`69%9Z@VR7jqv42($%8&0yt9C=X^RWbMpo@c44y>)5o(HcwBA zZTdRjzvtcOgWv8nt9VXQ@x0|W_1gA7=kKr4d-B9^;^gNpe)EJaEB&54v3%?QFF)d% z_RoJ$u0OtDU-XIL=h9advd<$-zgYUV)YLsIUHkKm=VX5A>HqiZzW!@ox&QIsYv1hy zIzLsKY1La#T(A5-^`GkfZ+Y?Y`_?=^`TL)(>At+$w3Elv?M|uJF8H&B`RD26NcksE zc7BR`bvf?-AFx98Q>)K?pLg6M@RRrK_n#-UpIjaE^!XOv>Du$le*fq_{`T90-*Yg) zpUh8NmDR!E{{EV`Cr*OFcl~(nqEB;9oGeySKi@gWMjgnhdouN!Bhc5ARB{*ji + + +flexbox | align-items: center + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-center.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..13c9837666df1f1d9e9924f86cbe4d8c956533b1 GIT binary patch literal 396 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRBr0&;uuoF`1Wd`->U$DhKG5H?^;lu`#t{XZ3-|GKLB7yt9$yX@x8E0?_f)pOt8KYs(X>t&ArQC43+!TrzvXLpuq zPMEL6*V$h^PxMpGy^n@x4U^6k&lCEodG>nhs&_S$&i~qF-S4iGes12=cHSqC!#;J? zp8hO*e!HE08@Sv@$RzUmeYUjxelaNr~ll(r)>N5|MTu&{~Eqe uuLvD{vOTdI=1@F-;CC*`o&fgx?ibwEw=`}qQrP|k6r`T6elF{r5}E+euEX~L literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend-2.htm new file mode 100644 index 0000000..c33c031 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend-2.htm @@ -0,0 +1,45 @@ + + + +flexbox | align-items: flex-end + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend-2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend-2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d755125b5385098ee6d6b03cfc6ba850ec700c42 GIT binary patch literal 375 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa_MkYi~UJ zf8aEbw`69%9Z@VR7js`Z2($%8&EOV!FnbBZBQBwf3=?1MpW36V&TGNH z?f>V_i+1v5l75p^JSVASN{2@6|5X2fv($-`$x7gd3J(Vw?F*Xb7(6@7YpYW2GQdCyG(KP^1lpL*i(lhBo)vTwDWo_c + + +flexbox | align-items: flex-end + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexend.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..81acb6161f78c28f7a9dfbbb09a27650e6d8e0e7 GIT binary patch literal 396 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRBr0&;uuoF`1b0>+(!WdZGllUVl^6oXlYY#MuW$@{ZnIaTmXN!j_eOPo7v#ocx?BoS)nOyf2OX zANTL@%WZ46Pk%mldFtND>-GQde?Rp;|K%Ub>hC9n|J~QK^W0?x`>?m{`|=*Ynb7}S z_T6WhRm%$Pmbuo+JUd;y>s`$>`!BWSao?-vC2!NYzg}VfwMpT*(RTMfe!SN2zu?#V zJ-LV1s@3yV9?qM;|FQ4xzb4auC9gMs`|WeYv-zLy-M#m(x-R;f|IbY)PELM0^_m~p b=edjc+n!I@Ybp?K3<^?DS3j3^P6 + + +flexbox | align-items: flex-start + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart-2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart-2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f2a887bd66cc684df598730a8ae3203640dd5894 GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRBq=4G-5msJ_h@b&JtUouRZ*DfNG7ftLf5++z>H ze|3pZ-+IWam$G?IQt_O$rFT(u{HOZk{qxqHuCl9pUlM)ob^qsck2l?WR?PS0^~w0M zt)G6^ov|%f6DcD-#)$nnH#zP>1ic(_4WO8@Pa*dMR4%xt-GJ!zwb{&CrhFauE9aj$`P;?so_f(IJDAjAfxA*bkE?iIQmbO``p)vxh55vFP?&nU`njxg HN@xNA2P(^5 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart.htm new file mode 100644 index 0000000..19e8504 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart.htm @@ -0,0 +1,39 @@ + + + +flexbox | align-items: flex-start + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-flexstart.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0ca4d0440980a13c362104906a3dd2b15ee38d2b GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRBrC+;uuoF`1Wd`*I@^dwu?E5TVfrucQ@P;)wt`>`lepnFH7Xg@jDI= z?B(M=nH9|Wk~TrbbCQbZ?G;PED%V@peJzT;_WEP_^4pv4{hw1VH~p*O{9=W8|9{(7 zPrd&=`M75+pA~Kuid6|f31T2^-16R|1Nv_SN45=?SK9)zpV9d7py+{Tv7kV+spZTGPckE z6;%J`>!Q1L?@s@Tj@y0v^#14DQ~yra@18TqM!o3MoD(ODmDJC7QjsIyHtm`M$nTTP aE~>xso3PjW91ANbP(5A!T-G@yGywo4+QsDn literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch-2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch-2.htm new file mode 100644 index 0000000..9bd23b3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch-2.htm @@ -0,0 +1,36 @@ + + + +flexbox | align-items: stretch + + + + + +
+ PASS + + x +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch-2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch-2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..3ac891b91548e21c084b1e97a188e4a8d85fbd87 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0y~yVEh1NPhbQR4EIA=mIA35PZ!6K3dXk=Hu5$Y2smF{ z;4}4A+wPUYjK&X`T&woix7xnbkU8G}NSWh!p$rQ8m~W%?Z1ZmBefN~&zN+_cJD>f& m;W}64mzpz+UoR>E>UqGwYLDgjKnaIlkbR!6elF{r5}E+zCpwn^ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch.htm new file mode 100644 index 0000000..9dec9f3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch.htm @@ -0,0 +1,44 @@ + + + +flexbox | align-items: stretch + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-items-stretch.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..178d4d19ea83e0ab668e4e1da522680ec430346a GIT binary patch literal 382 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJz4pel z{|8RjNSyrwRIcvn;uuoF`1a~U-@^t14HwrHT;Q1y#HKMt-V%z=css2Qt=FucFq5LGG4F$-n-P=n7+4G)4%SlKkxC+=F>}?=k0U$wAnoR zI;}W;4znQ|v`WpZ@EdU%Kz@*MDWdgTyQeflA-XFV{exoup!Vk-uJ2 VZHc<#Whqb~db;|#taD0e0szZ2x8?u< literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-auto.htm new file mode 100644 index 0000000..14fa46b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-auto.htm @@ -0,0 +1,42 @@ + + + +flexbox | align-self: auto + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..81acb6161f78c28f7a9dfbbb09a27650e6d8e0e7 GIT binary patch literal 396 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRBr0&;uuoF`1b0>+(!WdZGllUVl^6oXlYY#MuW$@{ZnIaTmXN!j_eOPo7v#ocx?BoS)nOyf2OX zANTL@%WZ46Pk%mldFtND>-GQde?Rp;|K%Ub>hC9n|J~QK^W0?x`>?m{`|=*Ynb7}S z_T6WhRm%$Pmbuo+JUd;y>s`$>`!BWSao?-vC2!NYzg}VfwMpT*(RTMfe!SN2zu?#V zJ-LV1s@3yV9?qM;|FQ4xzb4auC9gMs`|WeYv-zLy-M#m(x-R;f|IbY)PELM0^_m~p b=edjc+n!I@Ybp?K3<^?DS3j3^P6 + + +flexbox | align-self: baseline + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-baseline.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-baseline.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b91ccf0fb9de7ce73151e736a0f1a694e2580617 GIT binary patch literal 392 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa_MkYi~RQ zGXEbqU35kw3#i=C)5S5Qg7NLugTAjF1R5T0G0^v9tee2pqs`&Ptg!Qcm_&3Nvp8Se z64TFrPi5o>?yE4i^qi#PIcbSqS61!G|9|b>{4ei|+xA`ZM8C59`QUvgPI8}U@A$mq zjAhcP;<-Yf-p)KOaeHR4uU+WW=d$Oc=ihe-nUe4P@A%W5XY8LIE&IJizo_WbKV@|= z_#gT2&yDM^xBFMk{{Gdk&gSRssdM&(R~~sf?{mbm#+r^DYIe_Teoy1i+?Rj()lB_Q zq45EAwv%iYt@?8*@9@O$y7A?|KS}3rxt+iB%JseX{^sTXtG9dKJqHf{RD24XI2jxG zzif}!lP9H0>g$CqE9ag#`P#)VPQB=p)x^oyJNIm|Syc-1vx;Zz7e-}Co*%Q~loCIEzrzg_?U literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-center.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-center.htm new file mode 100644 index 0000000..bec1a78 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-center.htm @@ -0,0 +1,43 @@ + + + +flexbox | align-self: center + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-center.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-center.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..649ed6ea1814c5d6175141122525b7fe0cb24e30 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRIcmk;uuoF`1Wd`-ysKqhQxISOzawu7$*p4>~Qd{`oG#@ZRq3T=_!T# z?0vrdT>SQp-y{{!Nh)6hLteZ8EBW>6=dQKaFW*16J$LW?cb6UN?0?>FoU$~#m&%gg{(=96c^zGBV>Y3ZVI{vHp^wHvJ|C~JyK-2m@%P23inXaiAKfkhP z<2=i&Pl7`$_dWi3?YX^x=+oB|{ufv3ea^4kc-Jnsd(NCa|4*ERgWvsg-~hLT-;*b$ zO6u!{Ei31qIQiPeFK&5!Wk|L<(952acD)eh7W{Ji7)RkEP*8fh`njxgN@xNAMib5H literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexend.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexend.htm new file mode 100644 index 0000000..395137d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexend.htm @@ -0,0 +1,42 @@ + + + +flexbox | align-self: flex-end + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexend.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexend.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..129a5c72f8c7c6412e44ea546230c8ef4bc21c85 GIT binary patch literal 392 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRBq_$;uuoF`1b0>+*b|)ZGllUxJ455G#dgOm829V{mxg-KHfPcT{`n* zy`AB$=gn_#&QxwWU;)As3XzG^)G$!5ENx(c<*-EW+q{(fsFQ+fYW<#zx3Kiqcz zy)*6Cp6k2szny*m`S#L@lPBlv`*+T>q<~g~4<)?3b+qdug%RE&hzU@!{>DxyCr?tQR oRv7!_$;nS%+3Fx~PulfD*mcr`y)ySc9R~%cr>mdKI;Vst0C63~0RR91 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexstart.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexstart.htm new file mode 100644 index 0000000..1e4519c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexstart.htm @@ -0,0 +1,42 @@ + + + +flexbox | align-self: flex-start + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexstart.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-flexstart.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..914e7dbe4a20ee14c48c878616fc82cd50e1764c GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa_MkYi~RQ zGXEbqU35kw3#eS*)5S5Qg7NLugMo(}M2;s$RWPx-EM@RvwJ=#={N;YvY@aU+jz8J( z@&Dg8^XlfGCq7wtOj7Zjw8U=JmbxeY=YPLQjk$jNlO6NVx+m8k--vtigz?kDR~2)g zMVM|Yp2zX?)0x*%r*EqH+ATYETQ-0DzFOv~8u4v^=AXW8^nY6W+i!)jPo6xnoH+Tp zi{Ct9%Su1;IbXv6RmETbz21L^&9$$d`|N)npE+kwc;(fX*Phv(Wdl0u&WEoV>GwY^ zoSA<*=j$2upKJK1%>S;k-fQaoZMO@a)SiBR*Y58z^ILDHZ@cn*@4dfy_y7F=vr(_8 l=+oOLQ?EIKJgriCQ9RIe^4A%wGi*SC>FMg{vd$@?2>>0E%EkZy literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-stretch.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-stretch.htm new file mode 100644 index 0000000..a8a94ca --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-stretch.htm @@ -0,0 +1,44 @@ + + + +flexbox | align-self: stretch + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-stretch.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_align-self-stretch.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..40f8b2b55519df4594c6e37f8ae3da4865e8b584 GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRH?c4SN&i>j(}0vvfKQ0)|NsAiOa_MkYi~RQ zGXEbqU35kw3#eSj)5S5Qg7NLugTAjF1R5T0F*skyST}*GN1MZoSz+h@Fp20kW^q2V zOS68~rEb}-5x-|*PpyDu0PG5)W^&_{U-O{@T5Dzn5&UjDP>+`Q-gypFCN6N_pM)ImeCIPqcT~eKdT2b6bta=i1qe=lxWQ z|H}IG@60EHp--FVdQ&MBb@04gl5>Hq)$ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_box-clear.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_box-clear.htm new file mode 100644 index 0000000..0bc0638 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_box-clear.htm @@ -0,0 +1,34 @@ + + + +flexbox | cleared box + + + + + +
filler
+ +
+
Yellow box should be below the blue box
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_box-clear.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_box-clear.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4d382dc8515fcbd2820e584f674f1c5e9abe8a02 GIT binary patch literal 896 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK53n!;Nxn}~EgWJ>?*?>opV)r7Ej%ge$+C^ zjqS|&JN};kjKQEgqiXYu`#()NnY;xenhprDOjFpv(x}RziKfkp7xr3pN&)YpXdH`kfqVIVao0bTfNtc9uAjhyZ%47-jgV&KplMTtN>P* z66YUORcnGLu*Q73z#{mh%=`4DcmHc=$-9SF87+CZ^Y=d+`T72bUq9xqe^dQ&?nUQr zR?XtG`&mxfH#{+S*recGxlUNHg?Uo&Er(N9TNT&8{`Ya$y!PjEzuWg7{&n)X?DwhD zPrW|=ecJOy?P5%uN)G29zR4DG{rBU;#ijN8%Om9N-WRs@{En+l|8gP8;Z{KP1RK8P zfBtGu_)|UMlX+D}p~BNmpP5oBW2SENKB?)kyJqWGNssOMw=eJCUI*0j$YIy)U#pE% zUK-4sulV*@%=P8zC-&vrJOBD#_gnH4^NBM49||YJ?>nDbnz7P--SJwB(!IQU*S(&% z^WFU4K=*y9e4Ui{KL7WvberGjUMxJ?(9kV6OQDHFsY9VjK$uXHKn+4%35RG^%N4{X z<4A*F&o} + + +flexbox | multicol + + + + + +
    +
  • one two three four
  • +
  • filler
  • +
  • filler
  • +
  • filler
  • +
  • filler
  • +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_columns.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_columns.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..93570db1517305d27bff306d31fc13100b565aa1 GIT binary patch literal 103 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK6&Qg8!=cBD5kN}M)5S5Qg7NKDM?nS!9_E8T z%z3pA@w}baSQ~e)Sz*HaiC3B(6a-GNrJR#goDe3i!4dg;#*W{uAQL=Y{an^LB{Ts5 DEx8`; literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column-reverse.htm new file mode 100644 index 0000000..504c3b1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column-reverse.htm @@ -0,0 +1,36 @@ + + + +flexbox | flex-direction: column-reverse + + + + + +
+ filler + filler + filler + filler +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..20822b7d6d879904fa2abe183ace026effeae4c7 GIT binary patch literal 588 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKA2Bfl$@T*8+(^H2(&)TYUY`xqQ>QP;Kj8Wuj7SIvlu$wd0KEwBW&{F zpV@sD8JyzN!>|7TFZ4ioshJW8EPB0)F>6A6{hja99T5&!m==i%Y;9P<>Y}TV#oD4% zcJJ*g|F`@>xqi3rILl2wo6D#m#`H;N<==@5pS+SPniD2>Oq1)#_e|Ld8=px`$Z-f! zSo!95obLSzN6#@cEh+E#3^j@|kh>!a)pWQ}n8S&r=XclryYf%r>a#6pZJ!w4yd%bt zq^NLy(k*#OuXXDbk7qtjoU_QGX8tus$?QJHlWZ=mlTPh>d-W4<pG3|K-&`dH!z|$V+?c_Sr86`|0Pp^%4`V xbGfj7+V>tDcY%Q~loCIDwU`W*lO literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column.htm new file mode 100644 index 0000000..0e7f5bb --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column.htm @@ -0,0 +1,33 @@ + + + +flexbox | flex-direction: column + + + + + +
+ filler + filler + filler + filler +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-column.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c54628855ea4c982af043c310dcab85b2adac4b9 GIT binary patch literal 562 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKA2Bfl$%ke4U-42RkGLcWa3UP0e1n=iQ(G#s|Vn zj&^{-z16BKod5mdz8|lU#2Cok5#?}&X_1(~)`k^R1&VI&Ie&ZpF1aN)Bj2^Q*YI2a z7IM&GeG=~dUpe}#n3R#<>Fs|{Fm={OF>f+I&sfO3h)MJ7&A(Yk+hWZ4IVN0HJje_& ziPc3{Aq&-Tgb=6V^s~SB{9XClan{_YaTO=NZ`iEQcqGW7ZtEBOO`-V<6(4V@x!tcb z;l^@Ph3d~T0_F-@6N2X7T_2WnBK30vL!_*b4Q|7SfoG=jToOug0fY5l + + +flexbox | flex-direction: row-reverse + + + + + +
    +
  • IJKL
  • +
  • ABCD
  • +
  • EFGH
  • +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-row-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_direction-row-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..be94316ad47d1603924596b1d5fb9bcd555dcbfa GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^8-Q4Wkr_yK$S1@BDYgKg5ZC|z|1&WBuQ*`y87R{3 z>Eakt!FcujMqVaE9@c=Exi=n6$dFK;I+MX|QG!6*LQC&!scdEoe3p0|JIKN*v|`=K z{6#jAo&Q(e_E6q0`QVMqr5&RK1=)C%$wqFkAI|8eKb&bQF}`w z{{Cg_xqR8b(ymuM`Rx6y>vL0#7|WT`M=7OwpZ|SX|3~Rgy4-bp&d#;Jg(d)<#Ng@b K=d#Wzp$PzP(N)_3 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_display.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_display.htm new file mode 100644 index 0000000..596654e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_display.htm @@ -0,0 +1,29 @@ + + + +flexbox | display error-handling + + + + + +
    +
  • filler
  • +
  • Antidisestablishmentarianism
  • +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_display.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_display.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..140c04c286ed5d63ef474369eaf91ff5c3996696 GIT binary patch literal 559 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK7cnsd$s>J^TY(f$fKQ0)|NsAiOyjgO|IaXV z#&B$6U|_uG>Eakt!T9!yVV<<1fLmhxq=cQdRoAPzl+!(aO=Y}TV#TbYx zwANKo-8$xdxAUajyX$W*ua@{!zCAbQ_)EPb(;D_XwVUU0+Vrg5L+1(q7l%gQOrQTD za>7ma8onh?>fP$nul^GIe%0>Up0}5`OFc>7&AYML>5Sqx)}(NO-gk~(dOP!Vk1l^Y z%`0zTQuLkU`Gw;5P0M1w{d!+D;a1X?RsCUGZ~eKyarXM78NI)+SKYgKO5(ujec_fT zqIb1zDdGLN{K4b0f38BGes=zN@T1`C-ne-k{ga(fEnqV&oBO@fxu)Qc|9{3#$4}}> z@rwKSoh;$Ok4|_zsMA22WQ%mvv4FO#nrF_y_<1 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_first-line.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_first-line.htm new file mode 100644 index 0000000..72789ac --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_first-line.htm @@ -0,0 +1,40 @@ + + + +flexbox | first-line + + + + + +
    +
  • filler
  • +
  • Antidisestablishmentarianism
  • +
  • filler
  • +
  • Antidisestablishmentarianism
  • +
  • Antidisestablishmentarianism
  • +
  • filler
  • +
  • Antidisestablishmentarianism
  • +
  • filler
  • +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_first-line.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_first-line.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e3315bc92346571c1f8966d408e9203c3bb0c54b GIT binary patch literal 978 zcmeAS@N?(olHy`uVBq!ia0y~yV4ebGA7Nq!l5_3aHUcT00G|-o|Ns9Rr=0=v{-0sc zzQ<<v=LP9yioR9TjIKU4 zoUVD=?0}AWr>E)T`y|o|L?`~t?#b?xq4*YttShfmd!{}Z+OEp!7_ruGO03dvhp;~P2c1; z_jmOMf0K|qaL)FCP+@1-yYMI9wmx0y%U3)7mr_G-*IorV#y5B5i*uP@-1(_@KF8^F z^qR@}-+tZZ4{+-G_A5HRbR$bW>#~gJc0LFD!V;C2hNWKJw=dmtf#-Q6yT7bk8STE< zn$@QQ17*@Q3)C=cX(cA4u?9abLHi3q_nvIt`SR({q}zoaljo&g`TpbXoiC4HM&$yd z$?5d#6!{LREjRYn-7!jCd+_{byK6lkEb^G91eo01CY}J)@HFHb^NVukl- zmI*6gyZ;wj`|sIx&yK$l{eH`2E2Ey! zWg&;W>y_C+@835PmtU51c#5g`?~N{7P34z8{WI%&*Q99^cVCa)FSShSdFJ!oG8dMl zYi{BVNRd7ux0d%t)l=7o4V(r`PF{R}$?fQ|mB(u8&zL!gDd-B?&I(^!I+wxd_`CAT zsL6|`nQ47{T*va^;>P1^av7rE+dR9q-DWMPu5E+Y*6VS5_rD7H^EE8j`?SGNkDs&t z&RBEm(2rkn4I;IZZ|{5h5}3AT|6kFZ!(SM%d{;=Hcm1BnUhylx$j1q%%vsi soZL84J$kU$O;Ckoofd&l`+u@qv+aHzXH&fnm@OGRUHx3vIVCg!0A0h(eE + + +flexbox | flex: 0 0 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 0 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 0 N unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-1-unitless-basis.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-1-unitless-basis.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ab79088f36dfd8d116e113502ffccd63c07bd1d0 GIT binary patch literal 603 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($(bL5-q=ND7RogrzZ;|7H`3uju|GUShbE82uf`RdzE#vR_Lm|n> zRNC^6O#LA}cYaN%C;PQP(eJL4R6IdQF7k41t@plv>#KkDrv$33R*DooxymEVb(7Yl z(1~lhQbJLM=BlQz&RlDL`PRGTUkhhko9`Yx@yyhk`Tmpd=zjZ~-;|QA;;mvo{hi9m z`Kr;g_kNA!EnfQX?4IOpzV_=q_if*HgLiXA?)~$R{{;L`yD9%;Q}ed-PoGZzSFOT+ zc>Cp^yC)q#t+1?9zrAkmIlJlao=i`gU-q-kZuOHtY$yNnPCEZ#*M8OL)l*|+;tRxc zA5S{HeA1lW{5i8#{hcR0(VcYsuI(SUzBlhm<8K|`YrARp_S(q*llN@f&3^Lm^?!9@ zC$Da~Haj!-`{iF2KHU(%1Id&m70+d^lTxAvmP$W)-TlAzejBV|jv5W6({gsL7YE0k dif2_qJ>#TavqeTpHASEl;_2$=vd$@?2>>|b5GViu literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-shrink.htm new file mode 100644 index 0000000..9366af6 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 0 N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bda4a23ef5cda8d4a6f9ea01d09e15faaf77274f GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0y~yU=#$h7qKt{$tjbcTmn*}0X`wF|Ns97G8q^^^#22= z*WP&6(9poSL~9dJhqzRW@BCiC%p&g4#GEp5-RZX9@(YvI zLstv!ZocKQ@yk4k)QM-+t(~1+TwJys-BOl!^4*5KY88(O3XV-IoRh>=->$s#GwPk$ zwCd_9)$0R`UY?q?Q2uTB1P|$ncMgZ0JEA}F*5b38=bXN+GwxaUEoM^8lDdw|-ivK7 z*POn`x_d!_!rjB4eQP}zJ+$zD=d(y9^HcgF$Iq9aJ)h~rC2c$Z{OU>P6eVRRwYw|* z^}V!ww!y?tlO9ak@>Bns?#$(7p10=y=uIhmp5s0Fo7!e;^XO^*CojEQqC4-~b8}DI z|KFgF6oR>yAmfw%q`QfCzxA5KbWGA*8w>JzNTU1^sf&}64JMxlg{!BlpUXO@geCx7 C+N?(a literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-unitless-basis.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-unitless-basis.htm new file mode 100644 index 0000000..f83dcb1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-unitless-basis.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 0 N unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-unitless-basis.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N-unitless-basis.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ab79088f36dfd8d116e113502ffccd63c07bd1d0 GIT binary patch literal 603 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($(bL5-q=ND7RogrzZ;|7H`3uju|GUShbE82uf`RdzE#vR_Lm|n> zRNC^6O#LA}cYaN%C;PQP(eJL4R6IdQF7k41t@plv>#KkDrv$33R*DooxymEVb(7Yl z(1~lhQbJLM=BlQz&RlDL`PRGTUkhhko9`Yx@yyhk`Tmpd=zjZ~-;|QA;;mvo{hi9m z`Kr;g_kNA!EnfQX?4IOpzV_=q_if*HgLiXA?)~$R{{;L`yD9%;Q}ed-PoGZzSFOT+ zc>Cp^yC)q#t+1?9zrAkmIlJlao=i`gU-q-kZuOHtY$yNnPCEZ#*M8OL)l*|+;tRxc zA5S{HeA1lW{5i8#{hcR0(VcYsuI(SUzBlhm<8K|`YrARp_S(q*llN@f&3^Lm^?!9@ zC$Da~Haj!-`{iF2KHU(%1Id&m70+d^lTxAvmP$W)-TlAzejBV|jv5W6({gsL7YE0k dif2_qJ>#TavqeTpHASEl;_2$=vd$@?2>>|b5GViu literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N.htm new file mode 100644 index 0000000..64050ab --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 0 N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b45b172347fe30811b59ca12f2649cc27cf2742e GIT binary patch literal 605 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($+0(@_q=ND7RmVIjM}fA$_=UL%^Y0vJ;*Mxy-f*Bg{ovpJ335I@ zBF4%(UXS?e=EuA`nzLqk-YdsRDxM&e7kRm~*2C`K71O^8ks>Eod4#!c(wY=HaZOiB zsLE=kNKsTF^^?n=maP6-GW%}H`rj|>?#^mT@ra*LGt<{Q@A>Y3=889^P70me-fHdn zDSOgdTifrEe8tQDp_Xl>~sXL9^~%I$5BZ$7_o{d3)o<+c0f-}fylYkcyK>11`?UHM6CW7RF~_dLD zz4ewyuU&ro&9dsd{_Y^J&zz_-Q_OQx=AfYFs>tX6UH|7fu7F31i&c|TE|=PRKqBxG a?{D6d%Pm^0r*9Jir4dh8KbLh*2~7Z$?iA(# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent-shrink.htm new file mode 100644 index 0000000..6c0a672 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 0 N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c37d2519b0e8ef420b74c6d9df71f9bd499c7985 GIT binary patch literal 441 zcmeAS@N?(olHy`uVBq!ia0y~yV4MhKFJfT^l10ppQX90fB!BXn@29sZD@KpMJ?*Ftu z*KUKEV_|mu{w@0_T`Z{ZdpWmfl4o$8dSU1DnV(J|9%Gg7mXkVC zRnLAY3KDrbscc$G@V}$yr`fbGNht5V@A2^6tKv-G>eU71bBiPYPMq>)T_(5vxA3(~ zR + + +flexbox | flex: 0 0 N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d1eb5dbb1a83b6a59126899cbaced5a57bb02657 GIT binary patch literal 558 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@rtL5V@L(#+pC6oO5P%Ef%y-mUi_(K7t1*yq|>lq?r+vN^_;G?wq{ecT?8)ThDg*xWBz!dw5N; zb+yXfFjs{F6La~ECJxBSa-GWP$mPS1PoPxkBXeE06D z{ifpb`L`~`eJ^|dZr_&Fzu}wG%k?L1KmT{{)+dE)E?2HOZFB8+t`5YzbSJk@sHwZ? tp%OgN$JI#mWC$+C)$J*nYv6IR!G5Ej!Jq36TOWZU-P6_2Wt~$(6965!_F(`3 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto-shrink.htm new file mode 100644 index 0000000..69b319c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 0 auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..304f96b45b970b93be00fa6a3eb78d1212b7373b GIT binary patch literal 424 zcmeAS@N?(olHy`uVBq!ia0y~yUcZ<dEicS!yW)d`$N`s@PXzgS)>JnyDEP&7Nn-a*70*c~8|@g} + + +flexbox | flex: 0 0 auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ab79088f36dfd8d116e113502ffccd63c07bd1d0 GIT binary patch literal 603 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($(bL5-q=ND7RogrzZ;|7H`3uju|GUShbE82uf`RdzE#vR_Lm|n> zRNC^6O#LA}cYaN%C;PQP(eJL4R6IdQF7k41t@plv>#KkDrv$33R*DooxymEVb(7Yl z(1~lhQbJLM=BlQz&RlDL`PRGTUkhhko9`Yx@yyhk`Tmpd=zjZ~-;|QA;;mvo{hi9m z`Kr;g_kNA!EnfQX?4IOpzV_=q_if*HgLiXA?)~$R{{;L`yD9%;Q}ed-PoGZzSFOT+ zc>Cp^yC)q#t+1?9zrAkmIlJlao=i`gU-q-kZuOHtY$yNnPCEZ#*M8OL)l*|+;tRxc zA5S{HeA1lW{5i8#{hcR0(VcYsuI(SUzBlhm<8K|`YrARp_S(q*llN@f&3^Lm^?!9@ zC$Da~Haj!-`{iF2KHU(%1Id&m70+d^lTxAvmP$W)-TlAzejBV|jv5W6({gsL7YE0k dif2_qJ>#TavqeTpHASEl;_2$=vd$@?2>>|b5GViu literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0.htm new file mode 100644 index 0000000..55db99b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 1 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 1 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 1 1 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-1-unitless-basis.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-1-unitless-basis.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ab79088f36dfd8d116e113502ffccd63c07bd1d0 GIT binary patch literal 603 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($(bL5-q=ND7RogrzZ;|7H`3uju|GUShbE82uf`RdzE#vR_Lm|n> zRNC^6O#LA}cYaN%C;PQP(eJL4R6IdQF7k41t@plv>#KkDrv$33R*DooxymEVb(7Yl z(1~lhQbJLM=BlQz&RlDL`PRGTUkhhko9`Yx@yyhk`Tmpd=zjZ~-;|QA;;mvo{hi9m z`Kr;g_kNA!EnfQX?4IOpzV_=q_if*HgLiXA?)~$R{{;L`yD9%;Q}ed-PoGZzSFOT+ zc>Cp^yC)q#t+1?9zrAkmIlJlao=i`gU-q-kZuOHtY$yNnPCEZ#*M8OL)l*|+;tRxc zA5S{HeA1lW{5i8#{hcR0(VcYsuI(SUzBlhm<8K|`YrARp_S(q*llN@f&3^Lm^?!9@ zC$Da~Haj!-`{iF2KHU(%1Id&m70+d^lTxAvmP$W)-TlAzejBV|jv5W6({gsL7YE0k dif2_qJ>#TavqeTpHASEl;_2$=vd$@?2>>|b5GViu literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-shrink.htm new file mode 100644 index 0000000..f476090 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 1 N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-unitless-basis.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-unitless-basis.htm new file mode 100644 index 0000000..25f3bb0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-unitless-basis.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 1 N unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-unitless-basis.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N-unitless-basis.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ab79088f36dfd8d116e113502ffccd63c07bd1d0 GIT binary patch literal 603 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($(bL5-q=ND7RogrzZ;|7H`3uju|GUShbE82uf`RdzE#vR_Lm|n> zRNC^6O#LA}cYaN%C;PQP(eJL4R6IdQF7k41t@plv>#KkDrv$33R*DooxymEVb(7Yl z(1~lhQbJLM=BlQz&RlDL`PRGTUkhhko9`Yx@yyhk`Tmpd=zjZ~-;|QA;;mvo{hi9m z`Kr;g_kNA!EnfQX?4IOpzV_=q_if*HgLiXA?)~$R{{;L`yD9%;Q}ed-PoGZzSFOT+ zc>Cp^yC)q#t+1?9zrAkmIlJlao=i`gU-q-kZuOHtY$yNnPCEZ#*M8OL)l*|+;tRxc zA5S{HeA1lW{5i8#{hcR0(VcYsuI(SUzBlhm<8K|`YrARp_S(q*llN@f&3^Lm^?!9@ zC$Da~Haj!-`{iF2KHU(%1Id&m70+d^lTxAvmP$W)-TlAzejBV|jv5W6({gsL7YE0k dif2_qJ>#TavqeTpHASEl;_2$=vd$@?2>>|b5GViu literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N.htm new file mode 100644 index 0000000..9c9280a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 1 N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b45b172347fe30811b59ca12f2649cc27cf2742e GIT binary patch literal 605 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($+0(@_q=ND7RmVIjM}fA$_=UL%^Y0vJ;*Mxy-f*Bg{ovpJ335I@ zBF4%(UXS?e=EuA`nzLqk-YdsRDxM&e7kRm~*2C`K71O^8ks>Eod4#!c(wY=HaZOiB zsLE=kNKsTF^^?n=maP6-GW%}H`rj|>?#^mT@ra*LGt<{Q@A>Y3=889^P70me-fHdn zDSOgdTifrEe8tQDp_Xl>~sXL9^~%I$5BZ$7_o{d3)o<+c0f-}fylYkcyK>11`?UHM6CW7RF~_dLD zz4ewyuU&ro&9dsd{_Y^J&zz_-Q_OQx=AfYFs>tX6UH|7fu7F31i&c|TE|=PRKqBxG a?{D6d%Pm^0r*9Jir4dh8KbLh*2~7Z$?iA(# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent-shrink.htm new file mode 100644 index 0000000..ce5c0d2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 1 N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent.htm new file mode 100644 index 0000000..fb1fb6a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 1 N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d1eb5dbb1a83b6a59126899cbaced5a57bb02657 GIT binary patch literal 558 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@rtL5V@L(#+pC6oO5P%Ef%y-mUi_(K7t1*yq|>lq?r+vN^_;G?wq{ecT?8)ThDg*xWBz!dw5N; zb+yXfFjs{F6La~ECJxBSa-GWP$mPS1PoPxkBXeE06D z{ifpb`L`~`eJ^|dZr_&Fzu}wG%k?L1KmT{{)+dE)E?2HOZFB8+t`5YzbSJk@sHwZ? tp%OgN$JI#mWC$+C)$J*nYv6IR!G5Ej!Jq36TOWZU-P6_2Wt~$(6965!_F(`3 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto-shrink.htm new file mode 100644 index 0000000..3d036ba --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 1 auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..297113b2c060aac789601a889cf01b73048fc1d1 GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0y~yU=#wf7qKt{$@(Sn7lD*$fKQ0)|NsAiOa=xJ{r|w} zwKtwMG&FE7(b@#mVe0AP7*fIb_KI)bAp-&T!gU|iJHHn&vxqx1F{eCyxB1~e_ZK&o z$a8Icy=v+kkN2H{VmJS!q~8z(f~_;MzH8o;e_iI##KI}0;xR!X(qmWgzSFw*?>?`t zelmNT#?Eh*CoL*|r>X=gPdd4^NbRZ6O6!|*4zEdhUv>CJ+IiEHp;E^TnxDLtd>;Mn z+)Y+X=FFn?=3iAg+b-LzH0GSN!gGG7+gbnFPiNYQnpN4(zwh;nsZ)P~jhD)$`zp?R zIE6KJ3tcDa{XHt?{p{v0m3MwW+=O-)Z(Q10J^9Am-MXjSQ*z56iP?XDu70xY-`j}_ zjxcu;$9NL2@;mu1AJAVA_1v9V+m)4+l(IIIA8U_ul$YbH1%;}otDnm{r-UW|Akd@R literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto.htm new file mode 100644 index 0000000..e4483cd --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 1 auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ab79088f36dfd8d116e113502ffccd63c07bd1d0 GIT binary patch literal 603 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($(bL5-q=ND7RogrzZ;|7H`3uju|GUShbE82uf`RdzE#vR_Lm|n> zRNC^6O#LA}cYaN%C;PQP(eJL4R6IdQF7k41t@plv>#KkDrv$33R*DooxymEVb(7Yl z(1~lhQbJLM=BlQz&RlDL`PRGTUkhhko9`Yx@yyhk`Tmpd=zjZ~-;|QA;;mvo{hi9m z`Kr;g_kNA!EnfQX?4IOpzV_=q_if*HgLiXA?)~$R{{;L`yD9%;Q}ed-PoGZzSFOT+ zc>Cp^yC)q#t+1?9zrAkmIlJlao=i`gU-q-kZuOHtY$yNnPCEZ#*M8OL)l*|+;tRxc zA5S{HeA1lW{5i8#{hcR0(VcYsuI(SUzBlhm<8K|`YrARp_S(q*llN@f&3^Lm^?!9@ zC$Da~Haj!-`{iF2KHU(%1Id&m70+d^lTxAvmP$W)-TlAzejBV|jv5W6({gsL7YE0k dif2_qJ>#TavqeTpHASEl;_2$=vd$@?2>>|b5GViu literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1.htm new file mode 100644 index 0000000..6593dde --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 1 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-1.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 N 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 N 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 N N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N.htm new file mode 100644 index 0000000..2e6ccc5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 N N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b45b172347fe30811b59ca12f2649cc27cf2742e GIT binary patch literal 605 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($+0(@_q=ND7RmVIjM}fA$_=UL%^Y0vJ;*Mxy-f*Bg{ovpJ335I@ zBF4%(UXS?e=EuA`nzLqk-YdsRDxM&e7kRm~*2C`K71O^8ks>Eod4#!c(wY=HaZOiB zsLE=kNKsTF^^?n=maP6-GW%}H`rj|>?#^mT@ra*LGt<{Q@A>Y3=889^P70me-fHdn zDSOgdTifrEe8tQDp_Xl>~sXL9^~%I$5BZ$7_o{d3)o<+c0f-}fylYkcyK>11`?UHM6CW7RF~_dLD zz4ewyuU&ro&9dsd{_Y^J&zz_-Q_OQx=AfYFs>tX6UH|7fu7F31i&c|TE|=PRKqBxG a?{D6d%Pm^0r*9Jir4dh8KbLh*2~7Z$?iA(# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent-shrink.htm new file mode 100644 index 0000000..8341ce3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 N N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent.htm new file mode 100644 index 0000000..203ecec --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 N N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d1eb5dbb1a83b6a59126899cbaced5a57bb02657 GIT binary patch literal 558 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@rtL5V@L(#+pC6oO5P%Ef%y-mUi_(K7t1*yq|>lq?r+vN^_;G?wq{ecT?8)ThDg*xWBz!dw5N; zb+yXfFjs{F6La~ECJxBSa-GWP$mPS1PoPxkBXeE06D z{ifpb`L`~`eJ^|dZr_&Fzu}wG%k?L1KmT{{)+dE)E?2HOZFB8+t`5YzbSJk@sHwZ? tp%OgN$JI#mWC$+C)$J*nYv6IR!G5Ej!Jq36TOWZU-P6_2Wt~$(6965!_F(`3 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto-shrink.htm new file mode 100644 index 0000000..3aec849 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 N auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..297113b2c060aac789601a889cf01b73048fc1d1 GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0y~yU=#wf7qKt{$@(Sn7lD*$fKQ0)|NsAiOa=xJ{r|w} zwKtwMG&FE7(b@#mVe0AP7*fIb_KI)bAp-&T!gU|iJHHn&vxqx1F{eCyxB1~e_ZK&o z$a8Icy=v+kkN2H{VmJS!q~8z(f~_;MzH8o;e_iI##KI}0;xR!X(qmWgzSFw*?>?`t zelmNT#?Eh*CoL*|r>X=gPdd4^NbRZ6O6!|*4zEdhUv>CJ+IiEHp;E^TnxDLtd>;Mn z+)Y+X=FFn?=3iAg+b-LzH0GSN!gGG7+gbnFPiNYQnpN4(zwh;nsZ)P~jhD)$`zp?R zIE6KJ3tcDa{XHt?{p{v0m3MwW+=O-)Z(Q10J^9Am-MXjSQ*z56iP?XDu70xY-`j}_ zjxcu;$9NL2@;mu1AJAVA_1v9V+m)4+l(IIIA8U_ul$YbH1%;}otDnm{r-UW|Akd@R literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto.htm new file mode 100644 index 0000000..2f1a933 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 N auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ab79088f36dfd8d116e113502ffccd63c07bd1d0 GIT binary patch literal 603 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#($(bL5-q=ND7RogrzZ;|7H`3uju|GUShbE82uf`RdzE#vR_Lm|n> zRNC^6O#LA}cYaN%C;PQP(eJL4R6IdQF7k41t@plv>#KkDrv$33R*DooxymEVb(7Yl z(1~lhQbJLM=BlQz&RlDL`PRGTUkhhko9`Yx@yyhk`Tmpd=zjZ~-;|QA;;mvo{hi9m z`Kr;g_kNA!EnfQX?4IOpzV_=q_if*HgLiXA?)~$R{{;L`yD9%;Q}ed-PoGZzSFOT+ zc>Cp^yC)q#t+1?9zrAkmIlJlao=i`gU-q-kZuOHtY$yNnPCEZ#*M8OL)l*|+;tRxc zA5S{HeA1lW{5i8#{hcR0(VcYsuI(SUzBlhm<8K|`YrARp_S(q*llN@f&3^Lm^?!9@ zC$Da~Haj!-`{iF2KHU(%1Id&m70+d^lTxAvmP$W)-TlAzejBV|jv5W6({gsL7YE0k dif2_qJ>#TavqeTpHASEl;_2$=vd$@?2>>|b5GViu literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N.htm new file mode 100644 index 0000000..dcc063b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 0 N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2b11d4515b4572d19185de0fbc8940e8e8514f7b GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wF#(0*we)^q=ND7)w90Qjv@|;Q4)1~{H+|8HF^XvaBo)U{-v+@Ic0J? zZ;D{w6PI6hg{DdA@@IdU2Rog3U${LNxz)dIu zYgE^7TfO`D)x+=hE}x!kfBvuOKSr-GJI$Yut8{->Y@S`Ou6jJ+(~bCPHTQN*R-dwF zkEZ3Zt8eGL{kLbH;qkvkeTAQQoH;A8yYk()|M}0D@1I%lxYD?7QogqyZ$AI#J13Vu zF>1HAlso=#4vq + + +flexbox | flex: 0 auto + + + + + +
+ one + two + three + four +
+ +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-0-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d4107b044a2bb4d6eae9116283f0362f587051a3 GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0y~yU@QV+6DDS$h>v%`YaqoL;1lBd|Nnm=lY!xXaIo72 zpo%U}7srqa#6ckqXhC9Cf*k$K*Gf*l?AN)4^XDx4{QXw$*L54CHwWPlL;e4Sr-jy){(19C_Ub9V ztlf9z=+&cD_3P~ZeOvhbS=pA)X;a>=&?#S*-R72R9?w*Q4}95L)w^IZ5ah4UUK+y$ zbmrq_@7{i#{yr3>>&yMG+jjW-UH)19c3ai;8*3wWUAraSJYm&3m1W4`_-*MdW`~3CmcWX@_05IIXE&4 Pf}+RM)z4*}Q$iB}%K+U1 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0-unitless.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0-unitless.htm new file mode 100644 index 0000000..de81e61 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0-unitless.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 0 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0.htm new file mode 100644 index 0000000..92bab58 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 0 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N-shrink.htm new file mode 100644 index 0000000..b4b4029 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 0 N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bda4a23ef5cda8d4a6f9ea01d09e15faaf77274f GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0y~yU=#$h7qKt{$tjbcTmn*}0X`wF|Ns97G8q^^^#22= z*WP&6(9poSL~9dJhqzRW@BCiC%p&g4#GEp5-RZX9@(YvI zLstv!ZocKQ@yk4k)QM-+t(~1+TwJys-BOl!^4*5KY88(O3XV-IoRh>=->$s#GwPk$ zwCd_9)$0R`UY?q?Q2uTB1P|$ncMgZ0JEA}F*5b38=bXN+GwxaUEoM^8lDdw|-ivK7 z*POn`x_d!_!rjB4eQP}zJ+$zD=d(y9^HcgF$Iq9aJ)h~rC2c$Z{OU>P6eVRRwYw|* z^}V!ww!y?tlO9ak@>Bns?#$(7p10=y=uIhmp5s0Fo7!e;^XO^*CojEQqC4-~b8}DI z|KFgF6oR>yAmfw%q`QfCzxA5KbWGA*8w>JzNTU1^sf&}64JMxlg{!BlpUXO@geCx7 C+N?(a literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N.htm new file mode 100644 index 0000000..033638a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 0 N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent-shrink.htm new file mode 100644 index 0000000..529c49d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 0 N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c37d2519b0e8ef420b74c6d9df71f9bd499c7985 GIT binary patch literal 441 zcmeAS@N?(olHy`uVBq!ia0y~yV4MhKFJfT^l10ppQX90fB!BXn@29sZD@KpMJ?*Ftu z*KUKEV_|mu{w@0_T`Z{ZdpWmfl4o$8dSU1DnV(J|9%Gg7mXkVC zRnLAY3KDrbscc$G@V}$yr`fbGNht5V@A2^6tKv-G>eU71bBiPYPMq>)T_(5vxA3(~ zR + + +flexbox | flex: 1 0 N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto-shrink.htm new file mode 100644 index 0000000..9ca1411 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 0 auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..304f96b45b970b93be00fa6a3eb78d1212b7373b GIT binary patch literal 424 zcmeAS@N?(olHy`uVBq!ia0y~yUcZ<dEicS!yW)d`$N`s@PXzgS)>JnyDEP&7Nn-a*70*c~8|@g} + + +flexbox | flex: 1 0 auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0.htm new file mode 100644 index 0000000..996a9b7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0-unitless.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0-unitless.htm new file mode 100644 index 0000000..c949b0b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0-unitless.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0.htm new file mode 100644 index 0000000..c7c1aa5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N-shrink.htm new file mode 100644 index 0000000..190fa01 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N.htm new file mode 100644 index 0000000..5f85c54 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent-shrink.htm new file mode 100644 index 0000000..ba1ebf0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent.htm new file mode 100644 index 0000000..ab07ea3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto-shrink.htm new file mode 100644 index 0000000..22bddfe --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..297113b2c060aac789601a889cf01b73048fc1d1 GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0y~yU=#wf7qKt{$@(Sn7lD*$fKQ0)|NsAiOa=xJ{r|w} zwKtwMG&FE7(b@#mVe0AP7*fIb_KI)bAp-&T!gU|iJHHn&vxqx1F{eCyxB1~e_ZK&o z$a8Icy=v+kkN2H{VmJS!q~8z(f~_;MzH8o;e_iI##KI}0;xR!X(qmWgzSFw*?>?`t zelmNT#?Eh*CoL*|r>X=gPdd4^NbRZ6O6!|*4zEdhUv>CJ+IiEHp;E^TnxDLtd>;Mn z+)Y+X=FFn?=3iAg+b-LzH0GSN!gGG7+gbnFPiNYQnpN4(zwh;nsZ)P~jhD)$`zp?R zIE6KJ3tcDa{XHt?{p{v0m3MwW+=O-)Z(Q10J^9Am-MXjSQ*z56iP?XDu70xY-`j}_ zjxcu;$9NL2@;mu1AJAVA_1v9V+m)4+l(IIIA8U_ul$YbH1%;}otDnm{r-UW|Akd@R literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto.htm new file mode 100644 index 0000000..6e5500f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1.htm new file mode 100644 index 0000000..89fd32c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 1 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-1.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0-unitless.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0-unitless.htm new file mode 100644 index 0000000..206b9c4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0-unitless.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0.htm new file mode 100644 index 0000000..cfa10fd --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N-shrink.htm new file mode 100644 index 0000000..5c0b3e5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N.htm new file mode 100644 index 0000000..1bdef9f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent-shrink.htm new file mode 100644 index 0000000..a6c8ef4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent.htm new file mode 100644 index 0000000..e7315b6 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto-shrink.htm new file mode 100644 index 0000000..bd2c4b0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..297113b2c060aac789601a889cf01b73048fc1d1 GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0y~yU=#wf7qKt{$@(Sn7lD*$fKQ0)|NsAiOa=xJ{r|w} zwKtwMG&FE7(b@#mVe0AP7*fIb_KI)bAp-&T!gU|iJHHn&vxqx1F{eCyxB1~e_ZK&o z$a8Icy=v+kkN2H{VmJS!q~8z(f~_;MzH8o;e_iI##KI}0;xR!X(qmWgzSFw*?>?`t zelmNT#?Eh*CoL*|r>X=gPdd4^NbRZ6O6!|*4zEdhUv>CJ+IiEHp;E^TnxDLtd>;Mn z+)Y+X=FFn?=3iAg+b-LzH0GSN!gGG7+gbnFPiNYQnpN4(zwh;nsZ)P~jhD)$`zp?R zIE6KJ3tcDa{XHt?{p{v0m3MwW+=O-)Z(Q10J^9Am-MXjSQ*z56iP?XDu70xY-`j}_ zjxcu;$9NL2@;mu1AJAVA_1v9V+m)4+l(IIIA8U_ul$YbH1%;}otDnm{r-UW|Akd@R literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto.htm new file mode 100644 index 0000000..2f0f628 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N.htm new file mode 100644 index 0000000..b71a784 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: 1 N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-1-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0-unitless.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0-unitless.htm new file mode 100644 index 0000000..44e396d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0-unitless.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 0 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0.htm new file mode 100644 index 0000000..5bafb73 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 0 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N-shrink.htm new file mode 100644 index 0000000..d5b521e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 0 N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bda4a23ef5cda8d4a6f9ea01d09e15faaf77274f GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0y~yU=#$h7qKt{$tjbcTmn*}0X`wF|Ns97G8q^^^#22= z*WP&6(9poSL~9dJhqzRW@BCiC%p&g4#GEp5-RZX9@(YvI zLstv!ZocKQ@yk4k)QM-+t(~1+TwJys-BOl!^4*5KY88(O3XV-IoRh>=->$s#GwPk$ zwCd_9)$0R`UY?q?Q2uTB1P|$ncMgZ0JEA}F*5b38=bXN+GwxaUEoM^8lDdw|-ivK7 z*POn`x_d!_!rjB4eQP}zJ+$zD=d(y9^HcgF$Iq9aJ)h~rC2c$Z{OU>P6eVRRwYw|* z^}V!ww!y?tlO9ak@>Bns?#$(7p10=y=uIhmp5s0Fo7!e;^XO^*CojEQqC4-~b8}DI z|KFgF6oR>yAmfw%q`QfCzxA5KbWGA*8w>JzNTU1^sf&}64JMxlg{!BlpUXO@geCx7 C+N?(a literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N.htm new file mode 100644 index 0000000..79b20dc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 0 N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent-shrink.htm new file mode 100644 index 0000000..83d4efe --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 0 N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c37d2519b0e8ef420b74c6d9df71f9bd499c7985 GIT binary patch literal 441 zcmeAS@N?(olHy`uVBq!ia0y~yV4MhKFJfT^l10ppQX90fB!BXn@29sZD@KpMJ?*Ftu z*KUKEV_|mu{w@0_T`Z{ZdpWmfl4o$8dSU1DnV(J|9%Gg7mXkVC zRnLAY3KDrbscc$G@V}$yr`fbGNht5V@A2^6tKv-G>eU71bBiPYPMq>)T_(5vxA3(~ zR + + +flexbox | flex: N 0 N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto-shrink.htm new file mode 100644 index 0000000..d6e2e31 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 0 auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2ba886edeb0c0dfa7268a1fc09d693c9056572d0 GIT binary patch literal 426 zcmeAS@N?(olHy`uVBq!ia0y~yU5et4FV4Gb|Kw76_5IDipT~Ure<^(T{kuWm z|LRYjc5k`s_Ln+!zMf=(bmiE*<++dQjl6}sy^mF&t`aT5( NxTmY1%Q~loCIE#zzc>H@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto.htm new file mode 100644 index 0000000..bf9fb04 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 0 auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0.htm new file mode 100644 index 0000000..71e7d40 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0-unitless.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0-unitless.htm new file mode 100644 index 0000000..8db8400 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0-unitless.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0.htm new file mode 100644 index 0000000..c21c35d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N-shrink.htm new file mode 100644 index 0000000..d6b4b00 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N.htm new file mode 100644 index 0000000..9b6fc89 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent-shrink.htm new file mode 100644 index 0000000..333bd48 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent.htm new file mode 100644 index 0000000..b2f5e95 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto-shrink.htm new file mode 100644 index 0000000..7ced231 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..297113b2c060aac789601a889cf01b73048fc1d1 GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0y~yU=#wf7qKt{$@(Sn7lD*$fKQ0)|NsAiOa=xJ{r|w} zwKtwMG&FE7(b@#mVe0AP7*fIb_KI)bAp-&T!gU|iJHHn&vxqx1F{eCyxB1~e_ZK&o z$a8Icy=v+kkN2H{VmJS!q~8z(f~_;MzH8o;e_iI##KI}0;xR!X(qmWgzSFw*?>?`t zelmNT#?Eh*CoL*|r>X=gPdd4^NbRZ6O6!|*4zEdhUv>CJ+IiEHp;E^TnxDLtd>;Mn z+)Y+X=FFn?=3iAg+b-LzH0GSN!gGG7+gbnFPiNYQnpN4(zwh;nsZ)P~jhD)$`zp?R zIE6KJ3tcDa{XHt?{p{v0m3MwW+=O-)Z(Q10J^9Am-MXjSQ*z56iP?XDu70xY-`j}_ zjxcu;$9NL2@;mu1AJAVA_1v9V+m)4+l(IIIA8U_ul$YbH1%;}otDnm{r-UW|Akd@R literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto.htm new file mode 100644 index 0000000..7ed3906 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1.htm new file mode 100644 index 0000000..d49d01c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N 1 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-1.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0-unitless.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0-unitless.htm new file mode 100644 index 0000000..de5b00a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0-unitless.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N 0 unitless + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0-unitless.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0-unitless.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0.htm new file mode 100644 index 0000000..b4f5c7e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N 0 + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-0.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N-shrink.htm new file mode 100644 index 0000000..37fdc2e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N N | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N.htm new file mode 100644 index 0000000..a78dc76 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent-shrink.htm new file mode 100644 index 0000000..ce21391 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N N% | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0f46f7c743d7fe89fb189e66fe368da99fad6594 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^mwBHnnd9kP;2>332`Z|38q)zyPBEA2_}C z#GZR4BSo7qd)vI;JH z`%NqAClCvbayTZS#SPm zsfk}^&<)%F_EB%{-JGWP=5EsWylpxDnfs~}Ps{)QKKskhcxABT@DWS0%1_*u;{<`1 iyq-H(cXV`cy_L?JZ+-Nf?1uNCVDohKb6Mw<&;$VOY@PK0 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent.htm new file mode 100644 index 0000000..61eaa98 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N N% + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-Npercent.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto-shrink.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto-shrink.htm new file mode 100644 index 0000000..eb8660d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto-shrink.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N auto | shrinking + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..297113b2c060aac789601a889cf01b73048fc1d1 GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0y~yU=#wf7qKt{$@(Sn7lD*$fKQ0)|NsAiOa=xJ{r|w} zwKtwMG&FE7(b@#mVe0AP7*fIb_KI)bAp-&T!gU|iJHHn&vxqx1F{eCyxB1~e_ZK&o z$a8Icy=v+kkN2H{VmJS!q~8z(f~_;MzH8o;e_iI##KI}0;xR!X(qmWgzSFw*?>?`t zelmNT#?Eh*CoL*|r>X=gPdd4^NbRZ6O6!|*4zEdhUv>CJ+IiEHp;E^TnxDLtd>;Mn z+)Y+X=FFn?=3iAg+b-LzH0GSN!gGG7+gbnFPiNYQnpN4(zwh;nsZ)P~jhD)$`zp?R zIE6KJ3tcDa{XHt?{p{v0m3MwW+=O-)Z(Q10J^9Am-MXjSQ*z56iP?XDu70xY-`j}_ zjxcu;$9NL2@;mu1AJAVA_1v9V+m)4+l(IIIA8U_ul$YbH1%;}otDnm{r-UW|Akd@R literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto.htm new file mode 100644 index 0000000..86664de --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N auto + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8c69849c4144d4664886a54720b44ec0d4d48d3e GIT binary patch literal 554 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@q(v|V@L(#+pC6oj{*daKa5EfdSkcKQADqSg?)j6E$?^xLw3?) z+itx3s~hm;?~xO-N0)DW?mJ1v6NKhPu3W$S$?xe^zf>f_Yn~L&xTUWV>1w1@DfjltYVWYxn(aSlst5dD?|XPnZMB+8 zdFbK7c?+|$&Zk;s{WskvUX!z+RPOivFYQlqe$>vs;&*ORcUqqM>xk{?v+vE_qqS}2 zzrQijMVp(RY-vjQ^md{Ez;=}F$5^gjO8%gHGk pcth1=S(V4MUsaPKE?c7hjs3+7U5kL+)K8!Y_jL7hS?83{1ORFl_*eh{ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N.htm new file mode 100644 index 0000000..ff31330 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N.htm @@ -0,0 +1,40 @@ + + + +flexbox | flex: N N + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-N-N.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf2cda98be4898de979f3132986fb94c29e5e2f GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0y~yV44JEFJfT^k_^rU7lEW`fKQ0)|Ns9%A`Bq<|AEtM zZ#-*gXy9C;wTXd&@sg*DV@L(#+pC6oj{*daKa5EfdSkcKkwx60i8-L6TIyYWORbsC znk~2Y?q?0!H(z4s;vXj8_IOTG0ijFA+Fwhj*ZdFipReLP(Z|(D^kj%iFm}dO=TFJf zIprK>bHZL+SG_ayNw@bqbH|gH4YtNdZR$#K`DAnJc znRX|fcX?&os+G3WU#|W8@J8H+jg8kTdG@^IKY8=RZ?lPW=ctOGz2J9kW!`xpXOGsl zl?Uq9Y3=m%_RIEkyZ>%?%GK3ge5bu{x@han{E_mnT>W>n|0gq*`lcsEyPxN$JSuyC z>tfuyvPWg|w>SP%mfU@J^OL7l`yWqg4E^J6bi;5%(|<3v4;~QT(wlrUjo6UgbmjVN faD+@!vE6uo`Hh?(Zl)hJK~e7M>gTe~DWM4fYXI`A literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-auto.htm new file mode 100644 index 0000000..88216df --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-auto.htm @@ -0,0 +1,46 @@ + + + +flexbox | flex: auto + + + + + +
+ one + two + three + four +
+ +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a15159b5bd733ebe9dcf69cc2d600ce29435760f GIT binary patch literal 532 zcmeAS@N?(olHy`uVBq!ia0y~yU=m_rU^HQ328yuw8O;JxoB=)|uK)l42QnEL{s#xU zT>z>$=IP=XQo;E4ilN_O1CfSA{e6wKoPHBq_cjBmi+pz_9|+BvB%-*`A* + + +flexbox | flex-basis: percentage, flex-shrink: +integer + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis-shrink.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis-shrink.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..957ada77c22de7439f5e70748e6cbbd04394fa20 GIT binary patch literal 452 zcmeAS@N?(olHy`uVBq!ia0y~yV4MVGFJfT^k~0^ET>w&|0X`wF|Ns97G8q^^^#22= z*WP&6(9poSL~9dJN0z6HV@L(#+bf27j~qlA9>zRmePg%NF(qC>NT%V?7n3{xIjqgz zuey&Wyl0n7?l=LN|GTf8zYy6+Alf@odW!L}ohP+$1QD<#x ze(1Y(#gSqARZVwi-#pa+_o|lUEh+!pxy!e$`@YSCQXsdtK5sYwq!Q#%@c6XV-7{w| cIIz5~=yRtnwp9NnT?dK^Pgg&ebxsLQ05fXF6951J literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis.htm new file mode 100644 index 0000000..fc6dc12 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis.htm @@ -0,0 +1,42 @@ + + + +flexbox | flex-basis: percentage + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-basis.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..40e9e1b5f86c2cbffa6bd2bd85a1f333d615ec44 GIT binary patch literal 495 zcmeAS@N?(olHy`uVBq!ia0y~yU=jqf7qKvdL<*ZO11Zq}pAgso|NjG-3=AOp|AEtM zZ#-*gXy9C;wTXd&ajK__V@L(#+p8OU4><@p1V%|5IJc0Qg;&9mkwwi;=$HP(Yi*a0 z-itiRu~*-%kKe^IM~TF{pS2Cku_vds8UcUD$_X;mQvLvux{gK%F^qD)$A1w_~ zdv?G7n$T@FA!p{!bnecMRMlNCmx)hYG5y7CkDOQgXPi~J=bRMTnOWX*zvI;1LM`v) zThdc^KaQGqbiT{kcQdxV$gWRO?c3=$J?6UN^}jQ_Ax;PR1ndWDGitUSshXd?{ns=| pXd&6*EqZgBT>~gwJujJm;ch=UPo@6rmkv;@dAj + + +flexbox | flex: initial + + + + + +
+ one + two + three + four +
+ +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial-2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial-2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c487b725d14568f940550fa47f546f9fc3e16ef1 GIT binary patch literal 581 zcmeAS@N?(olHy`uVBq!ia0y~yV4B3hz-Yq63>10fZ9N-EaR&H=xc>kDAIM~2_#YhX zb^)m3yQhm|NCo5Ds~dd}JBTzSnr}aNSIFmKn_X)@=Q0njDz1no&5eRV1s(jHvM*Tc z>v!$3c^$lHInN{&&!GERANby%YQKKNHZX{-XOdCUlqrIX@2vl1QSKuIYqj>z3rT=_pwg=h2UpIBoM{_}1Dk?#_p*Z5He9 z|Hzzkf4%M0?f>~b$pJTmB`v*9bp!^vfj#g$Km>RouH%)YYkA6MR=@iw$lDthytwQII7$a(uW{psV7WhSxpk4{gy{B4cT&Sh8M zRZe?$Hs-hH{L6p#Z24Jhn*KCt-#zWe^EKbkKDBV=n;+f!r-JI2opD|{zx?{CJCQTD z>pzix`e~8P{U6_C<@NI_Y@=na=l?M|_x}34Q@8)OFChoKS=l3_ntDV-QyJueQ?**z hANZP8JSSb*YQClTz&rh$Z%aUF!PC{xWt~$(69Czv9p3-| literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial.htm new file mode 100644 index 0000000..e8ae362 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial.htm @@ -0,0 +1,45 @@ + + + +flexbox | flex: initial + + + + + +
+ one + two + three + four +
+ +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-initial.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d4107b044a2bb4d6eae9116283f0362f587051a3 GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0y~yU@QV+6DDS$h>v%`YaqoL;1lBd|Nnm=lY!xXaIo72 zpo%U}7srqa#6ckqXhC9Cf*k$K*Gf*l?AN)4^XDx4{QXw$*L54CHwWPlL;e4Sr-jy){(19C_Ub9V ztlf9z=+&cD_3P~ZeOvhbS=pA)X;a>=&?#S*-R72R9?w*Q4}95L)w^IZ5ah4UUK+y$ zbmrq_@7{i#{yr3>>&yMG+jjW-UH)19c3ai;8*3wWUAraSJYm&3m1W4`_-*MdW`~3CmcWX@_05IIXE&4 Pf}+RM)z4*}Q$iB}%K+U1 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-mixed-basis-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-mixed-basis-auto.htm new file mode 100644 index 0000000..c81c8b5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-mixed-basis-auto.htm @@ -0,0 +1,42 @@ + + + +flexbox | flex: larger integer, mixed basis, auto + + + + + + + +
+ a + aaa + aaaaa + aaaaaaaaaaaaaaa +
+ + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-mixed-basis-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-mixed-basis-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d288537188945fff723c4e3e94d013bfc96f2d69 GIT binary patch literal 396 zcmeAS@N?(olHy`uVBq!ia0y~yU~&MmC$KOBNg>gbU@E{T#PvT2F#JDodhLy84Gj%I z*-b|t_%JXqntHl8hEy=Vy>dFUAwZz*;+6-8*jTg$tS>Ot`21gcq?hH4(isl5BcBi6 zKalwD?*tXks$$<=>+Tfnez}o>k%{GnKf_i*&jogitfy!9NUS?;>h5!Os{V@DP2yM9 z$DfY9u%kBqUDLC@`(Cl?zT}QNzf?bCw_dGdb*bwDkN688cU&vBT<|zB_xjU@zT=z1 z**Pw(XZYD%$B=cY^l95|-+opO0R@K!kUfKg@nwFaisz)b8|u}Gf$g#uLZ5-*$l&Sf K=d#Wzp$PyPCw#jA literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-variable-auto-basis.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-variable-auto-basis.htm new file mode 100644 index 0000000..440ee8e --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-variable-auto-basis.htm @@ -0,0 +1,39 @@ + + + +flexbox | flex: larger integer, auto basis + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-variable-auto-basis.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural-variable-auto-basis.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b307e75374d7a988cc2036151ba47cc2454fc7a8 GIT binary patch literal 470 zcmeAS@N?(olHy`uVBq!ia0y~yU=jkdC$TUC$wxMJwLnTJz$e7@|Ns9$CXo1l;Pl!X z&l(yUas#@6LX7pEE{-7;jBl?V%su2F;1C!kv4NXG%i#jUB+UlqzuPB$bP{@+w*I=H z(EIL-^J*SszP;%&NoA{s-Cp_5w+q)juAHmq9J}2;wp{3~ir?;1m34O~?C0v_5ORHV zZ@2JT6{T&z=Nz^$K9ye9Dfrwb+G~!|uGbc^M<%4tB!#s|Casn>S6nR z{mfqB#V!{!>eU}ftv618HMc_R$&oL=FM1_yUvy-H!=sHZN{{xc>?(R3tn$qJOSj6l z&--Vr_TJNW8!E2dgpDb4x${lCYR z(&{U&O)Q*3DjpLQ90!oGXR}L9m9*!iC2X~fs~^}k_uOgt0*oC7Pgg&ebxsLQ07L$| AnE(I) literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural.htm new file mode 100644 index 0000000..4d566ba --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural.htm @@ -0,0 +1,50 @@ + + + +flexbox | flex: larger integer + + + + + +
+ one + two + three + four +
+ +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-natural.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5c99cad5fc4808ad56a615f5ed5665e782acddde GIT binary patch literal 1095 zcmeAS@N?(olHy`uVBq!ia0y~yU^Zf4U^HQ228yuWmDU1Mq5(c3uK)l42QnELK=l6u zr`O(i*3i(vxkPIdP{&(O7srqa#y3|t=1MyXv;}T8Q0m`#rBT6Dp+T$Rpk?}^f8HPN z%~asM@lvPm@cYBOX3EX&H)|)UctX&nKVFe{Q|HA`y}~{7!ignrYNkdjCwlh!WTgZt zPnMc_1y!i}gv`C!zb(waZ}oe1{Cmr~loK^IaWj=;kOfWRv#F0ZS(i>t~rXAw`lD@Y4ZK* z7Nz=ACW#Xio!fXMjZ#hw9>z2K>6LY%LHa39Sb`i?=&fS#saMdbo3!NO`ERdoZ0l9v zIJ_cSV&d_bR}K@T1RO*UJeXq~_+Pc+z0U-;Hzn8hKm7iXZT7^2hu?Vp;-0!`*J+!* z8Y|O{7UGU(sGZ*?Sne_XUYGZ-cp5djPN@8EVRkxZT4{}O%BmACr(*oe+RjW^ zX|<=X^7gXDA0HoY41Qj??L1+;)=kemq%i0ryX{Q9-sdlimdrcZLLjp#Prl{6Y|2%0SahrSzGwe&r!8x#Ti#Jn OY2fMV=d#Wzp$P!Q!Rjgi literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-none.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-none.htm new file mode 100644 index 0000000..edc748d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-none.htm @@ -0,0 +1,46 @@ + + + +flexbox | flex: none + + + + + +
+ one + two + three + four +
+ +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-none.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flex-none.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e0990312648690f5ca3b6dd8bbd910d09a3ed42b GIT binary patch literal 529 zcmeAS@N?(olHy`uVBq!ia0y~yU=m_rU^HQ328yuw8O;JxoB=)|uK)l42QnEL{s#xU zT>z>$?CIhdQo;E4ilHBqqe#QU%x7%I(?5vZz7c$8cgymIrh88M4>ejZNhjQ&i{PRzL^Kk@&jb2C1{=Rwm`HE@U_fFxI|Yu(EZ|EQf3y7!=_ q2GL+zPZJm<)pkYf@CABdSH*kB38BUlyVP|-@#^X7=d#Wzp$PywG2D0n literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap-reverse.htm new file mode 100644 index 0000000..0774e80 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap-reverse.htm @@ -0,0 +1,36 @@ + + + +flexbox | flex-flow: column-reverse wrap-reverse + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b70aeef7b6cab2c1c6321a6bea49cf802fa57c GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@MNG^UA1vhbU#PaXd$+1lYs?aVLEVX(&Z>IOFIhe$NLQUN z`2LzX@q(6%%ap17?|!Y`SKt3uSQG|KBj=tjoBmsPV(hM0hF{HO1C?K=2Z{>^2i^~! zzD=lm{??g6%l~d(x4Sv(f%t{(i&xEy5*5B5o}O=A{lQ*LRP<}>m5B=%!9d#T;+yY^ z>XkN~4$GfBFD7=)tX(s1Z2NVwZ136q=~Hj*)4Kg?`WJKAttl&_R)yaWubw?`R~=Kj zZhvTc{Pl^}3xxJFum2+F;Ocrd(7mk-2!1X9^Rh5y&VT~K)78&q Iol`;+09DA0djJ3c literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap.htm new file mode 100644 index 0000000..e3b631f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap.htm @@ -0,0 +1,36 @@ + + + +flexbox | flex-flow: column-reverse wrap + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-reverse-wrap.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..572dd56c09570355acd697c65587a589801d3a9d GIT binary patch literal 324 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@MNG^E-~Cy-ude4UuP6+dM$SF;*6+IT#Mo_C+ZVi&%u1QQR&SY7S$wR| zuOQp1>-(mjh`k=0oA@=gJ<2G=c7fH-h$p;f*1nIC|JCj4>RPK-nz8~8ZoUf1E8jJ* zOGkjX&$=;;{aeQLFNE>vv{%u6q65 z;%k!DM010mtK9A%5d6;`5E%G + + +flexbox | flex-flow: column wrap-reverse + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e3b1b681f5f43947e91d231c5067be7c84eea67f GIT binary patch literal 326 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@MNG^UA1vhbU#PaXd$+0)YwQw#LEVX(&a7^$0&EzSQXJn) zzN@QM;F;Ok(c$*gy8M6W{r%tH3X1~4SAX5Pv;TgpQQ9QE)=q8j4y$OhQp1g`BGU@3$sqm+q-_7nQrM%mUFZELc8@}pRisaRL_2{gny2wY;pGD zMKI78=pFSQZ05QupIV>jD<^)1Ub%TBGle?I>y@Dt6nV) zHT<|QYR|hj59;4My=u7KObZ3PiVV5Gvsy`Mk>g*6s?FQxT#Vrn0|kbstDnm{r-UW| D8=Z?V literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap.htm new file mode 100644 index 0000000..33dfe03 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap.htm @@ -0,0 +1,35 @@ + + + +flexbox | flex-flow: column wrap + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-column-wrap.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..75b3343fc1e443b9c7be8357d9bcc19a21789a2b GIT binary patch literal 327 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@MNG^UA1vhbKd831d$+0)Yv>MtLEVX(&a7??41pzXB^z}2 zJpXo`UCe}AP%!nWb@{)bc>8Z}c}0QXYxG-b{ogNtxu$АB=f>%dZb>^=Ad}HP5 zSKpTZ(zB{txY;ZB_3io8_}1le(F@dh^PT+|`>;Myzr_Z?#Rx^7W?Gx@-2`Sul+! zlz&B;c=w&gmA!Lc$Nt~A!g9Tt77BQ^dBwis=T0szp0!LzRJP4oqvxax3Jp(JKbLh* G2~7anQ;ysK literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap-reverse.htm new file mode 100644 index 0000000..6fc984c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap-reverse.htm @@ -0,0 +1,34 @@ + + + +flexbox | flex-flow: row wrap-reverse + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1a181cd7d2b1ab7bd178c4f251101e7ebc12588a GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CMISenfYnOb0EbT;1lBd|Nnm=lY!xXaIo72 zApO?U#WAFU@$Hq36Pq1)S|9peX}rP`np0%ic;!gwLAEF2GQKfWw;S}#-FT>(t>^xE z3*q~3Bm$f`6h)@W#_V5lzyA0)W>Kw>6#??EF5Gx?x2io|FG_c2?JCz5s`J?3;J>|9&BLL&N@4yRZD*wa@B?&FwmNuXV=9me*|z zNZw`!G;g6(D;LOM!&eiwy}o8`Qyd=Xy>GSEDviwOxl>k}eh;bdeErliyZW8#*X;RP zVhf9^_)AM{Za=+k6!hP^;J9r8t7%%cGti7z>8r~MYC$f(V(%@=;iPjiWELm{JYD@< J);T3K0RY~OgsK1l literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap.htm new file mode 100644 index 0000000..880ed3d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap.htm @@ -0,0 +1,34 @@ + + + +flexbox | flex-flow: row wrap + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_flow-row-wrap.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..303d31699409aab3af94fe7ef81d4178e39d211d GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CMISenfYnOb0EbT;1lBd|Nnm=lY!xXaIo72 zApO?U#WAFU@$Hq36Pq1)S|9peX}rP`np0%ic;!gwLAEF2GQKfWw;S}#-FT>(t>^xE z3*q~3Bm$f`6h)@W#_V5lzyA0)W>Kw>6#??EF5Gx?x2iq;omSS#@=$kC)!fySjkZ3U z_e#HTuT6UH`Mpc(-Uc643h{ftu&*zDcTKe0t9tf#JJ{DPIF^&UB4D9YD;L^i(#_>jEm5C-zFM* + + +flexbox | flexcontainer via generated content + + + + + +
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-flex.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-flex.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..3466de6b5d12339e29d424ca16b883141ef3903a GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKgP53smdKI;Vst0MVaSk^lez literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-nested-flex.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-nested-flex.htm new file mode 100644 index 0000000..48fc8c5 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-nested-flex.htm @@ -0,0 +1,27 @@ + + + +flexbox | flexcontainer via generated content + + + + + +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-nested-flex.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated-nested-flex.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..3466de6b5d12339e29d424ca16b883141ef3903a GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKgP53smdKI;Vst0MVaSk^lez literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated.htm new file mode 100644 index 0000000..53285a0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated.htm @@ -0,0 +1,32 @@ + + + +flexbox | flexcontainer vs generated content + + + + + +
+

FAIL

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_generated.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ce63e0a9434d18dc59f29fe61dcfa93d30ff592f GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK1DKeB7S!pexZwvypZav=OU9Bg-ZFDCDVmGrd*ut zaOsbrqRk1x#p~<+UKdMvM82~Me3kEHFhQ)CQ{lIMD~F(xONW4>QwxxxBKqVd=R(z% mX#!DB8BoSfg|xJ^+qI0pUbd{uR8#&0a=oXkpUXO@geCw2b6p1j literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-bottom-float.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-bottom-float.htm new file mode 100644 index 0000000..8211024 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-bottom-float.htm @@ -0,0 +1,34 @@ + + + +flexbox | GCPM bottom float + + + + + +
+

+

+

+

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-bottom-float.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-bottom-float.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bc7d80a9476d3f0153d9be8709f388978590ffb6 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0y~yU_1b1H!v{+$%iR{7C?$Kz$e7@|Ns9$rg7StpPU=? zfUHnY7srqa# + + +flexbox | cleared item + + + + + +
filler
+ +
+
Yellow box should be to the right of the blue box, and + never below
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-clear.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-clear.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..7f8e75d72064c2abeef4d311f6bf1140f944653d GIT binary patch literal 684 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKConMs$zGf1!a#~Az$e7@|NsBSX=nbQ0rDBF zG&J}=x7@nT z&V5t=Wv_C);=>Jj=ey=k9Ewj2`R)spvAJGYz#%2#*sPQhz{1l2e(U+vz-JTbQ4YuFXQ!qTdX$U(4C|;(OY}s zoNG#DlOhggPn?q_xO;NGtJjh`{`?~|0WYx!D9(Z6B@|BI~ZY6*2cWGi?- zp|fR|Qg=s2mS+8nYxS!5W!`)hBabWD6MoG(`7}o|XwO{JZ~o__?I)Hv zOI^;*iZO0counHbwP|LDZRX53oVIVbsL9Q}o~!UlVD`P#oJ}GP9@6u)HoaYZqw&Q5 sCvWB80Sb;EtdT|~!N + + +flexbox | floated item + + + + + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-float.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-float.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..71a3008ff05682d2e9ce25e2cb5ac275a180952e GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK8bG(WD!FtBC_1%pU@)#Ym8oFUie%PINhSt{54)u+qxQaZ Tu`Xo+x{krq)z4*}Q$iB}|7J_S literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-top-float.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-top-float.htm new file mode 100644 index 0000000..16bea62 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-top-float.htm @@ -0,0 +1,33 @@ + + + +flexbox | floated item + + + + + +
+

+

+

+

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-top-float.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-top-float.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bc7d80a9476d3f0153d9be8709f388978590ffb6 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0y~yU_1b1H!v{+$%iR{7C?$Kz$e7@|Ns9$rg7StpPU=? zfUHnY7srqa# + + +flexbox | vertical-align + + + + + +
+

+

+

+

+

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-vertical-align.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_item-vertical-align.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..62a6f301773e73e14692dd6b14b7c16627fdf9fa GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0y~yV9Elri

!d!14!l+djq*MYOtQKYa_l~zIxBWrWo4f7rn+0p zR5#A-MFzSGW@cvFzUREI+j=)H{(TMxsNax!^cXUjCLk>>eY^N}#~O1_t=o)NFFM#X TFW%$@I)%a0)z4*}Q$iB}aG_F> literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-center.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-center.htm new file mode 100644 index 0000000..d7ed4ca --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-center.htm @@ -0,0 +1,40 @@ + + + +flexbox | justify-content: center + + + + + +

+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-center.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-center.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c2731d3c93961f7419eed47cf942b6303a5089dd GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRFJfT^k{)hB3xJeRfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezR36~z;uuoF`1Y!!-=P43hQxISTNWJJ&(5MBo)s|CDN{2_dVhF-+F7(L;o!{%H zkN&(fS-ZXa^Y6T<_fJlrj=z5W(}a_2hO(>7&O}&WlyfyFcgm zO_!3NGHGHz&Ca$LM!nxNo&VQ2>;B@N^K+^vt^eU~XI52S1LKKuXNv-|I>|DP{!sXMjY)sMQMZvChKVn8pRIQiPe sFHXJalhwq@*I!1mdKI;Vst04=q?-2eap literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-end.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-end.htm new file mode 100644 index 0000000..0291d8c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-end.htm @@ -0,0 +1,40 @@ + + + +flexbox | justify-content: flex-end + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-end.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-end.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..cd44e7c312fd6d50e2fc679303f3cb8165218716 GIT binary patch literal 422 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRFJfT^k{)hB3xJeRfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezRPOKT;uuoF`1Y!!-=P43hQxIST-rr%uOGkrvr+oG`p-|FzP~m3S$h3x`mpYUAp|Y;q;%|Ywq2(`}zNm(YpPg3MWpcE?6JB@7jrzuU-7& t)Qdh@O`Lok + + +flexbox | justify-content: flex-start + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-start.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-flex-start.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f45f26eb6a5efba42f0e87d107e8532c3ffb1fd5 GIT binary patch literal 425 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRFJfT^k{)hB3xJeRfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezR37B%;uuoF`1Wd`->U$DhQw_K-w!bbL@;b(;*~zIspKwxac%ma`@!q`o;=A_QhzUOSvmj2$=@!1_mvt>fBwH14)49K1LH^n#_nhc??<@9w{mJR_ckcAu z9ao=Ro*@3_+ehoTEw}x5UXicfo3VX<@yz;D+2z$g-~Y^w{QN0(;$-TA{Lp{bm63g` pUi8VX_VnUtYhajoPTJCG$MEXBa`D%LRpy}Z_H^}gS?83{1OSKO)`9>4 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-negative.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-negative.htm new file mode 100644 index 0000000..e7f8444 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-negative.htm @@ -0,0 +1,39 @@ + + + +flexbox | justify-content: space-around / negative + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-negative.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-negative.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..44e7ae5cecd0275d23753351122b4baf0cc915ab GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0y~yVAKS%7qKt{$@kmUM2jkkvUY(86$Nh&Y@cdz~8{PoMGUMnpKSS7nM?)TSw>-IgYh`rr?bDqcQh}L z*_9vqlkeRV`Svc-z^mz5+<{Q=)>Sn6rGn?AC0ezN0lp`BwSMl@0EL#PtDnm{r-UW| DR-~g; literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-only.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-only.htm new file mode 100644 index 0000000..89eb31c --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-only.htm @@ -0,0 +1,35 @@ + + + +flexbox | justify-content: space-around | single item + + + + + +
+ one +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-only.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound-only.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f48978676b74d8310e871e48b0ca1adcade5a48e GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRFJfW_lE>MW14#zX0G|-o|NsAk*bM)JgWWCw z=^36bjv*C{Z?A0RJ!Bx_8fbn`A+jkms7C*wh{;RM8BLmwSGHX1o>XjmVB*r{_f_C5bH{_no{^75Cx*IrG%6kq#u-|JVeJO0^j+qGuj^-IOG z_kY^D@G@BGay-<0!MT1w`7f2fIJTO4PAWNMXX79J* + + +flexbox | justify-content: space-around + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacearound.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f45f26eb6a5efba42f0e87d107e8532c3ffb1fd5 GIT binary patch literal 425 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRFJfT^k{)hB3xJeRfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezR37B%;uuoF`1Wd`->U$DhQw_K-w!bbL@;b(;*~zIspKwxac%ma`@!q`o;=A_QhzUOSvmj2$=@!1_mvt>fBwH14)49K1LH^n#_nhc??<@9w{mJR_ckcAu z9ao=Ro*@3_+ehoTEw}x5UXicfo3VX<@yz;D+2z$g-~Y^w{QN0(;$-TA{Lp{bm63g` pUi8VX_VnUtYhajoPTJCG$MEXBa`D%LRpy}Z_H^}gS?83{1OSKO)`9>4 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-negative.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-negative.htm new file mode 100644 index 0000000..6e431f6 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-negative.htm @@ -0,0 +1,39 @@ + + + +flexbox | justify-content: space-between / negative + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-negative.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-negative.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d98579cf4c7742e4ae1098d40a68c833585be80b GIT binary patch literal 361 zcmeAS@N?(olHy`uVBq!ia0y~yU^E1>7qKt{$=m0iGXW`~0G|-o|Ns93nG6gd`u~B` zYi~T8p7k*bs9ea?#WAFU@$J<>ufq-k4G-fIw^%xCv2{4hIVDbkf9LJ2Tm;2`ZjJ)vnsL6K!p_2Ku`}!8gxuHfz2YefC}cx+>rN{QTu#kNsDu zw#}Q|dv}WbruP$FZQ_dBDz#E-j~Esfp55#=?R@!To7f{$(r0Q#&R=%gXluFI>8^GE z&L#CcJ##a-Y>%_qUG450;nUt5+|S}l{ymp>_vvXr-)`D8`*Z%9B6I(pQ&RP{%RkRK z{X_Bgw%PSB?sxW(2y)zS`d^ZGyzAr_`hmjB)78&qol`;+ E0HkcGl>h($ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-only.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-only.htm new file mode 100644 index 0000000..812a6a0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-only.htm @@ -0,0 +1,35 @@ + + + +flexbox | justify-content: space-between | single item + + + + + +
+ one +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-only.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween-only.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..9b8fa533aad90b8d2c0bde29e1f812dd09dc2b8e GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRFJfW_lE>MW14#zX0G|-o|NsAk*bM)JgWWCw z>Ditxjv*C{Z?A0RJ!Bx_8fbn`A+jkms7C*wh{;RM8BLmwAtIS;)m5KYH7I-g@AsUv z#MsO!*Z%Lnb6=KRepzBQ*Dkbv%j3L~UnaHdw^l9v5+1+r{jOhDi|YH=>aBji^~;@S z>uYjf0M+>|2P?-#{r~C$QS|(!@|9~Uo|ia(Gh7vm{ycrzsZx-8Jzf1=);T3K0RTUT BW{>~? literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween.htm new file mode 100644 index 0000000..c8e00a3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween.htm @@ -0,0 +1,40 @@ + + + +flexbox | justify-content: space-between + + + + + +
+ one + two + three +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_justifycontent-spacebetween.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f03ac862978fb8d12a6b3d739005d194537d9df8 GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0y~yVEhDRFJfT^k{)hB3xJeRfKQ0)|NsAiOa=xJ{r|w} zwKtwk&-xezR375#;uuoF`1Wd`->U$DhQw_K-w!bbL@;b(;*~zI*rdU zntuLUSadJT&QG#zl8Wafl}u^Zt^1z*pO;mtqOLD&SvmK_$=5D^aq2~%to}~1kAMEv z=%fAIyw6p$f7~fHUtj*|{^b4du1~zrANz0OWc`WmfA&9#`JJg@@AgJme!ETdsn0vx ztL@u6^v)-Wo$Sxt*K_&Tx$2(_^6Q@$_J2Hcb5YIyMSHSU`+r1#syY67?RkF%x&5D? zN#~XRw0Oc@R{dW#f6s0IomblT-mBO?zj$W-Df!!PSO1-N|Cwp5oqiE@!Kbw+Rzp2H raq{)fIeYw`JSqJ(&3!90T!d>G^e-wGPyNm>2?}vfS3j3^P6 + + +flexbox | margin: auto in overflow + + + + + +
+ one + two +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto-overflow.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto-overflow.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..33425b1c489dbde6ba42e6a0fbe491ad3b1aeaaa GIT binary patch literal 348 zcmeAS@N?(olHy`uVBq!ia0y~yV7>@sFJfW_lGY5%gn$%JfKQ0)|NsAiOa=xJ&24&C znSp_k+tbA{q=ND7)s4BW1|n^NQf3W>N|*Nh6Ww;B(XKQsfp6IfF`q|PC%FPA)W0mW z<4=%{-c>$9#dDI1XL!hATjf`8PizZD0s9+%*%rMv(78Wz--_Mezj^xJb^fbSEa_eU zbJk yuku%KPt5J#V~Jww*R59z&UZus{W3}A?`C;@{ioYboj9cd3L;NeKbLh*2~7YxM}T?& literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto.htm new file mode 100644 index 0000000..3f49cac --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto.htm @@ -0,0 +1,33 @@ + + + +flexbox | margin: auto + + + + + +
+ one + two +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-auto.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..86c0cafd0bbe870bf27e6c0e36ada0d2fa5bfc09 GIT binary patch literal 277 zcmeAS@N?(olHy`uVBq!ia0y~yU=jkd7cnsd$xOC01we{3z$e7@|Ns9$CIiF&;9$24 zK>E0+i(^OyOGuNS0YxGaiQYL&%pmX zpIaz+Oj5b3uYac?ILH6=w&e@JAfh*k=lkEiJE~v)krLTm-?ght^Q-2v@1n=A9G&#@ zd6)AWz3g+5Q_j}MY+Skc#b + + +flexbox | margin-left: auto + + + + + +
+ onetwothreefour
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-left-ex.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin-left-ex.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a192788dd5d1dc18870d66a1c96f0d0ae5d5be64 GIT binary patch literal 344 zcmeAS@N?(olHy`uVBq!ia0y~yU=jkd7cnsd$xOC01we{3z$e7@|Ns9$CIiF&;9$24 z3=E81o-U3d6^w7M7=|4(kU8*BZ>M7sr~k#Ha}{^CEZ1P(-5hf$_|_sj;RDYdYS#Yv z8{u*4_xEOZE00MkU)Qg>^KMB+RqXb3E->iY9pbaRoL~O;^{}t!-uhlWlB@kKd3V=e z(NylP@76iNg(o!I|5Q#-?pqkQRK|P7y8HJE=ZQJ%tbL?oxe!uADJT*l_C8&$Z`I)XjN5 x^#}XB2O`9SwMl0+AmE*UU|60x&^M*;d3Qz6JGY9XU + + +flexbox | margins + + + + + +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_margin.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b9d24bbb69fd429e36c3dab7f32ce45e103a2ee8 GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK6BvO6!`a>1CP2#3)5S5Qg7NKHMqUO59_9@{ z&3U!l4&9f^IL9p1>7o=lafz4GL=P1Z8<~2te~BMR;PGVc2B}X+E^3{e0W#Fn)z4*} HQ$iB}El4BJ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_nested-flex.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_nested-flex.htm new file mode 100644 index 0000000..2310957 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_nested-flex.htm @@ -0,0 +1,28 @@ + + + +flexbox | nested flexcontainer + + + + + +
+

xxx

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_nested-flex.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_nested-flex.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..3466de6b5d12339e29d424ca16b883141ef3903a GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKgP53smdKI;Vst0MVaSk^lez literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_object.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_object.htm new file mode 100644 index 0000000..3f745c3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_object.htm @@ -0,0 +1,26 @@ + + + +flexbox | object fallback as a flex item + + + + + +
+ this is supposed to be a flex item +

this is supposed to be a flex item

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_object.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_object.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..cee494f6e289a91be74fda7d954fe1db9b725997 GIT binary patch literal 388 zcmV-~0ek+5P)O0d7#^4BhSm_a=BctF}=SFCg|Ojfy2KBiGUCaU)K0T2_Q;>+g7M?xfx2l0b<}V z2PAGctw1gv&Okcq+N@{SAd?-AKpHlTC0BYyVy>8YFJrF!2f+CnH47yp@;^{`w_zW| zz~Q7LDHN`TMx^!fNX9kcsJYgUZpw-qkLe>_c4W50IEd6$Z0Oho3-5H&i36)k+{0+tmxcY!J43!99?v4l_ZvFW3yE<8TBL zsJW5m0=+JgrbN}5qH~3Ro^~@uzTk+I`3 i1|425m&@gH4e1k+ig6CT%Z~5>0000 + + +flexbox | flex-flow: column-reverse wrap-reverse; order + + + + + +
+
+ one + two +
+ +
+ three + four +
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-box.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-box.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..eb2a5e2fff61aab5dcad049e8a3f863c4f378757 GIT binary patch literal 426 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK%bA#gWLxbSO(4Y+;1lBd|Nnm=lYs$5bDN%3 zW?*29^mK6ysbGA2bz|Nq1AzmD)gS)o{;2)Ldvm?eapwzQ7KsXq;DQcmBw-{g_i{3K7(W0$#$ zn3bTyeFbsfS)b~At@wA=_$;~M|L;JZ!=@D}E1g49!e?jOOuR8Y|BaS z{--;yJfEc)YrG_y;Zq7br)PC;Q_F0HUHh$7eEvSKW1YEMp~1(osB%xl-mT|?(()Rf zwB24I@;%ZgZ>_<7aTT2}a-3oME+F?siXPb{c*F?iRFIo<_=OqHs)`J4v)78&qol`;+0F^?p&;S4c literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-noninteger-invalid.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-noninteger-invalid.htm new file mode 100644 index 0000000..2c4eb9b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-noninteger-invalid.htm @@ -0,0 +1,44 @@ + + + +flexbox | flex-flow: column-reverse wrap-reverse; order + + + + + + +
+ + +
+ +

+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-noninteger-invalid.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order-noninteger-invalid.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a766852212a1dd25a5ef901343b766eedd73bd2d GIT binary patch literal 92 zcmeAS@N?(olHy`uVBq!ia0vp^CxEzt5lAqkX-FFZDP>O=$B+ufw + + +flexbox | flex-flow: column-reverse wrap-reverse; order + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_order.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b043d02fba13a1ae3753ba5921ff3be472804183 GIT binary patch literal 436 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@MJ&uf@}XQrA&?RZ@CkAK|NlRb$-wac|AEs$ zCJ-JKQdkUB9_#7i7*fIb_G+}(;Q)d5k0+h)8Jluv?9o|J#_A;=!18^+sJi&lkbqM& z5BxW|-|igO^2cJHp_>a3ESa|YyYgMzEOQ0dE|HZb9ut*ZSIGt~@tBy^E}|K<pyK)sFLmW zt#e*xT{^gzf6e?yGZpX7-df0bi{bXpy?@pI)Gk|Ga!GCO%QZb4Z=e5epSgdM$?tjV z)N&_wc_+VIzSsZO- + + +flexbox | flexcontainer versus stf :: abspos + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-abspos.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-abspos.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bb85baad6dd0638233fe91b44a7d373603443fa9 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZAOr%oW8Z6ml(naeV@L(#+f#}_Q4VH + + +flexbox | flexcontainer versus stf :: fixed + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-fixpos.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-fixpos.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0a2c6183753e9e6f5eb27b58c8a7602cf032f7b8 GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^93adHBpBY5G^+tAQBN1gkP61ogN7hc2ZO)f#kW5& X+h1V+$*}kDU64XgS3j3^P6 + + +flexbox | flexcontainer versus stf :: float + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-float.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-float.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bb85baad6dd0638233fe91b44a7d373603443fa9 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZAOr%oW8Z6ml(naeV@L(#+f#}_Q4VH + + +flexbox | flexcontainer versus stf :: inline-block + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-inline-block.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-inline-block.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bb85baad6dd0638233fe91b44a7d373603443fa9 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZAOr%oW8Z6ml(naeV@L(#+f#}_Q4VH + + +flexbox | flexcontainer versus stf :: table-caption + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-caption.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-caption.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..19332923713c98a57ea503f117dde72e5adb66bc GIT binary patch literal 81 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk~Bp9L@-6MgNgr|#RNCo5BBZeT+CWGJ8#gaB8 a?>@wM>t%i8`pqpFAhn*ZelF{r5}E*uW)$H7 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-cell.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-cell.htm new file mode 100644 index 0000000..d456db2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-cell.htm @@ -0,0 +1,38 @@ + + + +flexbox | flexcontainer versus stf :: table cell + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-cell.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-cell.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bb85baad6dd0638233fe91b44a7d373603443fa9 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZAOr%oW8Z6ml(naeV@L(#+f#}_Q4VH + + +flexbox | flexcontainer versus stf :: table row group + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-row-group.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-row-group.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bb85baad6dd0638233fe91b44a7d373603443fa9 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZAOr%oW8Z6ml(naeV@L(#+f#}_Q4VH + + +flexbox | flexcontainer versus stf :: table row + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-row.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table-row.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bb85baad6dd0638233fe91b44a7d373603443fa9 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZAOr%oW8Z6ml(naeV@L(#+f#}_Q4VH + + +flexbox | flexcontainer versus stf :: table + + + + + +
+
+

filler

+

filler

+

filler

+

filler

+

filler

+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_stf-table.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bb85baad6dd0638233fe91b44a7d373603443fa9 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZAOr%oW8Z6ml(naeV@L(#+f#}_Q4VH + + +flexbox | visibility: collapse and line wrapping + + + + + +
+

filler

+

filler

+

FAIL

+

FAIL

+

filler

+

filler

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_visibility-collapse-line-wrapping.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_visibility-collapse-line-wrapping.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b90ec28cd82329eee6c9baaf2a0eed12008cbbfc GIT binary patch literal 395 zcmeAS@N?(olHy`uVBq!ia0y~yV2T2=CowSt$p?Zxoj{5+z$e5NNE@e}`G1Dt)8@za z3=E96o-U3d6^w7MZsa^{Aky-%-Rt{3#SAX}hqWzgg_`%K6o#}cx#rmS;s2}t^G3%W z-m0@IdvK`rv|7xLf}bs~<6}%^v-hn#k$cB|vvAN#*B zUbw(B=J(4RjojjTvkM*`dMd0Jv!muvEBAE&9R&|Raf<83*gXU~KppG=prZMhicVe+ zy}a?+>#jS;YDKbltrY8@b=&4O@7*gUOV2Hxl_i*ZrhSiB|5d|>SAN@mTYdHG?;zjW zok7pNX0CP(-^mgz|9z^_^n_D4O;5ePBY$z1w^`~R`Cq1t(~oSO-zN)n!zPFueu+=d a^gq7lbjN + + +flexbox | visibility: collapse + + + + + +
+

filler

+

FAIL

+

filler

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_visibility-collapse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_visibility-collapse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..294a0821d480e0264d3c6498369dc02e69cfb1e7 GIT binary patch literal 318 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKgP53sw%c)wm+WwzY;TYAv*@5aBk7|qsJyvX>_X6@gg_d%B@ zoOx#C(QBi!S@05H857HPfnoU89ZJ6T-G@yGywn%3Wb*d literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_width-overflow.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_width-overflow.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..8832cc7aea565c16425bc0ba025c2718e0ca7789 GIT binary patch literal 80 zcmeAS@N?(olHy`uVBq!ia0vp^0ze$V2qYLRE}U2Zq{Ka4978G?pB`f51@aCl{GNVH dA-(4c^DejfiL>6%Y6J>0c)I$ztaD0e0sz5!70>_x literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-long.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-long.htm new file mode 100644 index 0000000..3f03c50 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-long.htm @@ -0,0 +1,37 @@ + + + +flexbox | flex-wrap: wrap / long items + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-long.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-long.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1f9bbde033a947505b27db5f40ae2462d03a18ab GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@O-#%{GPh&B7m(r%@CkAK|NlRb$-wYGIN0q1 zP!*e}i(^OyN5EfePj9q5zmdbtUFnjr!aGH3M8t$VLkjm zGkyLV-?U|df`MUjcV2q#t?hWrCkg~#Lt~rr8aTgly}G%p({=UtMbnq}-Lg%+_I`1A z;Q3`j|9L}hu};0>RnDjd1FtUi&5o-x+@u#B``oZ}?$`cRvsZT1uFbpV8~bMS*7xb5 zfs(UAe;vHV|LW|9TSg(dcXuCO{rhwAtD+Ba_uuH=zj`r$!6nN8&$E_W1L2_f%2HYL zziL;FuavG^oTs*Ik(}-8qf_jbuXyvd$@?2>|IKlz;#L literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-reverse.htm new file mode 100644 index 0000000..b1aaee7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-reverse.htm @@ -0,0 +1,34 @@ + + + +flexbox | flex-wrap: wrap + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1a181cd7d2b1ab7bd178c4f251101e7ebc12588a GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CMISenfYnOb0EbT;1lBd|Nnm=lY!xXaIo72 zApO?U#WAFU@$Hq36Pq1)S|9peX}rP`np0%ic;!gwLAEF2GQKfWw;S}#-FT>(t>^xE z3*q~3Bm$f`6h)@W#_V5lzyA0)W>Kw>6#??EF5Gx?x2io|FG_c2?JCz5s`J?3;J>|9&BLL&N@4yRZD*wa@B?&FwmNuXV=9me*|z zNZw`!G;g6(D;LOM!&eiwy}o8`Qyd=Xy>GSEDviwOxl>k}eh;bdeErliyZW8#*X;RP zVhf9^_)AM{Za=+k6!hP^;J9r8t7%%cGti7z>8r~MYC$f(V(%@=;iPjiWELm{JYD@< J);T3K0RY~OgsK1l literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap.htm new file mode 100644 index 0000000..a5f7cb6 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap.htm @@ -0,0 +1,34 @@ + + + +flexbox | flex-wrap: wrap + + + + + +
+ one + two + three + four +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexbox_wrap.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..303d31699409aab3af94fe7ef81d4178e39d211d GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0y~yU<6`@CMISenfYnOb0EbT;1lBd|Nnm=lY!xXaIo72 zApO?U#WAFU@$Hq36Pq1)S|9peX}rP`np0%ic;!gwLAEF2GQKfWw;S}#-FT>(t>^xE z3*q~3Bm$f`6h)@W#_V5lzyA0)W>Kw>6#??EF5Gx?x2iq;omSS#@=$kC)!fySjkZ3U z_e#HTuT6UH`Mpc(-Uc643h{ftu&*zDcTKe0t9tf#JJ{DPIF^&UB4D9YD;L^i(#_>jEm5C-zFM* + + flexible box flex item float effect + + + + + + + +

'float' have no effect on a flex item.

+

The test passes if there is a green square, a blue square and no red square.

+
+

 

 

 

+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-box-float.htm.chrome.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-box-float.htm.chrome.png new file mode 100644 index 0000000000000000000000000000000000000000..0ecd77b094d4dfd60a4c2d69b5b496bfe2ad25c0 GIT binary patch literal 14012 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 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-box-float.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-box-float.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..40d5fba6583badae31f88e1ca9f2e258f7963002 GIT binary patch literal 815 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKr!X-CNwKv{S%DN!fKQ0)|NsAiOoj#^{-2T& z3slH-&(p;*q=ND7RNtc4S^{o~2lx`~qnvhgy_s4$i8bZJx5tIkCO+K#S6OAr>Lc6M z?3%QC-()S;a^`0YS=JzLscxk_5zn1{k3L*<$*>TN zj5}F+v6YqCK3edV|B{TSLK7$PYdJk$^fgs|yNXkugmzbicPMwDQ^j9~XxrT)+Tu3V~dOKpPnoV)-3ILS5ls7lqN87?Y% zYp1AAvQU3JMe~~7H2ovDMb@Uzo+@b6Ui*3F|7AaU&d>K)vZ7kfvwXgNVfUBf;;lQv zC&t)1=S3blmR&EGS(~-*oZabbhQGF0iltS=wv?v@s+cQnY8T6#K5^66l|NT{oIQ1} zU#U{b=-dXUB^j|=HA~c#*v}ka?{3p2a&7XweUmHp3-lGlh`BFI0h@L9(i0bzZ6=3j z39npVyhh1y{q&grXD%t{*JK?HJaTSboOrj7>l>wQH+P=+#KSHyG3x8A6OUEvloo|| z=nI8Qu2#LdYD&oB1=r7J+bXVgUAR7|>bj*;_#wBrTF{t_XQd`TojlDI~-V)1TuHbTmQV*C2#ueRQrj&DlNS#Fd3*Kn3)K= aBK#l2&B*+@zdd8kL86|nelF{r5}E*E-D3U# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-order.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-order.htm new file mode 100644 index 0000000..0bb4bd2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-order.htm @@ -0,0 +1,70 @@ + + + +CSS Test: Change the value of 'order' property + + + + + + + + + + + + + + + +
+
A
+
B
+
C
+
+ + + + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-order.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/flexible-order.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b0f4badae083aed2915deaf1bd176a52447e998e GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0y~yV9Efp6_}WT(ZCuo7Hop9<9DQ>!61<^YiJy*Pixq fi#lq;pz)N|gTe~DWM4frNT)@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/item-with-table-with-infinite-max-intrinsic-width.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/item-with-table-with-infinite-max-intrinsic-width.htm new file mode 100644 index 0000000..ddc9dd7 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/item-with-table-with-infinite-max-intrinsic-width.htm @@ -0,0 +1,21 @@ + + + +Flex item with table with infinite max intrinsic inline size + + + +

Test passes if there is a filled green square.

+
+
+ + + + + +
  
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/item-with-table-with-infinite-max-intrinsic-width.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/item-with-table-with-infinite-max-intrinsic-width.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..38b0d0675ff9d624aba3b35b6760e8799307051e GIT binary patch literal 575 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*GqVZlv7e+~&&E!h{ma!%1(mNX6PyX{`w|JUr} z=d8?G@kw>vOOP2rV0Pxwz5f&U_ih*Zx##qq#YNK9`)z!}*Z<$JCtYb`wce%nXA$+U zySLnldy?%MkaEJ=Fn;~hjz6Dv>zC#1Jn`>SOp22H#Hah_c~lo^1j_$8+0j1f^0#9r zl`Nm`I&N7i_w(uZ*zBcqbh19CTtAnPy>`XX>cIHNQl+n@r+5CyvlG>{o>&#dKCwtZ z(rc&Fkv+V#JWN`*d*pVvckG-u^;*lv!kXxewC8zb{<=FnFZK5e>~whY;pOf)7D$NH9r+|1uFoUr;^N@K z>v9E|=)IX1owMoXRaZMZd%Nk|zi%%`RRoux_I^iIVuykflTwz#A~vcKObiSk?thTJ W_VIkq7w3!MWZ>!Q=d#Wzp$PyX*ZOY& literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_center.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_center.htm new file mode 100644 index 0000000..27aa76b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_center.htm @@ -0,0 +1,45 @@ + + + + + CSS Flexible Box Test: justify-content_center + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a row in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in middle of red rectangle.
+ 3. equal amounts of empty space between the left edge of the red rectangle and ractangle 1 and between the right edge of the red rectangle and rectangle 3.
+ 4. the height of the 1, 2, 3 is the same as the height of the red rectangle.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_center.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_center.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4a3199da486744c2fab7ddad730bfc971c993415 GIT binary patch literal 2927 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 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-end.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-end.htm new file mode 100644 index 0000000..948516a --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-end.htm @@ -0,0 +1,44 @@ + + + + + CSS Flexible Box Test: justify-content_flex-end + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a row in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in right of red rectangle.
+ 3. 3. the height of the 1, 2, 3 is the same as the height of the red rectangle.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-end.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-end.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..061a84fb938db56eb7ba077a42abff539a6454a7 GIT binary patch literal 2104 zcmeHI`8S(s8;L=GE#5jkfBu zrmbj1i^JG5miC)kg5r#=3zZ=vgAy@HY{{3-Z~Y7Am+wC3KKJunKiv2AT<1FXbKBd~ zMMe2bWf%;m;)-`7z+j4K7);^hUPU?5j)c$4i@LXmpR-IRlON?tm8VSRK$;HWF$!R? z{nM^a4t^;LAM!VuU8KcS2XU?OCB)kUmHBB z8CC`2(PfD5jKEOcYx!DV09J|q@MU^29929*F!G&@a%1=TQbOPP>OpO&NPVJx76!c+ z273XNF!~#=Qb$#0i;32ce<;lDzOqnyj_=>5tuZpS=i*wayJ4nLUSgrs&t~x}s=zV; zPQrlYD*B_IooO)A<1pdX8sE<`oE1`YPby+j3=G)UIL>CpQ|7yW|2mJmDm$rFu*k_C z%}I{1)fQI?Q{O)xNNkNO*UHvTGiMDTWJzvhAbRXVFs1f*d0xHhfk#1)CsJK5P1U84gwWRuzPO`zheZVL z+J_E}QzBDY(Sf*-m6(>OUo_2_fxO)2^YA-JudFiP<+s*=?O+u>N;L#t$kz~fDZS0z z@CO1)JFf??n`%27n(^h_ALS&*et0?b6obFtZ;g%OR2>qmnlwM%b{K4z<%)8eZ5g7R z;S)qXyU=4)>3`Hg%C_0(U0q|QFX#2Q=-g3TjKl2cVR(LwZsA6h z?5h*JBmBfl5kk1xeQ;5WX_ZnFGAKB$}fk z-lpS;c8y);$zFJV1D7&lK2CPb!l64EG%?06)yC}Rh=Ut3s?Lu}-fRzwF?CumuMbxN zY862@j~erCmUlFY6R1xa=xQ=c5j+8AD}>etPC4Y0pI(Lx$YkbUQtl;k#ZxAcdfkRm z)hSGDm`e%2e}ZA6IEw#5oOX^nG`Py@d5`-t3{c#N&o%U0Srg_tyoV=!m z&cubf_%p+2fo3?V^no@Yc1MB{kYa5+ih{CL)3s3*wakU1NPefLZJgN_{}- zuEp*VfGV}xb-)`IKnUnybXZ>%q;H5A?&W?ndjPCa1iCd%pbm(RbK)U}`@Mn98-GH< zVU_gj_UPF?dYLN1g#Sw4*+LIek zW>ddQ=2}5X@|HIc|LoIGqz=L{`yfq4kf!i&!M4s^>e3$yDss_&e<-c47Jpm$LIuEp t5l|=mwwk)0{y992uK9o9lOFPKT4F(MVUJZrR6?ID=j!a~R0D=({0}L9p{f7? literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-start.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-start.htm new file mode 100644 index 0000000..fad6926 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-start.htm @@ -0,0 +1,44 @@ + + + + + CSS Flexible Box Test: justify-content_flex-start + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a row in a red rectangle and no gap between them.
+ 2. the rectangle 1, 2, 3 appear in left of red rectangle.
+ 3. the height of the 1, 2, 3 is the same as the height of the red rectangle.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-start.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_flex-start.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5281fb1645cc45c0e41c3c51cf064d2356523d2f GIT binary patch literal 2087 zcmeHH>o?m88V*`QwNj(1wB<)Tim8?^MS>=aBv^54wd>ZRRhKeoMS|$M42JS^OI;G` zR*mh5Tir_Z5}`@4Q_4;VLl8PGD;$xyWYXQU=j{Fk`(@t`@AIDL!*ibZJ@0w4-QCcK zRkT$g5XfOH#=#Q;QP6`x_cX#!6luD(tF-6v-Qe@bi3{MhfzlA`Q z{)TlxdMC(zF3BlaIH&qUg{u3Pv)@;|zKU+XTJ&bIiY-(pUyPrMsvw8%)1OAXLVUNK zn;=vA?;_b~lp5`#eA1KE8%<8-@IoA^6)(01>YTq^8%>$PpdNI>y#D>uaT zu~C~e|Lg;X>Xw#~oj7CmW1P4KGb*IFbBYfF$6NHyP)jXgKVgajaAnG)o*7{i3r{2Wi zs^Rq}W}y_%#n5j_+?jEmXGIJHW}Izj{e`^}a3stFHQfVBJ_=v*ez1e{ua3e|UCG}v zlbxX#taRV?TnX>*`aq}F7PVR)s=ii}_%r(6Oh>}XfO6SKzLjR-*qR?EDW0cXwna|i z9o#Zau;3vaDW-M4f%*7OSmgyOY|pJbRG>UZ(9C!rx9mEkQFSiGd5GBiScNc&We+}> z6?)vCH_u!2R=Y>>9n&Jsyhqmf35<+57QxSob?g0kYC;};XS;8GLNM0nF%=S7C;00I zPIs(5v;j)6AGKY=E8VNeusgAq!{7($Fkao(TX??sv-MowpEoNLZuclcAkBGfdV3*! zN>RvzdyRA&;W;~G!ToBzOdD})h70jL1CET1Nlf3gY%R+t>WHqFWpc)Q^CuMVWv#hU z0A`;--DLqzQ{SKg44TjPI-Eg`uXmI5=f4ei_ROhD5hC&gmPeNUb)Y+j9pH!1g{YBG(0hL)b2;r*{Z&`Y=~1^j1G+*Hs?PzY-0PaU77 z-fep{{av9bBKmdTp@8lcC}A1@9+$-!4@Hgl>hNEFa|R#dfeGL>g65f&XbM=ey-I%Z zp=IgGshB*v4va%P&9y=gO4UdKt69d#;@gP{r>7DaSWJ@L;gMirj(GoQY&e1xmxTW& zHh080ta_T#kv|~n`=$RTv^%!DJtYFIVy&(Vfjr!MmgHhH>J^7BB$g`f%vcc`*y4yA zGu`bQQPw7`faW%A_v$Q{?+>cpT?-1kb;aBKaZk>zw6`gvUnnHZW)Xi=J|x4Qj|hy8Oj=zU)!fm2RNzO#QSptF zq{WodzyCH)GHQb}3HyZ+*1I3gv*}a`za3WI@1jcWzl7H_Typs$U|}L2%;P_GE1d}Q zns|pIHY{d(Bl0sjd)7yN$lr=0VAuFFfqx~C8CL^>Z!LOGmt*~@2P ztRKBDXlg0+*1a2_cqg2 zTVBhpH&Km3xb_~5G!XIBB*bCDnf7FcDu%`ppDq4_Pto2PkLm5XLoqQr6G-+VIKzSS z9}h7IhW(rsv24%Wmg%e3*ZgtGgr@O;s{9_Otr-j7CmF5vTK1L#(UH%gDzp{WMIUBD z=7X=X{GatbWwyLw$z?-oX4l8^l>=B0VZj(XHb8tRQO^B+IY1)g2#4_v3Kh&OP% zX`orLf;@0-x&FBmCSD#0UY-GPP_Y&)DEA<+se#&uB_oIQBq+ArYvmKTK#a8>%)GJ* zX7_Kvb;tp4mz8Ns2T&}pBp4Q`mblbCci&3|k-HNJi$HB?_i3ovKhrb!UtX?cndvcQ z`l|mcns18Q??5a4L3T7~nxwOXb-7ypayk>5eN^4xj1wjo)B?; + + + + CSS Flexible Box Test: justify-content_space-around + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a row in a red rectangle.
+ 2. the rectangle 1, 2, 3 are distributed such that the empty space between any two adjacent rectangle is the same, and the empty space of the row before the first and after the last rectangle are half the size of the other empty spaces.
+ 3. the height of the 1, 2, 3 is the same as the height of the red rectangle.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_space-around.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_space-around.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..295c5d0a98274a2170c0effb07939bff24a61411 GIT binary patch literal 3057 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 HxA1=g + + + + CSS Flexible Box Test: justify-content_space-between + + + + + + + +

Test passes if:
+ 1. the rectangle 1, 2, 3 show up in a row in a red rectangle.
+ 2. No gap between the left edge of red rectangle and the left of rectangle 1, no gap between the right edge of red rectangle and the right of rectangle 3 too, and rectangle 2 is distributed so that the empty space between rectangle 1 and rectangle 3 is the same.
+ 3. the height of the 1, 2, 3 is the same as the height of the red rectangle.

+
1
2
3
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_space-between-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/justify-content_space-between-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1fa272066d2e82ba8c05f4800e51b9aef082b43f GIT binary patch literal 2933 zcmeHJSx}P+5)R5j5R@c1BLT!@xFj${ZWJ3+z|vii~xZ|>_rbBAj~Q87Y7bzf6>`ue}DfVjNb?SaQpja*C)srzX%XWQjuV8 z<{Txm=)e(KCigxuPL`gm09^Jtz3(LlTF@8kYQ5UR+{6iJnXP8IL~b z@i!Aj&@GCjPCw|Q*2kS&E|ydZGqwW9otrC8cgOj2Xfn?LSx@F-L+Hk%Gn;|jwW()~ z+i@1kH<3Z4;3h*mi#OZH<&ub!#8HNdBrpnyA%$)0V3CjnCFL9y0$#a|QByCCQ>4Ws z1L)xB)Pd}P3c+{}0Z z1b&(goBB;l-&Z$yvS59GX5EqCn5_TWV}^5|>J`4mr)y*W^hk7_ z2}^Cvpnu;(jFnHkZ;1|s!E8UN0I{`Kro(}VNpDubc1!{9=b~n4-*ARtbUD<9f9}oY zVT88rZBmy`(y=zK#_|&(BJNaA&^vC8ZRhT-W#-DsMCL>zjHm1VYLABCH0ea97FK{8 zQI`)GG(A7)`dtru4z=eT8g2RmcT%CyyrADRJc zd0hD!j_0SO=OW5}lH54v!99=gRmb(p0Qk)M+zjgnf=!>a@XZh57Yd-^V`R8NKIeta zOAUdqyz0}}0#eQ1uHYJR1Rs<5+<&cTp~Z6J zPnKIXb(b0gyvcrY>4r%}OS{R!sFL;Ii>oL(b@kU7t>Ap+zXDTZmWX3fn)=OgSSO&D z6R>HTHChcUE^slxx}fz^s4jAu4OSc7_O@BSrTzYE^SCZm&_=AOogmZ+?@p^PTI6@t ztd@d(a!K#0?2eqpyNWF_r3V}+?lh{n`gAs|$9&FX-`}~hRZBQGX=D}L$f4Z{DI}+xCSX^*`aSO6 z@#3=BypUh1*E2Q?6?=|{qm_h$CG}us3^M+>U0dYP2{oD@FGl4aAz#6#zTD>?dDTY) zy{8rpzpDytMY~&Y-$xYabczjI_5(udKZs|=)TR|)GI1Hl5FaI%I_IWi0 zv7uzE@OOUbUcqJlDT+kwNj=%Xn#{?kLO4t0WDH8!z)gnK0e#Sh3^l#gdRsQ@{K{9i z;yMWsXw}D5$Y_7fOxvS%U1b+ar7AWpRAh^N>9^Y;R`TWFGvGV@JVq6|wqSKQ}e0^xef6CZz?GTOL z_Ggz}`8N`-?5o|}4;c-&zr~2Kd-%xJk*y~ctdC)PmJ9T9SPa$Ok1pDS3Bmf7BX>u| zvPAk6bL0p=iPQO?wg|Yn&JQ4@zdnvb`Sxrkg>IW*C!+(Ossr#he67=VU)7gAw|OQQ zk1FnwmN;8gA&pR@WF)fkYCH{hnUD9_vR$Isu8<qK;?j znq1YAz8-|Fsj^j_=guF&$XMgAfa}RV>9~sc|0|rbQY?$diHWo@V1~{ z=q#VRq_6a_J*r$5>%T@8=aXSs-F+>ul-gHrhWzc-0D+7$oArgC+T-KZ_iB}P%DMBp zRE>kpfp9;cS5B~$*iN3SHN&NVfs0D3t1DM=o%@#HZ2@}oF!Fwiv?%a2ft{sqi~lAH zTqNbti3rZ+gW5B8o!I!JFkb$gB+yGLqOY2+;zC;VklcUL==o=C@Dv1squL8=k>)&r z^oW6TJ2a7drly@XDHKQsjHK;W_mp!2oZB`o*MZ>-nM-dULVf}v?}`ALVvssn(@xTV zmE2i5n3-LA7zP5#f^h;*blbOY%miJa6`Tr%DJfgx)9JEEZ5-+U!2gC1-nWs`!~^vN niQ$BUiNkAFCCLY$Bdtb+DYQENl9YJ+#|;uJY|U$MR}%gM7sfHC literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-001.htm new file mode 100644 index 0000000..ae98e7d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-001.htm @@ -0,0 +1,36 @@ + + + + + CSS Grid Layout Test: cross size determination with overflow:scroll + + + + + + + +

Test passes if there is a filled green square with scrollbars and no red.

+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e396079515853bdee906c75b64fdcc23c6c584 GIT binary patch literal 783 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU}Ruo28x*J{(1tWI0Jk_T>t<74`eblFt}ba z6k}juI_T-*7*fIb_G)6@Z7TtW#Gmc?4tLev6Bu4idOsz{C@RJ3QlTU_6Jd&?EQQ_ zeU|)tU5~!KygSuCul)B^vnD&zsn{3|y|8#i5 zy{g*ddvyy%)>qi)uDy19+Vg3Dcjs(hJN0={+`Rk!uQP6@e+i4umOsDIa`n0EQOCJ{ z+J*lV^Qf7f?{thasQk5P%ATe*$>%EUoTq(cTKReFHA$m=TsI$0^Yh5-+HrTQZr%jl zirX`0Z+Lf`TjkuDe!=I@Ki*Q$Jh#I!Y0m`q{ny3bmY1tMuX)>dMAuHiJ9qsZyXDqF z_tR~~ZrLt*FP#w=D8EQ{!kofpnc3giW@g`Y{$~1M!Z*VcZx#O^2+n>z{pTBr&ze)u z&& + + + + CSS Grid Layout Test: cross size determination with overflow:scroll + + + + + + + +

Test passes if there is a filled green square with scrollbars and no red.

+ +
+
+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-002.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/layout-algorithm_algo-cross-line-002.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e396079515853bdee906c75b64fdcc23c6c584 GIT binary patch literal 783 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU}Ruo28x*J{(1tWI0Jk_T>t<74`eblFt}ba z6k}juI_T-*7*fIb_G)6@Z7TtW#Gmc?4tLev6Bu4idOsz{C@RJ3QlTU_6Jd&?EQQ_ zeU|)tU5~!KygSuCul)B^vnD&zsn{3|y|8#i5 zy{g*ddvyy%)>qi)uDy19+Vg3Dcjs(hJN0={+`Rk!uQP6@e+i4umOsDIa`n0EQOCJ{ z+J*lV^Qf7f?{thasQk5P%ATe*$>%EUoTq(cTKReFHA$m=TsI$0^Yh5-+HrTQZr%jl zirX`0Z+Lf`TjkuDe!=I@Ki*Q$Jh#I!Y0m`q{ny3bmY1tMuX)>dMAuHiJ9qsZyXDqF z_tR~~ZrLt*FP#w=D8EQ{!kofpnc3giW@g`Y{$~1M!Z*VcZx#O^2+n>z{pTBr&ze)u z&& + + + + CSS Test: flex container multiline wrapping-reverse in column-reverse direction. + + + + + + + + + + +
+

3-1

+

2-2

+

2-1

+

1-3

+

1-2

+

1-1

+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-reverse-column-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-reverse-column-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1cbe50aebef9788e8a2278174f526c83fbe02e08 GIT binary patch literal 1622 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYVANq@28x_yKE(;7Bm#UwT>t<7f9A}Y|4SME z|7QT<28RDY$nd|aOyv#(1M5#u7srqa#y3~@=1Mz?Fg!GVz@!ow!uaMu0xQ!xMpm5z zoWJ!Cy5H-6$T%m-Ixo;Mo&8b!T8$Hi&a69g=8Fdw z?uxs<{<1#nMz)#H&wniz3pd=Gt9b8>!u7pJLU}%YF4q3Ij%{a-qMkB~C9hygSTj#y zs%-hxDj*T+IBj><-=O9_{2$wN6z}Qv|MueD)UqRP`tMVAitj%^-nXfF&0@Bjd>wKr ze|1h=JjhdI^NMxmH|C%TnN|DbPrupU_2_4vPVud}{ngjBv+v1$eQuq!HYw=eEH3Gd z`xTkjINd)g(9z_OsLaLG+N;4KVzx*j;Krx`$>A}viO2Ap7FW=OCXpoytePH~wYKmm=MBLR+iudl!T_EhstXphK~J#3csZ&P_%_jXNey2E7@HetUkmlA)|oNDJM zmQ8c=`>Rh|DzF~Z>JwIyozRpZ>MroIL4;-XjphjUB?=Koztq^rZ7)9(bYr!ICd>jC z#|0Y%SsM?ziLgLyq_IFtm*6rKze2n%7-raV$Ze0vlX!57lK=kubMpVc?=(3?zF+UZ z-{1fDyZz_od##RTN^mhLoDyA8V<+`}<-f0t43?1lWi@wmHwA>B!~Ox<`R^;i0_>tUs4d5MQDYV)OoZZQTF= z)dQ52)mSABzXhX3KPX6y)J||fGFMW_gwnrJub*iGy(RKwN0ZF + + + + CSS Test: flex container multiline wrapping-reverse in row-reverse direction. + + + + + + + + + +
+

3-1

+

2-2

+

2-1

+

1-3

+

1-2

+

1-1

+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-reverse-row-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-reverse-row-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2d73f1cd85c597a12517c85838f8e31f7e50d7f6 GIT binary patch literal 362 zcmV-w0hRuVP)MV)9#KY_Tq6pZi#8t9xQo92qPvL*J{U>i+ZiRMybY8%05cpUN5 + + + + CSS Test: flex container multiline wrapping in column-reverse direction + + + + + + + + + +
+

1-3

+

1-2

+

1-1

+

2-2

+

2-1

+

3-1

+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-column-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-column-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..45b3da248d4d365d87ad650f8adebe4c25c0012c GIT binary patch literal 636 zcmeAS@N?(olHy`uVBq!ia0y~yU{+*cVANq^28yg~44Dq3I0Jk_T>t<7f9A{?AdiDp z`5OZRlb)xGV@L(#+p7oH-uB{Q4Y=L#|54~uCA;*6d`s%KoQ&i4ojS8pwZ&gj zaJ32(=U0kKN=l}}Cu`)+=X4%_Ja^59dR@=k%FiZG`10|)s_M`6{qs#VBh#ccPiEUI zZJY8Wd*5ke&6{q$y816OJx_b-?T9T*$-iD*w&7o;r}z1jzq>3mtL-OGTKZMFTFw4f zU9<5_-77m{-J?F0@}JD!-<~3UI^SF8fa1$yZh6U*e~h;Md;R`%w9v^jx^vFVn4H2Y zL_V0*t@?8U1l$Rnl>6AZXy=l*g0}-5H}Ra@7&aj%RpoP;tE^`y$btJ!SycqRLnf|C zRdMG$S?2a-bCb$#Hcl+yizNUu=`?>i0g`Du3~qbCLW><7JCl zmh?(yiavX?b4#5L(89j?TU%5t)xJ;MGby=9*;77x$MhSqH-(_api=t-C-Fn5tQMGn7(8A5T-G@yGywpGDD_qV literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-row-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-row-reverse.htm new file mode 100644 index 0000000..fc8fb07 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-row-reverse.htm @@ -0,0 +1,64 @@ + + + + + CSS Test: flex container multiline wrapping in row-reverse direction + + + + + + + + + +
+

1-3

+

1-2

+

1-1

+

2-2

+

2-1

+

3-1

+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-row-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multi-line-wrap-with-row-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b715b147ae4993eaf802e789ed3ffbb1e8059c3f GIT binary patch literal 384 zcmV-`0e}99P)1RCodHlR=KdAPhyjNf$j`HqOE~F!$jIEbu7@x|kVhLhMA& zG*bSPs!DwNsEv650002s6VNL2k>C{Hl8O4~Pudq#*%Q3smhzgfFBaJoyh;7EHD4P2 zEN%KW>m}I}IDDi2@vwhO);jQQ1PA{gQ*6C5@*TX7vfr0#o0qzh_C*~9<9Vi4uJeAc z?gIb-0002@f@~g-$=)=dYe%yr_a;B ziQ5;u`nP1Qwoh2IxP1Lo+t-Tdg*bfT@jcD<{oJp|_cGh}?bF)krCL61^P;}{wfX4# zy}Azo00000+zSd~KFOB~(URkQMTj`qmjZnxvGsCr|46jP`r_~R%Dz6&sRs5hh`ag# e0002+&wK%xVTx7>M~jRA0000 + + +CSS Flexbox: multiline reverse wrap baseline. + + + + + + +
+
first
first
first
+
second
+
third
+
fourth
fourth
+
+ +
+
first
first
first
+
second
+
third
+
fourth
fourth
+
+ +
+
first
+
second
+
third
third
+
+ + + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multiline-reverse-wrap-baseline.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/multiline-reverse-wrap-baseline.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4e68413eef7af66b8fd69d41104c22048fbec933 GIT binary patch literal 1312 zcmai!YfMuI6vtb^iOh*Hff8gEwY@d+GN{#99`_34DKf{a zq5}%GXhevu&};$~xh-r66oFR3S`jS|;Z|VK(GIVG($#H#TC#mO=OpLD$vMCCKj%t# zSRl@Jvn>XL!37=KAA!MOX%-_}TUjyzU`doEYzPmDJYY7P#bU9!t;cM(q%d_a5dJLR zKz(olgR$-m+V2+$VrMj2g|}(zR*tmhHk`PV6ON_Tu4u~newqG0{>*vdsbaUF0^|XD zDVkXKs8-PUacjF=Qw!-HrPB@A4hHI;O7Gvk_LsykfB?Y7c=MY?~F=fh`v#&HVB!ML_-+Bn-sdo3>l5q_ZV+=6?&hrSJ7KNnwsO5gbE zW?o*db=1Q?3NRC_Zsl(;-WOt!G-H{Y20ZxsroPa}p_-hu@O@hS(^7{SI56(GDD2K9 zQiTNfx{B#~MAjMMt%v2yX!TvFgyUPIHKSs7U9aR#ei_$xl2qMITsP3N6=>$UJE)lRwxmU5GBWsW3DZ?Mq`Fpv1f|cHCqLSi zM>8sUZ38(m_f+7k?A!dS4DU8|(|1vniPnRqr zdoqlYt_Gs$6Ei1#yW5)fGYmKVNlPy_ieXc(RiM-5JSowa3#v=m;MesaCc7we z&q%@Y8hE6eL(d>ob2G`uhLgzmM3Eu*Z>GyMbh1;FSc{v1{zs#r?xW;`ilrk%0b18KRTayKF@4nN?$cw z%Ds(P;#1xuY$erE+S5WYC89oKf&e@;JUVFc83aR@uv=(lI{2u{UDsNKg0jGjn zsrO^pv8&7e6|@!Zaw+l!D>kHOpJ>R7vV-!iozV}=t_nO3G??&$vyjh^_KH*WUWpk% zW7O--^a-Kq-Qw^+gaPxgB>8MY!;_aYEZ%mWl4zj9*SuFDlYSmY;#a55A<=`oq(vm) z;f~MhNCziW9jx6PjSKv?vi9(fTXdjt+c_$uqQZGW6a8|%+Ol?{8q2UN`N@awC`wg9 zNojNap@fgQWXSsDMYP?bP~_JIF)(^yn06z0HQOj&2@OI0&uHCJ{J`HbO##5>|W&C V7ilo*NuT~((1Ec1_5LTa{{#_4tHA&O literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-flex-margins-crash.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-flex-margins-crash.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d4ea7f4a75b23abd66c645cd0705b1e0e8695b35 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK6&Qg8!=cBD5kRWi)5S5Qg7NL8jl4$;I9M-i zmU!T15j#_G#SGrRjJD0I&phZ7P<qzI{JM*lsZ*D$=p0V@tw!sZ@X-g zHgX&mNl6JfI=@NaTjKf)u0N!&q)2yIed=~P)oT!-!Zoq6#Qw%9j&tvmvX{9`5Nq}U e88GR@X-kI5Y4(Lbg(v+7Imgr0&t;ucLK6T?HAZUy literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-margins-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-margins-001.htm new file mode 100644 index 0000000..74cf8c3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-margins-001.htm @@ -0,0 +1,51 @@ + + + + +CSS Flexible Box Test: Negative margins + + + + + + +

You should see a green rectangle with a black border, 40px wide. You should see no red.

+ +
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-margins-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/negative-margins-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..7d90389d34f3bfe22e7daf4d023ac8a71f006dfa GIT binary patch literal 587 zcmV-R0<`^!P)^p5Q*>u1M=N-Sh%D-Bw` zZ!|lJu^qo8cqc*#A%qY@2q7epc^}!|d7biIOoXVfk=D6IBa2&Q&v?j}mvGaVun$*| zUlNjD5Ry)Xq_2e($@fCidqQ&lz7SYOgrtLzZXd4TPRJrizmzH%rFAKoEjrU*e9LCD zs^b{hx2Uh$B+qNLrcQw>fYjS+j+GoBC zl52aWX>Ft@a`er1wRUgps6I7`5nJ1)ZKbVdv3@?+6&%;S6cP!~(_j}QmtBaB^dc># z`)<1s_r{LOw5?u*$NFZfARTK0i;2>%D>$q>CIpAt)9BSH)ylF|6K0-XfAK~%U-)z( zhrQN4^de9YVn8hg=edRuMBi7iuRA8hFH5A+nZK@0h<$vYrlSz}oC}E)krwZFOa48P zp$hhO|GSVpAY^|>LWuRUwni5M>$s5KB~OKTKR`FZXl}FCwXA>06n)4?Njs5x!`FJ% zEZ$f>5*d2e66-%91l`Urj(fR~s|sLi>y}F)1LPO&r^Npr`Ccub7czZb$nwS7%Ud;l z@8`SmCGO(BnfLY@YNJPitY8Wi$0YKKA|!H*F_2qA>L Z&0juEBB|V}8KnRK002ovPDHLkV1mW&1lRxo literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-001.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-001.htm new file mode 100644 index 0000000..2f305fb --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-001.htm @@ -0,0 +1,42 @@ + + + + + CSS Test: The 'order' property on flex items set to a value of '-1' + + + + + + + +

Test passes if there is a single blue rectangle on the left, a single orange rectangle directly to its right, and there is no red visible on the page.

+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..156126b1f60065e6fdfaca6e17cf15f6e0d71ec5 GIT binary patch literal 992 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK*Dx^y$$u>hM}ZVifKQ0)|NsAiOa_Mk|Ccg& zCszCgD(v@kaSW+od^%`8lAZjI_6yOPTv zb^Aj9uGH>OG1TMD_Wu9n*3z&^As5;wex2KK-Yf5Ex$)MER{CG}?%378{@aa-vde>> z%k6GmdeoNbIb+JX-wHdK53ZAGOpw`g_9Z_0VkS9g-l_xSbqTQ8b@-9gP;Kc{ND z@(tDhcizmcc)KD$v+7Mp?YAG*db($~{`!$+d*a&4Mw#6Y-+e4^UZSnA!eY9^CB_r? z%#%bFZhxP@ch`bdF5cT0{Iu+jXzdhpPZRHK($|VQdO&}@vBdI|i>AK6xq5T(=SKER z78$&^iZ@NodAD%(y=a-$hbr&Q6G&G-w(ipR>c>BClw8cUuTnqp#Z%$)%a)(4yWid@ ze7EoRpHI7&uekp7dF%EMb4$J+SzTgYWNf;1#azDF{F=Q5HGCdFawnAf+A~XD5#7(v z(8=xhCG*#*!jF?>1LwS}`}AnZ|4o;L|8^|s<*4&?5|K;%xLNXZ?X>>6$0vWcb;?Ry z+Qj1dE8%PI;@tOP?_Jl=bEqiU;=R}V)0R!uS$jWTyj_#Wa#DMO<@f$6>=SAPm;AiL z(sXvZXh(^4&?X<(%O@r;w_D^^;6E*hugUQ5nrBVdVnem(Hgp@*2sJTLPvsy;GW2b5TpEE_qA% z+8qk7{>N>7lf5Z2Y^%|#Yq|>BOW*GNlt1ZgX0h>!^DnPE)ctxf$-E*6mZ$^YU#buP z@_)Xz5{Dy)lR%R|ivo)xZJ9L|9H1Ok8(q(M;zn&kiU=n^D4Tk^`njxgN@xNApbp0@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-column-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-column-reverse.htm new file mode 100644 index 0000000..19880e2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-column-reverse.htm @@ -0,0 +1,44 @@ + + + + + CSS Test: flex container layout lowest order with column-reverse direction + + + + + + + + +

Test passes if the paragraph below reads 'First,Second,Third' from top.

+
+

Second,

+

First,

+

Third

+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-column-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-column-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a9fdf56656b5c5167d4d759fa3dcd07888f1c987 GIT binary patch literal 545 zcmV++0^a?JP)xUs<>a$%qH0lHqIba5r|{VSQa%8QE2gG z9bzB@8OU1^J@n`?0w(=gv|HlqB;)7OZZjq){Lf_4h9_3bYI_-_r-_KKGuG>X~(VPhv?nv=_ri@AZr8826N?@7J*ts;*xvn{a#5*J|k#IHSYzxJL zur5f2_k}D4S&ku-03ne{8Zu$T^#U>#geS}wgpr!3Z-fiN#}Lj1$(eIQ_!i-c;67KR zq_RJvb%OLSM4wex*eygVa?BQ0DT$|oI=19=HKu{s~ZK(r6y-A)P;E~{@lDG2H8dp4ES-UO+Z> judWU;kbw+jAa9P}2tv%uj;!&100000NkvXXu0mjf;lt_Q literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-row-reverse.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-row-reverse.htm new file mode 100644 index 0000000..f780761 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-row-reverse.htm @@ -0,0 +1,42 @@ + + + + + CSS Test: flex container layout lowest order with row-reverse direction + + + + + + + + + +

Test passes if the paragraph below reads 'First,Second,Third' from leftmost.

+
+

First,

+

Second,

+

Third

+
+ + + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-row-reverse.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/order-with-row-reverse.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..cfa0adb7b142f96c47d47dc2aa4d3416d53b939d GIT binary patch literal 555 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKGZ=vcgW`*okqiuspFCY0Ln;{GUNP)@Y{kR& zfJ1B97r~w>mQ337d>>ql*>OcO<|EIlA9bfr&SsgMV%(jvVYkBZakIDvCfIUpuS0e_j26~G~i9K zT{Oj2Mf`Hl8J)#E(J4BI?U|UM+b>a*@3^;e zkJx>;I(0dvPjx9dEJ7X%jY!0&=gOXES9S8wmW}xPudKz|$$HUZfj`&l-gopmUQ0fC zOleYgrkfeV&kLO&XHIC>eCgtBH(Am{*-rVhL<_6OE(IHo%oloc{TcSUml99FT=Jpg n-K)TSx%!v+;YgPKH~S<%W0ttK(xa_zpj6=L>gTe~DWM4fv6J*r literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/padding-overflow-crash.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/padding-overflow-crash.htm new file mode 100644 index 0000000..d895c4d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/padding-overflow-crash.htm @@ -0,0 +1,15 @@ + + + + + + + +

Test passes if there is a filled green square only.

+ +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/padding-overflow-crash.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/padding-overflow-crash.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ced33bc8954ed750998366d6c5a47e01f51419f8 GIT binary patch literal 561 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq58d0A~$*~8^8Y*kEV9=yWu zpRNwl4b(r^@Xx+K6X##wS-nrzX#0xuual;?KE3*T`t4QuYTLj294S8UDt0~Qae3wY zzv5jf?=*_y1w~e_{g(J!_N?;mH}7;OTAu&6VNSZu`->Ugb3E)KcVAy^`?B!u+Be!Z z=IhtaQMu&RouBi6fos{#zZasGPJL)vUv$d&M)c|Iedk_B%Us;D_QMY$!QZp|U4J>M z_`P;qV&8G)-g*JaYiyd2d=uXagTe~DWM4fM??E3 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-004.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-004.htm new file mode 100644 index 0000000..c81c7ed --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-004.htm @@ -0,0 +1,66 @@ + + + +CSS Flexbox: Percentages in stretched container + + + + + + +

You should not see tan (except perhaps as the background of a horizontal + scrollbar), and you should not see a vertical scrollbar.

+ +
+
+
+ hello +
+
+
+ +
+
+
+ hello +
+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-004.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-004.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..4eca9e2330752003273608791cf76eaac4f33bce GIT binary patch literal 1081 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKUobHP$t>TD{XmK{z$e7@|Ns9$CPM>*>m@@m zpsLrNE{-7;jBlp~PFifi)AsP7dBg7lmgT43IQJQRf4R@m-e-Q%SMN3ftEp_uj89&x zvlmV3NqN1w=Z->)fD?xzkeFOFE4t3#`SQ`?sED;aVeda@oe8P_bbY&BS@g8Jw*`{= zSIb-`we9I!a+~MUJ=Vc$q=2A|?5QXi|nt_i+-lc(gS){B!-Q43#uy}IguqUrwa|1Z9) zdcFCl?V4FD?c050Uip^h9=_dlzSL&6?XxL+(~r1GEblq<@8kYSJ=+_;q%v(hF-x^W zcgEVvh8zDyJLdU2--u~Ga_f~Z%bI)pLMALbqfo0UdgS>JIhU8exRPG#B=2mQHtXUx zr)|GklGa*nG4sBYYJ1J$Y}o&Jhi4OXnj~V&??*+gkUS!O!tv9#9+yYg6mDsqHoca9 z=W1t`+q0NsX_?D-Eqvcf?&QA|+jwLSKU<(|=Rx!4ALd2!D(fA0WeaRIYw($RY;~Y# zacV~P*Gp-c-Zjd;`$E>f+^ZIPwp(j?b#zkq%2O^^PsN?Ta_QR68s$%Z+iLg5-I7^( z&FH53Zj+|SipY6?yVDIuL5_=J}LaQv-sPNO{}Xw0+GLsoLzw{+mZ{U#y!a_*!~C z??>B?O-sT!1*+F6_|A0v^@)pf zOpJ@Z38~!s_uQc*EM!6++sk#1)3(0xl*-MHG}Y2P{ykyqiZK24QP;w@ZVE_!SoZPS zzUgz`Y$~hd6o4gIj=jt5%3sgmSBNg%mT6a4`{$PM6~X!YnLho#{{F5)buGiqRUiM9 zg$3k4f9ojwv9iYP-ud@jum0>)R%WkZxXEYqZ?k;L{`2=woOs^YQ^WMhV#4;%?XlHb z7X;_Gzb@Y1_mhE%L!n{N5GVe#vH-KG)tY+7yDrW3#Tg#KpaQ_t)z4*}Q$iB}9rxmh literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-006.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-006.htm new file mode 100644 index 0000000..6082262 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-006.htm @@ -0,0 +1,48 @@ + + + +Definite cross sizes + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+ + + +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-006.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-006.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + +Definite sizes with fixed flex-basis + + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+ + + +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-007.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-007.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + +height: 100% should not be considered indefinite on a second flex item (triggers an obscure bug in Blink) + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-009.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/percentage-heights-009.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + + CSS Box Alignment: space-evenly & flexbox with single item + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+ + + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/space-evenly-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/space-evenly-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + +CSS Flexbox Test: Flex item as table, specified width less than minimum intrinsic width + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-auto-min-width.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-auto-min-width.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + +table is flex item + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-fixed-min-width-2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-fixed-min-width-2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + +CSS Flexbox Test: Flex item as table, specified width and min-width less than minimum intrinsic width + + + + + + + +

Test passes if there is a filled green square and no red.

+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-fixed-min-width.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-fixed-min-width.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$pt<74`eblFt}ba z6a%Vw>FMGaQo;E4YGU3k1Bv5_zZ#`gzCYxcClX(@d`^7(mMNvb12Ui3zpR`vA^Y%@ z_(wCjfx2a?)_;GU|2cpA_O;J$A3pOtqk4Dj@ob*yo7ew;`Dq{5%AM<-*G;XQUH`oO zO>w{C{BS1cYuoZ_WAgJ&rPhD6)BCpV{M9%8hT+Dh@r&=R``r0ve$4i$_~Yu?d-8HE z_kDacVcV9<{@X>8i$6-e=a0_L^j^R9*OSzGw&T}UZT%Iu_WtqWlAZOBw@fHB-_LVX z>3ZDz2OJoUY|sD=+EK-OzqznM8C-UU&ZlyHCHiob*6!J6oB99P z>iH*b=((Rr&0BxVj^BUDyU%qxI`f@h{fob6Z#e&8QOllR`%l>O{IpmXUzYVX%)YL6 zUfkche=)ahQl3j*c@|l^cfPl%QP}a<7F)CSnqB>ue`a#_nLQRy4sSO<{Av3BmN^2S zzFaP^;{XQJ{ZF6$&+Ri%baLYGR95MoVB&O%_6$($Vt{|v`x&O}zMWt_hdmV}>FMg{ Jvd$@?2>_4Q{EPqq literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-narrow-content.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-narrow-content.htm new file mode 100644 index 0000000..5aeafed --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-narrow-content.htm @@ -0,0 +1,18 @@ + + + +CSS Flexbox Test: Flex item as table with narrow content + + + + +

Test passes if there is a filled green square.

+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-narrow-content.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-narrow-content.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1d8721367373f9d46a91411a73b382a48a24b8fe GIT binary patch literal 541 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq`)}pW{QGG`%-(;8KgD&ryxV$0W|sHAYvPhS z_ns(r4OkMDd-wWnU8Vhh-gU>i>23ONBR=zl`<2{t!jk^i#J;}rcZ-=l^=?6pZt-F4^9IVPF2R&;BH{;TXc7q{#yoZ0dInfJtP!Y40Vb!=(&$bHK- z@m;Qg%OcG^uA6k_loek*jT&DjvTP{#bU?Wm(SpjQMvvkJ$BZ z44CJd^ndwp;YzktVawm~PsCm29{<|1@~W;~o$d1Q_~++yEK`cvBG0X=oa?RlD0Kg} zm7-rWR(&)tDyn>zGLb#s-1+B2aV7B%8N1r+-H^~Y5M5Jq`v2mLO+&y85}Sb4q9e0B`@{5&!@I literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-percent-width-cell-001.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-percent-width-cell-001.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8de0e9e1560b952071474949b0a63b2a81b195 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^w}7~Ui5W<~T+Xrm~m1llom%`8~f}xBlgy;PT@7xI6Ms^^CXqG@VO&z4+c+vD}EgvlG7mJkPSbHsLPI z{m-mrZKb&kci&vxC3r{OliliL{`r9Nr(d<^?wNL7`@XuT`ue9^fy_^{7EiwZDJt%C icIDZNvUjq3EAppwsg|c*nsy54Oa@O^KbLh*2~7Y8fMx^$ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-3.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-3.htm new file mode 100644 index 0000000..9bd2958 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-3.htm @@ -0,0 +1,30 @@ + + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+ +
+ + + +
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-3.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-3.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + + + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+ + +
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-4.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-as-item-stretch-cross-size-4.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d230b8dd39a156c536467aae926481808742f273 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*Gq4)PHfuUGeIii2py?U&Ytes_xvIt|V8jw`s>S+q&1? zDQ`52&T({Yxp!>K-*fMjtKa0#-kW}AalY8+j^ni^zlF>v8Q=a;vw5an-s0=It;cKH z>K9Mh{`K_q&M&tf_f+eeZ90Ch{nDzve&>Il`RcR%(x*A=KmW1n3ych(qAT@&&R1Qf zvRVGF#~fAiUe7&Ip?=A2-G|#ZU5{N=y1dTDb&tjxnMFEf6EmxdQv{FgwRO>%e0xhx zU$p + + +Table flex item with infinite max intrinsic inline size + + + +

Test passes if there is a filled green square.

+
+ + + + + +
  
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-with-infinite-max-intrinsic-width.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/table-with-infinite-max-intrinsic-width.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ae0cbd172098738146d6f5effeb25b64c9aecc21 GIT binary patch literal 584 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK=P)q?$zT7}Gk_FlfKQ0)|NsAiOoj#q*GqrP8c$L!Ib1PkHKUpi!^YVKw ztls@(%4_R$nIBH*|G(1{cIxCJv2wn*Jl8IEMcIgdRb6*+gUvaUHRDc z@#Y+lJ{pyB<$btSJKf3PqDmHa+ zbgXFXkU}O5Z|AUc?!LLUJo*09w}0RMRrvJj*QZ;%4P68T83j!R0+~TX^0u3NN@bg) y-yhp|`gr)4JRU(87Y7$!mn+mGfG(}$t!J!oIG=Oyo9Ype!#!R7T-G@yGywp0TLw@7 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/zero-content-size-with-scrollbar-crash.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/zero-content-size-with-scrollbar-crash.htm new file mode 100644 index 0000000..dceaffc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/zero-content-size-with-scrollbar-crash.htm @@ -0,0 +1,11 @@ + + + + + +
+
+
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/zero-content-size-with-scrollbar-crash.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/flex/zero-content-size-with-scrollbar-crash.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..dcbee0d89a87c7fd5a82c8d1f3304dc517be0a50 GIT binary patch literal 89 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK1sH(@!?BraUxAc@r;B4q1>@VZj)DvfJWLC| o>f1*D_^^QCJF8Hqi_%PgreaAeX^umNS3!C_UHx3vIVCg!0BFh<&j0`b literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/render-1-inline.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/render-1-inline.htm new file mode 100644 index 0000000..fc8b7b1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/render-1-inline.htm @@ -0,0 +1 @@ +
qBIG WORDSq.

PINKq.

GREENq.
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/render-1-inline.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/render-1-inline.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2a5987a70dd4b3689f4d94da454f561dec127851 GIT binary patch literal 696 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKQ&^aRWKX^49w5aZ;1lBd9|V9LhX30RPIxzg z)oalw1_q`SPZ!6K3dXlr-xe-55OI6BrNR2aEzuc#62I1Ger;^&Xudv~@tyTUo8-y^ ze=fe`+!YTp;laXYmZlEHjv1vBX3sqOe&6d$p(6D!F2BwFmo+~r{eIJzkH+R5;dg5$ z=bfHz>c4UGeU_Q~_iX=XuIqS6^UC_BGiFUk)L(u%v@UDHH~s&c751pP{}5B*fBm2B zr13lj_8WE1d7mp&V&kK>NJ^+2uP9ELy0C7u)RK5PfhQ93ES#>VGE-SBZ?QWbI-=qD zY5nTVrrKJ8C9J2L{I@0xmp65myurv%Tky<8?f8MIp_MC-LToJ;z-I_IDAsU1 z%2w_uOtGxwE#h~4wDjg&h3D10LRm`+jpU>6N$;8UHK5;do|}OCS!tsu3J))As+=b9 zq{g1*r1M+_JF})IqV^y~r2?n(-D3QWn=Dp1wu#F`%5f;QDJ0!vIl0HY!ql|myW0do zBAqJW;=^mS_u<}?+Q2xHN->$Ov)(=G@(+a*KeHxCD6G#s)vW+Dd>Q8?X81~FPhUMc1KB0gAZ!h$l@Of>s z&)JEgO)dT3)z&Xzlf3+=<9OICRZX`ay$b599hb=$rp)bCNZJQVEC&wSD_F1v9!g!! P2;zFW`njxgN@xNAwpJsr literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-1-width.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-1-width.htm new file mode 100644 index 0000000..118c2f1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-1-width.htm @@ -0,0 +1,6 @@ + + +
+ +
aaaaaaaaa + text text text text text diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-1-width.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-1-width.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b243e7318113f0075e31530ab625b6dfcd651901 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^EkK;X2qYLJcBXy?Qth5Djv*C{Z>MhLZBY-D=*-&dJmF)b16Il$19{F29KfwTte`4j#+IumAmIQr_eerLQ-fcAzhW?GMB&ra}t zrj^!pzI3r}<_&>|(N8YyaWjd@kvPve&1r7_``UMJlj}I|{jPBQHN*drcI`jChfB{N sEZCT6^W3ub*Z<4E=gj<6#vnDD_t6FU1abZT{UE1#y85}Sb4q9e03D)HdH?_b literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-2-width.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-2-width.htm new file mode 100644 index 0000000..77d7aa9 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-2-width.htm @@ -0,0 +1,9 @@ + + + + + +
text diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-2-width.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-2-width.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..f88e0c87bbd89b2604b54cc03b2817738693b01d GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^4nSbP0l+XkK Do)0)l literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-3-width.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-3-width.htm new file mode 100644 index 0000000..ac00594 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-3-width.htm @@ -0,0 +1,7 @@ + + + + + https://doc.qt.io/qt-6/images/designer-screenshot.png + + + https://www.qt.io/ + https://bugreports.qt.io/ + https://doc.qt.io/qt-6/qtdesigner-manual.html + https://code.qt.io/cgit/qt/qttools.git/tree/src/designer + https://www.qt.io/community/contribute-to-qt + + Development + Qt + + + pointing + keyboard + + + designer.desktop + + designer6 + application/x-designer + + diff --git a/src/designer/src/designer/designer_enums.h b/src/designer/src/designer/designer_enums.h new file mode 100644 index 0000000..87f67d1 --- /dev/null +++ b/src/designer/src/designer/designer_enums.h @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DESIGNERENUMS_H +#define DESIGNERENUMS_H + +enum UIMode +{ + NeutralMode, + TopLevelMode, + DockedMode +}; + +#endif // DESIGNERENUMS_H diff --git a/src/designer/src/designer/doc/images/addressbook-tutorial-part3-labeled-layout.png b/src/designer/src/designer/doc/images/addressbook-tutorial-part3-labeled-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..cfcec31e69806d70add2a5451710eea25cb39a6c GIT binary patch literal 22779 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-add-custom-toolbar.png b/src/designer/src/designer/doc/images/designer-add-custom-toolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..fe165869dac753c2ffa1a90ea6cf62e89ef0f881 GIT binary patch literal 940 zcmV;d15^BoP)em=hW z{=;|vL2Ju7;r}ZUz{4wtw-M*uCWKO(3%r)aIP|w0k}Ly2&Wl7OVp$PIu~v@SHnr*4 z^o{c~*Y0iTwThfqF><7Q3hqs{elT#L`w{6ra*WZ@pO$VH#F5jl?T@#4O~WuKr5=`L zSy2=OT5c6e#^{fKe0A~q$>Gc{ykl6X>G?aS-stOjw3RXDb;idhbPIzq(Z~ zJabmjRDtS41J4f)zKq?W8^zeMBPhMky?gAPk-_7izxv*%&_be++?N@C^ONECzdZK# zNHp<6E|)J93ZAt_?i>bZa46-{rezu6OOm|6;)-bsrUkKGSu2L4Cb0>3^%}clrm5!_ zXJ)eJXR_bUUY>%30~f2SYnOk`Udm4XJTyp(fWGf56#knih+70R}mrUxQ4Trjri1VV#}rsnlKoKxC9c9z73r z<(N}+JJo(w{4M+S+Pc2Eek<7}zpsAa_yBX)}<@UUD2SbsIYo!P000;W0ssI2fY-s1000CrNklH@VNf=lSJ- zo|AiyW@GIy?mmfUHzrsSB}@>dbWZ@1r5!c8l0u@{63{@cz61iOvKR;eaPf!5Z^i^+ zId$(w4p@xOcDvp^*FiDn3M@kL#j9Q+B7gq&+1@tSy|d;5YZopLfAZb@8>d>-ob@ZT zc(d{O_eZCEN$m;KXUtOh+q6&{@uSZyroWgOKPrIbz0-Rh*2 zY;|l$*Ww#18R3f2s56)9DX>n=j+NIw6$)Ohur&h!9&f5*wE$`{lK4Uk831Eaph%LK zjrj={F<*(J!%$juSZgy;?_8n;TU#DJ`8v&bvLZ%EVJ;L|EE4#l0cM!7GU-Ir_hp3+ z1BzrtsW6!Ug~GzjrmoG__O!XerU4)lyq*cJ2F#Ya#!iMT5_YiGW)pZET>Ey9`{otW@E9R?Sb)1DDn@=P(;LC&99D9X zXyR6Y%8r*Cy3SYGYh+3E&yP=ByJX$c^WoU0!b(`|_Qgag$|+(ph2&sdkE_nO zI=T!7y(CHjpC>RsvQL*d*6pNnI2I9ZO)TU6$Em8lSnMq!TwcPVU@T#9_q88Kijs&$ zg_&!cv4!S?HMTM(9sfPEnJ1!gHFv<>+FL~JW)Vro>+#&QH{1MVb0!@RdMB3sV@_9H z_Yr3zG8JBo$_PPdzy8~*`}75SwVhJTs&@BF^~G56RDLN&_nEA}4qwW}|8(~pY_P|c z{l7%lRwTq}w6^d<(BkU1+p8gjcbOxX&Y?&m1&j{Q`BuC@Ka#F9srWd(6ED$OOi zNsqUzX|TU! zv6$K5V(i`ftcY_W0VH+E2}}lph)DygLc+$RH&@^B(%HeT%F0USNFA(-+-)5p1O}+I zK2&Noi5y>(UQdsV2dMRE|LK!Xr_*dM2LLe6_JyZhq4zugA*3iN$!4oITJ4bM_>o~@ zojAJ(hU)7duvjb*LV^j4+Z>J(j1*a7RVt~-Voz*WoAHIqUC*Ta-}6h-SO7X&2Ko;j zb~vg_N}!a_a77h8sW3%YNwQjXA5^wj0K&m1TpxWs@nTPX->G*P{YcTQlxM;15>+qi zi)23i(fgdc0YL3-U%XcP!KpLjV8(07*qoM6N<$f+v3`LjV8( literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-add-resource-entry-button.png b/src/designer/src/designer/doc/images/designer-add-resource-entry-button.png new file mode 100644 index 0000000000000000000000000000000000000000..e29fcf8038bb16c72fee9afc55d1c67eef0f2aff GIT binary patch literal 899 zcmV-}1AP36P)O>+z2=TVK5Yceu`D=F$b*8|{pIVG&^X=(4QY8G~t(-k~v0>X$?l_~F z=N&C8PMthfS65dO0hq~_WbODDLqS+_2hXbmiU1+*og;+MszR-uhYO~WH?8T}v3%Z$ z#kdE-8KcyKf%hX`U?>C=LUE6fuJiC_@TqNDmPLJ(F&k!HL`w3r--7BHrFP%Lhj)8= zF$n+>%YmqYgFd~gVszxwm8;jQs$odF_wH`qyixji83>?}{hF70>&k{!kR;whBYAwUJkE`I(vJZEu-+PYpDPozNenVHb|-DZ00TFJY%rr@e6R^$neYYH}`6Ef(ee*(B;dcklBkX;THtPxV>$AELv-t1&HpY z%Qr|+x9N1TkgJdS_jh%~Vo{7qBofhr8U(Ej1W;MFtU8_E07(H5ns!sgex!nIie9BR zH8rMEDX0j9Kp#nM%O}go32PE#C?u70#2oWk@2$zNxliw3ha0e;kU+qXTndsoR7x+X zi&BfI$Ta6vNlMEKz;*53F*I~HES&HvWqm^$XgZXZR0m~gVXlaw&=(*ePuImmJp-AJ z#)w{ly^a0ddpg>-GB*nqzn=uK&&+-I0#_)~r^8e0;$Z}Z&Pou_IZo``aJz4;e`rEg z{#N2278(e5#YCqMZu8oQ3jtkYd*jO5Q26t-WjmroWxmI_rvA#Dk_5my8mdeuBa9j_ Z{Q=6nwvp@3d*A>7002ovPDHLkV1i^Y= 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 E2M9Ao)&Kwi literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-adding-menu-action.png b/src/designer/src/designer/doc/images/designer-adding-menu-action.png new file mode 100644 index 0000000000000000000000000000000000000000..5b0ea9964d5a735da563ac1d089014cabe55a541 GIT binary patch literal 6414 zcmX|mcQhQ%_x45!cC{!gdhbg_?|q|p%PL{@=+Podf@SsSy+nv^$s&TNt9KF6y97ZH zJqUjC`JVUvWBR@Kx%bS>bMBmp(brWY0nz~h004=Gx~kz_JMga%;@s72(o%x}z{4pG zRVCw>^S|<~^^IoQ`vaQDWb?8wpEJFboJ`I>2KWRy+HcBsj%>?iNVVDdEJzJmj;D{j zRO8;G6`P@zZ2stNXilp0z*&>`4KG3C&wHs)IGp46Emc#M-^&xb*Bz4R21VXemM@xigd!Ld@|fFB39 zd*>f+*T_6?`f~&5#oaDH=-jpi94~lwR4>-P$GrR9=D#Aq%R3Q#VzBXcXlGo0a`%+D zTU1<>Nj$s#=F&C8LGP{Xg?ms-|M$bdHaR)D!ke2onny>n3i9&uVq$*h`%80kX7?a@ zPnw@P3o(g4Br)cS5oHu-bQaGMaNM2PwChityB&W~blOv4BrxWgx3?m#S0J@_mM8DO zZ|SrpRj@KMwRf<$M{*yxQ2&{btWdDmOZy*Rr>aapJf&~*`rfB%|95jVx6xBiM^A61 z?W#MRfD8}Q`N8{2Tqpa9k%^|m(Ln4G(A@#*2Ro$^o3h^NSTr>aLfnS+g)sab`aPB0 z%@sFiE925oe>P1ipc{3(a3;P_?0<$XtFJ$9U0GR}3}SuA!0@nDQa1SFXk)_Q-iN`# z!G%W8@sW|mj*we!%tsjYP`>8In7c7oBIJ)2_O{&ujuyOGRq@L%b@N9(xvKc|xzy#u z>w}f^S7ssa+7G`dv`hbm)i-aK@A0@QEC<$|=6W*JObU*hHHMmIDv%SNL|sNjMhRLo z(YA>L6<+R6SIhYCFShs}u)-^q(!$f&+3bBie;N}*6pHWJ1c1lp$HF|T#~`|M_P*SrsyvN%Xof%b2bCBw zg9`3UFKp_r!wwM86Sto%EJ6s7L7E+E+%oZo)vl&!vABzZep;)l}Sz)5+fnCWsFD}SoW2d_b1I2A+# z6Kh;qR1lVKky_F4t>`|$Q0BO9$Ru|OGUOJWye%U7#GMLWOu$z_b76HDbP%F%X>M}q z6)^W!?=1Loj$6$7+fP@&tE^W9r2C$tqB*4Wxuc^d3p40^xd}e>K*+Ak)}m#!d!012NO%Qo-pO9r@+7J$D^NKTeQmE<+wpHvI9F`!h4*@T|o5%o#rukq}n zsm{Mh3DEi3S*C@NnV#%MfAVZ;5jcjxYw$W-8+#tsRP&z%E_x*2OwoxHva28_rJ8Ed zE-pm%t|ke2Dv5BQ-i$^-o$b?idEiv&GWS}BG4L|Y7vqet*!Ktl(f;|jQgU)JbYZn2 zW3{o~L~toiS0?<7?(OI%xnRoS7P%bFH?gEPDd8o8CRK6%P;2TVvJb%C27a`&1K6nt ziDHt>6r&)DP{Arv(w9r%TUwnE$))q_zlI;JsfF+_J#DwJXeKZ)`X^9jIyH5rsI48N}~O&HP|BjDr2l6?yv$@z005? zJ zOW5Y$3ab5+uf-xqc*jMX$c~aRnVt_$85&?z=w2+JL6z2>!zpeB9^!SIPM<1uqnN&s zrH`Zj{`O>ked4vfFW1sguwQZ0YTkH$Jpb9ew0$4ZjaBK3?AD1I&0zPrkm^tJ{Q3N? z%Psh^h;nJ+%*@=>67|nrXsqr_9$8`GuH!gl!{G#iwWLU1&8$P$EVZm=zK<+`eW`@Z z9rjd=kq(s>9l$Fm&E+ExbkS>)xsi`avew`tp;z7+fDVLz#menM{kbHppzR9?zsLw;1 z#m2C5%fjHl59}Y2Tl=%t{(Lcrh2cCTPv+mnprL_SNk&@pNc9@Z(NO!z_Rj=6(wA}Z@@_7>GiUby@} zoZ^Y?&?s#U9Runrb=m?UE8K#!UD4v(IJ!kYb&GtkX$uq^2*D%?KDG|GcbCLoAP()i z)oU`_Y6U&*m{wEwMZVC4rkm@R-ms-t*pVNURDX*N#G}RmrBN?5KFlGmd$a3pJ&bzd zhCf5h;A3q8lIi(2({~05h23D9^LKNDERFP8dAIO-Ph_eAmD?Z!hleL_>tiL#lk(@R;9W+=+1 z5Y1=c;P4@g>%6)lwi5v8(w?yjyOIPl9x#c-n1x=Z`R#;WMshQ@wl|H z7n|}dj`E&mTIOs=V=gisfLX;m42fsuKCpEFDV$IuQNkHgLPhXZMKe5*h?HSpEBAS| zC6eW2$6IXTdk{8203Qm&crPe+kvtPYp+%ko;lMfo%t4 z?CBkP__2F+)sP80a}tu1><{)`@z^(l|I^pnWllk+4_HY5iFrJT>NPLoPs@1zLMWn% zlv>Q^yZehKyeC0WX!LDIba5paL*6uxU zWJX#+Veu|pZ_u{)+KM$2D1+=GvhCd5h4td+<;KmJOFa!Jn;;SW9EODAUh}|fxnF#g ze%>1~<<3XRaR}mOhu8D+o}@R?yta`=ha#cYRA_DhsBdm!UYTRWFQQa~(+~%8I_UL#WT;+*!}>^wW6o^d(B=SDXq^DgU}qU>Cz3`2xqJl}<|$Vv*yiUx15 z|K3O*HvM2pyP?)!V@==jm?`^Vhg5*i&UE#GXNZE_D}pa!Vk}^i>A&iDKojMXh+Yac z)&~cgaP-rVB~F`Kh)G;TZx>gUmLCcb$INajZOo-wsb$4u`Y-j6bH%KmvfIi@^UHu9 z!we|Gf)4389$s9uu_A#Qly_Xhh)!l%pL{%<6g|-G)2Bh(==NPqosds8t)hHl2c>?N zPHN&+hs0-Df6c5zC|To_CC%z+`HdZqcCCYl|Aoh6IB|6Q&?~vjXFb0d|AuU}3lV|9?Ekx`WnBoFtVT2SpG zDl{IF`eAHHPfAu$IE+DituU-Xzx6%KLCSAALvf1;Dx$P7I@=ENbfC%aoH&EAm>t@K zv2np~#zbC)6z*PZCy6;dG3UB9>f4OPt2Ts&zxti}?@2+Ix@=pNM!VvUW!Z zK-Nsh98DbRQNv0nSyA`WPNu3ffVhi44Lehw@gq5xLKryFRPDbmj<;A>9t9NmG}2T8 zEBLjeBkH>$^agtd$-g+NHNVACmU5<~1j}`HeMvb2ajD?4;$#e%?00ODlAb;7c%5Hk*Q=94MFindEmNmTri!6q zfFxHB4IN>zO0xoilsQBFX|p5Iq14?boV6B@B;le={7Mv8b}>^71!0HLXUNw_I?ODr(I` z$rrz7QJ^LI8=r7hg*=PdC<_=mvIj^TAT+264=77LcOnA^TdSzx^0xjx8ZHRA*}l2H z(kbmr<<^T-ZkxS2Fc@zp$BCsIL646gKB#U7OB^KsiS0l5TGeGw0P0qDH2%GH`>grwlwmAy{$*2jeB-JQ5je0aU z&fKG&`bUfKS75F6(B`MjzH%P)?dTRewoN8a|syivLmL}GAasU1wgCY zQ(W)txQdCHkF=4qWVa%)J1m3`ACM3l1$biybX?uANHwUpJZZL6WN8@?+)n6`{fH5* z*g3Pj{Q3;-swf#~eKBJiO)@-wOUbZ4Xyo>OJ9>_$2NDZ<qQ{_rWRsLurdZUKzs#!e~=}Lymbs7J#*ceDsDh?0c<+9vX z--X5~^_H)u$Y?%F&$fFY{wT~U9v!8MgRfDVen+*1?O5gdYav%@hWM% zlP1z}TH+Urx?j|&Aeo&~pgL%LoE2r~BHX3cRmk#-auVU~`x4hkg!J|Ns@vpt_7D6x z`&%0XqH$Fi|i!fT~9t+Vc;{rnJ5%X017SvEE zky78+Y$@A)RtT6Sm$?sD5?)5#$-0sRP!eY5+#!@-vL)|WTGWs$mVc{6hWGw5X7np9 zb4Q?xWDTN$Q4Ro+l&>GI63LNlJZCjbc79FDt35^geoiJTtuV-V3rHW_Js*fWUvrXv z-s7;%LPC8zsTk&rAm2-1L8P#T|CO&LJ(>Ncs@#DX&$7cZv0x;({H5aTH$}76`Rk#8 z%(RW%jq>pE`w}Fa!X#S?>1IWwok$XPKlQpCIJ1fP)~Q=enM^;I5jp0eMO;k`EYnnV zz|Y!VT38&|f>Lw|R#S^oAkTJ);>sp{>qS884|j4lNG~_t846lgNga#?gla4?fJRV5%RHhd_esJTVzl7BCszuYHq$|w0| zkp4Jf?+u1c$U58sM|*J4!-iuc&aajw{0=q|ACy2;Ptqit#(FGXL(p`$Edzg9-IGBP zVKn|!Dvb9Ma$K@L^I$l3IvKh~V@xQSvlcsink)dciq?W~7rFN}l^LEe0GG|_*cO4I zWA5{%Eymo(=g_PKn`#lX`>Cs&RVjOU%~uNUCPS=da0Y4KgAC$LS0N4|TmTZDgBEjL z)%3O+2Hy)zvr0p!qOMdJM=}tk27Tn3PMZMvllv?zkDIyZsaET;XYaVikd1dQ&GPqx zD=3$j$v0kwp~5?gL&qCmAT$tIx~`ce-*Bdn2U*RPC~V3{i%4Yi%SWO2-p7iX7?^Iy z*jUBs)bmSGEj7)qzN|eiMXqY=rs~}8q|QnWr=@6K>vHA=ZWo-%S*8pNf~_tG0!W4` zBM~NzlkQ>+XG52I6A91!IyXP7-?K`YM8QzNk8TL^9Sfa)rtFK`OkQXvJ#K{qVQKJ| zPpp)rgFutbGdiHKViPd9yz(KU$Cpsu*#Z{>%QAR;ChkyZ{*P#YpG_^s=a)w`)zjdZs^ zmgwW;QB+_QCj=PRRfLIvZU5J5FHdqEa2=fR7GokfAvOkZs*Hb~yR|!G+RN2Bt&{Zs z&min?IQTyV{yi^{7F;x-(d9_j6H=i3Ka0@+Y4bpGT62wZTXS0{%aZ;f{r~wpG2#Do z?liREcfQ>D7}vFaL$ces#-8tK9Z_Jj(X?g5h?bt8x*lQ&n~c~nR?1H-9ZO$^q2@o& zw_eoaRPWDB$>p`0PUY?I?Gh_=YWk3^z^D4h&d7XG)n>JQ%qF9Qm)Jt4#Pr8eet$<$ zYisS#CaL(B|8z)agkgATE#Q7z^iNjEpbG-YwJ2Cfh1aic_1T1Y0dyS63}wv3 zv-bz~LXnsnQSny{%pm$??8C0lf$uMjgdeykJ2x+-NQ9WYn~HrZ5OVZ-&vAGw<`9E# zhPd!Vms*^2ai>me{(~eO=jio9-fWpK1CyL879>bItJR z(UKfGGrw)ym(TK>!k2=7uE45#t6H>|TP`DovwfSQA=vyCcsRS=oEw+U#@ AJ^%m! literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-adding-toolbar-action.png b/src/designer/src/designer/doc/images/designer-adding-toolbar-action.png new file mode 100644 index 0000000000000000000000000000000000000000..9ca083ba0da5ed61373860c205d2d740f95fe26f GIT binary patch literal 4793 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

PR~xXT=7{))=!BZW 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-connection-editor.png b/src/designer/src/designer/doc/images/designer-connection-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..09d9a7e9e3009d8cdf62f4c945e9bbb7c9f7e8ff GIT binary patch literal 8404 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 ask!|9UX!vczQXN4$f>HVsRU852>T!3K!Ak+ literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-connection-highlight.png b/src/designer/src/designer/doc/images/designer-connection-highlight.png new file mode 100644 index 0000000000000000000000000000000000000000..089d1e422b1bb7bb02712ca078285ca65c75156f GIT binary patch literal 5297 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-connection-making.png b/src/designer/src/designer/doc/images/designer-connection-making.png new file mode 100644 index 0000000000000000000000000000000000000000..a7ce33fe2a9a0d40e871e1027c74b0a3a65a40fd GIT binary patch literal 6869 zcmZu$Wmr^Q*B)XRI)@M#8W|LjZp48hMHsqM3F#7$X6O3U4Mjzid&G-QFY&m16>jg%G#rlU`jhzj<UD`|0yqIT&HN)``fbD_~N@|gOYEZ zZ?jIHYHE&N|IFn{waa^L_j81seSby#7hvBT$2RRxc*GLMxz+n(4~J~uuKQS^qpXyNb}zL?*y z1k5i|*Q$J4Sq!ga?z`#xcGT_d?Fhx!uce^=ZiP*bf^~4VBT>oR^9DPk3WJXkdF?|; zhQLK##j{m-`x+r`e&;KDs`Wg^0uT=|xe8kZdB%Gs{PlN?Hd`)QU}kbHaL5O#xa=Lz zGZhp!!}RsKo>R_XJ~59wvo&U%9m1gBSk@k64y>Um3QvKe-I_5kBEA-gr2Y(%yrpvX zd)4%11&Bky3?f>XYEII$bO|Ew9}S9aV!4|RCokRc{)0i%Tb&8zOFT>b-6xO!aG+h- zJ_G~HX#4H3vBfd43gHziNkMJyr@*gAY&O*vl)@(x7LJ6O{95+@#(=+gVdo~LkYB4KNrGY30Lr_?Q*xdPrx{^6*^s{OWi6nV(o z0r^Xtg~JmT+=jm;K?5S6-wKLY%igu#vWYU{^gpDTvuC(#A?VMvsX5##>>J_l+|8kT z{&6np<>a(^?_?t-AtUnA=plL0f{{6dd6VW{;{jjTS=CygdES&`wWzkcsyg)+6*Mct zJUgC*f}O+Bt~C}(Nudcwa1qpseoF1E)E?}mo1rD8DeO9+71ApATX7srh zL;Iw{+gpFL{g7Yr?+c=3OejD)gL73?y!V*l$~*;_cSNSa($C0-_!!j5Mm4|%&`ZDi^9U+X`5*M5VzOe|lQRcDRv#AL)$BR^OVr~6OO%N`TFphGP9+dXQ&w~==@H@CVhEjj4x|#~^>Z1hb z4MkuD6Ge=JAg?P+N1+{x$&PM_D@N4c;Rv3RVxGWFvVOF;bDffI7>D>AsdQJK`_^J) zZbEI2uZoFF_nv4U=y@-G!FnzPy#Au~nQ-V(uFlZ2gtn7jYIkTAsf7vI&21u7ped%G zCcNgpRP4Q|q@=Iq0rsmi+7T+;)%kbjGF4-&R5W%4^f%IRX}7&=?sd=4huq6euh|%L zOj6!y$Cw9DtZe7vFFA?#JDuS4!mcIJOGu?8Gpsb4h0pb~ zICF*O;RqusKl|S=UcC>76|DhL@=ui(?<^G9F-p&yV?r>-+hS(~i;&+0o{)7~Ng0L$ zHuAT;UQ2Ja_pG+rQuOktx56}5+)GXsM#4@d6mgmYt_Q1Z6PNP=3~*5B+Gh9M0033F z>*pKf)%2YO(v<-SmIFlKQW8Ib0`mV0qH5Oe5|xkTLT9>d78J(N*`ph)N0L`KEUX6` zvV#TnLHS0?>t{bg&hVI@l~NfuwA+J2J2kK~L=^B1s!p6ufK*bsjCb3|`(KPVA6z|w zq9xp#c$@U}X=~^9o(r-h6++|E=SH1ofcdjeo=w2>wji~Zz=-w4W)Y&7592zHR!o#~ z**P?u=+LU=lA#|-?5SWdr5y#zNH%Sl`x-#NEQUsXodZUx%~kouV>V787r_DM8mz1v z!P8EW&iLCw_WePShf(oiIOx#B((^BkuL-4E&~Ljq@zPEEG)e0Ee%swS9kaF z!fUn|ecE!pH@jkHZk}IQh}YlWud1N|H#KFdtg2#{l9N+1H-CTWY+=DNJv}`s`rv?v zhbQIjTjbD?hO?`yjg1X%Sy`E?mey9S{hOT~M=ma|HY-6?NeKZ61WL}$%|$gd7&!Ee zj`mJYf<^fCneLv7)+Map-LbfSHN}sKiz7ZeKSvG?53{qe0R%Fhno-vajnB=+#>bQJ z@$<*Bxd#T4*3OZtIXOM-=;(;hm0>2=(9&vEJzQI37Z(>t7#qiyl-yrC+uhwQVBXo? z#e41NXZ56zAmw)3TiW~8D_&7J9PTGNG&JOA+u4f2T;j9#IEZ$Pj#4NoDKW6J_L{D$ z%gV^aZ*JP+gFxY;;^GD0zTMm3-*5c>y{n@G*TBGll7RtQ85Kp4sZnD13&O&p92C^N z_1f8)luKAxXH3Ofxt$b==v-gtFfcTvWxSMIo?R7ja0o+gaX!XAO&k+tT;C}uC_wq0 zi_6RF{LBzYtQ0W`341>I2&Zl^H4P1(T412;|AarehFqYL9NEAZ>^FmnVB(j$;ilHml4S*d$djaJU4^9d&)$* z8uuOGg`1le0ZIOGv$fRJ_&9FU(wn+EQD+yI8a-`?*_D-yc4Rxen}8LrxT_rz6~!ST zK`$pKCn+O?H@CbT)olYaHom84MG-a!G+d!i6w-gh&#&a`D={)UD);d0=0=tf@`#5A zgolT>C`a{7C1*J5A}UeE4!Ea?_{>*U6t%bKOiE6EB6ob9kdWYK8$Oz!FD4Ql6$NjD zLZOsPx1T;ekS=ood}4RHDKF={TkGEy>R1JX!Cg!EBZK$LY-G}5$dP^skbwW3ZkfsM z*bGf-kug1cI+2TO)yQm5G@3(YRo1rfA zqAsKeXGG4dBfq}a(LBDd%W;t->-1`q4k+e!QsT0H{jE*jZ#cEEX6&VKO6v1sz z>!XmN3Slg?y7l_I`>M4D!g}OyX6Kk*M6Eo(FVkp$b9Au(;kEsF@ZZW%_H$F?a?026 z5c!s0{$QTRe7`UEwR5Gslp9uydw`^T5q4nAtsi*MV{P|OzK6F&usw0BP^W=vbj$|R=2?AiX zvu10JFg+b*0<_+Av-gwuBW8En(XVmYv^Z!sT3K)}QSp&8i6w#nd?0`l3?L-_2h0yy zmX(zBOt+kYe#G}_fyVcpXE}zk0ShA|CLuD7_cT#QUxMD21Zx+NB+fY6Zc-S=(?^Hg ziD)y@!AObaQmd1VzBuGxf(rin=YRZYVDbIZ=sRImpydEX22cV@3Mj1%bHW;ndyG#B zj!oV8;qN5_J9Y%XQqmY!Xz3DXOo@n#_5J7HOf4>!H)lTi-(Qo=Zl*e z(d*uJb|q_m6|nmk{^8 zulo3rpvtM+&CSheB@<)gmr9a20Lhi#M^`~f?X+N(B$laxkcZpNLDvnM{N;5sH6ee4 zbK-I(JruA>nTrkJ^d&@5LvCkhXHT=|_WHtQr^fr2&fGVzQpxEGQyw89p{4n*XbM&V z--AV}M*so4tE2|6-ShdfJ}UsyM!mzndpPl35`i@Wuzf6cTEgUVa*!5lgZ|sR??gx( zTBf2nvBLdlNo^_kpoSHOav7mua7 zx~f=tHW=K*#BJaS3^B_i{rj(udw7R2>DhLQ;yOj&;nqxil6$RLD-B)4AADls7sJ_N z>-QXp@vu(I2K~nsu>dv9T$dFtehMi32Ta&$=14h+fS#xc0gxRO0E``ZsJPes5YGJ2 zOA(NRNa-*$YS?U{Av-(ULBtn?0Ekj5AjQ4@pFVD44Rv)Gy9096OeZTlEJx}|juh7E zasD$Spe_p!HJmH%g|X8_VC)_{U|;`Z0z1EGB+h?64jU&}@UH@v^DQzWz5ELM6IgeL zJ&k&0*380mBYwK){w6}8IT^oSdh2C zySuylWVId7_`6?CeKZ9`TF-7cn_DYSHl?vqQrmtsk5=Enpyb-)r7uM{A14ErH+m=4w?=PEU)`==n8C%jTX`SDj#43NsAN&RvAGqOPQKR%>w<$i0o11$b z^DD|Ccd_nes`E;VxpERqh0B@`&-3N)uh(a5o=5L3H5ryZ@VB0Lqm6F0SlQm*#$bpB zm?-dpzH(=kJ;hHmx@z239`j;z2$p`EjHG<_jEw6XQh<>n>3w~Dm~PjKXR;3;4rX_! z=NWJRe)DpoL=6gli`|pKU2HKjfL-*8XU!KParC$c{)8J>K1iGW`TBw!MK|l6C8DLp z7xwlG)2YBjmp2*m=~=nCi~BSvH8r&*MWoatU5X=fie*21AR)1U9TLR`b>2bg5-t0& zK?#Zf)Ba5dFHl>oWasd<;9MhD+u9rNehnRhRMd{kXlnOalji)Z<_{ zlmH|y0EQEa`X{FU7u^2~BrM_lSN}iozmESQr2jF~e>-CJPZHobg7?Lze{Ti&9ujBz z0|cxotyTXu5?V`qJty+-Aa?`nq}580;YDGX$Bvkuqod=Gn?I-2_lk;&RIx$`0r2t7 zDqg?Ija)2g@ae8)|NQ#8g(AoIlb~2qEL+8nS8tc1+5(IRxxmC~FOvT(UBDN^I}|u% zayj!cGSv6@jNkF6)2dvFKrv+@(lXq_)k1qgtP2HEHRrkr!K0`)vP&Zxusg)}Cgc4> zGRGaq&e3Ndtx1SfLG{yjRM+7K;eF!;Z#>&9a+#Od4eILURPuomYkz)Ew?(SjFBO{cOpS;n~U^`gRz!mA55( z=;d0O0u&@w?Coo1T6)rs1EfErGEf}xmWBk%nP9W_Siw_ zg*R(gmvcu>>Tb4CJ52H1nmWH>s%1=7h=W8Y? zj0#pc?|9r^iKT%LmA$-Z>P*OF0)d%T7tyb1%KC|BTR4eo z?MK5*3VVL(@=!sV=|B{asOVnF59$^`@pdM<*vpSA+`6 z+L`I@q9O73<{;T@U(bW5dC{MCI9E^qGSHF*lA^BW z36rd4+-1ZI=!1dP)zz4{wc{0#8ejL$9u`+*PLH3JPpUEQ`k3z(4GBDt?y9q+%wP66FG(u5E9_ z_opcZUPXCCxw?O9Mbx*)0O#e6yD25BxkufP{2Dam9E4X|DkAv&+N(EeQ!@cYaao}^Eg|})#wS##O!WP8dvy*1 zkR^i-EUy3OECcwES3$vMU>l#9oXeR6+}yk*`Uh^468Wgo)dn3~2C4hB@iS4me1WnP z8NmtI6^y;$>{M-hAx5N#hG`U7zM|;nd1K>cB#DkZcW`kjR;k(D*+J=)p`;C;a`1E} zvpLrq`TEKgUVcNP$z@uanq)XQ3N(IeJvNV>`fLCY{G;gnW^{`^(-t`O!BG49bSp^m zV#D}GFXWy=zDOc>iYjSP*L_FFmd@g+&+eX{1udD0HM3t{(~yAs6@#rN0YLkP5!na@ zdOWK!)=zL7!`hZg`%yOPdsi=o$9LXmw34XB%B=43!4b(r#LfyyXwF{MIz1;*%-?)i zhK|x!oGG2=7Tx(-r|%8dWrRl8RWd@*3N_~Nav{z7OxzYMz(JKb*yGqGU;SRni;;G- z*ugDCp=Q!yQu&>-e-!rgI@L>vkN6BKYp$(L9U!7vu0Coi-0-E=ustpAxxZng=b6y4 z)59K#F^=KYT~>D42VxIlS-;J{yR%FL1>z_WC#!KSC);mvh=~zZ&COYl^rWGGA$L!pT#hGAtbUW4JC({6Y4{6>J=yQbS~2eGP6)$WF^( zpnNL;n$8~+N62Z3k~keKe~e|Jxb}_SRw|>LsaeQ<+{mjar?e3xvqF|aDrh4Qg^LSQ zfdq#fKhUP;q1{L}FwrDFTci|WX&HSBA~z}5dCccmRBF#I@|@?A&uHkzYI-mX*ok0- zSqT+sBhW&f&p8%GFkP!;u~~{8CHt(69Oi5S;shfKHpV}kbSL-&z4n}(7wEJ;xArMT zw70_owasnUMg_Tl$-_=<`$mVAH7(egnfKI4vc!jTuIOseDy}~Fso&}ll}k%YW5&lL zzIla9Djy#*T(qTk1xPzM9U{1iNjzWTYNq-X7CE!Z_9TY0oC%%I$gj$AahI~IjR2|rUTHzup-iZ<&MsBnVqQZN(jQ9MvttTcs!SO+Hc+f`=g`7onrNv&K zyeK!TCpf5Tee-I!km_zsC#KsbLPdZTt(=Jyp(H0$3ds6ZmMJ%}(pEwkQKFfSuq=_K zZ}O$(FrteIyTh@;<_|hfL3frjzgJ<%Uj-RaZ7iYTWCMSHkQlR=qj3|J@kBO;>X;}Q z>BBkyC{@?ihTk&a)Wogj4ecGWwl%STp|Uh9L_jI2>N`;3H?7Sw1~0jZI{>mOr_yxD zcP4~3YpGp^R9zC)HB=-i465K1u#FqL4Sir}Kzy44wG>bCeLGsih|EJ0Q|AHRV7oav zkulQ3F=%DLdxx(J$F%zTNe7xGE2p<^baf~di>c)Rf*n|+g&ScO3SpHE_e@?RKpXvu3&vxs*PoemcpGJRWtED8Rm#PZA5&bhfJs{)- zTazKy`8KXKrG{XbaqCL=xlEciQGs3Ta5Oad7TjYw0M%Hu#usS3SzcO5vTJPG-}Jw; zLm+&#e|)BzE|~VNyLr~i6hDm1wtJgX_?T~?o^p=`h!4b;1o%K~wE@E+B*xYaNL&=Q zoD{{~h5}4p`~6e_M0L*enY`{ekOe%_y7dF?WldlAqT3&0ud4uRN}BLW1uNwL0hGbj A761SM literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-connection-mode.png b/src/designer/src/designer/doc/images/designer-connection-mode.png new file mode 100644 index 0000000000000000000000000000000000000000..1bf7593ae70c621fbb117832be53a71c74d767f5 GIT binary patch literal 9595 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-connection-to-form.png b/src/designer/src/designer/doc/images/designer-connection-to-form.png new file mode 100644 index 0000000000000000000000000000000000000000..320f70ff4794d083b202d36e0d97916e74bfa6b9 GIT binary patch literal 4504 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 cM4br{pF2tSwH3ME{ zvh%&?ec$!%t*o{DKQF%=eEbXF+XLLg|L!{Y>T{3o8wfy(|7!2OJBEjc$+8TsHJ;~T zj6o@dFjd~mT6r&f!!VS&axB@c=Xp*X$26NwzV_&I03QIL`v)`A)0{YQg43r@(`Yo9 zpP%QgrH|O?EO--@5xXA3x4DY}HJ`)oYvd68>&dxGDJuM;ggC#ih zrdkb*ZQin{Ua4i&hiX^@QRuU`k6QQ$I%Hq&pHXxEE9v4#T=*-P^;aN zwbHb*Ci8;<+_d|Z1KW4L{>1e3s(Ecj7#kX6_l?`Q|BIjCp|9UYQjJmC7eH@aHd)nPvPy5rmp#DB#Jb595ar#}R?=v$VA2z$8h+(9jS`EfF~e1Grq= zmV@$)3w>I9C_-xSD35*X9tUD-_5zRXKTJD4Pn_gDao|PXJvztKd;_JySSwP91R{-- zp~wr;G#w0}f1p6zTpG>gb}OYC)_M5BPqTi_Fq1Qj{NSmV`SCAb;mFZhC(p4{^Jry7 z0;hLLeU_&9zE7o68GH)p2cWg1t|*+QlEqvOOf?+l`+GjeclZ5{$?0>T6j%Uw@Az5n z`lJ!?^1NfP>;OTheR0K_a(Oehopf?nto7Y_~9C~T9F;e_MIPSR>Fl%VU}H90$PZ?4{W^|}X&!f^It#%F@ng;Ls48-!IX${F^}cP82L;Su~WqSG-l?|vvnyOlB1 z$Vn4eSm;o6TPvGTOjMp{pm<}R>kvd+I}C6 zMw?TorZ|3bhJ}SDQ5cgO=(IZ&#&E?*Lc3kyhYFKr4lGvT=>aAzohtB8o?f=o@|4J)p(Fvo4q>@h{?we1lz>XK+QGfGcFas9b|0a_x42T)s>$cPB zxbhK2LZ;?%@sXMX-{5Gb3i z2tcTn5D=2(;qpXC2m{7ALpmo{VnwwQvU&3+Hf*?(daXiX71rW_=UGNljp%k$@&bG9 z_=j4nuBe+pbyjBJ*=L{S$dMyLd;gcOgxr^=bRV7%Y`WPss>0@sj#SY_N~dMW@{GXq zQCg8@Ig9NkwWP-Sb*s(BjlsoA;2Tq{+y@T6e!^2W+@`=PYps@yMOn!J)@7QuGh)4X zl~Hn1DvYj9@O_O|+IBk9zyds9k*3%vg5{;0E5|AqhKK7}B@WEux#bT6crq5@U;yBA8VoL z`$p9Kmsh3YV~jXn6GxiB7gKupT9bC$U_7%APyjiQ0$$%- zzXaLgL(iD^j=bG}gvFl#7OeG@HdZOCdQpBG09?QI#uWkBzG7WK_3l%^|2F_f_doII zZ%WX8VR|_SG9c~U7kz8I@Q3FCjsXa?{(sT$fuI+DS-3CM3cyV-ie6-WQCMpYD_s8q X&4>18D&iK100000NkvXXu0mjf*I?IE literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-containers-dockwidget.png b/src/designer/src/designer/doc/images/designer-containers-dockwidget.png new file mode 100644 index 0000000000000000000000000000000000000000..f4dcc0b7f05125b3b33d9cb50bfd6ba2777e270a GIT binary patch literal 3259 zcmY*cc{CK>`<}trvoj=Hga%{D63R$f2Sbdp%nU+_7Qzr?jbuxuj3u8^*+xc_xn4)-{+ru@AJOT^PKlR?>*<9n_y#YdW`cpCjbCAW^Q)rG5`RC z9-V))Gas!7Qq&0mfa$4?rM>YH1OkCfOiawo%peemgM)*Ulaq^!i<_I9hlhukmzR%^ zkDs4kP*6}vNJv;%7z_rBii#dTeq3B!{KSb95)u+JGBPJmo|Ki9RZvh+QBhG)z#C})7RHOfBw9op`od%skynig@pwi4!5+lw6e0Yv9YnWwY_}#vYnls zy}dmWiF9yqK%r1BE-q*^8iT>Oy1Kf#xm~$(#lyqH)6>(-%j@dZt3EzHH*em&efzeb zpWmH3cLD+ea5!98SQs9UkBEqfjEszqj=p#AUTkb^e0+RDLPBCK6~~oJw2U3AY^1@WM*b&XJloS6&010l~gLVuCA`WzP_QMp|P>Csi~>Cxw)mK<;#~Zt*xzJzkdDp z?OR)0TYGzZM@L6zXJ=PeS5Hq*Z*Om3UtfQJ|M2ke*x1lp#jSXdG z(UFCPWny9?Dk`eGyPHO%L7`9q z0fCs9m@{Y23=IuYC=>wT(B0jAWMqV$oqc6x#oF3hL_}n6Ztl&SH&`roVPS!djZIEY z?$oJM`T6+;1qD-6Q#Ca;&d$!PtgL2cX2*^lQ&UsBcI}#hfk8=0Nm5c038wb*Z-3m1$qqNP@FvhZ%gZCFvOUJU< z5GY#Woyt!CXJDo|!0!U3>uaz3Q`&2IP!Y5@ugbn31Js|UEBIGM&1+9JTapu@y&$;J zEm<6rDu=mtJuNCNuTj*JOt&jV)HG&Pthm!ZtM^>_?XXQC?Y-7oQMp^|pFXRr=jZGC zaL%85P(%YO-WB({Q!m(bO_cdk;>AiLgpX)oULEBk^}qmN-QT&9BK1L?>VG@*VZbzfDIUg4Ud$+j4}=Z0YV##=g)AQFFr)rd87?yClMT=u0B zk2_|*nz7feCgADI=Tm5m`tJ8xxf}EYuch#UkJf+oZYsyePpu^GNyqy?Tais;svcf4 z`cXh;z&(ST0ZLk9{=+{bHxBGzX$1#xYRH0A%~u$G2k#JZaR0G%HAgo0>J#@Vfh=Lk zmhA@xJTgi=o`M*5D_)8vVZq{pS+{*)o(d2{5Fm%Cgs-{q_IeeL8MXK#2_DTa2Hb^K zElRsA;{8F=zPdg{vz7!GKz=0iezzc{3#tu7VA zVEWhg#EpfUC$5`TFWt%&9)ds;qio6{&Z{6>L%s_j{!hV|_cA)h^{aE-P9 zS6`)W2QoaiIKFsyoFYH%aal@UCK$JWhI9(N-da5rr8SG>r9HqzE^^|QAiIhHfc6*E zb6?Knc+;1SOijqhKx}?^sFQ-SI42HUyE{<~n zR_7d_ioO?xEq^`Rm$ICVFKlkrsQi|>uezASWvWDAD&dtAU1 zrv<7x&b%eVX;=RdeaAhDZ3~;`Xm-7SQ-+I65oqM<#1J#ZQIAVlp~5ieC-uQ$)8loMx84opFclW6kreg^0Vf z-A*lQ@64rorn_ho9H^2WV(6_M!&*_v@yJf%G+BS(#8)VOin(2K+tn)N#|fZ^b*u5 zJB|v^3tcvo>fy;5=mLtxEAkT<@{x7iFgsGB4Gc8+BRhw#seywKaK zv+M?14naM{4voPJlTYf1u6P62kvjhR0elQatyE)w`&c*;2SGb2I8ctvO2X)^QkkIA zYStxPCTUa}Y6uZE_NwBv@{v8wN6tVUbDK&xxw4Ja&5}nSD@{~J? z<|nbEVi#fEh;%V1+5}OnL?O|;E&zrEaT$JMHCvdPexz7MpquRK&@9j|?pyC(I5_AT z&an()0m0r``ySka`~JS@#W_h!m2ljNcMKPC3zzpcRQ4S1lH#pBXi2g6>#QWg>x$Oa z)WYu(+D-WpYawbC%TOyE9i(ZV@#fe!;k2JoA8Q=SN5xO5GY2If=}_XbD;`@({fv;w=g}(Tk#be znQ#kO4shW?5=as)t^{o)KdZbVV+Brg{|}|ymI@lQ3LrlJy{fJfruu}5YlKoLByqFZ zC<`jzm`r0-7amn!wKWZYSTp&~>{wFx4xZ!myZThFKka4y;V4lU&VG|xSLKqidnB~1 z9$U(x)I?R4;91|g#Ae?Q#)4;omEKIIK1p~~Y*HoMFgVVwmaD$m(-dLnIGUWX^5g}B z$w3OHRT_U<+111>xV1ePh(IDFfIiR;oO+j?;|f(-=Eo`67-&TE!?K@_7NHW8d?O?% zJ&nHuBS0EuRKM#PO_9246L5XF`Af?)J(@@dM&nO>^^2L?kV+4F2&Yc=&ub%t0tv}S z!yw%_=Yai3a}<>YABLKQa>CoaIOd>YD8?410IqLW)bohL;*XaZ!ks!peiKOe$T+SI z1dJ-eCcU9It3~HeD{rCGpRPRF!c2_4+97$rk!uj1Nc0oqCt<60r6ZD5&3vGDQKjLi zsPw8w8-j@IS{6K_mX?}y5mWAf{duaCIyGDEd@z*t;BUnFNPX16 zSR=lFYPa6`7;E8yOFL~mD&Pg_y%$LqHBH-ujm zBf!f5k*XKOJWy}9PQeIb=82BW8fnzG>|6}7a7<~p6}@&9jvZfJa(?4aEkSzh;0-e2 zM@l)UegD;fJxyq8MJVHv#BFCGH5X7q@y>@Qm@+MBX@!qsShkJ|tGY`h`8JyZputcG zv^;!V=(3^SPsM?Ba>(#&BeyekvAVIQW){IH(Pt7+GP)lGg+2G>m?Ck7!cX!|4-wX^4a&mGL6BBfFbc%|Ki;IhokB^a&k(`{I zrlzJF8ymB;vm6{89v&VaA0H?vD9+B#*4Eb7*VowC*fKIQ5)u;L-rhGiHvjt z+9=XU0v?(?n6UE4-XIL=jRm_6}-H>y}i9ZKR-f3LU3?!6ciNmd3II+00E>) zL_t(|UhUl1it{iOg;5> z7cYsnL?!uzhzunWB1DMl2jZlJxzdSh=r$T6M2HY+m(uU8XqWJb5FtW@NV~An5FtW@ zNW0K35FuKsQmL#*o0uG|9)cyBpX%#GE1A3bTl-`8pI4pr#Q*t1?k!UgX`XJp+@-=g z)z^t?FO_OdZQiFZo%Kkkn;GkLw?xucjopibkWck3mCa_yEmf1RypKA>o)lGGincVX zDe;Z?yN`tIj=rl*RIh(n!_= z{riR+BjTDWrmi)jq#uu^H52BkzD|^XC$r}|@pb>~XmNygfd~;I=PrB~Awq-*k#=FD zAwq-*k#?b7AVP!)k#^yE7up3PM1dIR>jd(K31&(@me1;QDTDvS@H&CW2BPmkgvdQ( aLF5ycA*6|2tpDWz0000J_4W1k z_V)Mp_xSku`T6MoSdAUot>VZo}ZterlzK+r>Ci@sj;!KJUl$LwY5J# zKSDx63kwSd1_r&oy}`l3Mn*=)#>N;J7#SHEQBhF?0|V33)6~?|*4EZnS65nET3lRQ zU0q$=+}z#W-QM2b5fKsK;NWX(YvbeNaBy(t<>hj6av>oh>FMd~>+9_7>~wT=B_$;i z5)ylRdwhI+g@uJVIXQ@kh>D7eIyySi($Y{+P^zk`tE;OiDJfJ`RJ63TDk>_rwzexP zD_L1tEG#T7EiJsfye=*-z`(#>US7h&!o$PE6%`f5#l>S|V`yk-$;ruSX=yStGR@7+ zY;0`N(a~>jZ!}93CDXA0Horf`WvEga`--0095t z;o*mehd@9;BO@b=i;L#w=8cVwj*gBbBqZwU>O@3Dk&%&@n3(SF?wXpKo12>y6ci>V zCMPE+C@3hUrKL+tOHEBpdE}Ge000G0Nkl#D5JuIu!<=DeW`@hm%*@Qo z%*@QpjDK(yjb%Bd?50+8rJ2<2@5Qc;7AH1hBQ|1x!UFS; zk>d$$LUTK?bsn$uu}Oh)J%IY2f#Z=9TkX<+ODZO^3p#>}tKC>uhFF#b{@l6=JWJZ* z47*6A>H6DE#QFEitIiiIiek&tO<|v^y3}?c4xQ~!Kv#EThVbRDEx)`qht0@)zPB!Z zGRcZuubod3+H>^=F|Br8dp1P=fbg1)$FCE_W_{d-Us3WyV>~^9=hsc_h;V( zxF?6*2U`+s(@OfGHNdBPuP|&|Jz*J}Q|GOACkY0Ffp!2`MH)b0KUo2A?XR}d)aE& zdf4@JGdKM+{y_<~411pa(w2Wq3T*$meef7WR{|W^2~~GNG~2^4mbUMXMx)n`yXSV< z!|}sQq}3MFd5KL|jEwhR*e%ZwZ2aIf{7IKOcIIcOO0e!Sb{uGy4{V#mj*eO1-pEMU zVmdF;>AJ8LvuX%5L@RdkDcC*oV##55{cZ&+W2J_z9>mrV#4g??b?j;FlIDZwHew?- zVk0(o$<#va-CC3(#WTZ4;;jk$*rJkCa z4B3UKIyNzi#fBZWXJ&c{A-$6mc34nxAIU@{myyZ!fy7u>VU@|pL$b`e__cOyJH)6d z_e97R*uqCTV)3Fs9QkhF--T5s9}gAxwItutk3BO@<}yC+$L2ngWAi-LT~KB6@wB`L zV6}eiX%9Q?!xlc$kG=GdK*A}gGWmF3){i~qVyFGsOlQ*MVfWQE{n8^YLu8 z?{;)|Njo&PWCeDQT|Cna%to~(g7hOPi6)$Q&3rD3V#VbW0|3=CO)?(CdS9p>Cup_L1k*~ zl9ZZuNgXQF{C5)bDiehRVk0&hS2V7=FQ^kKsSD~vj9*ZP*oa-CZ%I`v)e)4$h=QCl zXr+kt@#C9ipQ9qD^mR$uN;Sdog-3-AWq>J0A!=+~F_Ri`H2#)!sq?JVmO#FEy3tL= zlY*SmXr(x{&q|#;xi()sRa5b#Ag459d(@tlBEh=uJ+gRGkW(751+~ySv9c?&cv6s4 z8nFenz=l+NOcGBja!R9d^{D;Eb?ry;HFQ)?fhVU}RT{(P6mU7E&u??;QNQ0_G5D+` zo>b+Oc`GH0CxtP_t&}XD6vpW3lEO;Ked~TMo(lIjPfk_D@PA44Bzl9`h>h5YjaTnr zmqcvy|4S;G9D`O0p!xHl_0liW-J6RETeecL=IFAs!|V1J0Xt_DySSBt5!Y(TcW;W) z_3axJ1e8uIb&MPW7WwW?QF<;OR}fG-tyE{0ZREQ*{iP&G0?KHYl&w@BIXs6?f(h^L z%{q$BO2LF%@(ZXG6;Nioq->>Nqyc~HUs4cITCEhUxwQQ3@a+BqcW?Sj3Ia;2 zl>&HhePHA3%L?4R=`Se=DD75CJ?5>{9rT?9g#%)n5B`=!;egnPjo1tSI4pcZoqgM) z;dn)#P)BRZdq$3D;v~T*)Zvpw5gV})8?o_e=O*(HXa5ct?&;Ad00000NkvXXu0mjf Diyq9I literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-containers-stackedwidget.png b/src/designer/src/designer/doc/images/designer-containers-stackedwidget.png new file mode 100644 index 0000000000000000000000000000000000000000..3239e5291692bfb6b1cd88892f357844ba50fa82 GIT binary patch literal 2192 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 AEC2ui literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-creating-dynamic-property.png b/src/designer/src/designer/doc/images/designer-creating-dynamic-property.png new file mode 100644 index 0000000000000000000000000000000000000000..4ef6225d9bf86aae9d975d1ec21a8837d4df9954 GIT binary patch literal 8528 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-creating-menu-entry1.png b/src/designer/src/designer/doc/images/designer-creating-menu-entry1.png new file mode 100644 index 0000000000000000000000000000000000000000..19491f7741e7c42468535d817159fca5e344f9f3 GIT binary patch literal 4705 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 I!S6o*53<)l1ONa4 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-dialog-final.png b/src/designer/src/designer/doc/images/designer-dialog-final.png new file mode 100644 index 0000000000000000000000000000000000000000..c1363b8bd96e16d38c1d787f876f53edcaf052c9 GIT binary patch literal 7267 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 F{{ez73TXfU literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-dialog-initial.png b/src/designer/src/designer/doc/images/designer-dialog-initial.png new file mode 100644 index 0000000000000000000000000000000000000000..6644e758e8b54ffec44169dabbe7a65bee144aec GIT binary patch literal 26271 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-dialog-layout.png b/src/designer/src/designer/doc/images/designer-dialog-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..bae945d4516ecb98202af55ecefd3d9f94274bb7 GIT binary patch literal 18892 zcmYhibyQp36E@mHfucnU6fZ6Xg1fZ1yGw!;DHI7-2<}!0P-t-1LJ5W95Q;-_E3U!a zUB0~c_uaehAIVNmW=-bov&NozcC?m;B0df!&a-FF@RgP1bWrOe>Q(s?4fQTbn8Wbw znesPfxet2Ya|c;i7JA+PdTRtF>9OgVUKhPoI?{*qhUAYZ)Z?;wn8K6`Bw7J#k6%RjSK0GSsIe6ywJnUEw0hGM+ueW{+Au6 zprj8^93w}ZYattglp7 zLq;@8CJyK!=cnW212y$4UY5H%3x}0qILLnuqg1Qbd=$Dm<9Ne*vP#~OOAisg6rt`1 zLZR*UBST;j>Tm?E_^k4s|f6+p;5OqGhpGd&P17BOTCac5ya1~5!rd@ z@o}n5R}uKTzpERZVCJ>{a-W)t%CUxO&`7%ODJ3nw_TSJRt94oMw5OF=Sn8=9-p+fE1VDnAOGQ3HI-K_nEkY95GK0{&&+37Y28VQ`N8z ztc_0R-9Ph!&&GjY+XuF9)05J7_N;4-)OMfykf#|7HA@P!jS365Ieb@<=7*XJ2<{p1z5nl>}+hD)WJ^JlDCA>C>AKlV`G z-lleRc4}+Q<@)Vg@fo|hALRQMxc&MCCMZeE;S*^6ELvy{d(KXQX%2AOW}bE&7@n*5 z#ZOu=GnLar4p7$pns+mU^nWOCX+77e^I`Mx8XJgx{GR1>btz=ZmCiUYyc)e_!nsK_ z?K#(SFuJTr;^)t~f8eGjdN42aH3l!O?W3wi&?~Mny#sswS)d7*#(5HN#=Pb&8*SC|M+*%-3o8 zTy}Aw^0T*mZ`ilMqnO7c&$|!LKUe}aA}e8RurDU3*hA1Shhl7!6Jd`ytskG=-o=x$ z)YKZs(o*d7i`PFbRCMd-S!$bEW&=%r4s+N8#!}DED+mrKm%dB~-LV8hj9ZVRukdb{ zanHUbO>D1qJYrnm;n7TbtzMb-E-amGkA*q$23{U4y0xzjfrcQ*ooVj^Ol#+Noq8Pk z3mNjk3_>S)%ChtIpRW6KbC&&Nrx^otZv97iqbUK{t$OGRm^6S40pB!V0{|mqLDDyp zcQDNm~~{g2>RSNno5lH%SXGx5krB!%jwlrG0%+Z z;KQ_`ViHz?)-Ed6oSWX!-#5A3i&!?BMgq+a7N3be)z87>gxicKNJ?*cpi9klYE1>`s`=KQOZf&93NxlT<>RtA9QqTczoL` zmuP6oJVve`;ca)avG4Y1#Ip(Y#XV+LymYf4c9+Dl1|J{?n@*9v3;$|>i&f03^*K^m zVpiT;w(7fGJ=r&q#|T8LNpXoC0Q8G7h1*%cz`XUm&KtXx^UXve3K&Ydffa8+jyLbL)x7~3W zUZpg%gf)JIvWn$Y&svGE}c~^F;sEuD8^THm_|rQ9l~w7nm}GYxEWj- zLg_#Ug^{iEvNnA6^jEw8tfPi@e#X7^T<9z+F3F0XMRd&1LrXu{8x?NjSox)Cdma>x z4iLm?^-?Jy{*`_>D9NEd`Gd@9J?YpMUIvko8e6+Q2~7Zvulwh}K%0+3S!rnzcFVue zBdu7Mh1Z9p>4t8PABC69=PHLb9(?PKnj}~xB->suc4;oZxn^?}*nlaA)kU zIOq>yWzb0xjqlDuXo0YR6K})3zAUwdi-xrj+MTC_bKNrtG4bQc$t5nftkv1LlgK3> zBY%C&oa&$2p=>HjN}i1|s=A|V{=$ert)L@!z);S`YS@+3W0{v+96~Zf)8BcCoS)A7 zu={2jfA4q;#47A0d3}1u$SQp^%dfd~K2|}xFLiSGK{ap*%t3E6lUpY$dim$s@;>$a zH_gEQv7mLxA7MHMI0Uj&4P!5huFK?9SVFVH!ivm`HL2j?2G+w&wtJ_k^l_&tS#yT_ zlt@#dJXT8fVewUQtXAC?dK0Q3kgnWMSIN>eqBn&3#$vIg&t%1UMA{2xNKQojU0iam zXBg+Mt9?iEGz#EkmJHOS{nz8%+=gB37jF|kbVU;rYZTeqTbtTVeI>CR(JJnXqBIv5 zwnVS@WfAp14ie&RemuQ8L$m}8feep!79akuPvX(Js6rGjgQYdjk0y3U6B}+lI|v>r zo~D;sQPFMm{%;fbL^%@LRc~(~%*BOkvru*O>TOHcjy@UG5zHXXbzZ>B?7fI-J|eeg z_8{y1Qaam)KT0IAZ8P0**2ybhd^@j&shJjU?d6M>aPU+_O2B5iV5j`+q~iqpPve)T ze?3J!s3|h6g>SE~dmhh9HhbZ_f`b;P6LD5T`}mTXhaTqT4L@4M$9h|AAB^+?(%j3; z&$G@4KOYV*FPpj_eo8|iyx)~LKlzQ1*KBY568Qk-rO!cqZ+lXt`6%VF&2XBm5SPW^Hdfz6v?=`C}&VOi&zuDOJ{E;4@PYNm)I6 zAur^sF!t&LO85`2vLzJ0eHbynKK>0=W^7_~jLT;7iUW!rw1&nr%OhIRxr`62U# zxlvzwnX$$!RV;MES%3m}!^yP-c&+X9w&dzKllrkQ;pcq!b{to#jv?~%$9!-*)mTm* z#srR=*yR;n#JfuBnc6#xP|?xe-X8}@SF)4{a%IiPiPi6Nm+QR%3MuU+UYhXZpkd;W zuQz~Pqi}615{Yqj)72LISrOKGulsGfhzRvtD6J$*h3!yWh7g~Y>xsa^q4CSs(~93b zi2tXK)X{l|2?f)ceL4#I?C30r4rD8;}_)oG<5mw8dRA&--G29L&#j zFp5|t2LYzfPYbR}6y>=#)@B6R)4zD7bU{{aVp*x;? zFFwzc@jFcR6&5lVR(X&I}Uqp}szzUCuCKmFpNS74I&C@)UGiTifr=T~yAk zV@86XO^^a`N*H^83<1e@3SDF{C_a!nOCpCHOFfa!`-q=}LS#1qxCA#{zM1&-DM!s@)$`bP= zCZ-)~55>~e(Gm07XJZb$KT$L>F^MFgDO~>(jQ+XaWf`b)d9(~o|B>W>cX@I;SL?{i z?0a>%;Zy50Z=iCyKiipMKT#Z)aQr^->EVk}pOA7OC@KTVYTE6tl5KIm7wZ)rdb zW68K0FzIK1Sz~22`kZa*5R;H}@;9FTeOJUJCYJ7K|F0sAW7TD%SiR`u-Q~ga=g-yC z1rn2-=IbypFwDW-q&n3o#gu{$0uGZ=l?+U#b)T{E@ttO>EI(k6gi*;=qjaVVIttm3 zzE=@)nv*NOI~@SPlH#a@iTUB6HmScH%wY?AQ{}A8R6?IEgya^RJc~X)++GO0XJdAp zu58T*capv zT=YU;p;HfgH$LKIAvP&0yYp^ST?tz<(rHPKm-v=gGY^dH(UAek;4U(GSlv91Cbd01 zW-M_o=WM%3s0d?ItvU5Cbw6KZl2{txZdOx4U3I!qNTe3?h`RIq=Bj^6!+DSvFEWCEITJ9BoHR+t8C9(T3U*I zXjQ^)%|e+zCnK|5X(+tgyFrCJrn=L6qx8xso|qQm&C(!@Z8>Qk2nBUpjz?#Fc3iWIMgM9hxH%c1VWLqt9392Qi@J1u!d_1S)G$ z0L3oOjBJSO?qBEQuIL|TnM(Ii*IK@t$}l2yVIf~1Wr6&xSKjx%d~4tm$EHYrk(m`0 z6$8xH*E9-QkiD@1hLLx5IyzxsUOf&8Alo5PRPbsb9N0QOF~My0ex}AwlZmnfGXSWQ zbTI(nMGW7|J9EeZHn`8}tS#X~1+bgpa+0F7qcs1sp<$ESC~D&Ai>oUvMRYl)Of>^oE1fo@qATd0sn zE>W!r(5K(5R49m$ji>dDr%aLjoO&`y;rHXu#ajAqq@oIFG@Iwn=*JJJ)GvtRZm1uPTn4chpI>v<#jrFI)o?62X)gb*b zRVjl|%RrN+5nWB7;OXUho~*jvu=ftB2}doRwD94obRMkSutXz6fr&&FgKFtBpMEZI zykLA>hn(vXJlOE-`SSpu1#?6C&>D|j2Nyv2z0r67k=jm7iF>WnmGu@x>;Tt!kR8rd9U+ zrHS=eSXjzrKNReW)uJU0^#X~edoX>T^J%UVY}cL#$Xv-y*Wyq?mJF8AnstRk@ZKT5 zw)Ua%WYcH7RS;QQ$(M}V_&KICoJ{N`FM$s>svmYexgcJ8$UCp-6kZw)JPqv+jZ94% ze%V^Fb_1pj)Jhonq0b&mt_j!Q`!(;}N%nMqHu7cJzux=Zx{G~>(sw7zjjy0#R;-ER$0TIe^%buq(G}+&)`ryAf+e^$E^-f3_M+b zRbod&4fcP+q9eSl#<>OuBc2ob-=5Rou`>N}4{PPf!WEM ztyfFUANz^9v%6OAZ_c#W_TX*V{)TX%`e_1JX9~N}mXi5B8+yybAp~w7&Z)(krDm?K%cH$ylTe+OqEUinQ!)m z0|#J$!0XjW<=q0OmOftVo(0sie?t!xa%eZD=COE2&imzXA@E@jWK^t3=l_~(wa#Z* z=59b0z^c{|cvc*@?0>cB2yg9yYf9a$qxL^4UPf${Z=2R04-(Py413J5lxe2pmGpgr z&_c)dSOJX7)N4xL@Pw~eBMX&}*8`vKmPuGOu#uzE5!_W8SPES0-TuBEDYq;mQ=;=r=mv7! z5L1Dh;#$M;4|8XNev!#fo{jnLiY_W9=6bc)(`E>|7a-xf*tERtV`tFp^^h)b_{3IN14gBZ}ljdUAKxCPkGVI zvTx-u{EZ2Xm;7CfiTQMHP^ z24KzjjKrZgzJ=pFr68|*n^|l~*`;9>GlHA?+u@fH$t&0OqqZkn_q##eju za2e_2KjKjV(5w~21_eGE==cePejo?C6Tm71~ z3tl~*aS1FDfkn#1)0e8dVM}8Mmh9^*?+!5&~>b>g@T!6*BsEDm!cMK_1;`HSs z4OM)`=wILTp^X?{vVwQ4NNZNj08|lTyucL`9-S`aL^&;Mn74;a|CMEr%@Q}Dp#BTS z#_UuT&u=^Ur)H&ORKtw_OIRzNlwZTBs=%uld`Q0y$p2!BghlOow;Vod>l`!x5t;R6 zsa-)P$MU$dAos{p9ptk?dN1xLor%r6qGAsK5J|)XiL0I*|5b=)iK+V@f8bL20M%mr;r6G9W%i5no*N#i4CbSj{TccQ5xZTLd+>J@P-Fy*5tu1 zZJ9mB@%atQeI#7~As7h*La^i7>j|hu2puC8G0RuGwB{bNK@E3oVj#7$snJzOmZ(%E&q4W19X?SEM^zH4LC zj`20P=1f3&{INIC%zv@zd}m^aRa5Ld%DlaVrPCLK&bhyH5`|No|CWypd>hcu z7D^ZfEa_GSM&R6=>t=6&LVG!gv z&`<j-Ft7;z%+QJxNmlTl+pESVZ3-#qV8_y zQ}hD{dr1q|gOpv;L4n!G>bw#;x$6ShP{!7RD>}ru@z^`>QYY61vt?c4J+Ta$grZWW zX8F&em6N~7UYn;eM^gh|=|cx5zL-NzKGt4h(v78LmRu6+Zci z#6$_=pi)N)2^1G`n@>$QFTiwjB2V_;#dHCT zV0yvlxIyk;xC)P^eae4pmDNWu^FMBd4n@wu;PvBGX~$^-(EUR?!-GhmJfQ|+#V?8B zzQNCkVV^ap;N@vF=B}6M*_81kpSbUm@bcx(#8sx>g(?uxor&eG|I_44>FW3!z_iKl z!Sdytq>mXAWm^wd<$j*j1;hHSZ?{d|OkE{&uE9G08 zKjkgRO4?bzS$q5pbXfCC8t<8kdg+u0$d$O>mHtY>FZQRcsTUys^t1sZB}%+VHIOy; z9(TdpaYcB01zDdM2Brzk`MViH;PyH|BGYXoko8GeE7wbbH`h z?5cv~<+dywaf`W;H{mm(boge%ZoF%qUKT)ufH+%N97o`1fFPf)B(D~}bmD7~Y)5Hv z(C@#0X;U~@1zU<<4f}hHib68=^!3`g`Wwt^0m*Aq{7EtU_rC$_rd zi%7xN$xUrN3`EoW<%Ry-51~x7SlNtW{*1>BD~$&}SBqPYQ|V8a^94O|)}R?NRFTck z3D8i`7baHMit!c;S;24Kie}Bu#m}VdY2)!|5Oz8cb~$qU4NIzj-WDDI;$0e5M5!tP z4ETI8TF1?-&{1?xbqV^kM)xKrF7;_qcyAhRGutvVL$c_5-ty@>$~u1GH}=)})cyux zA|1IA6|=#Kh*8GphM3;Opk5mSa|@^AhV|dL6+_DUVZ$lU()GhMa`J*>M)ST4yDWVc z^ls5wick_jCX4>=+l*!x_Y0F7xWd5leY!25{-OVhB2L)rph=J=f`gc}2fpoVK-9J)?-*|Vxd2|gwJ!K1v` ziDwXIYRfgyy@@N6zw+F`N`i?65G399EbBl8WK)ron3S}Cl;gLY-dnx;!0)sB_o|la9BR;#_i2K~b&`V1 zqC*sbzWnrX6lf*cxXXhW>_pb?5wXf(8>!$41_gBf@J_py4Sf2BbUxw-=SPv~b4vhw zAkN$p1f{d6+Q~)YcKaL#QowYlIuvHaY?y^gKZ;0EswzJ|3lB<;&+EupQ(-~DBX+~o z`^*c?Zdb^alV4rYX!rCz{6vwuJl?t1Ji9hPhIRL1eV#u*g#zwjbBQqdsj`}Y#q@>- zl6FCvq;E+^FX|Oxhpc$R*T1$mUI=RF)$TR+H;D27I^ef`VQW{iJt6kX@2mMJq=&AQ z@{hU&ZeO!LW8??Ogo0G3Q6RlR(aS$i7#7%8k z=~GXYylp=>)7rlq-G+2NcTQc=Oiw!Bn-lEdEMNx7fE#!FA*HO}RCS(vo8rCDG|{~? zjjSf7p5CGtm@JF@h^sJc4q7b7&|Z12FVToJu1W@vTqbWb`R!bnw-u%2++)Io#`4={ z*ztPu+tLr`B=~v~!-GG_R!%(I$kXUFr=&C0*1-BYqCUX+I z;;?^G1ncDU&Xk;g^$3&>%%pAD`VAc^J1M}D@Tij_f~ZYl4(7$wrg8Pfqq_YNX}$lF zcq+}+HVgKobhlFK58ZNu?T+s5HLz55aecGg5L7eAS;D9K^cWERUeICTcgM|c3X?R# zXCh)Wy*6rQTR+dOTaPJ;vv4kc&OQBMxrEEPky(GWoV*JeuF~bUzQH|KzD*^C4u$j6b??{<#9cQNp6T#)$|21v)dMQ6w6#3dgxr&=H5&H-A~F8>bChv_p)YY0(GXcXa) zLK$DbY)v|wYSf3)k;_jK&7SXWVBGBfyS;e8J)UZxTLJ%?OYfRn@6`_@_=oxU!<)JS z|JHzi)50mJ&giFVzDp94gO-&XgVvCLGnv70w)!UB>R4n<@f_+J^v4Dbr9SCzbT7u2 z_2TN!o^};Hor%up{%IusLY$O{0JPZV4y)LU#FZYN2u*xxlC*8A#FId5 z%t$XES?2yNsF%5#TMumId1+xqb?Qju0HgZSZ9B@93+|Zh)r*Hw%C1n;Qnz z^Rl>}k-x!kgcldsv&I>qpI+kU>cRQJ%&-96EzXp}_8q(dLJRUgx7TdN?{_cPH{B|? zvpHE0eu;-(171^L1YeLax*%TM>~0s;1BKtI*26+%7#xP8KBD`-<|Deeu;>0P2*vaw z$)i@_nH8%6Rp2^}4LP;Lc2~<`xFAV3wl~AG(Sh-r>8{J-NB(`=Jj}ejjeKWQBPHDL zJTafLCGYl}7ik+WP`4Hy)2>DCP+*k#dZU21Ock?EfNt*KkQ8(LGBK&P%UU1 zIcUA%wUrGwPd823xT-2nH2Q2GLQ4rxe4l3WggiTYCiWIS152(7;nMH~ z5C0%d!J?GQ#ju8>nB9X|sI;X&c zs&c3}bs5Rw_ZEu<3ixYD7AkI)HFrWL>gQGOPDF?k1j!6-2NQ!Td>V=K7~7dF3Mapc zI#p>o^kfS8=@W^$Q~W_XP9Jd-L#+?AUUO@&fe{R^iM3%Vr*4sU!?68q*+8CE^r zH)m6!Xy~I7GdW3cZV_fP%->=WWchp+o>eduNnyoNS~^f#8njw%gv-RpEGqg(`rxs7 z>Vz4B_lmHfD1S4An5EQWcY*0zmeiO$IS7R=Iuj!p^oSrvMt%3;ZKT41nbRykQ4!_} znXDjj-p>k69r{v}kWnwpT)rwY&SgX%`G>T-{B4nL%InVPSjDH(-?+ZiVm!ZoClbFg~5?ex^21@b1lxmCEqi3Ex&P?k)H?A%crUJe4w7yAa5-j=kyA zZeeGF!B3$@6vc}7UfbBHbCR|OY!)3-OOye=QqA}j+mliEQ0rKXGC?J3OC0a6!J#%)_&8%k`w^|G2gr{SjJUSI1FXtFIR5TjZVR{7-ZlZ9|s zW8NqyPocwt^825pJ65){$F6bt$@l$ztMoA-vq9`)ic7`J+`iD;fepuQ}@0W^Q6tZR;D&k+a?ND6V9G28a9NhZ`mocet*%pRSBjG zg+^G*7(o>Z5wD`5*SpA`IQ;YDsgctu#~Na94Gpq?eS(O=bD?zNmR6F(zFxyERy39tPEx^8t6wTx z$rf_@AI-JUDiyI7nEZ;;-t6Iq(NdZqn=#tRU~~XlC@f*Cn2ZXBJ499l+!|^7m=Qps zUlK5szA8fY3X~-(X=%Co9AaT4_xDA082k~+Sig}uJLKHLd$C(?6VOt_z9H8deXe)Ozr(o|I%yHNqBpxj*SjOqfITBaZp1CK zF(_*#R1shvM`M-lL4PsT>3D00pWg^y zB38=p1U|nx@4&qVXT-v}M}@sxYzDq_++sq@U(!^l=cY4-bAUUMUr({yO1-Ji)rHKu zk?Y3S9Q>O~N?3KyC|+VuYxo^lYJZR@Z}sIl6QP(4eOYFJnMKoLbD*@>ei#&YL*LF< znOu^BPEp;RgQQUs|K8in$o{BE8q;gv*_P7wuu!c}6#{eK_ivc>O&Z?z%Wt`$fA<2aYRc_7h(?=JQ%HU6Efydlm0gRs zV6e_9V#Lmo{BslUqqT6WUgWV=FtDDZ)9OWLsyC!}SaPxHYOpT|&FaT+fEkqJ#(ANt zSb5@IKeTy=8Jz#3TrgnY>4DZemv6IogarES2t1f-siE9IIq-X5+&AW5S14m@2184w!YC#?>@SqtVG<|N&iAYXC; z_jMNEcb-rS`a~S-ZN}ZXsN{lTSakH5xR>Y7oGzTs(bczRN(&(DWacja?OmDX3q#dg zUMH_0m|?|Og7=lt5U}aq+XlYXqxHHN1LC^qIbG&Wo%ep55+o@y(_|HH^KjeD&sTXg zxid$^m@A6**XwWr`^WBR9y*?xcl-9n#Wd*ER9qoRm}=4wLf&Al6>$QLxMx4D%AOGl z6so1BSFOA~lZh;-erFNaW5#`_V|-RKA{APu9R68DPF-DXA2l`@2

    )C0O-pT% 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�bC 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

    Xa&>QI=- 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 P00000NkvXXu0mjf9M}|| literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-palette-editor.png b/src/designer/src/designer/doc/images/designer-palette-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ef0e89508685cf0465c2d9788a8db1ad7982da GIT binary patch literal 45039 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%0D4H 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

    KBF>ZzxoNPw>b9w$zmz@h-L zZmVpdLGIYmNmp90ju+8z1xqyROvj3Yy3C^i_DaBUwr!~@4UfVe%ye?a`s_Du{nl6h z>A91O7oB$&v`sMCw*E_Gf2B4t=`a2G_y6?SPyXhCr_S7d_Hb7UVyA*i#~`dUTE|ma zn!-<^rAb(Av2e1Zkk-2iD(hKQ)|HIwYy-Q1R3MOYa{vV_6GD(KYu$|K7sa_Zyx|Rt zi;FUqxYvUZJ_tUFhlysR6jDGXuoQ_&UQZdY3f#I#qu^I`6g@RcrsTL_ZFKc1iB|B7 z58AN3BhNoI|IBUi^1*w5{Dq(Y=5BPRmEOvpYrE;3=eY*^H!U|ge*D;phrfp_4?XzP zFMstbbpS%UQAO1DI&Yf9s0gP#Oy4UCDqWOq33Sn~ZD~CU+BPa8=5YWL7%B50&P(8x z{`^H@sn6TIdGmGGT_=zOEb*k@{qA>I8aRc63OwQeVjhymE0b3~suq0}DD;(N?JN4~ zam8#U$k=qzsPZGvqDOPo-N6(423C(Ha=n%tKYe(&&ml{Om;L3g*}wb5_r8An6HU{_ zj~&z5cleo1Pj9|!D3MLfAKwk5R4lSGblE9v+VDv}^-9$Xqwr`0q1KGhQ#yX7WYGcW zqKEnjs{}9{PD*HNYFUZQUV5Bl;r^J1_uV5gfhsd ze3ex!KbME$NR)93_=QMf69iRX zO2gZxUNK4Op|XQ$XsTW&0ML6znTsp}eMA(SWy4f2FE0a-m@DyzfJXvN+XRJneyLlO zJOYFdJn(=Rb8!n$-DAg&p5bQ2b;T>+jEGj#a(C@{aAo0S$u|mf@o8RMQdw{}iLbB{D6;Wi3o>ClA*4UQ)8oiaSfGDqxiU^9S%Gy8( z0Z1X{fTB!@Am#rj8XJ|$F1h5AYp%Hlh6cefxW=$^=bd*#0hM$x@HvH<3O-7mlwv54 zRN}1Q)L*HXqvE@`*aWmu6J?YQYncW3L32ZE|Lg<*<->pfxmVoyfu`kgIUeh|LkFi9 z%VcY^+~F*lyeW#nN!{=tKKRjp_{=*$@X5~MGjL0-$m(!l@h%dz-T(xe~*y=4~ z12??k?R{&{MwXTu5aLq8^M$~sz*GCP5>-_&)V`saRfXl7tI4UJA6Tsk;_E$jfK#GkdHpbAq_2GfSb z%CKbv2tkrLDbt8Lg>+im^82HtPcLrsD9EP7ISO4hinRqrM#+y#wty29cc27&hz7LNxcAVULL zfoJ$328U1aj)5SnT{KE<1VN*({l{thBDjjkCjgKM6?356Ux1;X4c;&kMK4|Zip@&v zRO*)F_@Tm8@hnPOb&v`tIVjksV3vKQMwlynLZ}qQQ4HlWB+MtE2-b*s!-D`w`T74c zSSmOrFd7iO|Ni?y)-fPKDP@X)6EIcfQc)g+a>`9~)}j&FQU2d1F)9G`=c=u$GNZL` zl-;V@-p1STsi>ytqG+T(VdY_pnrfv|Nc+6C$=*s5{uuoF?y6r32(_{nbUzq$Am#v& zz(}B`j8?Y)m9T6hI8Zb}++ntb1O$u}d=jUcdOX@@L8ng%Z=3bC4Ulc?v2BejHYnp% z7NuT`HVq-F2t}Pxx+wwBAGXm|{kh_t4(!FKt`0z{tMW*>3}6E#6LJdT0YCzr(O3T8 z1k3hW29|tS+}URT5j(tjc8iq>t5sg0Z{YPQMALdHO-06CCT;2&TK(uy76%kY%c%+ N002ovPDHLkV1k;p7uf&+ literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-preview-deviceskin-selection.png b/src/designer/src/designer/doc/images/designer-preview-deviceskin-selection.png new file mode 100644 index 0000000000000000000000000000000000000000..af7c5b0b2ac4d7cfbde7f146c1d430c2678ed16b GIT binary patch literal 7110 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

    BU+;VcnQ3nY{_)|;TF9kEc-ZcE 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-preview-stylesheet.png b/src/designer/src/designer/doc/images/designer-preview-stylesheet.png new file mode 100644 index 0000000000000000000000000000000000000000..d54df72855fdef7a84b97f1558393b5e81a523dd GIT binary patch literal 30103 zcmZU4bx>&P-<>BtIxGwJQ?(XjH4i8=2m&M)P-DPpN1r~RA=lFhAcXe0y$E2n+ z)#-FPon(^kNJV)GM0i|yFfcGgDM?XfFfefKuO|l<;tP|28AAaE=B6(tDx~VUc9scH zRUO&vwZH<27j%2*>)*`jx4)p8gmQ%*5 z)2unzZ0T+P0Yt?PIc^6ar)xlAXuuDi7;>&K8lBcMXKS3W2D>q)3mvZt8;r>QC)1i| zMj;|22a}46kcJDN`ui#k7d90asjEn!RkS7`(*83M^e%G;6$M13TfmSmV-}wKTYjSR zUC(Eeo2t}a&up2YXxekY$agg|-D7&n-_z=$fwrNddb0XDBjq|n?^1vkkE!DatQf-; zjwHpN*(kK|_ujt;X{5WKg&lvO^AT*iES9||{SVnXmOR{JmeIPq_+P6$jqdMPQ#8yq zJbAYZ5E?$Yc{&F7uY*CI7-8qoNNWdGAR6IQ>08Wbccdj_FdH&mLjk&TruwJR0rHWO z)U0f+6Yhp}w}(snhVc#aO@5x+M;4Z^x6cFih6THWOW11IBOvl9FDG9VuNyJr!NQpi zrA2C6zTO%Aayog5MJj%l|G1TP+4ul?>~7RKAwx5Pf$gnh-ErsNz|D*=4vcm zJ&wL94}~2~B0CyA6Pl5nm|#}7a!*xxou5B9tb7re8N`e|z`KhoOxAF0Kog#cUAVFf z)<%GCpNV3hGt3SBlRH|PE83Vqyq29zd8t5Iy+Ap2KpB%1b}tPyB&0tzqY|R&0=^Uy z_v2y&*YSrpzGEPwtCpb7gg?y6KfuF$5GJDK43qVSp#$ZL9Q2r-p$Fc|8dP^X7QP;f z00G#lbgn2=IeKjm8yI}^SuVoIz6s<9 z961dFMRa?G@v0^R1rH!`cZ3f_AtOJPP;wD)8rbbEo=I=WZo*|J8#u_Ian^F z$fkbqO(j5P4rtS5_{lb$yKdZzrqon))|?5fLNBR-YG`^7akhvEzu4$OAy^2^nw-NW z;%f*Ldx7cT$q2}YiIL{ssgk+yF5;_pgfb&8rMImMsPXcFs$R>IX{f*GB)ecH7-b7q zW7}%z)R+;%jCTMVt3oBvG#y?2SC1U~vzSO>nFYbF9%I6xV!e_t)^_gOvRDBm-;@f0 zAeJC;5Bu-RLvBSgFbwW|$W5Hvpsgr~faUEwSS4pFWANr(|I~Z_;%E8dKkQ24=#Pcq zavEW^mjZ}snNDfxf~g@VTelEb4=G+JTS>;X31IF@MrYhgIS6G6D|Jmzvf5XY`~KxjpNGPXZZc9YFq`qjkqgmm$IP5 zBQX^qk65IESmW_4yg8eFi!nguqu8<`5YrUMittJ%^fJ-f%;l;|9j4e8^EwcWo2DvGg57LMM@>4^?+X=SG=8>u)Ug){h$##wTPpKJ*N9*_x+u6s#b;Z;fAj2P>btR$}TQ;c8{ICy){ zETkFQl-EUI^2AiCOeJP4o!|W0+%RB{ce6yNaGSCZTSBvlli0Idx%_4irPTQ`@OnCE z1I!{siI|yBKfJ=<2F*(SLVPxwJ=47}qa2uJ{E?rPyiB+Ar;U*^>Hod^J;1z4GFI8; z$WzaV>)gx4Ut?S6Bc=cH5Fr!%;INETvVd4TYU@y z_C(Re?UCP=RTbL!imi9R#{k#41e4a{0XCypJz~4mqPg5Fc`AsFT!MgoxdH!os@XD6CDD2Gd`O;f z>AzI7+b)kO6YX<8#i6p|fmn7~d|JDZ3Z1c?hR~BH(Sk0kk2^l3%>^eFDfH92Boq9L$!49xuEAbD@lpxel>sY zJXw)s>OkBB{Hw#zV`eOxmM8qCh{0cHjZBoZ7>sis3I?08mN*eS-xZmwX0A`;)$eP8 z5eyqF@1yFldyjx~Ib7KVSQ=QrA-P>!9yQa8c%QLfE~0S(_oJ!?j}r@pgU}{stKaS! zUsd@FDrzj(Zq0VdDeHi1z1F0fN)Vgf+zJG~N(k&8B!r1ePaJcHNKmC@=~ndM=4JIp z&rVqob1Vc~_UBeo6A`!Ltx11hueXJAM>Ha^FR>68kwf6b3jWePZ3XsH2jpJ}l{7f@ zr?XOkW^Aq1F;C^P}^-PyMG6k*zj?qd1)DN_sp|INqhPl#+bG3?`qAN_WkaTY60okwu)fW zKiMPS^u*u1;NbYD^9?NFbp15081B|-a&;|_PoF-n@*kVLd8D8TymSBDKO#z(0NhOD zIVgmZLANCg1$;t79?Ls}`gBakA%@YNU!x5R0w(4D6N<0Ng>P27&=Ttygexn+B{q;6 z`Ig>4uMgyA7l#I9vOK{vJr%B-8V&Zn62JjkUQ7|TJ9^kdCkAwNpNvOte6|rbZ)3d?#XD`&20((_gD}_KN}=| z&|Pa>zhdrc=YTq;X5+dTE~dM zAK2hwWLr0)96Ed3$<2Z?rO9`&i$Uu5*UojoJzT~mx%rcmC3{kn1ipZXpe;0wDZT?) zE6YghUQWE(pc@c$7#XcEMTV7Z&1vx3*xEVIg|ENux5Xg6)$R2fVtlhRfFXE= zpHnMGL{J@tH@2ue2kXl|#jt~Q^1sAm<#9j&V}w5t0AWEFOf#4mNrd+Vn$wLdL(Liz z2k0Z#7bF`GMsg&dncHOCZV$72Gi)~1Yd)1-mx|3FAkSf34?U>nA3?s1ntG6%NEszS z_2#_L#aD7}L8h1mTMIYhj4>;mB`v&(U5`yt-G>_Yt;fqOQWHvefu_#YNVc5bEedqw zVkLs31g7ELUQ-B8`4og;{!8ob3QlF~PWWM*+b)~8D}jfM$) zImXy5&!NXqoIg@l_%P0WeX#rs!$C*6*5LJv{>(w_1D9xK&uRX73pdBd)OA3bJwJ)n z4L;1&N6ceXg*G&SmF3VM*eMIeguHE2?hu>`-U-F>s@smy!NQr0VAKlkxfQ|&$1D8i&A<`*f&;$*lH?e7; zHxi6*SoLo4!e%isnLRYsLfUi715Yc5`|NNzeJp=(+-kBCx0?^Cf8dF2(+5{TPx+uz z-GeUE4fV*Lh<&rUHN~KR%GQ3S0~J*}Tjb>gXt3f?kf+dX_&kI(k(S(XO<15Si_OT0 zTIxffZUo8AT5COzUQJ94^a!cUVwaLcLoSSje>l~scj;}r`bll1#tim&DWLjT2?VIN8e6_&w_Ihy@-G?tviI%P>E%z!10jgJV9$DVyH-lZ5~+0~uWI-THX z@^l~yaE;>DM_zx*>l*!Tu(Hl|m#gKv&~07^j~0cteDHMSyRRy8Drt7=s7ft>RJ9n!@(hDXa zHUj)hA?MLo8@QIJ(G5&jt0Z^R5h@G33p34&fYZhV=Cuzjni)!Ee)|Y!%H)!?EhbgP zg|1SGi(<^_2#;z#Y3-OJ4}Q7I zI85k%Hz2N}Rh0aFTwc1-*Q5mgv7Qpfp$$8&=_ml5H|$z&~%d*BX}(@fwT!Xk=T2Wa6Wwyv1iPnf2$O)D45?a{ri_;uoUwlQCe* zbo+z^LM9junV=Kc`^y$+Mq?o;8QkkW^a)9Rfv)+| zz>tO9?$dI6d&I!NUsoe@b57L(bt=(4TSzmo6|C<#5~0?er3Vhka+zc7FeCjua_|C; zsUkU*6W{Y@hv-Gl#LiNR@cow@9+aG70qCaL|98W77k((xC6#Y-#nrZ6DAFy*dxhoh z-^aaA{qGHpPaQ+h|L7((v~=Q?=v_?j+JKu#{?G)qftW=2xKQCROku*6*T_q@s$(bH zcuTuJo#Cv0Z4Lz4-UIg1V-F}x7l0MsVQmEy`VwN7pcR4}(qa-9V}8VmnI8~Lzmq5E zq;aA%p!ho_(#1&%dZtMPgcJ6XipdZtme!(}Xa)>hkg0AA)Brj-hDeZSb^z-v?0frY zbH*7XV^^&e5!*^aK*jXH$!#4BMD#ZCuqEa4=6A2cH!E9uiQu@ zN{FBc80*uKh2+U-4Or<-uRg=d0Q8t{ZAKJ>jG{TD8D@KJ6r_7?A3MdE`VXDfnsP{J z+!Zm%pK^u(l!Aqj`l#qYwc^|}`(dgUL-!WmcQFl=?+Ec+tBVlCjHVVW;&99Wo(%Bd zxwzG!kSpV6viKlUh<)lKn%`~b0NbKKMAq6=SpHv!9)Oj&KQe0A4#)QiYo)H-P!P}a zBGz^Q9?z`|pu3 zBACn@9R;25|>+2Y&tVl>tRTci{yPuuwPQ`8o3& z8~U$4r-ru40_^=*aIeJ3qeS5svBSgd{RVn~!hM=gF#suv$WS)JR~JXhtEqXmG8xrVg}6*ZH|20^=D7dlkJ*do{u`yU7tsqILeK!xP^?*xGY#WG!OIs847a86Fq+Q|5hx%YN!<=XYlP zPw5L8cM~@CGz|AUW&K^jwIljxoa5_c``x_QHz9Oy zVk2-bL_~q|7*tz(zlPIgfxmmj!7~eq&QnM%ItBFferq{v4OGAYdQK2|DpuKd23#vb zX-6r!$#K!)(oPM}IB2&kT<#+uXo4$mco9CnL>K3yGlN`E_szm+ZgghhPDkAzr|Ut6 zIijmKWm*VV^b|(X?{{?p3BYz~-1j&GeSyc=CgcGM8Bf4gU3s* zLSx)&uA|A*20O}W%b_q*--HQNMRS^KIekA)xZ=hStHVVf-hkXklWr{P9JOv0CGQXf zOy&8Et2%!PA4Cop-|85;SxjfK04ku zsKF4tZ}#i0{}km4OhcYqUwr_n@h59|%s*wu6UP6^j7^PCj!w#t%iYLJj-MN8V2HoI zyta=NmNao-4MOQ&KX#?+EB7CCZXu5rT9ekn=cOyZCWfE9Ga+=SNMv4B?^PdsY`!W{ zA~uizsnlY0*v(j%`B}G8ujK!}Ohk*lFXh_XMO2#aQP8Qh-ly$NRg~W1Ta&%gWAJK= z!8O0AtflYYl^UcYW`R&bz2=#Oul(@uUq;dXlh;epU1MRJHO-%KyQUuOHSx}KTfenc zS#kXboyTZrvvc z+`y@cd1>vV@kjD~7X=8N6L2k0E@14j>)<2U6&1I&duF<<_-vaGPa}H16waS4R>X@} z>%GdUiCyT%oH01IT(xeyxaxRPJNq=U7iP{1YB$*rS!c=EZHV>B16szabm(8-6D+u| z+L3?zmUMI^EXkSv$mke^G__sp+O*rR1YzT2UNZ+Vd-l5@qG%ZXdm8#LW`sAjx*{Xm=ZKq&Xw} za1)i2c9KY|Ei~9*Ipr2t^R)I;c19%4+zkycK%Fl?lS{W?g&z@ifWU@O$x>@l^=r;5 z7eKmLHYSi9(F3T{@m*a#c?e(NVsCSfHWpazW3aI9Qc$}sF)QSg7}MrMTho-5nU;Z> zs^WXNorpwfP&N3Xzx z7Jjr6wf3)KKfWy3nn)~DV??F^v^RM5xap(ewfvB_PVVZQIFitS$TnN9En#!a24`Pv z;fvPlw{t|i`p@k20=+-30Cb>eQY0fcU1O^iQ4}j-3*Nys{`^Zu`geHS;v#Hk=;@-R z_aS5r0{f|`?@Q)*H{VYVfSdOanl^9jew__>YVckZ`Z^0b?OO*rfc1!<4@?DPOSL>C0<9V zTO=hS;>(%RN;Cv#+o(nxSJ?R`0QEI|J^`u|fdV{IoFn4z66L;~-26Ik`Nr``u z->63zEWIlOTZ+oQGxM@?($YT2s~ekb_z8Q7+nejUYb)EyS*waNBT@@@c)WT$ZY#LP zBP$5Q;70%MTzGoHgwST`VkKD-;Cg=`Xe^g4_iR2ptbL)frD(Tec#+<~U_YyK`=S1B zUS((K?Eh_(hc}8Ghfa5pXJz-a{g1#LINxjXPZ22v85^(UN4QD7Ff4nhEwVZIHhg#z zp5Do^gTWsXE+$zjgq}9Eya}^6x&VnQ`=wz{Jexfs@0Y!l{!W|GU96a4oqRevKZA0} zpl0IYWcBEQ}A3?g6X98J~Xv~QUG*9VxJ=K?r%cf(xendLvugkjrzH+_plJ=OkUMgm#s&}`tM!?67jN+?0l`tyDsBJ_{nxnBYiN7A=*jsoQ- zfc!peR84VWkxm@0H;-H`BfL$K{iQJrKU}1QUAMe9Mx^lqXfKbqc=t-UX9QAJweHIS z%0#3rPjJ|VAe$6z^v4k(*k^re%ul51hV@+}#QzJ>sHN6{= z+LU-vuHi_4L(btk6smffz z#siVgbJeD_q&t0H#vpkoY$M;T7oV1rma#~Y?RGdn&RozrEBUiaz_u)W{#azPBbDB*}ORMAeAlY*V7Mv9CEFr$SF4F5&YqX%|bgLD8 z?EC&4P8^rvgV`YLt?V<@%v)Tl1vlac!)MR1*`wlV5k~^Jl%U^LV1y0QP(@f1G!ifN`Kf zE#;58_t;+#y9Vy#Y<*Q}c>yv?$^tth%TxT7=SK7l@9A+bqP6UE9(f9^$Ci?ur4V!R>D64bU;Aq->Y9@P^z23-+wrr1X`t+f zG@j&UttO}#>DhTquUgqE$4gAspe3s*U!M_t?>aaT;@WE@g745+(nLl_ zO#*2aRFO_FeoyMN8SiMez{=$DBp~RWj{o0r_zBqm3AML0A{7D1unT>s$GIstxts!J zRE#JPL2pi@(~)HC=${nafuFQrh%fMe`utB!9p4iM{D|v%eX~E2z2f{3IE(VD^LfbJ zXs|dOnwpr3%KG-*lQ|oVgefRq5f$PW7z`enE#10fLI*vTEp78|FjI41mxoNfF8QPV zc-NR*V%6rU13eZhvk`>WXl=%!%LRf~``q^Ad-f~uV;k1RY0nHNKU@FB89CnS=tHyJ z-;smqEReXq!fo%_Ug9rXQ?kE*8|;WuzVQD@04%f_#IQSd^?s^}_{dP#&Rn$$HIS+ZYtjytRSgi>BR61HP@hsf+Qire%i(0ocR&3rsIcxHKrp z?7O8EO@Bo}6?GSbPKVhv209`lchQr#WJA(DaW1x0qLXYdcKhw#>tABVQ-6JJy@_-8qofdlSJBW5=lxcV*`0 z2Lf*r!w*MWNImia0-etpPfLw+X8Z;#9Ob&!Wu2I0a~Fu1?#?qN=q}QHG7BpOnwARF zuobxakV41~I>3c_x`+FfeZ($A^<8NV8P8!~pQ%R|C7COEBZytk8K`VMA+b`GOni)jwHwmdG@5oToTU4 zEW$rs3fp}96(!^KpT14$@`tmu7CMA}jDQBTF1>0joA+jcBBZ687c_`*JwKi^o)UTY z`)HrnWWb!)kA*u%!n1urzVVMU{6nk#)P)GW_Sss{w&_J6@n$McRND(Dw&72lealRKuDsZ~jfg(R?n{Kteg@FLh4cH_Xv$8-C=x8r?QV3V?P< zbt__c={=@B)-#0(BKB{__w5u`Cr5?)1O5kGC?3Dl7^n`D@BpRViO-eae^n1xr-f)8 zD*7E&hP{b`)}MJ&*IfBJM6!Gd+ioQfOYH!&MIpkD7&Z~K{8jsx7bO>oNo#;UA&9tg zZaiI-(zg)ccR6?~xsC%Yn!kybM`GhP6x7t^`pSkAv@cTrnHbJvON0#G+hRNH-9SXi zgM}Tg#GT2f1FF)%M2XCMeo3-S$ z{f|NC1is36Y83m1YU;=-xETNFR{wwpv=#*M*vv4b4u`31I1mab(EFr9-kbD(QkFBjz^z=eR0rkl%g7+rb)l>C08ykQ%gwbd1p&({rB=3 zI=LTOhp*6G^%b-n!F?x_a83`^TdpPHJ|P-xb5;|@6z0fXmA~b$qi9a-YvACbYc|G% zS~m}!6`f5yWXhJipx9~egLhYXs|`iW;u+$uy?8g1O6TLe?-3@^#iybrhiT*^Dt)Cvme@#BA@ zUGntLqVy?UJ%kH}VKLc8*P&&iN~o8h4gB(}fH!Pd;^8dv?WfAV`h08Y@tB^&u3E|4 zIDvP3BaP7SwL`nYO*c4vI1PLb`xa0ld14O?{^iP@#L;rUZYaclcA560y(rY1oolQ+ zgCnL0%y8ZK$3>32qbu)Shex%UgRu4`n%c~47Mg2U#yaDddcU3HPc&?uffk5)?Sr+# zo&=NCLG>AsG4JStxuN#B7J9v95~}Fmy;rDFp6LMGB(xRoLLffONmH5UW&L9i9!%+@ zORk=D7g54|GgZ#9>|N{r9y%KxvthUrKVLIddgZvY|1?(vDH|!PL7|9&(h;Aw`HVfV z-&qB6ht_*}V(xgw(R;`qm}~+*MN;Xt|L9VyCl;8WEHKjV(5-cf{UO3sedqq`aOr;tV%(BJ^-~0dwW+?IUZw2#aGd z^2aE%=k!9SoFXYh7Mgah9qPE9a!bYTke$|!X0K{f;I^9VNF3lMSaPQyzDWK%%!kEZDk6Oi} zW1XsR5P06Zdq&8~asyUuPJ-Xrq?ConupWGaIN!Pim+3Dr~(C%X(L1Z>1*Mt{hg;SToxjYfCq+mXZ@PP4Qczg=DxOX%DTCy zY)QDHp~qoPq$orgItv8`va*9`mCie6%Jy|CR6I+&O@tixXMx{5cU!({`Mr1pB4(Gi zNizgyl6gR*VMBV8G*3+5w-s6MDr+ByroQ%f9+-kvTUP7|_6n^6^|A%)swuyUMs3@euN-*LXa&u}djUe)#Fqu}0kca;a0H*X{(2z!yPaepzT4t&}HY=Jf(Y>5%jxjhJ8~H+!*Sb_nrVPX#+CCrArz z#9(5sCxL9xwb?f~!{PCI+(kPv5=us>rYSos@&RbLg%!&%#vu-yXx^+WVlGd;3RIqL zUvMn`zPg@V8#GL6V}|~1WMSsq710PH%L_wxBRk|Yq`qz9k9Tx-*V32)CqSlK|0p~m zIT;!L0<5iZIN_p~Cq4qDhU|3TEe$^Y7HXgvB05@Y{Vmz&4T&e;=rjNXo(G)Sk)nX2 zrHUOU0lfXe$OxMp9xSX}lWI6$HG^;POgul#T4AS5cc7n+u8!Aa8kUYUMK3Nk4eKCq z;=Ci=7dkFCC4NZ6vr)D}X$sZ<)UM?p??lrV5Sb#0_>|y!I?vGc5Of16#$BUk-in1G z?Z_CRgD}Hz*%^59-7uI)pI~v*9;Sf5D^NT!QglrziK)=L4-@(Z*pu+H2rM1I-zkK% zdtrK`LrGYd`)ILo>|l9*{vPUTfCK`hq^=%@D`6B#_@UM*THoM!Nhr#hEY^35iw^57 z=-7Q_SUzL?)bc%lt0m%$C4keuFP3Q(T=+ZH=n{w9u774Kxc|EmHFK{;%DO-s{MRVl za5c!7&7$Be_~kwG#Ky>5U12_0XDq2`fJ4Hke@DVu9JH$7{o=er_>FUHEC|@7}zCl{u2I7e*CXp-_6aZ99u4N{q zPe1oHfaGQ!^~~W-q+$*z`MW1}t~T&Thao%jE1>a&_5JDtA`Yp0bFWq_Iras0j|8dY-EQefB^T=?YERnuT{&1 z=Oh49_o3s__|pzanKkiqyG6^4EiQfMk#ai?9*`4j2rX%(74179;2D}uL^)RC91+t$ z&^vQKNriSfNe%clH%1ZwMW~^vz7gwgxaAP7Ouig*%d z@d|BzX^~b$!|kM{gWeHh+|}=vw=*QD=t3ucYLJv8ArvYKBV?H0uax|jy}v0`48Cbq z@pkU{A@t_wV%X518!nGfg=tP}tNv;e1LI9*8@LFO1@oI+k(U@;7RGY%h)QA%O_o8> zA#4^b%l@*XwlB9KK%=1H(~2!4I?O=>V~XQxgs=M2nyOt)fk0PdKz<_~KUM25-yn78 zNzM|ZI`*A+mU_daZIhPlLkT|EU&d&Pq#<)QygLfobZBBp;HEARlgDu%ogsTfA~sh; zgc&?-obv&k0oqVIj2(vmnJo4l=kq+|8G6uQ;CjIGaT@jwhLF(Gda#PoX`eZk2)cm4 zM$eHa)Ta=7;7yDz>_F0b#uahhfR_wA_TR_ISB3E^fl| z>dV!RQ%OxXulw1I17sA%GZ%@0o`S)KE?pGtBozUQyV(Y>7#7uk&d`AlP}O<~NI=PW zZ+)OX*ejKTo&>~dC~n61xRLC1hxOO8Xs%2O5p??2<4gA!RoP zbbPaNN&KhDQ_94b1NmB@GEc*uRx85#ORxZ{&4AzK#J@O~<*?c&b_Q%-`8o9Q7~)yn zjrcsQ$HQAFV*>VkO;L_S!5HknP`M8OtVUY3n)D3z$Unj~PzNc%Ky?vxW1GMXJv200 z7Bu^kh0jXD0I6;0hfLz%iV~Q*@8D8_u+AW8zGZ%qZ2ZC9wIEr39vk#ui=BcJEG&^Ou9gb)UU>+G2B^7eMqHwAGtQ=hKtpr&@+StTb7!A=j z{C1O0=?fz=U!X!XHG&Xw7R01R`;T4%sLG4Au{V$?Pg@8P!MjtPdm(unqnZGh{jX-{ zA1QsSMb77*G`K`i&)@YRPwYnQ(kpFh`C-B1LkYljbvINAR|Dt#d)e@#J@D9SB0Ap#3WiMl z?-@_rZFT(y~h{NT>dCueUJs#M+_s5GWrJGh7uJ=~f`t!OBaijB^KHX6ZL> z8XtW{xkvF|Fh*M`-RTdb+yH#;)obD0RJ=sXIT{@ub^Js&r>1mb1nm2{ zQFn9r3BQ5XE&tt3aLiBfWv(7g-*u*q>13UneN{HgIIOca^ByGsvkNtj)54D53o$F3 z`}T3!lO@~XB>-=K0aVsOHGl)R6^e_Y?SN1P))1O+i)17i@GQkTC@oo#9*y+i+=YNV ze<*cw>};wsZ+?K5Bz^Ey#1iUvfv{x_Y?VtSOjYyaoBo}j6UGA!4aujkVjswHP693V zKeIgVaspgNXRl{?dOq)jzu9pB^tEu{kl?k=>DOXPjAkd+K7I;*mN#dAStFj~b4Y?A zu^1Qi-KD+W%(<^@443DA8>k7m-W>u9&%DAkmVg;<4eY!$R_fvK*b?hIhjJXAxk2|w z%4@zjF#MDn<1UOUsL2i7ys7&8gL3wWrilI~y%8$0Iwe4Ta%i{1A9jVnmY#-lrHn5s?OpiW5Ap(v`2+1`;wkdrhlRBvvi`0~YL-sOqD z4$y-k;_E>9V_g*)O8C+=GEdsD`xmIgm3(Nv+~cdKcQ@w_$S*$Oxn_R$Q8y5syKOLm zow(JWD9(fF3y3t4`~*-kbF!JOP2?_q_mD*0&&(=wgTpXMz+5U$mw9R=0uMs zNt9Gy5r>02PNFl;HiM&MuNS2(?$z2V9mUeXsT=|Yo_~9K;?s$lGY{*RW%|EUG(q^K z@#r8mGW}8bDol2~J8n(eCnx5a0^*@KC1MJQnl@$gt5d}R5Kcf621t%-zc)f7FWqO9 zDl6gmYB`!&UW?koF=gFH>v6Nq&rV0;@595n zT*naR{ThQ^OE7Kl*=sSf;OZm!nGvFlCgOJ}Vd3v|;HdqHwWj|bL)7kg)Py=_m;SQn z6a0PTnw~8m)A{#U7W@+5qFlB%3uu3Qo4?#-0!||T$qFZAyz)bS`)Nasuc={A{w*_F0fc$UG{5bP+ycI zPr{qWv9|78G&d|@YHO(4E9Bhi;(rU4qhCIfadi1<@od7+ z^jRRtY~@pyrsaiyvn$BW30gq*_3|aZdKR<`-21Lr1zCe(A1dgKiLIX3g21X7%7%Re zU57oYruZ9F!%Pg&49Yf!MG96qB!0BQU*H^9XBMOkW<6vzc*O>cn~yp%9A|)o(5af@ zli-uWnxKK_gmSHS1ATTm)X}g=*Y{6Ho)Zzu1NIZJaBz_kq*K4?3Lr(gl9L-74@fD2 zjc?Vu$~{w!D~=reP%6xkjD+Q6N>b3P-}1y43^0Rfy8`-b6CHih-w!aD7_Pjj$uN^j zNh-wZp(J4e4iEropr~#zCT8-W2qofAxS4sRrcgzFXmEcfUT`sgL9@OtYe{NId$hqK z@F-|BQbp(OC+Z_3k$9r@L&$CPWTo6NFnv?1d%!3)l6ku5h2&BrZwrf3_IU8=%munNQ}T?l7>-D?OMv|<3s zFeENF2@6ajcOWt_2?mVE6eVyIx};XWJx~oI94$8oiX=#|K!^rhx_Qfx7!OtY>Y~^* z{++E*Rmg`Oe>@->at=CzKFFu&$_*Ztd;9H2X-QDuwxRh*$Y7)4pDg7T?>32p$J%xp zMgtk5JMJO&+P#2WDq#FPCHH~|D{-&yqGKJ*}| zC1GF;B`9=Np7+l&?bZikX)iUs=WFn$R0Uo~F4zD#%2(|cpFxZe3f1kDgP(}c8!FE1 zAoLj(OM}Q3h$%jlw5PLaaJJgw@krG3jo92AwSz)6uC`BjegES;(-|D#GAOq0W9Y$xtwv&G?&$UlHo2EqL0ys^AnNx9ai#<@Y}&h|gig+hjd zi^9KsotGdIQs-Kq1iM{}I|RP`Lf-9ke;#R%LjMEm5kr#}-FJufhP!ic4Ra09trT_6CL<$Vt>K0gF_^II%MLJXi*zEh_$0 zXcODl@p@fNEFc}m<}1vH1zxl`6L(T&4afwJaC>9>`MMtS>&y7bi%Jf)HhYUV4w3Y) ztoaJO+U^5i+>IoSv}MEHAeQWg<@=PPqN1koD^Vo%Z!n-gw#{p)M7FkcTKB)`+&c#9 zf~_{5BRZN=Qkw3=kvP=PF*7!Zad38Oc}}{NBS6Zafw}{_D7z>WomtzawX=$+uxVC; zds;=F4lM$P7j6$^Kc*nZt1w5fYI=7r-Oh*%i;xz*lY|@MoG2{Duw)N;#sP$)tkTP^ zNsx*&W@w2-nl>5!8!jg{E^hv;Wnf6io&C;M-%mY@`sy7d!?5=D_UPzug2KYy98SbW zdmra_&k*f~c97d(aL0OIg#dmcHb1 zIu$VqJ~2rbG^c0QKg&j1Z5pK z7;tdgU?bq|NzACI{owsy`2QDr!=s`8hx`9PqRft=Ze=~UdVE5H-pGp5vkT|Zsu#W^ zrDvg!t*NQ028+9Gw>myC8Cj2RkOT8*+vFRisCnXttNGyeAvQNqY6yw%?-2cv1|Sncg^il1ma}PFYo^_cZ=c zxbybd`g@k#jAL*d8TSUBIc7x$Llte>GgOt+kkhx+hVt1~|X zH6Bt5f3B{gWNAk@US%}t^DQojy~vR{PP&W7;sQiq0H|7E-&`nh0@@MqpOKI;tbO!&>UYGzz2fH=?{w9B5o)r zDUFy!W~h)orJ4V2qiwHQ0GB#V-Z;ydS>l+BoEbBQmz~N#*tA_gY+A30q&v-ge3;!I zWOW{uZD}~AtG(T}xkOM*Q`AnW7akbGTJ$(`+^+xgPNSo7`bfCLFO$IC%2CA_4V~R0nP!ibNs2D#7Kt$hr$G9_{+`&kvExn5 zlMpjX=~cU7u|ID$Ajq5d*d?h{eI736M2#OS#2Nzn)u{Wr)$%kK(*^P_cNsBQV~lD= zQ}*&UQ16+!WY3r9Q=k8i`ustiJXI_{s_fUQPF4eM%9&SQM5#yUer5!RAHP304QSV; z$O;43T8%=t%wkL1tc+1(=zKDq9I0bw2b<{VwG4)U9ZD;x9Tu0_T|OQ;kri{+F^GS!{b+SFOwNn26v4P=JLp|EsOH42z=)+C~=^vcSS3!NVdUxCM82 zcXxujYtY4=V8PurxCV#d7CgWP3GM_9aCqMHo$q>ooF6m2Q{7WLU32$+^;KQfBi~|- zwNTn)O!SOE>XD#t8{z*h>1VccPc>@XDb?DV19g$ng&D~**VT)fyws@f(9;|H)!IEZ z*@Y8<@ic9#ugJ4ro$kxm3~QF0jEqwbTX4lq1{Y`Y)ZS_>y3?hPTT#GbOC#AQ8Dw=1 z@|?+bgY-#kb}Ng|?Zus+LUpDgcO6?()DVs6%$fcydtNTZDl_$x#*$o%J@z+IK=z;5 zs(EsPOsOAzwtYxnr+%D)wrOHS6fLo>Qx#nNIp&x+TWhY8l^GTH_^agD1ENRXs!u52 ztV)e*ax=4ztf3TNqz|pW-5kzRh;sTAr*JJWXoGdC-<_r+VDvk-+deQ21xvdsKLySH zuI~!*2+16*`wYgtYie8ByGz|kI}A}!V<@T$!Xnn;t7+|$^DkO47-I7$T0A!(K^esP zM;^Fh8y@u~%aMR#aB4->wMM1qhlQsKt)e@%vO{8AoKhSnmnv->5HV@NQY}jQAZ2** zMk`5LNC2krY-roF?rs05GjD%+}E&sVrKtoS+I1Q|l6z(B^_HfR}{qxicFFY3#7jeiCclA6?`3ir;xXSM;<+uwr z|El>vDbuHv*A>B={>bfVxcr&B!L zPWimD?IvE#GY!xB&D#1&pTE2vAt_0xGss%)E$TJd-IQDt7EO-E)sYQ$FZo0F$mv(e8GIl_A9lFJSPPG;!htNkj7KSz`rK` z-B#a~k?bCRy@G66Cc22PMK+QY|4t*eDSV%rqNeFdX4e{zOUIh%nm_TMv0t>%a?VrP zr=9E!#Z21M#`|5xrzA41Y?&ToT#?JdDP_W92hpA7;exLT$!v$k z=VzIz2b%q8)rtvQ+RWcPP;y=Za5iMXG+@OxTIl7@oRGYZ-CRZA4o``{;WIjG5x9}<+{8) z_G8|iUx;L={Y@+cNP2q3EAs z!QnoWx!tj1*}t(;DPG|z4N<@xTbX(u#~GyxJWkQUwNHc)+ro@5tktW8dvYQXPVNrj zDj~0{Xz1%`PSaUh=bH4vQi3I3H?*lPdpn3QsUJkL?em1k?LMa>8$|PjO9Be#wep#X z+b0Xr0cKV*Lal}c{gyP%nFmAx(N5|Y|JGt*PH%1Sy6eJ$k79ljI_9bgsCkQwY;1ME z7|H*XX*XfA+lhT2w^iFWuK2!T{=xG|7+m>E%>kmEYZ~|5>1t~<7*o1&kxMDqM55L@ zDx<6Njx?nf2HlygcC5>($1*RmO14Jk_V*l zuF1A~o1i4ud(t0Ts=nF8RF-j3GH^c*uc*Y6$VcjOkNy~N!Ak4Hmp}PKSLo&J33htd zy=*VV9uHeF27%i4E-@`So1@>ztvVsCgJn9r)@zQXCNX*7$!=B3#;54=Ho~{6K3mj~ z6tE_DrQnv0>BY%Xp|9SKoEF7>wwITCf_H!ZJ_^ffx&Uj34}B67{ddjB+r;>K?}rmT zA@vghj8PILblb~*tps}vM$5f>`#uDZ4!-u`$vP&+Tzn;xll{GLza^vgbtwL3)(SVb zfUx>+!^5;6HTo(_-6cFc;`!K}&CAM9n$;Oy1jP7+Nis4D=;(Zb?b%Z`1I{z7m8utL z{nNvH&7&R<4g^a?=sr2C%80oMu&5QEtjX)kg_HqK($m~vZw)bL1z&b5ejYeA#YIcc zW=Ukzb(QwKm4a_p2=sT})Sp|t&|CFN}G2lV{f2?+*VKF}X#XwgfS2Dii%If{8tJ zeGz7`tsG*oQlr+jUBHUbHi({dWr!f~(Ocm0V}uFJQfPM9@bGwa^7p96#W@+z9v*B& zN8?Of&)PJ~3NpYIr^gw!auj}8e&nI=4p>WV_Vu@{did1SvA1RN*>lR0{kbrtuwZer z90MknW8c+TQv5|j>V%AtZ13BOK+TW|Co^PykIsFpUDelL615`pL*kG7rxVU)Jx-4& z0n&&8bz%>em7JC0g-WAH*ng((l%#}`ZA}ut!raj%Z6wd=qYcekedt&Hhe5Y4Q!VKU z4`DG&M@dP+7@B^4$8qW+y$`uQbR_>ABV6jQatW`M;7vyx_c8HCp|Ip{hP)2+F_LEu zi~CI#la;XV&4$^1d0Qs-QufWn2E4+aJT@udPNN{$5}^@ic7_HA4~o@r8zyUI^m@dmH66 z9B%I}lscqI|0l)un;h$&?=Wl>zl{2Bz>lRwM{%y^UbEriNn6cokX`3p`ea>V)CaH_ zb;Jq2fe9BQImy4YGq0Jce6kxl0sNshoH}=lJXA74e3@+AX~)SrO+`yBb!M8BX3yF1 z2%Xvk3P<6_9vZIJ2UoN9$;#DC;~My5u~xX{!i*nLZFWzV=#?C!V$Es&*W&#EP>;*| zk8QA}Q?0<$^n|rQtAAmEzai~_D$gH1a9>v@ZE^$G{an<9C;f#{9;jhQ(QmLP*Gm$- z@cf@;hMZ+1ecmWby7GF3y&EQFhvoBR$Vh+myj^D+iEoVTWr9nPW6p!;TKU@y46s)6 zgl*c|M}5y>&njE#Sg7z+NVH-W`qlKPlR;Ca1wR4lnd8W%KCb@NC!4$};h4B#CaF=S z)6c)gmXbEjz7TsfqJUmk!4l$}gJNT2|L^JF%W)3K0C(iM_m;fO>h36pB3kM#RkUdJ zYw{*|x16+F<}ETdHqtj!%;~*Gsum>4Sk}=iGCavoPEo|1Lh>T9NcJQnoYCaYtzNXd zPnLT(JgB!mn?kpjbW+(guzIN!TYb7l~_ z2>lwMQXTkL-yUCl$9dWUFRZ{SD~x?dpd_KM4xivUpXMbI>}&1NS##D);j8a-wb2|1 zY6%!wbFo{isq|cr(RyJ=sF|+oA-#I5vFWeTI$1N1u;4%-kp1uKV9{64So0{$v99|r z`LXh^zPxpR)lvi;am+h80GcR*i24ERe+miEhu5oF1k~5U{r^{d9!7h%DN^ z@u$rKm@@B#k}iOONU>`ugB(?%LiD_=z)~O8WFk98n}x*IdmYhE|#tfUSe1((wt^A{_|SO;7(BtcA3U2(_f+qh2JU;qH1t zE!@luPvGX%akQ`hwPDhYZ2HLC&#XySpO$|@g~&^vIioNQHywID1csZcn|LFB)Z-sq z48f)yw>v2UVg-xJu(tJ*w={fG6XeL}3_0Kf$ePYrGMRwT2T_s4d}q1#6Q`uxm>QJX4AxhywraOCCp=_p3za$V(*`ELezN=*N;Vk_!~#0e25NZH%j0Z3NI;$x z%>Qh94s;&xy}^HD02m;eJ*nc_2Yhu0I+E?r2d7b^0my*?g8;bUP^UC`7lkwIgQ{_g zT&{qVwMFEr(b(TlQ}>A2OY(%xlGJo|{#170Hm+C{#=3%FdzWb9#Mw8{>&>NX$l&Cm zNEHAI{sAzYHeJB3KSN#BJw1~9*|HV!rQ_b_@H~!wvuFO4!8)M9^-`?l$!s_D9wiHD zFj5EkqkxpDkoqi5vDprz=@g8f7R;QF^aFVsjV#Uat8o{HK^mMP&Lv&qVrZTw#%eic zfEpm##})L$38~+&ZD##nfq0(%SrVQPuL;r)Vl^ybDj$2@;(q)ve?(Lx=7b3dii1co zLoIt;?k2>GLV8Hgx@L#o(}(P(4P$3-BU!kK#q=WT5$sQWZff{EpWxU3t`tZp0iIvT zVz+AvG;aL!vH+f2aH%-luc<7btc+CFG<~O8@J=K6rTA%aCPdkK&qA&fS+0s#^C=`I z9Aq4J0Qk-!;&s8rRDEZ8HSfPRKEIgasHf7vRcr3yelb?QWr2N3WqL_faETn;AR%m% zErN5x2x)!$pl88}GV@^L&`4?E% ze(JN^p~V~#FO#P%)jVrC^bjgyRKLZe{8YtQO7It+Ck~wHMmZA6#Ra@CU+z2@-D8kn zqDd(p#AV5&GbW+WBBPDDO`7_fBGD;VXgISg-e#y;-#p`F9-k#SyiZ#GWtJDE8iqi0 zQV07nG5gJrk^wih*`t|z$jU0viGBNB>(4-zP)&AB9k*l5|CnC~WRy1}e^Z`*BFX%< zS@Ow7N|DnL^T4ODqdKD5B9+#tpj=r_U3Z9{agR29x|W25;<@*vs}^E@VPv77mg1oQ zhFN93BUTLuXyMjPHG8_15x-S0D>+{sgE%7Q`?p>Ez418W*>5kwH)IXZ`Gy|n&psTJ z^$1Xb&Dw*7H>p}`mTOwS)z*OG~9&!1piO>3pp>){>y#p#TYtm4kFleoPj zv^j)dLZOeyS6_WZ2c`b5BXY3ZJ+HczNnQ_1l#HuAJUBHcx-zwTp4;yxu8b8lR=t@{ z=3qCUdpJlN=++0 zLT9GFkjQkTx;~;R7h}B(U(v+J-r8^^aQ7gOau4N4-X}JdEdH!&4SY8^Ulu8DAA=~x z-O;=KDXCX%pZBh|bEv?jTb1h5)eJ4^*ElQCNinbVW<@Bw zdS?H7Q0p^w*LUfO3mz~PPq2IA>4%QYjUf*Af2W1^JQ2*?zGwEOzj4aqfg#;$ad-fH zXdZFPPz!8P=HUBCJRHgP7nRXaVR)cT(K#a6*yuayGpqNhp_2PK4qs4NEOyq<-|sZ$ z7b`ytsroP$<>1dJe@R(H9ZPbF=6tjxUz-GZkV(=_TGD}}v@xMYu!w=cD zJ-8eEo{w3j1CpnXeFU!wPam34_J3bQp?Nz45-`OUD-d4_E{zoZs`GQcq$cAt)Fh`E zXMffj`X#sQdFivcaxFb8*SMl-i9MHFD-2&j2pazU!`s_Ah?I;GJ&CISEt1nHNdk3| z=JEYEZ`+>tt=}b2$?%joJNL}ecuQ%jfSw^HE|nO3fE*FSc(bp>@SezCK4$~r+V2I= z$1ta|C%eIdDjG-JocTj?^Rx1b*?01Tx2_4 zdMM7nozwOL%?rqfXf@OT(uYZQ`;27l@d&4C3<#{Z-7= ziM2iX_<=Q$DcAImMTO@rjgwx?eq%G`{3<`1@B5LbqbdJ3ebs~E)}Mm8zA3crp|EIG zlo@t6$${W1xy`XBCG0HMYJV1nrCxmUmv9kIXb+{Gv5Kdb2=Y^_bz zvnIZ!Vtb(G^VWsNMyY$(mOd?!6ZFr-y-Bs{hQm`JUS4L>_3$prF5oW2#$ z8rH~?lIi59gu*dN$e~E_Nt1t5XdcFYtBQs6a7=-_V>!kvE`&l}OrmZ9QEtdI3lrIfQdt z6@<*`vi#Lj&)^)4b0Lse=x_jjUN!XEVi89*Btyij3 zG#V*KTV3J(_`#J|BA9gas9$XX_@_$Ytw#6u!G_zx25a=je975m;2{Qc#(FB6`r zlUGSpccXjv{jfJS)qcsd^jA>wE4EbM>$HwrX4j*fqvgjRkzT4)T<^!EBHd?L2rNMk zGopDUD&V=`GcX^ZH1HsUr5)3U8O&ak5TEmH+VqUYuH{*vwP|oyH$M2mesdRl;|w#J zJzJ?rxEI}Q`QCtiLO>L&V0P7f~oV8OYL)8wg$ocW7X2wY|{iE04QoF#^$-vw8d zk=Le4273fuvQ*8+4xolvuhm2DG~+I&b6>to?iG1VtionUUXTW>OstvOE;d&sJ+{sT zQ1LxxSI}Fy-9r>k(aT(xSJVFDRPW|_hjaa1!)eN?a@Iwzg2BMoDQ33gJ>D26b>q3w z;SAbdFs<4=ZZ=Urx!*ZMeCMGch{9>@>wDk+E?bmuy?USo+QF?B5y zH`Yh@PyO%2GECQ(l6kY@VtHGUQEtr91TqN~=pohu5w!cFZP~80?7Q>s2aWa|5|Kuz zP^3=L_i9$e@mno&FTKxUo^5xjcZoG`Wo}qk zj~u^Gm$mXpgn=)!9B>!6vatqx3Mzsr%}aHm=KD;|Fl_u|Cml4FN;MSK&n5Y5B&P_< z|3kLojghu%_h`>*B_s5D0B(rZd~KC=S*m+{{i&?S(|&3t#kdAAt^AQ$?QeG7A!!|3 zZjCpM#Bzc2P6kP)M$8>!}&dwm_#> zJjCbS5x|tvwh{8M%f?3=OayMrkAjqvku-04U-xxpQG7qn7C8#x*DuSAkK;b?uqBKW zS_3#+zBgIEQfAd|d}#x@(ElTfogka1xXfNakdv;i#4Ahz3kc(~BraX5uJ~G}obKEg zgz&jk!^RB+kt+yHE@Qs_OB5{v5~UiA9Ol2-s)l6RvEtR>1!N7!Q_&Wdt>YJV68%>6 zgO!wYOCYr6U3g#};&(0SfFjy`^E zom{M4g1p z;0|abBq2s%N+J{uVJA~HX1`k|kMm5N%>0mLLzzIRiSrsSi^6PJa+4F-$jZl84y+uk zK*TV$N2-!{SPZU;MHAr*Qa6weZr1`~Z^oUkm~m3rRBP5d+~iD@=_dCfw{n9g?BDX; zz1R~jnP917_O==T=o=sax^s4VcRiI8h{Co{J?LToHQZVJgx2EfXBkWP1bfd!&b@-4 z-t&cfGZrRA0`Z}IDm7mG@gMLahZcYG0zz?F{kC&-?8BD@1dU}vr`jP2+A!dn9NN$7 zR|AS1*uQBEo4gLsb@z|knn3>eb%oEvJ-$PX#5{+Xz{~|vEXEcZ z4eyg9Gq-(vEnS}nk*VcpY^m7a9uim3`CzuO~eiYV(P7*CP;pBRC zD|M~lkIyUbiWXmR7RU?dYh~KBN~+kstB0Ih0w!JBn zD}AXeJBBOb3N?Q@hXuE$e2U3s3{vSu!SdmF7Hl)3z5d3S$)!YM%K&M_i&&Kr8<7 zjmg_>q$JAuc2Peoe&l|xEuH{DVM6Svj)BXb+2H+Q&xOPU@R=#?{#ce9U#1^#*A(Bb zV*I0-F;Zid0g%+iYDlVZ@bqKy-?ycD#7%6m zmn1P``GP!)f=!jAy0GOn1;%|!mm|90@8nC}@s;;!N$i}h2tD%2!aRcFg4|6!D%RbD z;s9PqfIB_*eLzwq0C~S~1f>r3pF+-2&hckTo=^IFtu0>&JbJ$$G~LvBE2y*no#>V= zglSRI)v;qTeazLH8E@_7kTJ(M;31b-7H+FIF8{%yacx>&uKl&xuY8b4V$~wESUu>P ziHvSUT!Q!4>nkM2=>5Sr6TBn4?qGGL|0&K%aX;05*ENb^eeF; zok=}%W*fZuEAf8!MxQ#YC8$n!x#H1y9_9WR9|zhLqH8?2a?3FJ6mV$o)ll^m<^`SV z-gEwA_{csC*^7g+bZwuDTSde;@f&Qdydd2|Fix0Vj{^$r1NOIFs2R>=e=<`oivCox z@Nv9B^}F(i@mZmt`OaLfKWUwvkUcwwr(RGnY!9TJkw7=@9}Aqw$olzhXMdNs|Gr4z zjd)Zi+I~#=mxU{_Qzp#$h(5DxH82V$N=e<=6%_Hg`ej(GQ% zM9M>)0jK~f^we@ItD4!4f)hKI(;vnhYH6!!ftle-HFF#M*22&>cHP%kax-q?mwMi_AAA}%VKdRgsWjPRK~vj@?Pb)<>XYpWBbbL2=oBS&v?$cBRxEG zk$dW6HHZNhdMA@1)J@3{fHuX0#A*s-ynjnDQ0$Sy>aLeP90w(d6X`w1Mi@^a@*)n( zB%JmGxdDP;H|s71XHs99MZf>#mIS^`U(e!w3?kYqvrtiZ8L6Ig;4}rC>)jj6R^``67 zOHU-=>)rY^CI`R)y{h1{+Y^2EZVtV#DV2!qrCl@cmbuafN zScHoCz|=|TT$)_oxgOKtGnI{|5{g-{n%*cmHaxVKRpkTlK z@P4)!HHsT`^Y2ySD82e3mY>(j*}V4b@$!b^x=&u04|`{w!Rf)-*?>ql$DNSN%ph-w z!kkHT38kUl#zgV*o5OFpze=PU|Vx&yJhM~nh@WR}ZmGcIagT#;@Cu~p$yUQ_nm zJ;GA4e)qO8`oVploNo#pj2Wf5I7Ge!`o1c&Xh!_;66!?Ybf?$~;-fwqOI>2MPSUbR zhD>#}{Q-kK1#E6ht%~A+)pT1~lBZZN*Xo18tr4>b9=@45{;05hKRXLJ}UBig+88fXp*wZ7THC+I^@A%-W;*4nhm4>6DOF(FC%dAkoK-&a+T3 z=={Y1@zh_@?b}87mcwk(!OhIbk!YbS6p2y0QpPm zLAo6_nCybLFjHO-5R6N_TA}c&wvc7SM`_q$**QP^b;~q{t82dvrvqwhEJ+Y(?jQlN z!%08$a_($TEH|7ReQ@#`+?8i>n%{lBuPQ?Wo+Fj3)Df`?<>gBp4f*hzX|W5be@C*(=;<%`C`?{L zqHKBWXzth_jI=jQiZ|`2Tsw9A4peFUlFyfV>pCc!bt2@wqDU1$+@evKOQ%wS#LAD& zEddI*krd(S&7run{098lHDfI^OY~g1r~ZY9<2u{BvMbH;IswSr_$7n3HLhKdw1F!YCSjjlle9LM1Am^F{j&o=q^Tr?G%n7*q4b$9$Ux%HLJk|a+ z1zG(O3fw}Qa-xT{cUr;@`Z>`JxAN(TNmBH`Zd4ATESipB^3#X9Aq^t;hhVb! z2Z_p)aJY;Gbq+q*g+ud~|Ays|>P+z-->J$Ve=m*nK-z&&8Iv>iI#WS1nRhH-v)G84 zQo=t6b4gp{L2}PDNJQG?(JPRS-;5NVhJiox5Hpz!kPoU1X1;&cl0B_wCCMIOQpm#s z;VvA!htNj<@8dEI$jqUpQC8J5b0@FO?E=^LCyP_nkdP$F7J@)*nox*cL`Y^hMS)TUroCtcDv5=g8_M2+kmpaP(;x8B%D0mFArHT{RWP~G$nwFp`)@X<# z7@CM0mK-imSnfN+JGy=4DjjaA8^e3>Thr=qn)PwP(zHB8$#u6=)

    ZB(FEgkL&zl#JM0Tk~`*o$dV%Zs4@YX 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 iuw{L$gV+AoBmF`uO#hg2qZ;^ literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-promoting-widgets.png b/src/designer/src/designer/doc/images/designer-promoting-widgets.png new file mode 100644 index 0000000000000000000000000000000000000000..789c4f4ad7028a68e227a0c20c8dda8328e8ac39 GIT binary patch literal 16376 zcmb8W1yogE_b!Zpgn)#UG)Q+zha9?7x*H?}6b>OM-H1q+fRfTJrL=UHughhGeNRR$+?f1>eZRGO;5kj(*$4 z`oWFH2tLU-tAPj7aYy#{OgZ3tmHhTv$|eSl)ht@Y1$dzGEFQ>A6ZsD9{BmXK^V@!B zc+!xg5yOQRIw%@?O1(S(*d0%ticw}cjxsPBOL0r+cbz!Kx7XqGhlC@c9i;-2Q99<=vj*z-CE2aoOJO#oKeKv% zq#g1YDT!&!bsxyEDvY5%aqUHu`ATTsBEoTdMauu$#(5^@Y9gF`DzBw!v6A)avs4D| z&5M?(*;Qw1qOa<5cVaz6H;qBaSP=V(r63Q)lZ(2w`?+tMq&^oDw)#!lTl4CAH#6fZ zpyBL`JT(qd;+KpSO}Z*t6U=Do#S+6NKHrYV0u1ciTh-gPD^qmxTU(3u<-|FXwRUud zB-M=SZq-%PZWi?z#aW~jnp>yxS$es|xlxmiyA+-4}}nz(pj zcw@5N_wOxR56$qEcL*Vq;JbwGknIt1-AdBspK$$&`f+78!%G37%JOgVRQdJ}p5ol` zRHVXn9eF8Z-`o;sAaO@2116nLc8-}<*8GCe_D4k|PE%^?cjSAA=?|6oXsbL77e4A` zKO%DIr^_#-LOH^6iJCux{T6rIqdDC=&aR1mW#3m9R=>J}+PUNR>f&)^+M&=|WcJs2nXxvQ&- z170=`ep>l$l3PIFqI?wO=t;n;{-?xPa+Ah`a=NYspG~r2xT0YDEWc{wT~~$5*o@`g zm9k#fSwdRpy!Q^TBl)=R<%yWT$g>%tn%E@zHx1BLW`nJI zg15-OwzK4q$DaJM>dPT?oHNmP`@Z()`KEMZcB21sQVH?Y!9#=2Qn{>;k7Tb8j(zrX>m)G= zxHe50;H(q8w-z(5d#)V|;-a=2AxHo5z8|N?v@e7o!{Q868u_rUEJrkIbl)s?!e!fM9IFKW2Q!!Q{zz)T19m$TbIfY{Wm<6 zrh)s+WmwNYC>ai)}TH2QZa$g7aE0AEa#nhn-K@g82VqL0prB>d&z`0|1H1wG!ERTymB%A zA-bV?X|Upz!YeInh|1Hw*}#Avqg3@s(jTJEm%*5kMzYP+8x8aXS3kq_ci>CHWf(=I z%J$`skm=}O?Jv|~oJkDaiF7P{s?186ErX(qY8p>`I9wBF+}2%|Y77TdJf*kfF9fjA z_rELsvUZH#&1Y~!LdnBr_U-1Q(0_YBH(A!%R@E>YEuG%&0p&~A+FCF*LNGOTw|;kg z+J&!3WXHZZuz?(F9Nzo4c0FQ$oPs-mD7&2F=i8jw#kv3f;s4m1ScYnKi$;8$lbm)I zk9XGSPL?yctGW1EO9N5j0?)iSWbDm4= zF0bJu>X4c{Q+Tiw)LZAws>m0ekWM)uUf&}`LPXH~x}MylXNGq+tjOIwXJ#tetCNhe zLyd<_>)5}=5_kBdcobrK=gBA*!ZogUB{a)!M$x!be^-=vZpElE$)QW+e zUvH;=slYs23S7FoaLN2PHwDdP7Hk@+Q1;kpGnupK!bF(D_>DDEchJ&YW1UZjQaOyx z&XPHPEHB2!$sapezoEBlh$#H6{^tx;b^$wj;e53*oJTwZh7E zj!HP5vp4FOXtTe|YD+C~(CLu-rK|gNvd;a2;*5gK`0ZF89}(j5q7~?c5Z$b89J%xh zPsv|*2ZfP@_*Q)SraUSCcON>26)V7^633!XKP*4jr#06x3)yvhrl548NaLUBFdNeQ z82dEj)o3|(Li#TmoN?-}@@!gvsJd74gTfp(ELdKb{U&<>arLDu>5)j}I=B7cQgu|b zlFyITW5pG%D7oqy`Q6Jr^;qeW>DK6#f#Z>MqmEIu=Py( z4gcD>NHs<`>|c1`9%T7RHD1lulU=TFRWrk9Nn$$L4L)sk{cB7&@h%zdKE76P8aCzpJY*N z{G+-toD&ifBJ1mWJIFCHpwrRSb+$dZA1%BG-6`*)p`nq2JwycUr9akw`SRlGN*vz) z3H0HyEw(jTV=Wurql z{30G}xihM5d;}2*X}{@g9+lFEQ%H#9s`+ZKO+`*_u`mb1Pp+l!=63X%Ns|J)_@QC% z$E)*$rMGY2>I;5)cQ)rS%EQQ*uQ`5tc=!k#o8Qw;T3R{)6?@IGC*5QIw{U!T_#=Xt zh=}dcJZz4OSXl~mEUdRUyiYTz^?-bQ%Ztw6zemu9>yxoP%QrMJS$KeiLKVHby1KWw z=j-dMKhqxeB)0LJV&c5pplHfmt+PoSWEszUtuOiJ>WqVpt+=>YdXzya=?g55Uit9& z`1|+oU&-F?X~Fhe?tCv(8+o+^n3xK(va)h>gW-|HTo9JX@NjcNCMG5`3fKP`MzS{? zjTbL1F)^>Bx2LD7DnrzpLd-{ZdS%hxZhU;)zP`9t?b0@1?NN{lAc+(x45D zwxZhvRLW!e?TLGOoc*Fvj=O9aVUQhAUcO>&^=Q^KGP=<|@h&Xt^$t2WV7+Y(!nk6c z6|+5*;M0HkGXB#ibAn3P5Gtal0>sso+k}pe?q;EFcLL(&B{b^OMOUVqnVQ<`O#IXj zh<(-5)3dK#FUCCncWhPaIwR&)AddNh*be{W2<+W`cQNeAIrHi}LgH3kuANJ4*uT-qNolmYID6XafEj+EZ0IO zhLTIT^{mHJpTqtA{c#BQg*e&R`VH{?l~eiPY)%7O>2$cpNJ&0sX4J3X^GoipmPWoQ z?&L2$bRLQ($-U_4=wPLU&p#IzU+8_Z(EKhnM?q0huL)gFD8+_=5Q5el_{%u5NFH8(ee`tvwzI&w7SzE~vMOB}7I>ej$IZrmdY27uVK&zO&=t zEVEC*&cw7iUPKhZrl+UpeYyibRNB2d-jvKpY`caa(~|dBKEj}uqG2-@z6ivD5OVx) zrwXWEBMzh+W(9|!QAZH{ZzsJ*R%0}3OLzs8I`Cr$#($n{3kjWX*ZsgF=Ez45TZGQn zH=l4Ip~PJ53t!ph&#zW{6Yr=Y@6|>W^yOL1ymtFj`E_lPSu@sd&*96eOUPk3_XT!z zN~O}jVRpxNfpJ6}##EoHK536A480nzJimLykgNGE9^9b$M0iKJ)c&UMZ=uu(?2auh zNhKas)DM%>zitV9GFpH>TBuiP9VK=Omf_QxeNh8n$ZF|0Jtpw>yJ#IQgI``1Ddfnkg9yX$j!K|w*Wel>}h z&v`PB4VhzvELKmKp8R!Qw4dQ7g+qnZJWMu<o1x9bDx zD(OPxpr5u7?D+WjYc%B)UZz+=>u^kLlev&EdF@O&=R#&b&7@Vxy5*c?f-H=sF#fD<)SFdCxCn@rPoA+KNl5RPacIHr8XgE{E=b>deua`{Jv!{Q+%+e$LsF4DX{>Ren5Gf{P1+w01+iw+}d9r zp-;eR?uV86n3;+M4x3WmjQ+F$`*fCBBD)cV2-FnjeYW>p7yIGHGepwn3Jckwu;K7u zKY6E1Q|D?1(uEgV{1)0h_UBh~bbfL?)_*QQMP7mP+RaP$+t;s20z5o%J>;@_7htT{ zb&u>(IP{@j-q+_`T)YDLV0W-JC)u=54Gogh_faN~M0)ZasLsjO_$}t0UhwR!;o7F< z`{yQi)X_-|`4_66;^LZ+a)LI;sES(+D-0wRz`mWFWaB3*Dv{^z@!_vv_dhbA*{8B0 zDxW;ky--6Mc_(zWqeXF>uPe;nsExn2K6E8_qh$^|Jak=K?pxK%VPR*#wF*#ZhtRU! zdXvAYM~8;?4h*b$4)t>O3wj+h#=YsJ#I7`+M~+Tf>TV36`jMo9HNxBnl$mxAd7@p( zG+@gmZD#JFc2L-aJ$MC?{D`uDl4`D`P4Tt}4%Ua6SKhx#8yL_ejYlO9V)MN`{@woF zJ8-A2)2<`v%AhsX-tD(9u)N9dP&ZAt<<(jov&+FdE7~?CgTj9Wpz0MKac^#Jen&c_ z?Utl;9&BtxCxdrJk#Rfklw#L=bHeO*KvCTPB(NLx{HS3-?Cmjr&`xc4$m$nD`B6e+ z$}>aH^TE#|MW?xr2%^a=_n|KmuO>^Z%1PZ;)(dgTiHupcEcG%Y%=^<@8mV>bUFkd* zxt(Y0e$gd<{>+)^vAwv7j$Bd`rW^XTLudS#z=65U)Z(vzCQM-_un+uxlPeoT(HMDh zNdLNhtSz70o7mOWHJVb~nBcimo>0{zm*4DIp4KUXZuF#`dPAm)pZ;SO{t-kR(ZpQ- zC&+xRn52A>Z&BjTB7O!Zk7eQ<46~GZ9$8l+A;)+BYZ|P0Ha0xO-fcC`3@igZdJ z6LY?I<9aw>{YvMV5KspZ_QgY>1By!=rHOW=o&v5ba{Kd5b|~IYdwP0ig$~#qFON5K z9f2=aMO)@bLzry`r;*B2V6h`mFPSNARh53-YwEnkD3S)1 zX>@%i--p1uIKw#H_g0Z12x!@+r)#zdx7kC%_1KS$Q3@d&A3R>aHF&FKFl|4f?b*hy zYIpcji=uA*Y$`%LQt4X0DB7OIU^+ZDOx$){-O-B0fQuV0Tr5;r>|n(rL+_jOQnot2 z*rB7beSK~|UYov0t)?QrxVxO5PR#Fh`2i#tTscS^IV!=N5yU=05yBGx>p*=0dq8Xa zUk3$380tspe;)|66fa)4x)P!=55o4xpm5Lj~e$NBN@j6D$ul^0YQDBhQ+wj;?k{>LGL zfQNlx5# z;2MEs6ZV|~fvR$Z z0dU#Befu^#I@($Xia(!A%!6=ZW@ct-Y3WLXg@q*|B7%W|AqehN%W^-==FHm4N*;dx z+Oo2P+%ez;Lc_(y#MmP}gvi5CSgI9-MzFXbAVX2@F@8j{lb)C4~&hg^|3_B zqzN8lb}5LwAc>FJD&GfhUPxZ#eY@g+97t4f1i5>qpn~6}pW9a#9wKnioIP}CZzp2G zeu0e3Pxcw8(Te;(RsN~!HzhpzF@J@bc{0v&@U$rPo^8 z+Vl(zUG44o1O$4;+O+F^$!_ZdBQXyj+0E>pc6ZAqi@#p{dAvFL zjE1J&FFqmR_8>Njl;2$QaZyPL2%P1Vm4Ti}rzItY)-^R5VIBw!Q>m9gltL;jEG)_g z-(8)0R=G<^{3^HT!e2+KoHo?e4ekBBv0-aiaC~wS);Mh2*VEJb3Ft5!Rk~XK7S|8b z&95PHJ6C4b7W+(gmdEsLZ%#3R)${tiyuK`prNwH}Oy&yQU#1d<8mBqt5Xb3?hcZs~_9+4`*^!l%mE%uV zT?mR0{(xA!+`OaGW{{KI{WSml>sQ(xlPoCXA4_h~Fcv1};+0;5dvO|ct?y}?^lO@S z&u%vmsx(KzGDSSOUrn$=6|51$`+-vwMob}9&7v?2+(4@L#vVIU<*us=?O;=r;g@W% zd4P=dFlQvulT77tDz{ZnllPfaB-hmEHseMpb&unj?%3^C7>(L@M5T10liQmsn$C4( z3W6AmC7KsDI$hS4OZpP+Jl1^z&;u$hc0O@cJV>ZX=H=JwmoHxu2(9fUqd%dS@}U(G zA(OAN`Y6>A9{1o)Frk8m6b-4IozJOc zh&;7wHb=Le;4p3^Ugxx&^_ZWYuD2g&{=B;(o7MYO&Yu#U+Gi^+Gh>d~@u8-TrRDMQ zaW|$?;BBz`qZtxg`&q|15 z(yOYf7PZ!0>tAEh9=$kSaG6^alt){tG-%O)le82CVx$+Fv>02T?#*GOwgoV{4sX9e zGFrhV7Yf8{uBjn(F*2I_Dj)ZlQf#LhHY_RY7-9a@$eGLu1S&%Kr76UelpHCLt(RYV z1C1Ng_TUXoz@6I5|ApE(BW!P(?6)^7X!vWaNd&)*7)YQ~m9IY=mLua{ZS-b?#0Z0Kame7)cE|8Ez-RX+6IW)%F#`QP^AzJyv!fk!OdQMa=UWQv#im8r5~V<%`ozdJtK&pHcw zYxWkD!{081U{l`Gx3e5LDBc7Zu}@A4BM<7{ek)qva&F2xv$`IS99M_8cZK`i-QJKw z8ezaPT%ro6t7J0HKfhfoRkQqQp9c#w!M{tvDHV9T_4)SK&FOk^Z2E$t$|wgZj0^SlT$^rnlK@sN2%3h!r{ zh^NM#>GjmP0=yIw`$fGG&4JIr(UFZzq64)K1`w6)jLp9WlP6ipz2T-Oo&^N$lcgrI z%|Cwp`2Jlf`j_In_=pGzJ3Bh4<1#ZBCMOvVWpZ%yK6H0?A0NM|_dDKUW?_koi?c8{4-XIj zu3dEcA|H*JnfcSFPq5`>5c+b&>~$$ zF6QPmu+vlb4#Ydz4&gaQMn+pFr-W1hgmzOw&kxKL6dw61;!ci_XLXjAmdZwA68a}1 zauKpyFCm*48j_KcMlcDC_KIl#aGDcxq}b2B!Z0t_ZJ(!A!TXpd-~zcb#m5p;Z(Lq2 zN^GX*;o*7qOwy6hPlwtMQ#asy_+4TXPfR&e?{Z!Bw}YyB^rT9uQSbWZ#`{YH(=H5(ox3F9@8}j`G!W zq_AbxB(oGlIZnSKxxmWnye)`3{N0V74&!wIM;htZGEiHZDHJ@}el z$Q||ZdkR<}DA~@^ke8QF7xt)0^cPJ;PX872vPkbI9oV)0-;0QL&q2 z&0~Bn+aanPle*VtnPTtQfsNmuV4De+kE3rA&4P)d;P20}5j=DfCvs5;HPG z)5_X9T&XuC=I%5w{EV2C-wAU#@Swqs=nfZ*KdbZ{7Qu1w1C0+>S7x^v7CdPJjO!F;av>!D2%m6wh^ESgq1pS#H9C=7|;F(?lTYt-wX$w_EMj07v5H<{tV& zgtSG2_;NXuid&Spfuhd2`_B{Rd5aftr*BIhIb1Du9Qb#O!yFyAA5cHpP$UL@L_kbU z$gqVrsn&UJyZc!Ea z0#A69K-00{_Wnf=fY$ogQl7sWN>cW>$n-fxS7%x&(9FYgf-&)X7koj3ZVT+w+?_AS z$i!Uv9j?g7a&da?(cYDNqYT`S&F5;j*2nvLi8$>j0!Dd`s&VD{ReH^Hjgqb6SU`F$ z>zfQ=6GvUF4h|y@?Hao+Kw>FHBOo-qy-S7|mj;R|;dFzf1nAXzhlWvvZ`Wkw9$zYdBg?uv zU9<7-*U9WPRR?U-3wTEGsd9^KRp2_#R5A6?E2n(@`jwbHr{9v5R8%sSekrgFUyt$A zuaOcX;Z8`K^%!TQJm!b?_>~pQe^#-3cx|G29#rw6Vgw8%x{mGww(X`X=wi4h+n_0K zM0%1^*5@+T-BtS4_Q0I;dzTY^SPB6+X&i-QTs;(Sp3~y8jIx`&@w9GjA}m8(oK;nd z0$}ovN?9hp7j(~rRs{^<#^wOln~$<=yD_fF=7t;DZv|~40QjxtP_!wM3vL(P zZ9UUZ|NHmH#Wr$ z8IBxbXAlO_xpg5`v-nvS?J~=xTRe>5R#Sv0?+8CU^u=MkhlPyob@sXNg9qe9-RYAr z(0i8`aONeRPMjZBrz*h1^VNJxAh@_+p!=PDl*bV;XH;Zf2MAmxfk7>7-tjva$V96z zHfXENZj^q%vzc`V6`hb!lobulb_%nDG+~`U6>aAc*Fe3OB$IJguN#ZGm#0(K`Sttv z(GhBmsjJCKZN_;nr}5hg@JB&3xew6in@rM(a5!(p}`&BSZ^}za{4(q`gn^BZ$=H0au_B5;v$8d?-5wetu-zXk7Z7#O04nQWQ}C~o-fS?; zP?}v3e}(FKn1NV{HNLGOv?F%iC?54O+0URvoT&SDaeV!X)ubr>#Gnc5yP4(48>GH> ze@M&7s1_V5KB93D$}Ea@vLY0Lxd0hI6(1By;TF{&q2$O!;k9H`%c4z|_0?)L z-ir^PQVz$`hB00z65j8c(e7w^4N_4c_G#V!{-=-qSOGqa=vd&P_zmj!i|t*pT6oNZ zgpl_B_sp-V4uwb%pP%m^2!EwCr%YO{*P955|D%#Ab~Dhe&ooaIo|r|Qw>Fd|NjK~Q zLV(JO3MqP?$ng=724GL!`2Dm@P0-7c^fSx?htn5M8hS?|5>E6%=X-y;aIHmGv;zD? z&UuMZL$lAti0vhS+3|`%L_C}{$Y{e+12dU* zN*s=dBmz*dn6sqxJ(R7y2vOI||)j3mLQ$x`Kk zfq{iDi(I?GDe~TvxXyJF?e7C&3scZSM0o)NfU%~L6^_g0;jR03Y!NsK*q?~7H5WTb zMI!?=_{^d{AfdJzB7eO+*$(Nl>C070M&v5r;kfgBR_io3<7jt7-2asT#+#m8T4i# z$-_(iy&ug??=y96guIg>!*(eH9o^f#*=ddNo7*Xtfe-Uc8JooIx+~?&pX&a!<_YYu zD$-laMw4CaR1V^Jo$h!ZEJ_68K9hMZYRJs3Y7g=`khDDzreQzj4kY|c+Ddv7nXzuU zF_g7|uIZ|hV>2BHc0V;-LYzFuMX`LMQhaQTC1Vk`h@TPJ?jOQBBwwg;7JQKzL<`_5 zGN|1HCR(a9SUEPxquc$oPZ&T8e`RFM4F;UD8alcf-QM z?Ck8&KwgYe*qs&eEDEInIFsFg`7jOKLbPY2AvrDt$*Sa9KK(}dwvSz1-)C5{j`YBbRU+sS!PHBz4$4A8$gCWuUXGj` z4Sb;x^%{Dg<&u$H-wn;J+r=SNe;hILR=@Py)P778?5A)?L|pWp%1$T?mjb8Pv8}af zi`Pk^Rc}Je?IjDz-peSG=g@H(pa4@lBi}HTNl1>VBl}(^ z2uPP1sQZ&_yW#AI=sYTPivvE_@GgLF0cr$y2quL{($IweLk!w22xH_2>$o8z?XYy1jVf!FOB-8fI{56Qt!djn=TDedvl2VmJokDpC2NzMx zq3lUUXfiG49gi-GU3SB!IAP3QGO6g7P1^0u9Xw8Vr4^yv5cpm@F)|;J)9z2Ooa6k= z`$Fww9chx8BgweeHv96a{{)I)Dav5A1zQ%rptF1sWcb3~-hMP!IgA>INkv|s%PVbf ztv@x#W+e`%M%pe1s>n2PWl>PKkB=SZ_Ok9mCIAWYq?iA2xhv+vZsYBb7`n2y{DQAM zf;={%koyGCF{aV$)occ`_M1Fb-pD_rPw+(TRK^QmYMO{8Fx@6rpe2ROZ0=}FGYvXf z`NTJp2im@`6R_ zszsT*09ti!PmqH?($B(o{&p@Ki%3KGfs-}kk*HCGi6OvRASQ=+HRSPj^pmi=gw=}M@eCH}z z==lt25D0D(#o${JMMs3T(lX(MkwxNhWfjTj`rX;>wtVPiudt^ z)2tPlm(ap(EmVR-`+jc&TcF=1mBwec_m~cAZeQBv8L)LRL8`2~11Fi+WaJ0HS)3o& z61zcgX;2)){%(l4+xU0lrvr4f(X5Z!6vNPlt+bhO4fDTz`Qn7{BPEYWBgQ9zjKnS_ zP4@_boH-oGiYbO;NJ}bVh-{d@s1eNC9`q$i$_8cqx5%s#VR&f<_JG>hRIY7!Nz$7; z$|ixg4O`4}m*d5nwR>idCNg~`+W8z4PYwa9o4ns^eJ<%4`bo#-pznc&}T zChD#n9H@m8LFC|>zR}X)u2f?{knU%Ao^GH`!9SG-%~&CH>P*IHxclKYb90myxYiqr zaGDuid2ed}kd`W2rrQwRK<)uB#rFLLbX*bja*_11?31(dP<)Q;mX}e8vt8CS#wra2@+$;aIxA7e(QTl8s^N9jGQGvE0+F+dj%{J9X>1_>@vEjs!ai zuy5xt@b`C>^_>jitY>{AgDT=0jft>;$&%`;J+<&e*f%KCtzVgg__#AVZ}{C|4wT7Q z4(^f#Z8r7LGtkzqbl;+>u;ioY7fVvX)5luIlKsZMXafzk)Ln?oF4PSc^tQ=?RKA3!wl|f);BkqHcJ7v+teh= z=P44S$&^XK#Rk|g;G=@HG+usw273CDagc3DcMq?6{``4RZDwX>B`5#Q5$cN|rHPp8 zd9sq1B1J8^xw#cRuZ)f70ZJt&2iLeJ()983^Ru(t+}mTv*jwBKSquZ=S6mz%9F;U; zMZ8tj)#H16A@Vg(l&lmw^bd$8+ zAHbZ@i>!W{8bUen<2>>STQkU(OiUznfV9^9%~Xu<-L0quz(qDV2MGM$ys=z%^60C^ zeK5EPcj5A7aB_4sGBh;Q)3bAMu#8hIpPx78Rh}IhD*N$ci;r7JC#{%mP13#AO}i$E zJk1?tSy)(@&`sMkk}?by8``0pq41cRx!=rMKF|=CjC^?XF#p>Dc6N4d9-f$(nBP?&izEtG+&4acf9#^KPvTr316!s$ zGlx%4CwmA7`Z2{a8W(sn8HcxUb?_KJdy40aw(68T)(uJd>h*iPtYwsf56r8p1y(FM z=M<#|_aVBtxF97ZWn*KTfWU%yg**vh0Lg^I;agi}P!Dzb>H5V+{FY#UE(_n! z(Gbv~3=NeW&HtpOrCFkhb8$&1l`n%#4RABIdem25cXt>J%%w-{@9ohO4KfTGAgf$$ zY%n&!cX_H9EZWF*Ua6|6(37v<7JmiHCj5>WUsc<~)b#!E(^T z&=+BIh&lQZG8|lxkhUWU2`AEMtYhk^{LFG>=y>a`WjaGgq|ujR#is)DszCoh1^#Sk z1!bSB*^D=%my)t_>^JHgh;ejJFVF~JZ*0HG-)n|O<40H!KF8mGI2w)k9Ok9&+Y1on zLBF7=Kzt4wS*wwD{twsee+hW7ARGuVF8_VL=kEQxoMtZ;7IdE2z4X-{OcS&iSUL6{NOMa;!huVPWH>rFD22ssi6BzJ+60a`{2#@R`xVrl`o?(T zF*xla;NJZKYb$!`?82wKyc}>KK`>DI6wXWwyDR9r#%G|Rpn~1FySu;fk5PH}qpoh~ zV&>JWS3<>6>emK)T;lCvp{SwI7LWuF3q$u{Wxh;JN%0v>NliT&(k7;$P*GK#^t_IW zimEXTxdd`%prze-B_S;xG;rk+1KaHY5EJ0NMjvo6GEU9U^R3p_*6!}^g0ui|z=a0H zk6-$|s*Fz8YgLw_vKVz2~TE%gM&GrHRtB8fz1Bp z<&Tz@mi+wg&u6En35ki66cibmnYGo`K~LdEMn-@X2uz)Q#*B3@DhdjqCY`W@sr*0% z3BtXxVcZy&iYUG_DU#b9-*S5u6a2-bMWvSaRnagd&F`+ zyWQF8Yi6dW?=-Zrvf7OKb7>~iNb+Qg&!EH^JtdWUQo%0@E;?z^9R36s8wW0t1J&_D znMVO!1N`QiR?1#rld2s^DJe@I;Smt5MngSxx93i}+Mw}8{WXSBW14v63IKr+(* zNM>{vq(iENMV~uhlNX0X!=BI9EbmQE>lH7EiHO*i$c)p3LC+YCV~d|syA^Xh_)vPe z%qS)-Y*5LYvB#C!@%Igj7u(xcm|I)R==r$0RsQ%vl2V`IQ(9O!(pZ!12DXYS0^WsU zh-QhEl4i!IPu$|-r4Ln$8z#obrHSG1-n~nS&|%-s7@nCSId(8J%c9?VUhd}O^Zide z+hpte8S6g|y+BMSYMZ55P1X)ou>034|99~G*GJ5NS)8IXeEUSXjFp{T$bD>s=Cy2p zcX#Ntdd@dRgXXF#kaEa_zjbmdso9nyhR>+yyqo}F8vr?hMt`y4=jX4!k=g&Yba8$T z1x#PZRP`f7kFnW>1#B#=1b=qc_N&fT$JGWmWS3dVIx2t_k28L zMGzMIpZ&Y7|7_u3oxBf41?}G#yl6jXV|vFfCWedsabHt)KQ>Gd{J$$ock?W;$69_( zgvEfRH0lKT7$A2^%8T~OmiHrKssNX4ze+-0s(yeJGba}fMgcGW^H}z0IYT6&X6owD zmWH{8f$AMs5!L39s5({3)Fwe$-R-loZjYv`s|jQ002{yNg_%c7COE&=2Q%-EX|mKp zx3rq^IRY05?m~#rQ3AB`4!=REeTvWP>+2Vqx_5S1#|wObQ`f?B%g=#_hMcdmstAXi z>%#$_b2%Q7wEXXsP8jRmv%TM7> zo_AheDO=gh;$kT&Dei!VVcnn7*VhLeTE*=1v))I4`yh8S>_#t4H+iqz2wV=6Mrf?G z`9@3EJpbCJrlzJkvlu=qp!O-Ls&*YV8mX!d0+)p?1}51X63@|rvEO40x(%KH9HEN5t-@oMxBU*g;!50e7^NnGBIZ%x7hzRg}e-eP~0XF@bBm=rT^E?s| zA>G7~SUrz7hIe*$nwy)!M-Cw4hEGU1(D}ba|MNG@Sft5XYLI#UP%e2_!N5E*KE8Zw z<*Ag)LX&sbRtC!-&gL*lC6A;eruG-AyK?VNi{0G49?NnRTz znxo}I^%y{9{dp>u6deYDh=L+E$O^38+8B(@phine>-h8(FT_mx?i_9rVbbJP?R#_i z;=9_vwCLQUGpt}@F(xx9g3WpRmHCV5`~~Ayue6FE43ASUO)So&NKHTT>uw$5-OZFy za*2tlyoD$3(+dhFuX^71_tORe!#-lz2?Fu&IIcSd)Mb!;1R>}`olD!N#vZ6{wLO6j z;B>Ak{i{o^E@fe^@gGM&&B^8rluZ0Jjg7cjxuadPv91o5kdiwDoV*wQ9S&>eT7SZtI2Omh7TGIBHfMikx;MTPN$e{9NGzlka-8X$8#1N;VRJ|us z=BKNp!!IJzry0-NzgadKq6gY{R}yi7As4A5<`fr4soVhPOfM6_NS|a*zdms@yk2cE z3wh6APeMYnK|UPvdvLH;B)s-Zem)~VesZ z9?0c`H&nm1vol-kykPkq_(7q5Wc*9*hC2gD+O2P($)Yc-I;_lomvv8Z#lF+w#(aj- z=L;ZG;FH7UR6zt0$DnGPBkYvgD^qYDcD|Sa(oMfAxjKo2-sxoAy3g`pSQbVU`fxxlKxpdQMaI7-y>YFU z{f@>?+S|WYkor0r*h=6M;y}=VQ3SE^{U;nC zF8&{1@4v2qu+gaR17w_FML6C1D}iBPC?1n%2;y9MbPNs-9^GFFy@Bq$*!M${u>GU7 UD%D{FzUW7gmsXJ~ftdRLfBZZmJOBUy literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-property-editor-add-dynamic.png b/src/designer/src/designer/doc/images/designer-property-editor-add-dynamic.png new file mode 100644 index 0000000000000000000000000000000000000000..87868a903bdf1bdedc454b1e8a7b16f562048ac0 GIT binary patch literal 767 zcmVT6$H)BA6DDV!iYqC^TR{1hqG%qIjvG*0Uc6kNyYtWG|xl)vKUb(0VLr zr3H~zt7hAz!9ts6cV~9?W1ep}F{EjdHnH7Yd@RGv%gp@pdtdvO3C^Jz5<|qM0Bg`B zi2-1xFm8_Xwy^(KFE5KY8NizB4AS)B1L(>AekB-MYpZQ3@Ar}l8 z+svlm%HCgJQ<)|2$AVm1mdm=(b*}4{{(9++I$kTkt1%9gwz9rE^A;MMnUp+aTfr=$ z384j}4FoJlc<)E~D{W`Nfx;sp&f+`zK33y57hO|raxjImD3mzkl(-yd3{hi}`*Ozn zxrUoiN{j*QqK?Hp&#N9*WMAJ$68b7%LUpFxG66;he?x)7jEVDcTP9gHgyG| z13ia(PE8G$2UiALS6e6VJ-H;WtY9X^xOO9nM1sb%=Z0y# xd*^n38PP{uUteGT?r>6$!qTag|DUOm$REz~@cU_qsJ{RJ002ovPDHLkV1lNQaybA1 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-property-editor-configure.png b/src/designer/src/designer/doc/images/designer-property-editor-configure.png new file mode 100644 index 0000000000000000000000000000000000000000..797e40d11eae5e4b0d1f5f43d564561b57ffae3d GIT binary patch literal 941 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 UQKU_#cK`qY07*qoM6N<$g48OFI{*Lx literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-property-editor-toolbar.png b/src/designer/src/designer/doc/images/designer-property-editor-toolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6345e93c7ed78dd0461c3708a6c4711c4dcf7a GIT binary patch literal 3409 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-property-editor.png b/src/designer/src/designer/doc/images/designer-property-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..522dd1f7c2fadff6499b095e4a632e20bebbeccf GIT binary patch literal 35879 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

  1. Z~{u?g}xUVh{d;KVUf4%HQgyq@Ee<0Kn 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 n#y!FCko_oH$CiD%vKnoBP>!u z0LCt^e$e9Q=c4%viXAi5@n9m>jS%wEcNkdMkHO96GTuW5)u8VRBj;J=B&r)ANwWEN zKUSg5-UvHk&EHxvoiV=?XMfBRLTAWw0s&ZQ$xWo@e#4^oo!_u$49L2U9xJw|!mzYx z0OOgTn%xejGBWz}o0+kintmsC6{}TN(FU-O;8iFBO61QPiw@TcUNwK;x+8E4L8%nF zopQ%|e$EolV3I8*{juqb6uDdTv+{SkJ@%HS#nL5c%#ZW>>vcyr8@^GVi0l04*Lx@S zElgozKHtj9O3}LL%4;O>O+K|ehRIgnheil8;N=?xz=REjwI=o5`PtbW?9P5Q2%lq{ z02#EU8jeqmd|Qvi(w&N>7$O7n7f>U<)MJYM2vUc+jl`QXXGNEQpS|PXjmT@7@efKG6J6y7HbpP$>(nCh?R!qgAb-o z`86FL6sO&W1)}4tXEiPhf_{?n{(<}E??M zFmTiOhZ~`^{1#MV^B>~^LvQHb0!CrJUAFkpfzMezE~9SPThkmvR2@M_xSlv1rZ^xR6+i;
    spTYWa^xhvC&A~pu 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-remove-resource-entry-button.png b/src/designer/src/designer/doc/images/designer-remove-resource-entry-button.png new file mode 100644 index 0000000000000000000000000000000000000000..212fcefaa299800efcb5085b9e45980a139d3ae0 GIT binary patch literal 640 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 Rmw!)@lpz}6O8Ix8{|8z10fztp literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-removing-toolbar.png b/src/designer/src/designer/doc/images/designer-removing-toolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..be0871de1cba6348382a2525d9a739705ca36acc GIT binary patch literal 8740 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 JDPj=tzW_JH^ELng literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-resource-selector.png b/src/designer/src/designer/doc/images/designer-resource-selector.png new file mode 100644 index 0000000000000000000000000000000000000000..c814bf4f9352ca93c118efb92d758f3f4b1b72c1 GIT binary patch literal 14277 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-resource-tool.png b/src/designer/src/designer/doc/images/designer-resource-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..1fc0bdd61e3bad9d2278f44776ead8698d17fda9 GIT binary patch literal 2152 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-resources-editing.png b/src/designer/src/designer/doc/images/designer-resources-editing.png new file mode 100644 index 0000000000000000000000000000000000000000..1e935020d3afaea5beeddd8af90ba2ee4f8ae85f GIT binary patch literal 13809 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
  2. B*T1DbsJ8!b&N2iB(jpm+UE+Hlh$WKPq)hv^vgs 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-edit-resources-button.png b/src/designer/src/designer/doc/images/designer-edit-resources-button.png new file mode 100644 index 0000000000000000000000000000000000000000..14ffc68e43f24775dd7fdf45eb52027084e6ae5e GIT binary patch literal 810 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-embedded-preview.png b/src/designer/src/designer/doc/images/designer-embedded-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..163172822ecf4bb6a8a9fd78fa3803590c82a0d8 GIT binary patch literal 4904 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 WJ+;x!y!!ef(o)k?Emwa2`o92%X$Nxv literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-find-icon.png b/src/designer/src/designer/doc/images/designer-find-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..aa84bada048b950ea647a486e371a35c8c55b5a1 GIT binary patch literal 52951 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-form-layout-cleanlooks.png b/src/designer/src/designer/doc/images/designer-form-layout-cleanlooks.png new file mode 100644 index 0000000000000000000000000000000000000000..3a3b88863f146cce8e761a4ab3f0b71df2cd9a02 GIT binary patch literal 7626 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-form-layoutfunction.png b/src/designer/src/designer/doc/images/designer-form-layoutfunction.png new file mode 100644 index 0000000000000000000000000000000000000000..c94b8f62c681bdc463b5409ac89858b5539712af GIT binary patch literal 6864 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%0D=ZU z&;Wu45HtXS1`sp=X>wQ)DrKvxI@qN!1Pz3c$@4tvt0;<|LG!ccoM`_he$c=-duI|7 z%N2#;qiIK+K@)I75Qzv*Bo1isk)R7FP!yHmuuw6G15R;>(~F{UKyeD9VsPO`92P1n z8g-+K!~r)dSxAUM#UP3U4oM9Efhx+z6xFm(9*_E=DeG3 z0G)=01`?#zE?aXwARvIu&CU6Go}Ql6+1W|n-rnyuWTbIRi{&jXEzwI0l~Z$aa%g5| zh9Gx$cL6dVK4?^_dVG9jm_Wi7rDm(EtEKJSCMT4sMQvN|r(cs`9XAHr?!CWT!;NXC36irP{ zk&llL9UmW4XlN*EXQ5U(pV!>n%;TDxn$pnF5N&O3QG0tkX*3#~hwOu|m6@4Io12>) zQ(#~q_4M?h6tXy8OG^v2wzksM)fFWqB=9wjjg3j6q;U)TC?<%Q#bdR#wbaqkfozEH zH7F>Epo@zODlILgg@px%2N@h!3>wtvo|u^6DTq{VZf;apS4Y{|+59ET z&CR8hloY@QfZ$B%=;(;{_VySO85tQ|)PHw(m-lOGYG`h5j;Chv@$ob~JWLiA7L=El zM`dMYG%_+m5U#VnzRn=JzP_g90eRUmWu^aPB2>tzj|0$sHR+k6gaSYSf#)ED zc14f@C|q4#MW3U)yPLF?o}La^unB5lU_hgzqa+oefs807CWhn4&(9b4`}+C_a&T~< zw6ru5l$DjmUrMQvZJ3#v;ROf*GI+_xE%l||ym>=%lExYv8%zED{RCZJUc!T6xL8?P ziJzB@#D4Sh^MI2WHvm&vS&6OOn9|SuG| z60Za1rncGoENqa;z$`W~F?rJ8#>R#qOmlF|($Z4gzrVjHLF41&)YjI9?0_No@bEx_ z05B&fClciB>`W&oC;G+>Jr^sWAdY`}dWv{>OkIS2kTh-=7Z=1WJEryuA_Umz>(Qu? zQ3ZeiHvbD65i-g>8ko0I$UN)Wz#P_Bn93ltNKH+JAH&&zt*tHTB1|8!dNVmWNg*L2 z?Bhg6Mn1Wp!^1-nHuxJ^kS>Bpf=mZ+1LWYNC=kX0=*01r%HH0dAZb6WQo*~@|NlZ> zn%9DiD%GRWh76_;+MW&C^i_FzIr;hdQCC+N6&4o4{A9017r{`44+KB~1eii-3rq1Z zO>b^)U<|V%tczgS!vC3`o~Ed%C>j_TASFT{n7@EB?h%;>JQ_@&tgWpPFR~W~i&V(m zA|fK-g`ppS%yB0sCepi>=CvWCN?morwyLTMZM=nqcC_iE+1XiITwJ8JwKako8yndi z9UB`XUteERD*Va=QsLp@WM^jw(^z|21A`eEA69_aI4&tE(K(}m;R+DqeSm?D_>~Bt zC@wB0FE1}LG&BTw02gJHHoFVdV_td3bnWd_V@*`LCTjMnNEm!7v&jy?Thb zffgP~yo=a~rDqUN;5od4khPjt)9=8h$gt@5AZ#(qRKrhp0xQ`TX6Dc3w^q&iSKQNh zs~lKs@8i>sseDZ9dv_NfOLHz7YOUgK_lF!$SJ4ig447bfc!b^`(Y$9dGnqFiA%hwI zkp9O41sV$(0Hu`RXA?9#WB@?3Lk0jeJ7fSrdxZ=Dg61tr0N?`x*5Zeux?Yk10000< KMNUMnLSTX`_qGH8 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-form-settings.png b/src/designer/src/designer/doc/images/designer-form-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f68dfe2a2563b8354226204f2c2be5146a2221 GIT binary patch literal 14971 zcmZ9z1wd3?w?9lucO%_WgTP3GBHi5#0s_*~LkrSKcS}nM2+~7$E8QiXL$`c~=Y8&d z@AqeFpMCaTJI>1AUWBWu$l_p9Vj>_Q;K<8KX&@jVCITN_bR?h#a{+XSfFLX>FZD(X zI)9MvsYYx?+56+?pmU2Gvrc11xxt;R)QCp%0A_E}+t!?9pV5r#_ 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-form-viewcode.png b/src/designer/src/designer/doc/images/designer-form-viewcode.png new file mode 100644 index 0000000000000000000000000000000000000000..48c889ccc4eb91cbfa5f38efedf95f633d6a3cbf GIT binary patch literal 21529 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 QegFUf07*qoM6N<$f*GO_`~Uy| literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-french-dialog.png b/src/designer/src/designer/doc/images/designer-french-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..ac4ac59edf275e8295fcb3186688dbc12eb32fb8 GIT binary patch literal 20524 zcmZs?WmKF^&?bz#ySux)CAb6+I=B;LaDo%u0>PaCfk1E{+}+(_fZ*;9J9*yS^X;DV z{g^p3eRr32UDb70cSotK%A=!@pg=)Ep(`rLXhK0jPeDEcNHCBVV;b!tC@8gSMVSxU z-YX|LUf$ZDHwItNUjw~1jaDlww=1b&3JN08WeF2#Xg*6t(~!e|Wctu;R77Y;9h8iv z@KF&KMo}RoVPjRL_&%2zI@BNDzmu0SyM1_kj+Zz6#4qpMEjNjGKR4TVe`E6H>^xV6 zVq2+Hd&H8bRGTG+sUVUjI#M4cY@51c&c0#Vw$1B&qY7IVPeTS9UNPv;uq~Xa=v3qQ z*28X^$=O=7+wnq0mY@@OP_<)vfdH=6J!zU_{pu*_CdtOleKkb%NHy)OB=DL%;jJoZ z{lLclzD4=e_`W10-vq3FU<{P;S_nD#a82cNv0l29MfB6#22af>J5{V%a=7`*dmfId zickofRweQqjGzQBx5OQ+JjMRc-mM2rH|*5{WN`U;3rH|GvN=%!9UICYi#UAffZ3u*)DG)6G@Tq}}vyV)FO;uD;b;5O}y zC=<1FR+4LZbhj6;yNvfAxO zB~;B3NpZb%xV$~~$(b+#leo7;UtOW@Pv(hzy;{rQU5tsYH*mzVCB*`IkbKg&JAAyk z2@MUc`>jT3)LN`jCJswDKpK5r*<5;r)`t<teNvIYn-KF0tFF~EUNu`65QoMP0yU!QJK(q?97U64d%77b39 z>ulUyUFpnSyf9rY7AjwR&jXuI$~a*y&RWum96DagID7j5k#Ws|Pdo4CJ$iN97_9+6 z(qhA{!c;D-2x%>sg^$S_PU^z~X6yT9BYbUY^x$%X+vGwvelB7cI@JlgW#arOsV#uv z1Iin=&CM+xe8s9!fJ54zSC43Vz(+?%H)hn*(Lwm=iL80@L#-=pc4o%a&8@Ds7HUD6 zkfZ){awBrquvlVZB8PEXy6agsXHrLElDOH+gRz?A?M2?3(9gj4-O;qII-0p9|K|!p z=x_CN$pSQa!J-#ogj3tBo?pAr@jcCG_UL*ex8A+q+s#CBQmqf>RCl(F3f6gB zT=m83N|+H-k4`LK6s9`I$%P=H=?X&QZpsxxC)nFzZx=5cZRk{JJ_;cz5FXiBu@vES z!Y7q7aT>O`bGw4vNs8$EZA;79yj10wq!NSiK?<&vk^wK<{X3=!7HE9Pf28)-C7>%|}cl>hbSY_G-_&f*?r;1t)dUhUyBU@KH_s}J_0gV19T z&_4SjDTI9%qd`S-j%TOZb}zmlLtdsXK6;M~KQKT=>>l(G<30R>n+T7b7>1lE%UGnW zs+g9W7?8o#TV?JchB5w99t9#=x( zTYAGdv)HyXII!E> zwQra++u5<>H>5@xj?yills0HuS0pam|7|n~t59{S&|M-)X=F&L(6=J|bNVsP=$A*m z?WuW=Q9+aPEk<*9KG)J`ImB{q;rcy+oaS*;JKn~|j0!=OMpA-j^qAA|`GDR%keOG) zmy%;v7{WpohIxCwGbu$_=?a|?m6Cb;-C2UPSQVB-J_R3xBT?ziks*a+F$FDLb)uFz z_KyaoGAaypuKibol)jcy?*Udt^r|ZaCg+P)oCXDjItjy6_3{BJ$RLbnsdhi>k3HQM zKli2!eeciu(YVfX#eF-T7gx}a$AVp#c?&2 z=9?WQ4?nQSi$4veYfysaV14-xcGITve6IIC<)mk&`R=BC;IzPXXtlgFM**b65M~N^ zpyX^VaTor`rrFmCFy>f(Nh;M!<2nxwi9uwVumX({EB$iE-3Y86*;M>21u73IY<+#>@YEv~o0wr+tSEyUA8|=l z82Z1O%xR6gR`i_cC!0-^b)1-FK_3)o$>EyErz%vSEG;#e*9)6Ah`!nV98*y9w-_>= z+YGq&XpxTux%k6l09rqKdR^}b?%b2b#;mv6+*}~tkBnchmdl5In#N(YJ<#l( z1Vz{;V*UVxBD8P|&dN^L)7F*=dS1wKrh%^gI?G`CeGF@#Ab@%O1$SakF#oyh^?E0M zJoE8zKX3fQ0QsyOY5<^_CwdG^*^r0RSY{`Iy2^36mfm_i(`%EM#n!2%&#o_5gcN@K z0ccNYwlH?^Olyjf#=|@P@N_KQ=(MgRf*J zzwKg-w;iYgytuS*b;S`peO>|UdY!IB;oPQZuqymj4vyL7w#==wnKWo%Avt|0??I6< zMp=rJrn>K(P`liIm3n&$+RY&H#h|vdgG(QR(HXG{4bSR$-aigOjH3|Fz9{)q;5Qd} zKVAEE$BRxvRtegB8{v};5pgb&Dq^X2@`&FU`4!s6UL&$*amr)V7VcAgZbmDF!3rKJcYF%6UXmRO_#$k z5~!hDsSCLh8-pxT7AD7iK*u{YIydJEjhUIbUfSO%rw)4?iqHdQ?WUd&3z}Q>$W-ts zhLBz6_1NPW)HOaiDKE~+iRt0@*Bz?zpR6uX#9K@u1dbKvoNLtcm-0d`NV zG(q^sV1r2Hh7Z5lLr*9*QQ)L*8yaRA0>$hfxRQmJ(~4C|;-7AYagUDe6%cDm;LA^N zK<)`GnJg`4PM!qhSjyxFR3I*C8ABr5L?!w)KcqXWo1uH|IBwdF$ z%hl)oq^I4{jIzQGK&Jo;gU@mg=U!ib|BSFO8on_t0PtgI)ZGWUX0q+83HT0|yyVND zGG^oBV|!6(!buK|_q$c}63%o<%v<&>!TgQkvVy|LuHC1I9MQ{}0}aXqG}xX2j^G$v z+qajmIo3$7ryC714yvl4gM$=Hw=0Wh?JSbv!xqP5f;TkXDg*dPwM-#wBSlnA`rF%E zuxO{tYGcID1CxP+1E(h==&-FMW+(>?kDlIWNdf zPL33D#V)Z}MB*P}*iI#wbD@-P?3v!F{JKW?%T)!B^RPsyqx zR5z>)x_L=epMDJf zVp=F-=}^=vSJQR6Nbg+_&~K+zZf7-&<2%b`RXrb_Kil602C~VJ=z5)Gp6UINcx!o+ zsiCl2)1nXW)0B2Cl~_(07T0nTir{k!<8GKqH?~r#63D6IHY(w^UP$Md&*WIls68GR zM&Dya-&>R8<%~czj5ah-mZ>N*w;^fzIAg%*5(>W|*fB;?gAf2Yz{69(q9$Pr?C=T@w18}%I=3BF{_FId+# zO_OoxK4c_MI0VI{^r3q+pi1QNJI=`SG6crDZmsE~-+J79X8YC?$MC*V4@3~Ib{1Su z5C!KiwYy`+^Kx?d-@Bp&-rfLp;8d=W7oqf1GkCB6F2mzkK-Qf6V{ z<-aYmiOJjeqFwMDj$1JI4>g4)olhfFYxzy0ytvJ#Cv%WwVnCTn;{B8Cn8FpTBM0^A z5J`W;XZY0po2YO)74-8`l2Vzw1gFG|pP>Gwwzmee!o`q*caY2oj z0EI{e_E9Pb;cYm-h%hj8ZdE9goZ1Edm~a7=`ht#tZ!3T(*$w?@u~>VIPDJDcUI$9^z^d2 zy6h(3M3hvbROG0zzysf*K^bam;#||#{JG+8)?-t_JM!qO7an8P)UVIC0Z$Et&oZ1j zixS!WxWFx1$v1^>1|grGktO4^@KvRd-5nPP50TwWS4D?;5M{P}g@7;j1j?4!IrD zs?P5it4)rwm0#x&DTPRzoa~&}I?(s&iOufH^=;#$4T4iB9-ydl7OURFJvVCnN$*=) z{5oOHEq{$pLw{g`uT)`ZJw*VOcp({uz15S=D~q5g3~6XGp^K&=-3@%}S1m^5cBqwgWTq$o?E9A7|iOI|8)C%nN{ zI4fO>A;jrn2$>Aj#L4z}yYTT-_oZ+l!Z^tz&092a)6%tdY`|f&WarHbZjwBPSl$oo zx}nolxvJ}l7C*Ze`JD^8oXXyQbp-{rJPCAVjJG?LsyI?Xn#q_i4S)WOoM8gZEGe)y zgM$uco79}eY-rc7uCJ;$f~qM3lNJvv#)l_h6e+*{-Kr^+hqeq&&sm70MjLcR;*k~; zq~-0SOa)uEy3$8De2=gL$B2S)XxoFB(#NpkA1-dtqD=kjP5IeUIetF#w);R^3NaY* z8L)voGezULzA#95Uoy{>^77!U-CF961!1VIp9j^msUk@L?b>01x>XEavN_h&-zkKz zw!4D$f`bOlz3Ij$k#uI5M)m;vAEdmoju!1A($Xe+lwPMp2$tDOsU5YM~03Q?g-U<<*L37WKEdnTh~$G4gN^Hj7GUxwybhL)XqvQl=zvfaCw2J63kouXAQ zGfiY?MBZxG5x!Z2r}%IQy*Y%Oxq%NIh!^jZzOiT11X{Nr0at!~*H$0|Ga=GT0P zS9}UP6f9E|EOT#qKgBgm#63U9@HzSY;CO2}>+@Jwv?0;W(?;2x+)5?Y96>L|53g5} zHb~(vT=DK0@U9t1j#6=|z_wc}_px;SB#h1gSy85wH*HxQcuX7xOmNdU}H`T8ZKph3gTQO5wDQ@v1Z1x6jd)0YO3a)F5XGbs|nyNd?nhe&p2W`vLy zxFStz&ap}9uHak>sVGB^st~F#`v)lHGTk&dp($n?jI=mEJX-xk{opvVs{_~jKTXaR zp*I{?uCOu85CsBP>oo!&&6TRE2=ABctmvBpxiVIhsz+6g1s-az(@+5&ZJI>fw5~rS zXZb3fh1X1$@WN#(()jJLq$>r$>MOqLC+{Sy0-GHlewgBkK~OOe&0#6PoUW}Tt9a?* zH&3t>>SS!8>3+=>sX&Lq@7QeFTQxc{a^%WGY-d-HO7PK;0lbi$MxlRs@*|y)?}#C* zW~3N9$#ae0A>PON?615;-c{fIsE=-1TEgAWBti_1uhjBhe>u@=)YHESaS>>Nm5M?; zH`X1)hM%oDuRKer!{H6lkfBXFe+pEemw3!`i=9;>7d>$Z}>9U`3wB7(Zn>{@>jGn5ZlqM)euQJ!;Utg$M5F%6uK zzy}rVJ$*(k-WFYOASmihc5Wa#P)_6JEg<+5qRW=KKw~SVtdOzl_|JZx|Lo`N+(Mq- zTN$*6%w3%lT#c4s1!jY_KMV_zb6xrA#6^Yr2l0D#)dUxL+`)l#CVtM)wmT1!!Y4#( zTCKV8!10J+GH;;pTwgm;9k`Si{YTd2}(Na6kqr<*OM zOUp{=@G?sm(y2LhZ4PXNtwXU1Ybf7&5$^6?2nHAu=u3&vBF%b33_32AT|%MtuWS~) zf7>!Zic%-mHXNd!y_6TVNonvMu`gCdTpmDtJV1ERpE;S6P^$uTcIqoNxd>Kl5i6dvv9eGTqO=v{^p?3SF<4KE)6F_61+%$W_=;J3*5} zKDajNl5bi6!(lCeg`U14)-X-MvNF+tg`iM2T1kGV6qPyZ@+CCcDAV)j9%iHo+>T%( zPH_@-(%;F{GYtM^eS6`GS=g{0gjSZn3WG$GHy=dXt?d*!?S0~; z<&do8an%zNVc-(-O}A#kyCO6rQ;jlv;#zQDA5OX8OG&{9WElwVsFcy~b6DV6Ixpx< zlMMO1PK+NS)$eUm_~BKV?TC^;13R8s$I34P!-()#b?QIIp~uR41YfvPY^%IMTUE5%8^3C z+|&zRjZ191qarbv0wOV^NwcvP-<22!FFV)Ft_9Z2CaG!aTuG3bsoZ#1~wW@!ka98tdhlb^!O)gLU6o6Sf3pMh*3McKT_FcEE zac-G!KP@BrPS>ZR=Q+gVCh%>=2@?>#ekSujh#~JjvDWaO9{xUev6GTh?KgBMHlaBu z7c?5QP9gn2T_B9YCor|CS2<}A+IX;1p=-rM>+l+|!Jckc;glUGFIF{}EsvXDBB9G2 zLeV8HX|vI(fi)`kO-@V)@^C2VQ?P>~^5470v`Ab1r{Z!g^7;a_A5l)o_gSRy!r>xi zx~*t51i_#05jpr7*4T$orOm*cqG8MA=@ddD%U?$(7A|m8iEFp^*~WGqyx=)DINKK3 z(LD&SAbSQag=Dd3N{(ipk@y?yF0_qJ$lJK7xDh>guaIKl~T-a`4RQ{FH%_hA76_(QMKi9!db1-YH5L<<)6@k zAI%W3A_zxLJZa_o&<-r2m|#_;VHc1LX}fMW)%C!r%RM`lmY8AUfA*7-gMXtrpz|7= zG2JBWxuTY*p`*PDDB=$!2=TCNbAf7n-lE(nH&g|39EwOLZzc>bH$!3RJwN$&MrBXnUl;3+w%8)dxzJBiR8$JefT6u3*5mBQq^y0?G znZidHXx%Xf2MIJx)LC0T@3)QH<3$}dlZ`t^WRPVQ27tz)3Wg(5X4W|Gk0y&D6d`|Z z?y>xLB8;yUCX>!s#1u{f_J1MOOtn0b240qto;5THYwcEYkZD0x)nv&J+MjBE{u+Nt zATz-c$1Dv5Z z-Rjx?zR_B63zH>z9`f_2qh1azoRcY7$HDt%=*L!XcmewL{zMLKswUQ}IuDNvI6CK1 z2wTCZR27yZ!Jhvs4nfnEOOz=)qiEKn9*&UNi;z>2c?Q~drBd)SEaJ_%Cm2$O>-hC6 z^s&FcKZ!vVOkzrnetr4N@#GK03fe3w@$rH_a=*^^_E^$JA6ILuGtUcgxxvu^OvD*N zzy|+Z_jYuUy0#^qxn96{(Az7^y!~F;XBKF+qL(umjyqyhqKwVCxjviCGBq1B1#BKj z!9{Hff)g^O!$fSLFEh|XEhTGkq1tfdgQ<8dRK}?yDB5y=$>Ko}+!BSM0 z6*MzW6&!#E#wiaUY8AOFY}?#Ir^=vz{^8ltxYir8*OL&^ZWPqHsT{m|${Yp;pQ^Iz zP(`4!8}1R|ki8sr$wug92LYftoHR6kC^R<`g3|SphAsb5|2{C0d0E0i4Cs;|O;(Gf!o3pZle?A;_eNt2Fy)yKJ(~%Go6;4S>fza{2Kj|i# zsr@IK38E7wJ|e)jeRG`CwaHsk4KP^61 z;O#Vvf6tEauz`w@A3@4AKNR2qkQSPM4Vs9b^UGOErxI5xU9CRvL+R>j&9=r@^nClK z#i%wwCWh2cAtF zwfEm|)3bqZUuUM&)$Cij%%28|t8W-$spV>qgN}C(qvSoh1-!qyq$q{0t^1EpQHKo1 zHom-EPYQv1mfOD&QcNtJH@yEW(JB1~1Iy0N7LG>ocl+uxq?Tvl1hNNSXFbt%O;Ew` zNvt}sQ?E!56Q{-V_K#92A7tK%d!Yl_X{Cjf!>7>h`-#ImOywObzRwwNy|=#WT=+qh z!2k275{jeC-p(nUaX|{GZh3163v&UdXnA~2h_|%5dKrw$Mu5Lz(|&~B^hwTwVLD-ju_EXRNV7^`F4s6b@1C(R;-6kR>|f8G7b zudFQIVjjSv)z_>%MFCSNDnOm5cMdJ=9c=ZE<#7c7R6PZ*>v(t{yT)6+;#bLP?DxjA z#t<{HF?a)K`j8>95LQlKLyvhH7CJ4qoW1MyR+Zc|e_FD#Z1&jwAA`zD)e7HG?mh)i zY#^biP~QNVd4S3owM@epiJiwXis}jZgqG$zbvhNk%-4 zWtyJxi4h!Fj&C|*pS8;aKUwWA^Zp+EPRy>seA4|Z$oFh**~MaTDrsz3g&5@1-cek< z;@#qRNRy@8>GwEOb{}4cT(w?RiRg4gXyRS&va?ujbQ~ZD-~%T9<{8jIn04Sz&biQRHuBHVdxz4Mrz?{+1=YPs_}b-EJ|EyAk()tR6>r( zhLbgaL)$N-=O_}4El25Gz%O6^Gz`ola86Eo%AKweH@Jil>PGDZ3)`p6%(}e}VfRvV zPTn##+IDenr{I=V&1gGet_ZBmkVQO=q;TNxVm9CTddb&4ex>!V67hGmfg!BHM^vuqCeoFI^ z&9Kzuglsz z`oRY@l6X6KsIdTCE=?Df1k=V&1l`KVQxL-;R6agEQ5A$rT;9t5=xpYi>P?YmNUBL7 zR@W)jzKDsAR>wqS<&bdoq~P`=+CH!vC9By8-B}Di@93R}&-(drb2uA$0UX0Me9xfJfC*KmW7r?vT3DFIpsI0sQIKSPkI)|Dp&-9S#mS*s7bfMq^sL?Eo5dmz>{U5C8sCNhn3433bmiDiY z#s+J;$;P~QR8t=98&I06*-0*9dVIF(g*!7eJ}Y;f5&V9Mf5fe49H2m z>a&eG9O1FbB(KTB*uiZrZbekii1W(VXZ*6Vv7ybFT3cJQ*Ignmhp%rktoQd#2p)qA zE-U*0GegAEwmH%!!g5LAx3DK0n$_>u_lJpxl6@C{cPtGb-{mx;7kS@cqa6@{4iP#G z6(73EjRv5sjB1DT`AY+m1ve$VC7E}eg)9jOiQ>0f*22`3shLywFwXacF~)Et{*wVGW#Mj(kV7WRsIhE_*kKWbPGHscWTwy)&!LMlDo z+HnE&+L>=l*NxwRM;t^hyS!eFt!}EWhWd!HxH%a=-}$<*R!yF#qn*6jns$70($@gV zuz7f|85Yg7|Hjx|8!oe?;LNh53&_(ic4EtRopJ#s--eoHF$nn`kzavzW3r&4qIWqE zlM*ap_}YQ4iT@$U9of$k0ZhAM<-Aq!dfn^>${7qLCD5%y4-=>IEpl+|?}y+VgOLs? zXtg+3HcQcf^{ajG7L@F_RVizqE`OI1T@vYZ-9Z_VP6pw_$2Ji%GP-gEGd8AJWLPAO z=<>HC%Fgi|UPKM}fVG?3MHAOk+TLDl6rk)_mb{$YPxLGSNixcKrOLoU6fXq0weL`b z(By_i7k`J7Vq?)B1mB-x+QWXvwmL=#SLg+oF>R~MfF3Y$&O7fp$}llDzBes-VA7!@ zV>m(cnCVKsUBFh(Ki>zUQZB1KAO^ntXb%e#6c#4V>j|i03-9;$y{B;j=;%bJi$L}| z+4aQ<7--vpdt-LhIGaeKvFP+Z1ZP<{81??$ArY;<0Ra-8n4(cew|5(sRL7)UDFjSW zt2{z-X@{^)#Vwib>s~>q%8H5uD%ehw-7%0ua&EW(vc<5p5CtC}lGIJ}Gm35!oGLI< z8V%H^C4-MbOs+TB$0))kchWZD3J-wT?Bry=)7!-w-t>FKuzv4|Y-a0%gyH@M|B3A7 zNX19R&xpRFa4?X!iEfOY)u>yxH9@mF4A}b@&d#>m1ppi~QijVX9|<-H3fD48vajqd z6ddv=oOLUy%0Glqu)i#+z=1SjOp!tFB&p@!%n^8Ur+KP25Z5A5KKWE)R4a^Wg7X&L zx&L6%P$jL-20wfGzdou6|H~OFr?7ycFl!N*6s8!TDs)JPhI}}I`Q-&i&Xorr6G{Gs^+vjn?@mZ6} zKaWMv7biDgJ21unzQqQyf|U|+fi4GUU`|K>h(Pis!z;w(@$Q?rvuSA&W-tmUQ5y;* zv?A`!KBv1$Cg~x}&?-n?=sdl|=Lipg)=gZ4TJ(!ppnUxz<#~RVD_?;NM1|Ewg{6bm zz3Y~MY3i(UzHBb7xy;26GR1u^MdCBPlq7W=-rO#g9xIc6ZMxnstyW5lgFPYSTJ0jM z_0nI02qcS7>Agqn_plq*5cI47=Y9NB|jw zTxgN>Er=Bv1mjc+Maa`5@ny_(y=9Ty+&Jg>&YQ%QkQi@lV_mamStgQu-|CZ4)?iQv zwA03rks=Nder*r~L=|f_wvgUOaK`>0gE$68#&|Mel@=I_`{mZQK7FACK8ko;7`vb> zL08>GH~{p04J-#TicTu4xm0-2KSkE5aNNdf$m<9Is!=ohcxf?TXFfXb#;v9N`U;z->%7_0?}dX& z$5~(jnK)!nZt%Z@eCb&btLc~Foq*jc-G%4@imwpX;dU$Bq zR1o2sUB7*$^`f^6=-Q=+iFq8gW68-)CrVq-`hJggd0=LN>M;W#^UlWG9(K@ke(Egx)8Srph_W5~V?u^AyCsPvG zDk6%Ah-ilRyMM@7oIl0dUKUfF+E%YC;qYJ_?Ng zxmb3P@~f)_Z6HgLrsBkpm%z-<3+vAy=%KIL#T`L=tu7b>zarc$aCDjW0)&N zaG_mp`Z#Os5AcL49@Y6@uV2E@UJjPtLtecjkh5mwqEE0E0Iy=t{qJ7PJOlnw8>g$A zH63u)Uoq7xd43huuzoZ`KykP#g zAAkb4kVRt+?^E4APSVuWfRkktM-0GxGZb8e973~b>~Csl)Z0-2#Vpq}|Gc)2{J@<} zmW99}{wv7|ObZDnLC=E9N*SR~PNsu9&2Bcj6@X@+`H{Ti<4@RipL5zjFAyzMzr08u z?>(&r`;Wz6hol4WqSvIVP1Nb)ArP*l`-*UOfF$_AU$D9kVbu65v0K)>adyU5qk#p( zZ?T4fG2o{h;>us|l+ z*=oMIskO9C4Ck=Gm;fpL*bVXS(gqO|=)$#>s&Hf6>e*0WUibQ~K<}R;*>QA{6PrC9 zwg%@&*Dz8*PP1Gs+vE_2B80sZ0P}nEHzd2qzP|>I~O4vOx?1|e}2mjtm5Ox z=2GxRe@JNearMhi>AX?@Zv+%jzmn3yxvH#USJz)G#nPj+p2G0$gpmC%O9r>YQv*U_^L$c^W)G(O|8MekkSTBwRTyK+M0z;EF~> zZu5Xb{3evjx*lTq6dEA68Ml|y=G)Eyda=WQF$h8<|34391(TfcXeF(${#+0QUwx06 zEZ>S8G{>Ybwd_O8m0^{5x#}^1;0juo00?0P39SG0hWj726(O4Z*QmS!VS@~sMePBF zlWYC&AO~4u{z8M=NaEz=<>e_AjAaH3eju&m{u|*64=_gYaJQ^mE_L@YHwSn+i=+-) zLPW;<_ml-~Eh~Qt+$ANFvsX{FT=u(o#?o(E`ak5tbXko$ySZJzI;4sLFgPJbrAZmF z=DA5wMfvAr!q90xy<<_BrP_&Zbi08|qD{XU;<;kqu~Bm4v$dr-#qq>+Pu!do6PB7egWehyh-*>Nw=v9Fpk!2Gq6vEWi}V_2iarI!RmK|-dYmKq}jB1PK%B!$L4ySu8Eu=vq!rM>fTF*xnt${( za{M$Qzszf+k_V|%9@W>6ro|r2O@i{};hfjK^Alkzxu8VRfbz$OAH1A5Jw?bknOV$3 z8O1P$XW!U=QZlwc=oEkr>JoBN8~>D4PdPbvWo>_1tlYkGbN?Pt*5x0TYzmeJYvKi? zf_=or*QrSblXmFb93_hjR*I22UbcZB0VjVIaV6K&G3Ve;Bz1iFm|axombj~SnYqcj z#(Z=>woQ9qPUVNxaPwlDJnq_XLlGVm5aJIE_Je0`+uGK2ep@VsQm_8@Mg~Ci`W3u7 zJ4;@xTd8NPUcIB}ar@;JoY~0y%|k!K(^ot@B}3z3c_Zkxi{5%_MQpXn`OB9t;vQG4 z%t?$}URaj}vZ(z~y}_?v7Ju#J%Kx7Ik=JQL|W$AX!FQB z5d}ZiDE1-Y?2lnAal$kR^IhOFGSaV<6JUSg4Rvl)#|FW1$PPmj8Y;E6tWlG$;#3;9 z&una1JC|^Ba2PbYM;(~d3VFPDOd1;I=#|KnMBL@0QUry!s zL>$-vk$|Q!)V(}r)>$-+QEwY5Fb)V09|3qP=*>7?75|?(HZXuS5u59WB>Ew_y#`{x z$gA$&<6}9q{!W zGIEemf}WTPSaa?Gfdj*}Kd}dL-)S;O^q!oaRvXPj`c_?<84kL-7l!bi)Uj~V%W2)U zzP{~ONhEgeMY_$)q6wULu&_bH^2=ZdqWgthzq+6{r}qOEM9ssl&owb&3L@TRCI2}N z2?FS5DD$7-(U32QXf!}t3Q(MNGqgKr{*(AZz}}v-SD;*~Jiz{mb0=u4{hy zf1Ds@<>vQQ*SNd~Q%&^t@50xYKl^ob2SY>5MHWmC&xRZ?vDxvpuBiEuURE~tB6Ltp z`nf46Aloa9l+sTE{$8Qu>rS5wx!aL*-ed$RrMk2>577hK`zPMDb_Q>VrL|?CbTxp6 zvE{)6TcuhIRf>U|+K)`3XgE7okTGrQiqN{i`A?93bg;S_cz`bqUw(dq!;n{w>JCEn zkshToi^_h=`p*IA{tHvJ2rF|$gv2W8Tolk}wd@=$vGFk(d@di2jh)J7sS&^fS4OR# z1N{RN+1%|n2iW|*f{Y!xf`Y`0>A#EHv|Qf3+@BP`T@EV6Gtf^&k%-Fu&eptRHvR_& zCOh!mjr%A`3C;&)<+`n|x82cH8o6rEPc7rt9wOwAkED%G%WMO9goOUpp6E|mS$U^x z`ai9qp6%@&Ovi&?pHtb5zNtzo_IbCqfB2nUtAq4EVei-zu3PU5>W=c@Kl$T%0WEDp{P3cPN=j(giAmLprd!`0ZAM1=3MW@r zJx`VhNO@_k_hM&gc9!V#%k9&*c z>2Q$yFU+t3&o`Pa>Xv={CBPx69wyFJ^Y$OmNm=6-gLc<@rvoMn5I z17RB#=BVF&yY|%l(mso5%1C7|+oypYcRkTjP>XMMTRGKXl*dYO-m)YH)zI4SgA*JK z3Z94^Jc~I`{!ew`2nJNUvgd&$z8J?7VX{o@r^mglB)5P`H>E3dKR+@5l{|k|?%m>> zAT4{E)sO?!tb=ngRH0LH}{fqOkaMPcvtXhz6nPX20r@L z=sCE$^5A52LBuMUE}7id3K!Ud1!9lbgQFXtIPhzv*UhW7Sev%g6^LOVjMYiN_I3+P zOGNSgG`Pi?gr>j0r(3g;d=6K_`6p^FJYc?RVXPV+a`?s70h~Kg-^C zCnh0TyM6mEi%Q7Gu6G4_6||enEG}*~@q5$nPHYQ9b!BC3I)t_zS>?5#B|`i+O@m$J zq@*5)FE4GT)tt>7Fes71K_;JCF;3pE8gxx+j*b^PytjJtq@_c=&M~w~H#R=-jb&?-irf>UsKd(2JSqJXw$a&m~;Z)-EH?vRL-U zvdZKdhdr>X-#w>9DBJGuU$VaryJ>R{O)8Id;$JX^qv*1oAKe)2=WKY!8SESIG&QYP zPdxkh*1vsf5#JFQfd1zs)WC)!clVoMWl8<5JBCHsuH-a(q(+yWW5|sPZP^9fpyccR z%O5|?jsa>BxP>rH;eWrn?f>c!zoz)#`uY#~_F?4DLnw39nVO&6quy`5#*1pvY;0!#jLRAPt>H{Bu697ch=wO~D6 zT|pu5$z#Vwx6fr9Fb0fjMtl4JYMaEL&Tc#%R`0$aG4G$Rw%(ss;CFWz>#L~6Qi$la zHS`DF=2>;0oHqkv)fW^xFAQwx-q}kk5lfN56MiPnj?By) z6J4yWFqIIG_XI<*1Os}v#q1Oy0@WoJ*D$Y1~0K| zZZyBjxb(Zc#H)8L_%@dSvh*5mXGXL9)&CazW7*F)tdx`%)HibCbJ<_a6cf(=uEc35 zTgWtkkx-`$f@vLfUDaP+Cn$sbvRWth=Qdq+78~y4is3D^VER`B{+qp-ol)3XPKnWb z`GO}a01q&ON`)+dgoL14w!-p^%5I?s@x$B~Nwj|}@SGO3AOSh?0zZ+U#>=X6$Hef& z$NsC&th#*s0wtHYgMWB+43VO@TsZf4ZCNdvn)${>vupzyoI_%vmM=5oH7KFSJZ6ul`=vbo75N zNhK89$&a;~MwwTj12Grs3UYpQloYPhuAEXdJ?S<4NXfXIPh+HaZaVnhYH!cskxrj# z0mlK!#TkNQ*o%65rCSgxV*94!ND(8GaB#tWp@*PQ$|G5_9}0AAD_cMX-{;%oPJDcV z;$L)~nMrP!1r}?cwcMi2bcFv)XRJs-UWMMrrC+8-6v);=E^OL0O7k1J9kyl>zdzNB zJ61+H4gRk-t~?ydwT;UXVlcLtL21aY9Q!i%HDyU;7)y>V!&r_b`_hD%u_T6wY}qm$ z`!d$%kQ2>??E4@ojU;7_?=_usUElRx-#^bE&$~SLb1%Q={yoqAzNqOzyu3Uz<9VBp zo4mr1^a_-ZHYar;Rs6`6{M_r;vtJFe`S!!ZMbb5SA>KYdvhoX(%ZJy@7iX<_+4t_e zaBphC^PX2#Jy}>>4Es3x;PI+EILhN#tD=DqbSjYX%)#Z;1S|rwWL#>m^U2|m<2EiG z%C9QInj|eF6IB0O-69>4gFHpOlqScRuiBYTWxQJ@l_V`45Hd%tpz6qm`zb7Nchvbx zg~}$odW7#6Og9XX#S;Zl8&DlVu2Y(epqn!bM4A5q)L1~WP&8ipfeHDUasi7e@_li* z48a%9NA5-mu`@} zs2lymf^TVg_`=H;s5*@vj_Sz00EkQiDSlB_sve6gZw!R}K9Ik16u!!p_v;RI&PfTg zkfx>zYAF7lx|%>Sj@VS*hi-vI4v79{cA9FIGtA|8b~-(MI{E2Sbxn<>wI>EE>OUY7 zWJ z4~+u;Zy%tdJSO>2wL9#g*`xh9V6};G%G8*o%NDG(Qks zS{}*jxtHWGvi8*zkd!2kz0meH0>!D>8eM98GPO5r?}4Z!G92ke6hyJc|T zpj@FA!j3nO7QR9O9tdNEA{&U~6E=^Y=OeB6Q&k)^HI3cTJHQG$^h>TK$l==F$N5pp}s|`VH`&zY1H*axZ#%G(e5_A!ZTTG`ez^ z2d1~+I?Q=~^@H%<4v#{s?}>IWn4R%xW8!_x&1RSA-%KIwXO}H*S;uhMW&fH z2p?6YMFtj~sd1cW84-1<3G~6;tIf$`gkvv~zpUJ6vwrj+xDyMgD-XVRZ$O_RAaI4_ zb^2hA{NLm^!5WU^nf6;LD*WAwkGPFAr4;mdCbban`mWBgNL;@DTD(Jdn7v_C;UB3w zp?w>cY@nV#TsJY|d)1dNc=+<3%B)>TS;brN8E;o{nHhUZb)c-n$hbCQ_U)UYjE;GY zSdk5^|1FwZt;GQd2BePskhpheQ9cBDAb@jfiidkr-Vf$Kf%FZOyn3bkj9Fe*vWMuP z(ApU6uNTv`7|YPFITu}C9$0kr<|y1=wr2ic+=1FG0mYP1^{F2Zik|ioxCVP^qbA-_0*X*x8-=wobo%zs-W_s}R8WF;OLN zwzt(AysCOl)ovjWnOWKUqu4T4XP3}^^yHjJ`PZy7G|Rv5XbY@DG@=!A92jq}7+ z?5LyG>Bl9>E(`1b106Txl*i*!4PrYM(5tllrfVmSf99;Mv2ifR^py?#rjqk3^3uM2 z$TTSAS1AU{g^%Fy6RR;cSkzKJ1hRbm$ClYmCqL?4;{5wy4i01Q6|~>ox>+iK3S$qHAlt?WHKrwWPE(QLQ+w`KSR}>6Ntki_o%&g zV!Ks(^7E73eiQi8u1;Hmn4KsZ&&GWB-AR2ILFa~ZpwNfd+^`CZHs6#uqI*00*ju*9 z6{aL}!yfaoJvV&v`sIx4@qWo-YTMt2EW6cv_UkUTs8Ok`IHhFLe7kvH}+)Um?O zmhtGDW;~;C>$8CHT)!IcRt%RuN&K|=d3&;VNhZDZQJN(4D}86XUS3<4@cX3{2*YCW=*j+P6-7l-r0s5Nn~90&6+=(H>(&o4cGVz+OxN;Ex*>shd-dj{Q^@Ve}CWxw9gq7EV4Avs2IMtDX* zPzbl)Q0|MDI~G1xNtuFr5lp0-9zA)`q`<25uJcRg$Pa>j>+1uXkDd0z+payTOHtgs z@nCWzJC$-+OV1k2rXnC|;8=#LHI<}InwugT2pV# zMAb?SOAm`-5)!DN?Tk40_epkJh7Z;iPhXLl+p50t7i$tJt?58Un(#Xe{cX$?u7rdT z^4P>3Y~aZIj{w6!vgC2zWY5z>GM`W zUbqraueS2C!Rs7&#MBtaA*>bmwA>{vMOMox&cOQvi~^jE;!<(FHP@13iG*fjM)CFP zz74@|sMh*(dJNsiD`)V=RQKB|F)wSQLu`f0_`piToi?{;N9T@u$?iw04X(#?Wo1+j z6a2ZV@!Ro-I8}bWd-I*dHIiuI`2{iM7lU1DaR3Ap6_OjV-eU#CKC}?=9l`@aT!lusjwuE*$P21;lxj4tz8WNo4c(R=u4&I_B-!Pv=?rooq zSv!u7aGsihpClYv9crVljzR1!>$es*9T#H58fV=h{o0_3h>_;75VvIM83B4# z6?>yu|Dc_e_it~2N^o8ju9XMWV!_XEYvjzp5vw$>p>E0>X+{w5G5}vPIoEiHG=&G}cP_VDzeVDMiXme=PnI)njhv8ITA?^Y#bgTSVZ>T@l=1jTEX zTAm(_wYB4_AQ+O}Vq`cA3r5?N+BWvF+}6Cn3h0o9G2mu*qD+k|3;9_4dP9oJu~o6m z%vC#==pSVjzY~svdv61UJDK9G!6Vz170_>MQ>oxQ>3?U$dpe^cEkQdLvLyWLmF;h# zV&clm%1igR+uLUv8*hPQRg#G1P2csxb`6GDeIzdEmez z#}G*w8J^7KEf1i4f6w~u4cxF`9-wQsQbgZBjd?GX-#9ahd~|AoU3wD(g|Z+1-4f^&LQQni6{Z8ien!RdR~qn75#H-i_4na zNeq+YXERJ3dyybj&9$v!LQ=+>z~P(@4$?Bx@-AIR4E|?Y_3Q`OGDVeZTvMbm9dAbh zKnr@@#Lvq1wf6aWG`vq9k`{1g+Ju>rlryQz={nXN7!N-7Bs=rd)d6sOV8!^a#e&u+ zia8bh*8-Ap!X!+kDk1e*SQBS~6Ge)qR4jB8$iYr;;J&!!fl_$&U^>y(&Tce-Ra8tD zhEz|A{I>S?YA#`x8mjTMQ}hZ*6jQm4P%j!DpZ*x6ai_u7*7i>8_}JJ^Vuvn3&OK>T=#Xkf=@hJnY`nZce;v9^y_6`V>D+V z@}jO~3)9t;DvBF!&1uP%XO6{=FM`20qHP}EdN+eQ4B7?J3AlM~Y%^qBlzH$OW55gi P1(43n$ik4I@0#!*2q~7O literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-getting-started.png b/src/designer/src/designer/doc/images/designer-getting-started.png new file mode 100644 index 0000000000000000000000000000000000000000..14d949933ad75de8df598200ac5e25c1cc64a818 GIT binary patch literal 8437 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-layout-inserting.png b/src/designer/src/designer/doc/images/designer-layout-inserting.png new file mode 100644 index 0000000000000000000000000000000000000000..1b6a52c0e848474bd805cca7b946852ea8748fb7 GIT binary patch literal 7736 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 O0VgM|ELACC8u(wfU83y( literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-manual-containerextension.png b/src/designer/src/designer/doc/images/designer-manual-containerextension.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6552bd9013f4639f11933640ed8e94f4523414 GIT binary patch literal 11868 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 g70&o_HJwg3PC literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-manual-taskmenuextension.png b/src/designer/src/designer/doc/images/designer-manual-taskmenuextension.png new file mode 100644 index 0000000000000000000000000000000000000000..ea76727e96ec15409c6545b368f14668b1c5cb0b GIT binary patch literal 12378 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

xY<&JP&XAGpue?k7Zl_RG8*b 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 bIdcHtCCq@8Hvsnx00000NkvXXu0mjf!8*Wi literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-creating-menu-entry3.png b/src/designer/src/designer/doc/images/designer-creating-menu-entry3.png new file mode 100644 index 0000000000000000000000000000000000000000..2334e96a51c3f86a795af4b50b7fa75534057c41 GIT binary patch literal 5031 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 M?k$}vq)W{I067q9^Z)<= literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-creating-menu-entry4.png b/src/designer/src/designer/doc/images/designer-creating-menu-entry4.png new file mode 100644 index 0000000000000000000000000000000000000000..bdf3bbb86a1ed7a2d594f9a76d487408f61b1f9d GIT binary patch literal 5760 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 tECFz=1pop6p8(l*_3;Tw literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-creating-menu.png b/src/designer/src/designer/doc/images/designer-creating-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..9e4cd7d5c7415283291d8feee132d785b47ab0e2 GIT binary patch literal 2513 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-creating-toolbar.png b/src/designer/src/designer/doc/images/designer-creating-toolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..bd2b5ca7300f1d773ff280712ee000a83776ab6b GIT binary patch literal 12037 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-customize-toolbar.png b/src/designer/src/designer/doc/images/designer-customize-toolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..3afecfed2069d263ecbb9e43d66775431f3f9ebe GIT binary patch literal 80321 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

Qg;Kw(90(c0UwrwrNNu1ye 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 I3FE;33)Mg`@&Et; literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-resources-empty.png b/src/designer/src/designer/doc/images/designer-resources-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..012232127e5d4088e06079b384cb399d26605ad8 GIT binary patch literal 8184 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 U;ixh6T`53KSzGC|f>p$S0hTB{HUIzs literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-resources-using.png b/src/designer/src/designer/doc/images/designer-resources-using.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb99fec497ec9916770e4ef23f98bcafbd101bd GIT binary patch literal 4118 zcmYLMcRbYb|38Pb&Zw*tIeQ&)vga8mIT=}}vie*`MoG3a&M0>{i8>LP85ze>N#df* zN@b6PtWX(QQ9pgZ-^b(k&-?LyJYKKo^YwbaUeEWsV|(6$n^S}n1Ojneo;9-vftVf~ z&8^wNMK zNpYTx%UK;rymaHMJ+!NLK}-{X-S=HvdpZxX<3EYl*dtEAZ~M$2Js)s$bL_ViLa@q@ zANs7=GZre!-F2nsPPh+FCihaBOqSB}mY;4ui#M|3*RMukiML=jxw(To_KeoSel%Ux z=){U~1i3Z%HJpGMjpsSzJ1PVHVU_8qcbui6{pUd^@x%R%u?Bbl}Ygzfc@f<+KMl4htH9!BT=CTyEp2M-D<-k zBKAVUYKIn1y{!7<8B(;E>IyiD02L0B6qdKy6F!3yjDd($4k8Z-hKO%JP|L#hC|Kv8 zFF#(-zU4{IJ^>Iit{N`w<%ozW3wSx$z$(yswYoJV5GeQ?^DL-nh=rI_Gk$C94>pZyK3D?6b>CNXB4g*Gq@fQ;pkVZ;B^pP}zPr zPq1vz*nU^rWAv<)V`3^hde$Y3!*9hTU^*`NW1n;1%mN|HKaJMe4a^2pCIcIw$g`7$ za=V?&Duu$Mdk%BW!gK3qweL-(wg1cQ%SFjWVc|dEFG-;jLj3ViFw3W}xTl-8Oml`G zQu&Nt6}AVCx>&#oLzg|UNQ(YGc87Ffu%)x+k3IuXd}m8AeIqbA?@5r8Ab(dQo9USn zL%2pvL=7KYb^%Fs2B)Z$e^^6GFsS@Q5>2ef4r;LfgSSJX z)}~IK(anoITX#~k+i%dMW$VX-4P{nX>G3Ap&nR09xM?a52`hBbKNS?X^2bfik2G7+ z6G4_Z$H_>L&br8F&VG8C$6T*!Q#o97It7)(ibV*5EQ;~-j~+(xC}h(ORbhq-!Fg9; z35eqy$#kZlyYJE;IlHgLtZA&w9Xnmu`{QO*-c<;84+T+;l>f@hB6w(+5%zAmvG3%f ztR@bCQUeH3rx;H+j)tCCT4=^nr)U0X#0@!k7GJVx6bBilPQXC0XF%!bgKmTJ9s=n> z0#ttCzW=kBxyhi|2JKqrMdO7HZEGlXgV2jB(1OWwt6zy+%X+>nPCFr$Wz(iKH%R3{ z$u4>=hlt1vLLpL_jNjs6y!t0<_4`78WhmykN?y}cd!iPbNW9vp-q*GvCk$Z8mX=P^ zv&3HfpYpD-wxq&v1h)Jkh&&bm@k>hMeE;~=#7CFNH*59X>TSK)qKwn$V};1E0%3 z9O9uCTV!-x3ikC?;A0QhKb1mPGc?4{zPZ!h-kzTTJyueu-kMVq{)>}p43eJ%vmJOe zAGfhFcznSsMNI!kut=!})dhgz1#9pdy;mu;iy=HKdH&jsxsB+<%@#U+AO+RcMyvKC z)0&!8R8+uigMPQHthf%AHvSy`KK+&->nQbf`_3oN>*FAm2A(P&unfL3qVDeP_O21` zZD41Mi>O)UI6Xbhrsp>8+qSj1^H)!+bPSjpyfXE5Y3*@KP;1!6RCnwnwQS1N^2hCGIEiJo7|g+b(TEVNE@W9ZdSM{GmzS}7>tW`M zlBqG8QN#|E)Uz{E(_G!i3N;az`GF&btHw@}5{i%ZW(69}Uu1pKm3FAmg1qALiPeS2 zm~kR2cn=#stgqZkgUTgwmgSs7L6{6`qw8aZjLTjwXk=$sd5oN5q&l~}d4QMuAh;KB zCr#eJyeGGF{}a>lP1{G0KdrB5NtveJb6a)hLF(Ka8T#^ac1;M!M%l<6F*^$1WsKgK zJ=y|nES@R$TZotBZ8_#ZaUh(b_j|>|`>&>-+f9Mhm{+5`^|KYFex`vWv2(55bwbNF z5QHJ6FNq!5;jC-8W^(28n0qmN0^N75CgJi5d_r=k6t2`dnju}(kX7J-(CN6v`mt>= zj!_%Umd&Y7y}QXQy6*k&k#f|oYi^s#E`ooDsSE$plbh6BM=An>ZN0`tzEN>au*w!n z9fwd}k88BQHT4*}Eez1W)Q!KY=Zs>N3Dvhf=FF`|)*i*%UH|dgXL{&Ejn#C9HHH%b zfm!J=w56&LjH@C0?K2eX(i$80Q;&xG*^f*|qV|J~jgAVhzVHbQ{PRAg&%$-ww_>G+ zcrerfzj6cZ^yE&z*5{?XC$eOwj&t{47)IdFdZ;m6Xm>Luu8EwC*POTDO-GBLR@KI0 z$sg=T%2lqTonVf2CJ5)Ur}HEz_S2yB`c*AdGCw`%p*4fr9Y@MJBAJPQU75J;nKarV zo@mvamzk|DRb0(fa%Soco%_y-JVtYv<~+~lYF5AH;m>a!0GEYn@Qj+FC{McS8aKnI zVx|A+nC$j(cf37MseRo@VR&mx zGf(02_X9EFr7H(KQ#%}6$??m~*N4n6J37oImdS&^-B!WXDf%rG4182Ot}gvIuJBo4 zWTf|b`i<>p^*YabK5q8v>9vFlwvwmy7w7Z5AC5}3Ovbd~khDfHvfZrz46zR%h1%YE zvhrFzt`|nbnp8uPH_S3t(PEUJnBa!#@xi;_7xp*JeTwIW^0GAvtKCmle)GQMEg%{2 z@Oa_cF8I+CU0}e-?cgLSdiNhCR48tc_I^>-IK5Krgcpu{7aT z^9Tr`9h{hb$R|osGG6!<88JvJlKKPV9RA4OaLcX?zJAF-Uq@xgPhhRF(pTfM z(a^25ZwCjfvnGq?bITW2pF3|)PW_yWn0YSd5um)Ihg@k?1&wV3UE4FSNc<~hd8cV=z$><>#E z5h`NbbyT~zfMjqZ%D(^fYWYV>sV^q{p2wFk0gR!x_aF<`Y&PGMyghEEN`(fR8OiaZ7qGC$iGr)l+*r*G740 zkcIFs4%R}pp?7su7)W-&ymO?;ex#HhQggtRLSY^1sO`@1NK*cR{kNC|6bNR#5NIL0 z=rVjm_Q1K=#~P-aoat1S(-cR#KvF!8e|{K{>5`Mn!uZG9XMj9Ldgp$o#7lPXY=oqO zwyd?ZjO%))Q|_S_1W6D&66oN9_!3u5t#xew`3m2y|Dcl6F&Jj<?p16 z3_<;l&3H4bzG5XyEZ!9Mz4)gZkFQ>IA9_AwjP)1xt z-DBxE6G4|yChd0b_O^Msb=jr4-o4qI&|%9qB{P$sZ!umV>t6|=uA0xlV!6JSp%d4{JGdhmoT&LhjG7}c9bKj*(@QYnuRd9FJaCMz+U-tq(W_j!H ztzHovN%7UxHQjqq>U_cYe{D4h^XeyIv8VzpftfDv}O`)5U zhHE{|t(%6>*ZL}Ku(cN0V@p)!I3r(c2xU8%b-lhT5fEzX;+5XdOwlJL$|6d5M^d=bpT9tPpXPa z!!i2Feh3v2#2@Fv()QhQOZoBS()6QwJ~8+-+O>weYJSMHDnpNH$xaZho?o_12ij?} zU?tlW=2c_SzEvnc^PNyr@Ptj9a85H62b8YO3i4mm*WibMTT$t*6m0xV6hcdYyOMG< zP+ay(5KK99$+j+~Y)ygXQ%1#FCDp|aEN!O?E&d7+6sY%!Z4eO8MX6&%o z31&ApH>+3qnT!8QtK&L0$<9PmivnAa!hjksQ7!Yu9vXtZ-GpG%1$UF^dyxXdfpujw zuV{GN;>*d7XbyNd5gbAqtqNJOL9scoEO;WCgQRakYr{)}(3;%1)f$M?7)V87L!JMN zCQUUn7hCyP?x6#n+b4xyPWj%in*2Sw6qE@$np+IAx)oG738vzMqemR51^ctrz!IC8 z(!vCDal?Kc4l&xFs!t%Za+NU2in5!-8M-Ij(Zj?~Rh-J@xlK7dvYto1bCGqxE3X}Q zhq8~dM6$?Ulk-tvjSU9S3^=^LD)MGiOwKE@Ol4dlaV@U~jG4i}mggiZDmz?%*!M~p zW=9nMN(WlS44l=1L{*A_P^OBf;U5fttAyBGIp&wm9rU#}{D`%;{P{F6ci7jeuZhA* z&&)OSr%tzO?l8$|!BeeivsAmHEGD|Zn`lmemUs1%ebr;gMQNppx=I6J5(~M_)qkt3 zC_zP`L(*&gVWThwEk(5`S1?a#h9>=yTODFwXL?LF~6(LMJp`q=%A)uq@69~fA zG>aS$cFmH;1ewVaTYj>Kg&=}3pyZaWe3lyW6OlPMB7h*EfdVvdA4wP|{m4I7vzih` zduEL|qJoIlbEbUbGs5n);^??1w4I=5jdjp&n3Vl>~Tj_+Df@HSBsben^cqB;=j~?Ch^8~%*rrbOep8Q;dhsW zjU;YER?bhgn|LgH)0twxE`!#n!u~`e;5TRJA*rRr`wh<8<>A2QTq@Qz^e@9eNUl7O zJ!Zvlag;1Rzf4PLw_BqL@I)ug~m{nm-{Wx#AMXxD3LTLS|h z$?L1X?_PHM>$uSK#a*2azb03LDp|zC@K*cc*}bfW15pTY=TFM|%gx^1zTewp^xNH@ zls~bWAAFXKVOml|S?QWbMsF$r_VgTAE^RwCajGG$^ZS@C#%+7c-G!K<&N4R7%EO<> z4fSFx`kE#o4DMbc8E`x$SBx3+Bjys7GpwAMFrY3prd?F;(1#&|0b5hrM2ETtjDV?q z=o19&vA&5F2h+YUg(7t5>_+<3Wv29#Q@|fyBG!hnqsdak4hqOJMqUG*)OOl}AD!lL z6B>kmK5sT~6Jb+F>_LrqL_|{79R@-@SmfJ}hXV6f1qrLJ_tj2`1$uO|kl z-p^di4B0PRKiePI{QZ{6Y?%xZ5pULoUS8vM&MdC`k0uYP+aEXdpYZB!{r7d&U+VYV z1*e$*%rXD*8hEIEB6&Nx;vP97XQ+Ew2h2-c#(V>1$KcmA^T07q$6xd>#EOm z>V+a@U%zl!U*wm)w10EC*1lEYK;#QgM&#`KlP6;>cLaH$&hz|0i2we&6+dfQFfm>A zEJW^`tMALk-s^Uz@n9+wxurze`>aSz46%a>oHL@deymjSLL_T9f6aveGqhrXsr*G# zr}u?zLEDUB&wjBhF{ERbcH)rPUCUaEC$d*@cRvSd$5yF3M9o4HX}qTNFksJoNLIi$9DJn>@Fm#X}IU2FW<=Kc$msX;&D5Q5D0v`nLZP`7%~7q+UMb@Q^hdi z_3~dnXZu~(eEZJjk#X*EMY#T)Z1DQp|8^tvb`gf)m)du|X7M)l_LN-lc8Mx^o}b*?9`>aCP!~k!zl#8jYu{&S z!deY#eE`6fXTRt>q*b7o;SvYaccvowOn#HR?S}sJ z_2)bP-q(8itO$r>uCpjD6e)Mv`0eNT8lj~7{gzn!RQ|00+wqfMDm6NH+q2T!wG#Cw zx8o}Bi=(%*Z2!0U?DeOA6^4pp2n9A>M&i2>ns2gu!5m7aacCkz!2e=dY6Ng&$88@$ z-nt&WqAvKsS`0cxP<}r)8-yZ^^Ia8MSlevBZBvT|aoc@?{K%R5>%#!!9DMn1d4nSkJ`4<|)^Gi`|2~NOL|=a?rFHlAH-_tLI6SSN z=K6fsu+HqRsGuEZ(dy-oua9@@K?US;#IMi$4u<)U8%k{mR8NCU{`>Lv+H}7@l6?5} zx)rwWcl{tFmBgcLiIdPA!S2}}3BNRv;_=J}0 zb{ow*MG+#3Rti=9mlo`g4Md>UnmZX00?*O!({J#0P}JUZM}7A^E%5ew;?U4X99si= z=MLsfHa8A8H)jT00r zqi6=EXnpPkUC9`6^8Ffm+s%aYT~O1tC0ysXVL@A_`#~zlBDoQad+*h^?XwA{hpuv| zk2Q9_Qw--tzGzHw7(uX*aKa}|A_!}P1#8L&6u;2iE;@#25M;=9EA3=6%~lMcgkJ;d zG!bYAhA@>IHX*3OnF zspI;64|;bi&w^rtNv_}N(2vHtvE z@!z4IQ=Xg6x97w5iMRXyu(vzD^*8s0zAscg`$~=x0>6Ii8Kci$Q+G0vFdD2sZPafZ zB#vF94%T;P$27Jp{+G1>Tda>S@)?N)L-?TCs4K6&)Rii)p;TY#?&U9#DV`$29#2d& z9qTYTsy7rb`2OVY!c__rhp8RZfk5gb5+02=Tbd&e>3RKw^N8U;>#|RF!@$P>k%9N& zA1Z!b;!M;VKpr-O!@lfZ-HdtstL0jH$-JmxZy{X34iqx8ETN$TucGHw{bXkTU6Jnj zp_EvD$OI0@B@DVxlp8?55WoCYYXG&I9VsA}T%UUvIADJl#)dm$QYq0Ha|8B%Lr}9w zj4~k|>6$2D9I!&~z>H^tD(#R#TgHTgf1WowOK{^kbA5T|DH0QhfEb{sacnUty%=O8 zMp?^OCIxW8mYmCC|BaKdVBSswxqMpLO0y%iz6?Rhau$y^1IAuaS>mi1e;r!OCgqN! zs~bIbHNBQVk3gnmU9UD0Vtx4dAJX4*izDYPng-*w)kt4VM2SA%^Y-2Al@2~;I7>j1 zzJt*pI#x59|M(Fsq>WT9bS)BS(?&LcA3^u+Gv|{S7c69;1!$@dxXexW9|^MuQ9C;= zeNs!+KewGAC?2pqen0fTQ;94k+Q_7NVIwNu$<`(6yNCTZtpAuzzJ_{4yi!oK!Bh$A z&Kvsgmp<87%mK20!}R^A2~OTf&l!i;dmg`x97 z+9atk9j<&Q9^%>VnL2`U;{Ubl#za+)4>_P(kkzCthX`v(`2V&2Z$ZGKqJ*tsi{$@j z2@gLNe!oN_7cXoea0Bk`&a#%hD9a$_4QVZ+Cd&D$#@e)F>Ure(nVvT7e_e|kC__Ig zdmc>6vQP>&LmTKubCPeWw)%mtLzK4a0y0W6$t8&>pe#pu&nxL^kTO^D--V0@jD!$k zNK9*`$5zD^yiv?hdQ`Pe@FyzKc$Xv|&>G@lgM@iB)7ALQGuF# zv#*N}5)$+W=XxP6++022=u0~+Q@M^S7P1y+KeM~x4ax^6F<3X?{*TP}FEiq!6;oi%^_t~~l04ZidBk{8a0 zH2;WlUX9j}ue<*iZ%Y1O%X>rqPynM2CJx$wQ>@QPjBLHT*|N)9dliJhn{{7ZS^HTe zJDJl@%oPGz-Q0fY^DK>Txgh@)yNaSBFqilf>rCI8hE&vE;jhZm3TT3vDG?uzqC{)(BoyUHvHZp>6HNX4{At8Ds`~ct!ilCuE;vZbK)Ud>)7A! z+U!d3F|{=n9@vVFQM!)m{q%=~#DJNX>I}>AzDqyRd<`E4b?!TNujVLYxjovv6u0CY zBJ6W+L`h90mBPS%#wp2Qj=8-e4=;bYI@72Ux^(bX4YJ&o+Rqn?K2{NdIjmz#gQ=mD z#B27whlNlLFdi<(nzRFYQ94CDAnq&2B3XagN;+S2-m0CRXuJgGb2MAIs?x84&__xi zMZEOUuWF+VWLxGMtSqs^J91u6J4id58M0IxuI}z~W|xg2a1=7>I7JyQ^d!1kAa&Vv z;`;TcV2wJ0D!rL)SfX+J1Q&Ow8E1l2b=C#y?doO^aHGqSfU4D%sgLr=i8*r5%VFc< zMWEo$O4roNUv=`{p%yP)PWCFx>%LytKD@C*M_I;_s#?_F;|yct@~Gh+x><(uxqY*d zo1csiMyz@ll(V%4j`~Jb$I5z@O=12ApvgU8JW|eV^dKJjzT!gLgN51r#%fIU*PmGC z@I>z4PP6b)p;aX~u_GvT)V=WDO}x!s_Zn_W^!UvqL=!_C6%h^%&ib_Z)Q=4rKinz> zAM1q##X?g1X_j6_OqOqnl?vvS#vO=26au`=S>@WyD?IZe=r@k;&Tf@ePO%thO1OOu zr-DX%CnU-m1~+|MN?Hrqb-d?Gga{{@8ANwy3#q!jsRj$&=Mt_CWm~CDxnIkeXbP%M z^=tD4^|n20@>`C#nVB>#@Z8s*C1uny>fL{&OZF|?^t9^R#asV)%#HsVFS*ITI1J^r zTqaAO(l-Xlj+@Ajj>c9ZJ-@W*jdMRq?43V8ezbEFxb2ZcV{8l4_dN{Zy6yRH(U1i# z!3mG&r?0aUa$Guqj=nvq1a)DF*$?N-)mN}V-S z@di4>`FDm{hF?3UIXl#q}cM8~wTqI<7m&{7vNhn)(U2q`Oo!OgTCO zxs&Fpzyu;ZcsFM9_zo?R7XY;^-I*1ORgp1Ce0}5!g%_8<81Cr^v{pVu$47zf>^^SS zI3HhX34KGy!<+vNEyW0_R*nLqMniM(vOUJS=1As0k62|>pws=g%9D7XA$>6F*dXow z32T&vno`<1llJFXw(<7j7rRx1-UFSC1NRl*Z~;L~LlZrUV+R?u$N>wKL3po47s9Y# z5seS&dj!^Qtb-@}`LO~Qw z6i4{qq|Tb(;Pj;V(0I{{_wYD=l2M;D_02dBy*i@DUZNQpL6;}LEd7#XtN3^7XC_tR zk=IV~a`+TlO)PPb5NT!y(?&Njj|mm#7^yf@i1C-5`~eqo)YNplKDqG>spk(C zm6duGnNxXxQfkA@nD3yA@Y)JSZ+8$LI13~jwVWFrG*Q%^LoH1?C=#_iMjEHsu)Cyn zO9^rBzQ!ImuCH~`^?9!4nrfCZ-`g$LCD3w?#a2rUv|#1?d*xiyYDZhNQXK-v*>A%A zoG=}G;?XITu6E?UQ152i`HS%GSS_~IwaZ_g61ldz?BpO>rdh1@C%CyseJRy5kSX*0 zBr_$8q*&08UAQonONHeB(wU%U8nZB9Si0$|XS7R3`s=)oDm zH_rIu>r>&|p4RkRj-&>e!A0G#^;D?^t63>h{VJb4G&=gN*gSvQVRoc}eQrKqn>Xp( zX-fBTb`klwX<|=?=Wgm=Ne{1AFM2Fg>&#s~{4-kgoj_eb?z7kyTI!IE3pLt#Bx_z~ zjP2?U@UOZIeU|@bV&>AsEireT#;izgq9_}rJIV_QQj9WK$Qs;4yd}Wt4GE#k`9mkP zMA4Mv=O&u4@98tYMnSUC$Qtqk3Q7UXyDJrP^<_X!QA(b4$d6ksdl~z&yb_R?bgPqo zMcKN2+oN?bcXHoa^2WBd=>CGX6`*2N0I~abE<70bV*gr1^l|e=Z`GIRR-0qUVzlwc z%n3fBh0tA?P+OHw^5?K)f_qH5laXhdamzFUsZv-@Z+bY{%N)%AVCVZ|!rBR2xRzIx z`9E+nSC?5wx?0x11bCh9k`RMjqtR`2d;_7}3lzDbzE>4}e^aHeCqKq-jJ3s78@?@6 zr7kvJ+~!xbwh>TC8?VOM%;ebQ&N{>M6@`?CDMlpJAhYFHo!&N%FgBHk%)QDC&Wd(l zXOuJC5oRv#Uvs2s%vy-|6o&CFO3Ear%?_|NAF7G{bS_23`W4?V9}Qxz4vx##P9~ci466Vi1r6G5QhS$C{$ zwDjm;q+6BaxJF8g_WzOTY`}jclS^=1*F#cJkd+Ipo#<6uTBgX7-`Hqjo|ju@EM8y< zh8!dUcG>AvymcW z9=-o1Nt--3S4YP|nBB(Mg`_Cr9OpsOGMugt%z}`(UEA+JWkD zI$e}j^=CT0mbpl;m~C^$lI>cvUo5poRO?=`=hUOv&1txVIGGyiY}_TSNyYhSdu3ry4CT-M-^v?^LkZxO zJt27-alDw162O9x&cfjT`8+i{?D296b{HziTm)dUR7r=%I2b=vh<9P@psXj|c4$Ln3us zP$#UV+48B#A6E{Y5%2!b|GYY7U9li}QMw3bt!j}n|5HByi66c%%1G}K{6W;E3ZR?D zMP3UMEU&!htZQvlv13QNX?A@hraP zh8AvwOuZDI=A6D=q3@t~NzO2;pmyw*ZgXURc`s=+c6MQ3x3lmIO9$fA%e<@&!}9?>~b3y(P^D8Au=LCxZ+@)eydJZ*pJL8uU9z==va@%)e=RD|ei$)~lf?Ei`~FBFk@eTQ zzLM6o@?mCbGw1+IL)H}p6ca}2?hwZ_jB-g3hz&&A2w!1087 zjqv|i%TbxnPvo)U5_NT^gC53X<$PgFh4%~r|HDpHjc_o_ZYLgKE__|nw|B31vX-Tb zlkVwKV6zh=Bd+cgS^U=_?Efb0|1E8SL=at1@67PaPV-sI)7Y|>ac2LlAD7qkM4@Tf z;O?jEhL?d2fM&UVqt{JWuiQ?fK5Fi|m6Dg2{7A-c=0vg8y)k=|S-}!)E=wbkyU!yb zJU*qLhb3gAmd>I-NeOG3Q`OYu6W0q1p_Xj?)t+(B5sue=_a)1atrFIsi1>LLqCOe& zR9F$>##LH0*v17F73fC|Y^o*L07^FHt;@IryY0XrQXFkLfYNvWyYqs6S_U}3D}L40E3%(S$$jEsy9rTU&i z*#cD>?1hq)G*!C9k8$kHe|up;qs2uf8M+41mO=s%XYeS4q${*56-KtGgQ2eLm>ndC zO5E^L_VzzARNWoDmzxR-XII&n^B4J{s1@ZEu;=V5{)w^CX;=Jtia9Y{48rX^H}cu? ziUlE2XUe0aQIC?v3T+;U=^qc6Ya^=ua$f91?sI?VdpI|)tbxrtA7px(stJRBGkjN9zqkefN;R&%QFtQ17LQHR-QL`y4Z^F$UU{yx=^qdWf8Iin zP&w0UyI$KF#&31K8S*A&SO9;q+a^p(X4Hql4rNy)bI=ud^B=|m(niG zjz|F(3LJ6MXk?iq$fhNwLaSo_8foi|G9y4Hs8q~$S1zl`K%IBU2p zH0NR#SCeQNtb7LA_D$;mLeyMN#U0(Y5}cMsx=hGNI*l4rEj75RGX&f6<}W(1b7mhi zX+2S#wLJX|LtJg|PILN_SBBi+fyd}J3tuN%Mr*9V-qSX?8y-r+WDfmzwYzo16)xL}EHb#OP=htVtS!xzy@vCttIj_{2n;WJUvBLI2aA zfHODEP?S5T(df>`oq|P{QR!Mq?7z7EN>L``cVYU(kr`+Fi<(C(084$4p#Q;8m^0Q$ zR%qqHBnd-cswkk*z^9vVuv-VM0b1*tb11^sx#Q?{@589MZ2QmmIY8)T5=%xZ_UR)Z zib3eLrJ3Rqs!m7=Ega*dvgZ7R^7uPFEu(R;vFby(gdwN0HqD5T%lTkqWnD(Y)E}{* zQv!yF;Ng@GJG(E%g<*I836`TE7*&YguMwldr@~SJfj~X8TnDsR*FMjaVe^6y_>bFL zvA^Yi&mo){govQ+0-#?l$tufVCN2{`;sI zc-(Z=DwQ8zuRWa^wD_IGvnY;{dSkH)7k0A#1%hX$Wg$?&eOV2XWr&v1PTd4jYall= z?5A89WjSG-B;Kd3hE&TCR^i02DHZlrpx)j<>icJs1cAu%O4tcQ8JvNE0qLvB(j4Y! zrb9R67*DHMUwJRy_Am%)@VLFT-N$SSXg~nW)g&D?H6hbQxT>0(nvD$rN5C+f(S9jA zxoFQWM?;mR>?5-~St|_fznOpkNF~%~6iZI$>%M}>_TFQvgZh^!@doOF_&!OhnYlS6 z`aW|zJJh+Vuxf3fdV;%a6=$t8DS<-i9g5Bcs%sBhVNT1%vH3U!($piE0`D3&Hs}K< z9U~)Vl*H~AA;TcLq%TP1vJ`YI^i_%{;w)m8hNB8%$iv2SqvhXk4FoqabTsC16K2&p zYA~m3JZ30J(yJ(srw$S;(Rx{8VFL$N5cfLP@#{8W;>WJ50{Ove!0Rlr)9rr2BX;ajI{LCt+p9z zT7Te~PG|ceZymW~%ZZt&EN;-Y zxT2z@q@0FHY&rB0XI=F4c%M1$bFp=xK?k;ve5JIztepbKK9Wdn9XrJWUybHgWH{1V zY;9}c{wOBW_Q+f0Ylb7U6?31R`59h6AfODY%CmSs`RM(D}r;!Wf;g$ zts8&By6~jnv1>2m>--(D7L6y zHk(5J`;t-F0}V*|9)}-z1B-yL4h$pBifL)#CnQWap)Ol_HWv69-x{RW5)}V4ug3Z) z-PhM=L<&5Rp!ydvVm3|9bYGqXRxhx)G6E;{6k%#<#u`q%(8^CXkJ!o>0dBZ-j|5&mKF$+&F?QL%)ArZE=_ThO>Y`#dHRVqrNIS1Je7A9^CP#;| z68bt3RMe2j-2HzC=k|>2{|ninyNWfT0W`C&3@t00k0U|WvOFOi zs0?}>3K<-Hiy0)&ys+rO>RCe=B(m1tBXs|=+Id#50CLkRfoj}&nWyZvFcO-&IY=q+k zOhB>V^FLq2KDuj^Ae8_cX$*NAVXjuqulDQ{LoO>&Ab2c!J!^%7!;*(eppdBAa8JPH zB!4H-CM{7({hKZcA&I4t>)d&BdZ{Y?%_@!Lq--+bmnF8I4};?QaKm4d)Qd=F^TQC; z2Dw(r$sK;A1|B$a*b^8hx1|+}qy9w6!@i6$FJ)C)d`59A^ny zy(v2<2UF~0$sm9hRAU?i9#N0{GWB$)EzN$%?{SI)u$!q?n^Cww^@5_>YdO52T~xsN16bK9W>p5n5c&|iAJIFPnzH6 zc{l-|@58NOMOAG|!4zHHVnIKbY4N}-q3Rg4zqHB}5i=qgoAz9t#iZaYoBjP5^ff0du`4I9fC zp<7NL^PT1pq^1t5s2SL_a9xVZ!I`uOc$dw!mLW^_K1AH)Tr9BiVZcG&@+n<|$udih z_Ug+qW&jT#&xFfxD?t#qU-{8LoT_dFOdE};#FVe>{0q*nt8&R}&0e=FeARyTdR0Q; zC2yH5Un5qJ$i0<>Iw{)Z6iM(u@j|oCZN}!Ds*BGv^K5MAQlUSuHoM;IoBS&oc-4)= z%VXQbd>iW~E7r9}OOuh^Gw2?o`B4|{#quA7Aq)&Tq{kJgi@{xsAUf9P-J{pB zTw@v+)jg@QEUv7;l8V=u5P3xnHNO3gz9~ zAzKe+a~DM7a;{PYQ#b_75Vj%2rHkC8U2~V}v^r4k=(Lypfp{Ew(hO4YbP1TWfSCt! zFv{X$QZzK;#pPz$+GbN$mwWQc(ErVILpk?!50B`jM(FvaCjyIE$J~5}|2gtxy4hDg zS3}Uw))plt=sdJfyn@)xci-$NOuufidN`ujXaiG4MWsFjm|%dd=;DT+yazYB?NRpk zv^lBzY%6EalT%0E+e*hzBrer*2S;y{Xx)pwAr(l$Pdsl=IGd(5(mN#hCaF?^LUI}) zs%);w<)FspMn^qQNEZN{QLt(Y1k!w(5y+cB+Ay4!hY}EKCe2o}B3Vyg;!}wO%+O+o zxNd5@@@4giRerLvu`=p=Py^VoJ_#NkSzip0mNulnqvh|>jiiTKX68v}r^xH8pR#a( zP?B`nT%+iW~Kfq_Gpa*@E!2+&Y4gV1)4)4Sb(gqo%DjZ1iLq zR>c_P+L$r8SDk$#Ev7SWfnwT+Ws-IT5Yc!-;^9JwXw-O{??o%%%%JV^wHNTM#rNQ6 zulsto;9cA0N?2nTG9UkCZ1y<<=4w&}PMY->9Qv!`MU`5U!NcQVO)P79hI<#cocB); zh>kV>m69>6KvO#K)T0vYKHD*pqQjGM?~yyg;2HINES;g7_IjRs z69qLfoksm#k&U>ynQj|X4Yu}HM&~!NkL4}heC-_q;DZXK`n4JB+Ljh^)O9iMP?x}h z?Sv{>>K>_O&mx_Eh6QZML@1dB&B01tp{t(M~4q?U)na7W>VgQpiV8rJO zd`g6&?r3-|_S&j%3r>c^_T%tbk&A44s~o91%bIJPBirD}^sv|KnJNqS#;-XViy#AV zkPk}jbSURcvM9jsQXwp?KI?I7i_ib*-`qLu+jhb>Ruc7iLj&#vG8yQ@DzrC7GXKL< zXy_NNRIvMqEaXkLZAW%4@MwdnJlOUn<#f^Y-j5I=J69!y#}X$=S6_YInKVabh41@U z{F*5H3hP9dn*p~cPUHQrwtdoqFj2#JYta=r99kcGkQrJ`b@(A9mcEwh3G z^Ss10Hq70rhzl0{Zl9Lc130*WZ@;?u-T6ML{c~F5sGInn9tAM_;q{p3lu|Ycw;MoJ zodcIH(|bakkL>Du_`3}L_uOjOA{V#yDPm_;0YUFmf_2f_o!nnI@yR~|&tIt_i!ntW zF-6=(CxrA$e@O(Vv<>QcKoZQlI+Ez>w>s*#$BGp-6;?dIzr01Yw&|?=j0u5FPE1x2 zn9<_(fW?B6KiW>n@|n@RQYLtoSR~9)L@#EOrg(CS0shSd1X6{LNP=mN%JMw#gm>al z)*0fFdJFCZ13sbF*R&K)$n`}u7b|N21`Y7l#k!Jg;+<7U^P!S5uLggDbg2=v65!rh z{1WF>q~=r#nICACh0nMWcRL^g_qa8)*V(0-XLAu-&YHkNeiaa-a^8QI^k}%uSw>4D z&GwZ-yis6gzSzEe!(k}rAUi*Isld!4tL=DCzgu9T+Wrmlai>r3hR zA)JI<9mL9+fa4*^J}ZRh`xn}RV~g+zd+Bu=j=9*Shz#~}28q13<;0T(_|@sFM~a(74&+)5mzD ze~Z`>w+LFh=Tm zp@V;*75jh4;qMiW1XCn`TBxY>0_;%Aflcpxi)Mv33xnMFz%y$hPa80tIg3>*@!xBE zuB@CKjE;vJC-LG>lu5-svgED7pj09a1#PQ{ux4+4do(1n;36fCf)1TP9>tFsi}J6R zvLVma1yU{&b~ajWYIjqzM(aT#QuLhhLkpQvfW;LVHJ1x#DJSSyYgrmR4{k^c&BQ}=j0^m&&4?gW71af$ zjm;61mPW{VpxxMeJSzy0Ki~45U};X477`?K4}22v`UK)rLn!5WFRat69I3QPE#+Dq zT48kr3en;q!C--mbMT*`%Gt)$1Rfn<6{X&YLBI)(pZ5Etw$$l)IHe?&aPn%EPMtAx zy+OQHXwztg8lodRy9&Yi5&%J^_0Q6yT>&_V-iCKw1{iidznuU{_MCIAg)|%-=dowS zb?tl(YH2sm5gh~H9@_oKa}zUj&w8b@v>pO{Pgsv0aRNOp3tH!d_#Dv(`xTF3xqD$1z_89}`yR_E`$H)x=i}%fXXXr@27n2VAcPgKR`#UR6p;iLo(79v));4p650K6>admsql#05WCE6jilh?j|kFTw_Q>m?2oCs*x&m%D~zY0HDjM1 zCL}%A2KmU|raLg-ouRtBlouDv$&G(raSrf(4j-5-KU&D$tW=&Z|Jd9OrUxuOO#yBT zlS~?gTAlnQcP6ic^j;O)J*=Ks9QqaQH5?^O@|C@Y7y|y29f80deeky?8JfGoi9*jaUPN}EzHRQe2T%P_o%gcC4CWRGQn3>_wcg#2nwXo-Bmn(aW@j_>UXNR5Jlh?_w|F zl|&>3Ks6GIGqpZ|$b@I^Veo<@s@B8twI;+<{w1n|+^nUxO7_{3HcqL*TK*-^_fntd zPj8wnGhHcpulH{NWwDG?qMh2cuCRZ%!vM%=>2k0&*rkY&3lYxmh zRLz(Z@HTda$S}2VvWVL4F)#ZMy&5j@;iW9GR2h>FU@>M|G9!C?v^X{E0-|^&@&2OP zNb@BrASkSSR<``_99=rR&>_B#)qbl_Pag#j#X2l#@&Jw?T9WYhDHp0cVIp0WpnjE%1~(V8LqVjoa6*;~A?cPx#WDmB#IdS#9WVV*n*Wa(4_a!|4 zrP;#f==j)W z$^Al&M&qB*aP<+f7FcUEY1kRTnCUgnzWocoM zVBqAr{xCE~f_LdxPWqIi6ymbTnc~*f@E=l)YV&xX-k$&$_e_o)6(IyuXcn8g ziuI4C{lQ0tu@$n6z=(0v7g==)Li~cJD)#dyNE0Y8lA8`EOou^B6`hBpg<#H*qA`K- zIa_;XD@D2(Qj2qf`Wx9a)OF}HiW|XqQ?;fb8$4U}86&UzoRtL@-iUlxMiZ;dN24|Z zB4$0A4dM)yq#a!2BO&eQ`XPVxk0HIGdyO<7Z)`ZH82`ow{?tcY8# zhwPiB>N7b{`eFw8qpmv-{Ic3aKK9;B?RHK{%6K|Ec&a&Q=l?A;W6rJQZzk)BA^(zZ%gJ+cdcg{IM@c~Cuc-v8 zT1xl^$?)X?!+LJ{9sz;Kb8$a1hlm7uT8$6ozFltLptGkZ}8+fE}SWYAEx zH+>lZ-0%I=OD-7RlG+CJcRm)mwDhh%IL;i*R4@@Zf9bzd65#RHvB7^MHV90LUAMfnYl1Y4p;8QUxAFM}}mthLB9m z3J#mx7Avc}h)-olWKozcF5#@a)vadYZ>6%Ft3C5+%)-h%`#ts;tzPN`cLU3V1bEt% z`a`^H+2?*Tj2!oU1oc{Q?wzk+D3(MQzK%i*76y0$0og#AWIXNDN6QMO# zgmQdo*3jlk>5VWrdGVQ+oTZY6amL)qkY2=rstqn)c^BkO!-aSk0e9Pl@?xgDn>*P+ zS-U2ijWE}LaS=_zPbxwp=%D1GAt!nxO|gLeuOIxCzkLud1L>rfkcFjip~J&WyM#0m zLV{iigS0v?W~l9lhKFge2sXuBjqTG$L~s@6puMA45goP_|AH*{0`d{+U#z^!D0rvr zyrBEt?tFqR126Z713S)J3fe{T`8aF37lZg8ve6T<@zkA`} zBmwe`5mg_#;kz%> zFc883uY-pUxAsi%71ssI753Oib;9%9iBI4r)joL1%E7C!xXc7vH8LAAGc%Ht5|fjX zproWk$l2AaXW|2&l(yI+NkVyFLRnvIvBWpEJvy)5G+JTwenxt_KD2~{1SeM?zhgVj zg+6tCR;DvqeZ0cUz8d{_*6A%)mbS%=P7`RAkW+((4TEq)6N`h&lo{>k^h#+t7u;~w znQ<+o;qn`{DG%?7J(6Vv4?3r8)%c_-+Pnzq-Zahp$-;kMTB^+PqR-B_GRGmM4;$+GF_vgIzR??9Zb&oul z_{yuPne^mry5C{T`o|y6w>WNk4rf1x0ybUnJ*=?QN0un}zA2?5t4;8$!+zh{9rX1b zAy0azoCysiRx6^<0<9`;pjA@EO{*0??h9=;`9j3XO7+T0*~&`mwU4^qnPCW9qEiA6 zWTix#?iXJLf|kgwUPin2vT7HHi7&X#rX# zX0?>`y&Ife9~m7J7Z(SAa}2>lx7yg49AAIw>Jmi8=6QIAV*&zn7#aJ9ps}=5_8Yw; z4w_q3e^emY58Kk*oaVM{_4=lTnz)i`4=CO;^!QoY=(`_l`tQJAf*u~Kbr1m7t12sC z#4>DP5GMA+a|>+`KCokYf@werS_NDb0#4yve3Yk^}o+}k*LaL3?drCq2 zO)T+dkMx=u8a|gu`Ai}2;UUBz?DDbgrmfl~l-z zFo{+hs-%XU;Op^D%yZYL#VbPEg{X&$+@3vVVV_stzGVEnPe7}{nEc|B5`4sP{|V=v z-nBb@>h#(Z*kYYI?!mZXl`{0QUG`H4W1UNkqLo_v-63sGZaDb@Rq*iIK%o`&P{2?` zq19zw2zBP66@0Bmm)`3?zhYYU-jVsG4d(b-t-m_xUyn+#X`yhMiB=f63Q#kwb`U&liZyEGH(yY-#T{CxoU>QA%;^DX$nxuE8m%Y~8~Cd|&JRe{TKL??eA`zr$(I z=;YkfmiA$P`77hEe<}TA0MRVU!Y$JD9Jmcw@ zX6Nt_f>uPI0a_(dXa#?(qzWJNeXXX)_#<73AAiKZ{C2ynGbGjLb>Gtmql4W_>9k?l zpgDvgZi_Ao|9iCav!zK}2AX#bwpcbLy&UE8YHnIISOGpWw~Hx2tE8M3I(sZUqqMQP zwW_MNqJmjaP?wro9v@FnOvp=1%gf55)z;Rb(OMJuoqgzLjS1?GzW{hQ=TbcgUO4zv(30fLY=9czi62&$=|1{=bD23)`j`aNMJy4|SCMKz->zfJS{ zPrBd#W(B@lp6OK+x98eN?|%HgdU3q5x8A!YeScY|Uk9@c^IAtPoxuR^TwrNx6M{3GF|;z~Yqdt8-t4UGPSK=r;;KmTzG(7&!T1#{ zvB~ORwsG{Rs$$$OTIJJAhGwtvCq57>KNhP#5lwxJ6`uV4tn%)N=-WmsWL|lFcapdB zh2$46B|lx@^}_MvmIaj^bMvN11s~`QOD?nZh_i4Ce`%NfpPoGU+lROR_Mx{{_3+L& z9n$TSx}Vy4t7312JNV)2dyvoU(a08%KED8|@C*C^hgFh>cp3!CVD zGY745OWRt+BM}*ergij`XRaA;?6~6kt51&0^{52%HV_a45jJ;Y*BuXA7ednuhO3g| zA}sZLfLXZKnmKGYR8&?SgCz_}&OPSfTT;ag&#W(LVh6-!9CHjTt!hjvYRju>^@&P8 z?i9x1^_W>sOoYj7G0Tm879Vw%${Lzk)eWssU1NJgV{4j|bM?KyYyb5x!XJ3Z@9?pp z9lI0$aBtPm{wL>+ZFpZaUF~>R$SZ5=qvH}|;*(?Ildz&&*RDjy#K$HiM09;h! zn05dx+0Wdk=ME-E*8*KsR{ys|d)jjt#tC^^*JZy4B$4puDVl5_Sr#=mJ5;vJDI z=^x(jnj4TWG=^4iv7&MkJ%ekjs%aIR=U||M$r<+oqbN{UP@CxB#*2{l>j;=@x1|{f zyJ-c|FO#Rx8Ze1`Xi|v@MWvKr%JNOCHQcH7SJ!`F3I!4`JcuU{aMKTNQf{3HO*^%( zfpG4cJt$@qeRoVo6;c|PUVGb-nVLg_#Z{T`*qg-y*;c>{a_kns^Te5 zCpMgluH014@m1w@ZMSQBCnCCSw5q&;R>_qLzoHA=Lc+w@v|x~DL`|0TRnhl-?mJHck8IQ{|SjY0Q*PWi;<9EAgh4D~PaVg$@L6m&_gT4K=OLs3{d~@)QpCj}P z+MNjmSAlcPx4eSysR?i4(g%+?01%$R%Ic==7J;t8nZqM;bk`hnNq+TMNE*FyN5k>8y7-W={Bi+qrUZoTVVbRt?4lDxQYNjX=k)Y&$6Fy%CV!F z_NS^+Q!x>ozW=urCj@>u-Uu9-E?E& zTvQ?FmgdebYMquSU+KF{k2Xx5b1P+=F+V^jiVJtCx}I2*md;%gV^k+{?yltb?A1{iW()iq3+D|d!wPk z?5-ZnjCH5#>g>SRzZVzg30e^m5fObc(aLZ2^UNs(tw@<5x;wO@25fe=w_`ll9gEw| z{)%uFK`SC6qAw*{rD)O0auuy4Qo+cu>?^2&{$30+`f8@#9AVVb*yyS-%4v9Lkf^`E zcXY&9Rv^cQ?pR-`Y{aCjg9Ck}P<;-CqNsoBu}-Q%A|f*dluFTCtx_5OLJ?1;nj#`H z6h>JZPLfW|Yd}Oq^yL;Fe1pj^l$dsoRE9)E<_IVSpjx$BVXd!U^5KUcT)upXh)6H^ z4-vE?BKnG>)rEK8C6yr&kvWp~3Rls}3usjx&_5u#di4tFC?&dewnimEDwmh#KJA01Q z=R}_oTBT{x%0g?kO1o*bS`t-dN0&5FC9*U7#4C;w{nOf6h8Ks6U3_R6iAj99!3^wk zA{U3?iN(#Xw>Tbg>a=L~<{p)-Y1`wEyztUXue`c-*TIvyt^LcS=zltz)u{8bGg6Wg z^vX<6>6P64a?8T}ETf{-@PPD`q;6J+b^sSaDdoW$dl-=>iv zBDyhk?O6ezn~|1m*d`X!^9{c~IWjbuosnjEKu%^lzKW(ttJT@q)2ajY9fBHR@p1RG zITZJ0W%7|{9^LQQBv!nAZb6)6{S-qI$HB zmJ}18nqN}gEggRQ+|rb^BO^X4DYt4^In&1~e(`~aPWeYRbW16OQuNoKIu)c?I(Po; zyqul6_dtLgv$D@A8Vge*!z1W*Y}LY?fXR?57tf!a<+nFTMyAOvzcUrkazyu*u#{@K$I;hce)B+lRymH;0ojPtz9W3~EUUhn$tXyO42_6M>ygi$ zKYv!-M2idwNy%pnsTSu|gH;8zqRjZ%6dGr65-lNu9qg=%kBUk!Zl2Zkjm#T6kTl_^R2{Sz~!r68iDnb8^} zG&9q4nwhT7b~KtCHjCHYRaH@@KPD?Z1+65T)j@4yIzqInx!D;v&?<1XLomH2P~RbV zey%^j!ihVxysT)pJ?a_l@0rvfI6prWZt2iIIUan;dU0sr!7@pH9A8g*jNin0>y)E~g8st^kWKAvvDf&Nm7$jieOz>b}mM}VLe5fMQvJz7l) z8$$x4M>MM+FIt`!dO7>|jEtsw9^bOlsl2|nG}+lTrT6Tt$ZDV6;QUg5O-y87WnFby zd_roASUEo~&WsJrFDdkKIaAgyNcBH;!ojztOSrIXfL5x>V9V{V9``D&tj-Fwvh~eT zYx<6Edm}u%EHTI_Dz9@?%6Rk-4?2XVSLVk%c&Ci2=hcGBsEnMt+A4Z_R9emO^7)0f zG|!m)^6aQHz6o`L&VrY>?4sAT%ZG(I3Bmd0jPmS|!>7|{7bkz9=DrnRe@Fj zs#U9%AzHnCez8Bz*1cwIw9?xyg+0!Uvy7-xib||}vKLSmfmXdTMx=jsbC1@8YA9#> zPN&iT&)$1Qw~?fIzJ1QT&Dp1W=4BrCVdvhtyVJdQrgy5kySl5(msGw=rer9I6e)_L z_uhN&z4x932zm>8c<;UUmIOUTS2qCAK%@+%l4@i!&R;l*Kx8JAk;wS*`@Z->&PsLl znwRQLI$2G!TUN^`y0_HR-KVUws-`Uc(hvSLne8{Da7emHhX4H!|Ng_3t5>i5^z|oy zx|Il5YuFiX4`!v-#mh!ovN9_WDlx4hBRb^n)f@!N0CoAVKr4{FtE#-Dx~jagtb{@) zf?MPiS^$O5NRS}3I)zrGsn+Jz9uQ*xelgw6a;PD*KWhX9((;tzH2YRZ$`JD2_oi;o}<9 zUR#vjIAGS<(27W{Ns4M3$Ju^JQ_jF4C?)th`H+E++@NYJh|O;soovf?cJ-;KuCA%6 zZtfV6E6LMw1v zOpK4(DJnN7^RZ|}B;cbVw1NZ)(v#7OG1rrtTrAcdQVW2aME@uxl~I-uRx^sRR?25x zox-^io7GA}^ZDV2#8T@hP+Qjo0!8o>%`u6k$7J?IwDR>CAoCrD2?KeuB|Ra^=dMTV z9FI@#f8dqK(^x}zQ+)h}h}5che;aBX=mgu@HH@Ge zcO6dz3DPOFIvyfZlHya66CR6J?N2}}08}xLMJo_xmHxx;|L|=XQK&K-{m+Z8 zZBv{>v?88YOIrZtaJN1BrIs>N!ZM#98k z8F~#X?B<;*G5C{1e-^D2)NXgr9D!O?AM6EZD2xVecX3E^DNI0}%FQqCt%`{(8kOq} zP?x_jS^+m7tfz}iBml!Eokjs`%R8bK{Kz4gnw$V^S9XUW{2XZ2*$peIZo3~}_aLzW z5+q1(iaZkPqjMVTnmV2rt+WC{VR}N}JWZq18}(W)8WxvR&6A4Cq93^BcdN8oIkE1p zca21DLo0=-HqfJkDmNN5EE0yrmvhhq*=dD*rHV}Ab13l&tP4JqZ5d6R{+LLB}oh^_>-J z6ap%d!Z#=u?_T$#DYQJyU{-1#TcOnoX6{{b#>-Al1PRh9v}&lU1|ud=b!9nNd`v14 zKhYuB(RRuq=zvy;N72e;Xixd^i-1D029S_}+#CGCPri#n?IBuG_*&j#MSgNQq6Er_bpY9NzV>rZ|~}jD1gP(VXGE4YEj#e zAVGR_w3P)Hhn6Dq%Nm|j0z463=WOT%C8(nGYmRuScEl*DlLd%G^oTJ3X#a*fZrfbZ_sjSWHO0J#4i@6#8mfzFf4;sCZLfBWC~rX(puk2 zdEj$_L~j2B#h{f_2{=3+N5B)P430)`wPu<08Ud36nsEdY9hmvg=P0xWvr#V-@fFsC zK)wEA$rt2x)n}70;2n}LsCv7eJNbfFQbXaWzG%V_*gPBz%S#xoNE;u>o1_&^35urV zB}i>4(p-kLK!OD6r6{3^ct5`WNs6qPDH_v1Z_C)4g_5e~=gVsa{0t%vl|&$sDByW+ z`%_sj6Y>Qd2ADHNrZQD}gF!s!cHNt%Hku3?A&WvH5ZHW)-e5~HCFN5|L?%z9GZ>5- z1(!ymF^-Z>8Fc~bulSz}`d(IHp!10^bshN+P++VK{?^2}q8~7Yvxi z1rV2fD*1x?(c}x&m1U=rFC5w?h!_A=hgPeq{*+&pja|pj_jmS*hzC>5B9n4aENC|0 zTb?qsytXBxa<*jB6xT!kOZ=yb{J{4K#@GkW>4dCh}^gD){_?E*rZ+&LJYlhDJswsT>KE{PV3xE8pnc!ZAHiWIN{lxIek| z{>iKNLNz9}fs`CPdcgzsq5trw-UDCej4|xZAe~jwxS?#ySbn6kNkh?V`)$V!rIUuz ziQnQI+AfQsWWoU5A&$BmQ`@p=etH|{$g0d<-uYYFw;!Z*z~G3Nh*k!L5HmeN<||%N znsvQOL?Y6l?COwSPZ6zXm3lZsu-cAR&>Dh}&W%+NW$%~cB49F88z_0hujRKL)s54|RTUj^&8$~_ zuF@&p3=W@~-IYEF-6SV+iLPm$H$1(Kb7W;!FZZ}*#iVY8x(r_JpMvfBS%YIs`?g1N z`^!ZuNRZxfv;sgCyTa*rl zj$FDRuEIU{NqZG|RZM>9+{X;=%%CcDl zPP)#Jt|AsR`9sQz`6Zgv(mtaCyVu~CNJ2~dlxB=3swiuXX`~m9YX+Drbou%M)lf02 zszsSd^0kg}Wyze0C|ZHdsLGH=ifpxKPV;C>@PAPcu~u+A%Q$Ogfo?3eeZ3Z}nJ!Knao>J66P73UDrpn0e^Sy1fEbsm=)BEvdBeLod&{Ae^59wI73r5SPwjegM zcvRFlA;|1xrNczMtc_KRo=K%CO{>g#OhUJGMzXoHy)I%9`s;%ObFdqGyHX}iprJ@G_CD(TwZLyMM1rYOuCf`!G{PRYqJ>jHiX9cG!gMN}My#8t^9 zHEr0uQMGVM-=5JN->+O$Y*Pl)|9(9arCJ?sC~!#}7?W&)pkf@BAXaJThvqZJHKLVG zSax(KdU<_SUQ%3EIB(VyI)3;%0492*Wn!-!tKQz-v~a2TrUK8Of(9}WGB|BzbB%TB zo1l1vO1ffc$xaTa#F;m>@vdH>-Ox>PBH+TSpH>~e?MCY!?(WaBmL6?Wkptr#x6JLA zO;Ams8PNX0FL;3^Z$ReQ(!TAU*z$C=;;>nZ3-dTE77`>#&(z=$0sz%vHujo8%#%V!2GZv9W%BEiW%U7Om2{IY0r`qwmGMcc=Md0-p+SDzNh|S%zzw?P{fF zcp<(h9A3atMdS%w-^ zwDV;T>V=|}l`^-BpZgDxb*qClSvR9$!mV93ZLYei0nOGZs4chR7Utv|3nNVplWG%3 z)>+;$NKwqz#eU#aE;!XPc78#zsm8;8`sm_Cml)Ud#nc{dH_5cQ#`yNL2a#Q3U>j*n zi}5O+H*Kh+o$mU#KsU*WZikmwkBRNuoeRqK?ZaDVn5a8$b0@qkvy0}~5=gVOWpC+t zG=Jzrl64QZgZ;|v>X%&$Ja+d;Q*Fx}tYNWrL90P)-iYqcMoD;h2;bjDBrz*PxULg#rPWk{KTDmN8jA zr_wBmdYgDAQSX<3zv5QKpxV&tMr4m-dw^}gg9*mP9c$sQ1{tSH#iM5%mjTYw1vk1tXNxT}Do zesW5QZDV#3jKIe8&1YV44yf$8jxUDrA=BzLMmykl>)efE#y$f!!z#F2t)hcGfJ&to!d@ ztBCL7(d<^M@QyjvieRxnF)A!LIN00M=jY(YB!tYgv%jqwzHvR4wz_9g;BI_;)wPIp zx@BnVjBEpgD8KTcB(qO2L{wEGIE`5S^24?HzM)z1`u4+BtJUfVt^69Ho8&}RU>XrT zy$!HM<@V5zTh@*X!S9_UFoW$Oyvl0gBB&f&+P5LJf^=3YrF?b80$qnkvWjTbOH1Z{ zMBo1Y{=(|YYBXvklR=w7PT9~Z{V-){?I46!5L!JSTBUZffcz?l z0b1RI{mBFIXSb&BX-Zt~qG$Nn=Q!TC8}xq?Pv5N_z}QelIkvQKyTw6h1?jYST=w?X z7IYm>P3i*kIr{sajaC4tV(hV2wSyTwPeZGN?TuBv?zreJYnyv}4_8+=_78SfENkG^ zVl?g?K-}^!p;Z?fD5!Sw!=z8#VSn{TeC3C@8qnt!3=f*76s}SKBAmWo($BOvgY+16 zQrv?RKKM2;vdyuc6<5~#<8{~fzHt6N43+|F+U72Nd+W#Gro>KmYCHD2i`Qo^xv|ZM zmC;p*zkPP&cG}1j&(bTj#V|$wc-#B0U*5dn+nRjpnAT=g5C8BFKL6?cuRWboN0SaK zY0fvUy>}@qvWX7f##bMoh#8&V{N)cf{`kqyU$~b>wXwjbaYdaMFJAs8W-`9G_S0{j zE=NsU&vLe9>-670yZUo%Z*s@U|2??|cHz5g?|td|&4ctme|pQk0O=E(_5NpW;jPe3 zav~5xRW&MldK>46^5TB(`CHnz-QpVl3(?A;lIiq%XxN6(>PX^89X zOYE6S=_VBQv*wXwc+7D1AQ6f_{;wAZW4DEe-%THduEv|I@QT^syuPP22reFvtr*RQ zanIlKFpT~A(Ml_&)MbX>b9N4h&KkrptvRA~yz2Zij`pMthd9~XKBP80^^u~N^^_JY za3s*M{hFf{SlNy=*%sq>&)Lm8t7$?HDz)tL{3?O&oklBvfB%2^mw%a=K|T?!>V`6U zdQYR(!T#?0%92_xvutb`)iRaA;`r&#*0M;X*x3bW#Z$_K=tUHn#x}2QZY=ABTo#kV zSDV-N_xH4Nu~;hNu=ys7MFtj}T3Q8Rl@Q;&ZfFH=iXo?)q{<4TPJ3>&;yzY|1N@Q! zZe}kx{Rs^q{lv_*`hE7osvl z0*bq)7+e95hN?}CYo249)D)i^_oivoIpido#87Gt3Ned7q=TI4S{WZZJ25%E$df3o z`Pq#cE*Uj3K1E=QW!y#YtCutDyYMs)w1&rPj8+C*Utv&G?hKL1Ax{_hUX5rb>JC@n z1}jSPFf$V%xO)~&lB#qjosxl_866p2BrsGuuuPb}VM`^U?RXI4=T3b>p zWnzg`YhK&khunv+3tB0t)PXBkihuXJ?vFp3i;7Y*=;uMJ^d250NRWOrWyAR;L!u`s z4L}2HbC44+@Wb;2i>+XHg~_@7%Z@ z5gF#@6^*0_-x{=1DCFsBX{o6x$;nBHi3#=fbq;7X3V`a+YE^#}Wub)`L_I6fG8V-|X zTHAss=QTkqgG!0b%dP+K{~d7ETyzw%yzH516%do-mt5nMSmhC23SLhMs)Q<`$~U6Y zJHFzkZwmbUXl0N$rp7dlV^3T~vbp=W!kALZz3;y-UEu2ty1}M`;-*>5cw=NjDVMd7 zHzVq;*?H9;-+|LrKgesxRH=q(E z5H2n%0;9#b(Mr*t9$P<#IeBzQksi+RY#GJp`aP7~>hT$G%TFuqLXBIkRy1P&#Xo&; z*Td7x%hSt04$tFMB}O(+;0|}!L1^_Rpp}Le+f>y(sR)=7;6|J%!6Kk~x^4ge{vZGil<5SRTlh5KVZ|&?_9fG1I z;0WAUmdVsxJ3AWL5nAnU$s{tp*|NR62l35o7{+c;t3cuo0dmUdu(HiF(FzhINN=v5 z8?8(_COki)Vo;;k+fpX#<=8Txv_Z9s;&JI(Crb?;Btwnq*;PYmo7IX%L&gNe(Uc(W z+4_gQQLpIEN@yIzfE~eGlTMK8>oY)x(CYO@E2E+#{y`l|43cAleiR|I1Cj@oc88#x zo9*pZIfmo%1+4|Xv4!m#y-qEc3R(CxzuQHfECe%n< z68r*E3diU1P^{JKJVTI+=?(Yx&B!k)%T2g@-L;plGT9x13UN)StA9#Sd12}gAAS&$ z-_9nCMtFJV6c%{hxmh!WWg^=HeFF*$GVk7WhM|~mN30b9o4vi=-QArXFbX~dtDJ0Y zZEbFDZh&9YX|!q>&VDLdK?Oo92(2JNg3!ut;TFx0^fcAgw)YGXIbyKb2S}rU92}S$ z>aMS;9hk<;bq0GFyIgNj@+m`|jde{Oa}=I+8lXW*oa(D@XqhB%OeVdYzS!Q_FtJG1 zLumC{Ctpyr2~(X-fTr6fFf8jN2EAlz6sgb~6l_#yLrr~i=NNJpO{5#N3epUsp{BYW zffT89MvVkJ0jsU9?iyZ@YxHjiS^=m6EVaJAx?&HtGU|0&wPUOm0IDOj8iCLXLMsTZ zAVGrA>er_XHE6i_lBm0pd7UZ)bdjNyp>J1SE5td_Y6LXdt7oB=(V#!K zbTaHfNogI9r!+jrUXWfRfg1qO19Tl6C6$QIYqdH;EBD6H97KPwR;z)?pgOc#fh``NO(fvCTn?nSSfGm)3fW6UtFsfll?pkB&3b*Nmm9T8jYaA{ zWl{;)%drMC*3sBKW)D-9apv;#8)ORV{U0xWb@9UIpMQSw;>D{jVJwBPt{|@q$v%u` zHEM{6y7HP1xq_UYSS&iTQWa*n=I5`!{OYTVR(<_lbZ)a;Z#+)D1FJ0`!H}n5&ApQt z>}Xqk`ye#`=!wz^N)pqk>PIhKSMk+vFJAcai!Z+X@~dxtO0R+QIjEG>N&=V*X4^|E zW;AD~P7-Jn-3?u1sK*>L$`kbLS6_X7`NqAX?iq#d+-qDIwf2sKy5=QH`-F@t=SQm% zs6YiOBP0EN@Moe`)3Md6X*73mun&VqL4xEcu&lRZ1nYUw3OuY&q1AX>K}vDE)&@61 z%7CwLvQX!k&)1%njurIt_Z|;|=Y`gcy?XxG-K$QwJd=fzg}XO{*qYO4aXjX+R3iF# zUq^Q@EG{UhaU8>@;3#xbR`A^$ZUG&LNu^%P#!hy$G;xHLtSwck9QAWo{I*#1;)xZ6Fc+G%qu?qvbFYW*Q+r^sEg^uEsg0|78 z98)F0B}K#!qF8JSmVlk<>+HodL^`EtVYsKgqX$D5>NWfsBt~T?W4#PJgCde}6c)Ja z^fDH%ud}s#04b2Ggfs%4!8Pe*sHq{WP+`&vW|3$pO8VSU$=qLwRvEpIQE?yrvGqV; z37w=idVDh_xt)>R&WvxOB(~5}JJ|`%)P!bAS{LU@J3@M`l-kXUY2%)#6^LWiQB7PM z_no>R=doG=pgKaUv0U(eZFL1q3IKsxklqD>bQ~b7EQyF8K=hr}+{`riOZU{|p!1*= zi12tpw7Th>oD)%npcsuhN_)0%N|LwlbD@=PxF#wwyC^R?^wa)WEPc|73XIZH{ogcIJf%)R3#ub zC9cgUqO7vedQ6=F-%yoTUS3w1n^DldfNd)bDCyGiryhLsd)H>HirVbrSt5bZ>U>hG zRIdW9O8aO}8i7(eSQ(v^;xS=#?P7GzV(GXrwgDa2fGZs2WWuOXHQ0szG4{x+=XzYMLi5Te{Z!EcRLh;yLT*b!PakLB6WYL(4q zx3{-KdKUx&U}-e!)Z~OU*%>I-^&!{Rz)b+k$_j|e8Xp~b0kjf}gdlDj+-t|4#PR0j z5B~UnE?C_|7eD{xuUGCr39Vc@VU5vQ)nW;=I3=a2v(EP^XytIsLx-S>k>u`&lj-y_ zN}|`*+9QL?As^_Kh}^{LVU*Py%17LF%NELTcP=?k2sB`=g2UV`;3J*(?bEXL=YMkP zj_Xe_=+b8&erdas7r*%5fB2wk?D1&j;}u>Ve6JWS(=fVy0}4`OBA$j;*RECcjf{?t z4D{4R#$_$i1s#R4L1n#0kkDDnPQG=staor^s4pozq7uF^SQ#3b-=kCsn^Pi62Ikvs z$HXJ5!^7ecL&L-Hnmae62-ps1p90Ecn~&?CzV~XJt4;Q;8a2Mv9rqFiLfu~?S{1?> zz+MH^Jv3|I$b+#@jX=OymD|rQ!Iw-M|*EA{UM-ry-TmnZmjpHCDaY!7S#8u6ch2tnjQs-$z z&L5Ty(M$~aDqC)uA*pMIgvH|uBu!V+XZ^Hb2CVn7(lu&DqU<3Bj;HHhRCi5F9d8Dd z$Xwa?tnUUBBB;h5q~tY3D*#l{j%Wo2P68ee(u_iSs&+Qb>W!T@{y#ETGC0)bJQQF)0H%2lst0 ztCbS%;S|KuSj{9QvB6DakD`@MR+SXlG;V_i`Rv`Bp&SYB{*9xvAIb#2_FE><9jEfs zfM7V=N2cesTKW1WQKzz;Lz{*R!xDSuN)n!mR#(6Cii%H&Psq$KAICCvdNpX7Z*Qq0 zIDPV|Z&X}De0)Mu_5_|OLif121y4-&1^P!UFh%xbPPS%VzH}od4jd#tBfm~4C4}Aa zE~_m_%S!aPaX%_DvT79f7I)lB6!=pI{MGal(W-Z7roJC{td;>(!vOXyTIKh%E3hW- zjIOB4=^DH-xO_ake5x7+&fb~eB&e*+TEoW6 zJLal1H!O~4*?L1S$gyZtNoF;c_Z>Es-bOK-bY_nnb)fN1SRtZ zG)dJoqr$5;DJ*RPLextz^vuee=G4<IYz7E zK~6K#P4#nP7TkJ083%$Vj@R5I!^)S zt9cTiCe)=ridL1=>N&bL@7Rn4`iQ^OIw~rf)8UAUBBYu`SLcr^Fid^Ls0>Lr)C~*m zXjQwQnIx)nVEn8B1x;zGLWo90%k2Z!gVZe=u}p30h@7HXo2Tg~X-1yL(mX02W|-iP zp2XJ$t;PUQ9VTCB8PA`bvZ9qzp#TOdJPr$9j|FxHbmds{21&sh4G2hAEFaaZn(>`Q zAxU{Kcz1QO+uh)z+PL828Llsa z;^pKsjTyT7`FEA@F*X--|I($*nvTNcpuFb!rPVj=%8-dCXa&p|-Q8XP^iTg35)!P{ zs?Tq=daOh6)L1U%GiXenJuC*;MF@DxVJZZJ0*At>?UBd^H4%kY=#3_w7&VWQ=?sT0 zb+U0pFC2EL!I5dDt-X{>9_l-;htFZG$J8l=Smem?*woA%nk$hBm=q>otXBxolY`^a zXs{$ZpRzdAj~GKPiq-l@&i?l5NoaLab9Cix0HkA6h&+k>DIF)~5b-pQ!C-GG6EJWC z@S~1^_n}El+d6R$$2^qY-W`YWUMeweW@Mm$5KZA|ZCwER9kYOuTXQrVx75qI39zsahz8{k$>k%9ra91ZWjmHGeO@!LGb+Qv2XS4vbR( zXM!-SW3(zA;&hTtepy{HRnt8bV|1kzt=e(g?5@Y4)oC_@2?yxF&1V}p5i+XGT+%Ng z$(G9dY-mL^jG^TjFfeSJ+eRU@dbMcPwICl~r1ec$S&Gb4BeAT$2@Dd;28;!eE2aC< znGKW5X@;)IW}r@=f)_!mI%~MxcigbQ5SrW}g_zGa=AwBu!h?C$OVcBT6?# zQ~|GFMxT@*U#{vG&r2=!2%FYF4=e(j2AsYHGK`X?wk}pgF zw8}rUTD49bq7`@$1Gf-BD*>O^+R|*7NGPC?Nud5sl#DU#@1G_!IaV&~Wjq3EcDy(L z=j$O1Qp-J$JhpyWPDO+UW-w&b7}p?%Zh6HdD@%^+nI(3Yr4@HgT6ffuJe~YlN`}AF z1B%YFYUBm{{9SB$7ly(!Ti(1YLn7dh(W;}P{dY&j#l;z9^nZzH1?ja$D@YJpy&8vL z`4Fd%YVyzOimjRkoD^3z9$7xsL(t~;oCB?T1@(CS*rFT^&w1k-rqpV+azraI7~3o& z5L*2fu~z+4_*nuMu+$8m1Gs7m!vfoLL>@q{(&;y*F_NsK-Yh|?CWsmkp;dy^3UsSg2tgZTX=GBA4Hv@b zDhvP1ik>R&ieA-2uWx|etxME<8h5yO&d!H4Nl1^D$0rY>E~dcMp_wot8|n# zS>P4fL@=tc_iqJrbxRhLydp8ObrRiGnp)H`ZrxXde{j>6A*cD>^dM=hhoVe2rNl=1 zx_Najf)w~~*p(sTaYtzNun!KeuCA)As3hY+GsXn5%sP{Z*1*VTlhiN9V*g_Ur#!%AizB6bAS*?C6 zv;sCNFeMO0wTH2j0au}E;{GYz1d4IWY6Y+fgj-E8OnA|XP`}9(FA=$hvVJT5l+PMv z%B8vi>vqnVR-#+$92Ok+W+p=1fz{(h7OKP|H?Dx*%o&z1@RliJ3rn$rXKM4TLGgNw zV3{tp;OUwogrEUy03S^eRC%!1X@;N`tqLAND^P2jJ!HaS(BSo0AoPYzB0AQ*K@wSG zL{db$qZ*^4G<{W6n{CrB?gdJ5cc-{Zad!zccyVuWcS>=0DGntB4Nx3Pad&rjcl*q`w_Pk*3B?)RxI6t8{&Z8^5YA zhiKju%ow6a-U{061-Bv>p8~brhX=as%#X}2lNN+4^NiD{7%I{e@fK^C~LPk~2 zKSq6#9%}-vA`DIz5dWRzFPH*7y+U3Zt}Ng~4@2k2=N%gMx6V9qkft-JcM;9Na0L$T zxy`UDz)vv#*Cf;Dt9<^omPDi}J&y_HMoVeA&@cpKPb9h#jR$MbfqD=>d`O7o^Ge6_ zU<&I_%Kh^19kQ8E!dmMdQ!7hjJBvHlB?|SY(9r7fpylDEiz4h^eun=X?X8YlHb`z= ztT_lJ@yF3{LIG+DM31;Z_Q&61iq0d$%4}T?J_r^#b5{70Z=1%IcZKQ>_wgr9{(~li z13TDTGhE8(-RBcJ8``*PcV=j3!%>&|=`+WpcQZBY&6ehmf=Blxpq*i#wT`t1N~E+T z$87N0(y#ixPI%(!jtI>GgtCQ$j036sYT68K@h-N}5%zd%SFVT>ZII3vP1`b$7s9Ug zD9u3zGyGiwb)E+CVgP7I5~}%ecg&S9PKv;-mMlzH$o{Esav}4D50#g{F7OX((IEfn zU>*Sh4A9N3;qKN|6ma9l%bxDL@y*(;flm8@L|Y6MoCmr-)fOt4L1!>#B*AYBR16Ol zHz~}EGh3_HZJ(c@Y*WHXi&I$IO)*3t#4mzu)<1mNlLGy*j-NXu1P2D8#YhG{qo1V~ z4QCGPb?lR}Dm|;d^hwm-JT4NiBO@G0b+y&j!B1*91PNQZxcHg*y;G(Ny>abG-bNqi zxt|kyfOAdB+eY6LXQ}?uUG-kCXhVSl46eM8LE|!HO#Rj6jZG!NyVp%SGYZjcUrTaX$G)(~UazGvU-7MxgsuRX4-5GC`` zmBS3&mnDg^6p1RorU3#<2}}ujr)H^hT}*ic%smAP8_4%p8O-Pc8*x-Xv-bx2JBN>3PbkOr&EKwVlfNp1@|;yzHuPp!^&moNGv$oEj`g-bYYXt?9k0=~ zSBv5Bv3+rX5~jz{>oR!!P`3c@M?Z{5ULg!$j%}g#K_|u$^CQr}zfF%iO_4UR8=vW! zeo@nvrPj$DHrizG)0VxH4Zwmujc;j@m;Q^|cOtmpH4U;B(yV(Y533oWVapc z#;AN47!ow7ecmmYsk{0glnrM34}t#&*#FC*A`mz)-%hUczRl_dQp_h-pj%;ROvTOi zX79uV>bjsr;RM;a24P>~cx))v7__jpOYGq@Zfr4$ooMZBHBHdA+R@(YdAtwvpgd5r z3%BmED)9Dhu9G}{e!V+0^gwUFnn6B$xv7i)i?(ygWs^tWIV4ngcZ#3H`t2|XCC7w>2&nAStBXdGrZ#QQWf+$X!=p3vHH zzii<0?pn2qd>rOkNSZ}K%EnDiX*A&V)7uq;Rn@xB-0O|;>)lvoK5Yg-HDmb4IeH{} z@A-5V=zCCV39O!uv>v`ho}UX@uvVWn;wD(ABvLIu#lkIwO?$#Cvh)X?dNA!CcAmJk z8#x4MS(i%WDqp2-*>>6k#jGh+*+|e1-pvgBS*n5s2Judjt_3soLk%PXK$tDoPv_|V z>j#2@oD*^41}*-gbq(Bw4%<9HHgpUevkDDn6)5Yn{J)8jY{`PR zs-}~@I?RIBxr~TVMw@CrNF)Yeo?7IXuE)<%mgCoITjG`B&@V%c42O#X9t{B~ea;xZ zqF}@*TxR6k*z_%b$7)YJw7|@IM-S?ak6?_zwu%ugK8$o#jS5dtleI> z(GQnN&$AJ^KXiqVPbv{a4PfJMAIbb7P-=(CpIHhAu=79sK`%^E{6)Kd;U46Jm50|~ zi2UVe*M~hq@b?;DgxI?cxu4HBRRJSoV`Ia^&&F>@YmUvUV->b1B!WBG%X*qM$igJf zzK*hd>!~q3aam&8-H*GAnoeHci1x39;LXv3naF%WHmJWcg#8B#%A zUbZJwBa~d~(1-{5D5UE2~L!2KLvibg; zJ=GUh64ZzVHVTJkF;|q>O@ynGKHKc3~2Qe(;j?Hj~cx8G0aIZd3Cg)cl8E zchV~5f=!YSJt*rqG#9+Dn|83tZ<2H*=78*!|HVAs?j1N+OICb5&?q1l zv%?B4K78miu+{HQ7Brb07|jes%2HR_a&+iZ+M1kb=;GD)RO<}Bgg9!30KsI4++Qd`jC$R>!`O-== z=f9?`>)S&e6Y$%ok+JXpu}cI6oNM}2(NQciBuNI}wOKVrSRdG-~~`B`x6jq9iMTlhi7v{?g5F{lx!j?_IpMw& zalPttR`(6qaZFbWz ztDi-(nCv|= z$j-=Arpg(BE~42vm20g@iz=%=WfAUPoe-Igt8eE;)7i8gByW#ZZ)+a>!P`sF>fyN+ z0Q|3<7!>{v*i#(O6)`X4d= zQ>40<GuPA^q6lkZ|kkIGOG#!`JAeC6+;{-wWGM zEun*a0PF~=t;-uXG8ar5@869IKeO-%XFd2--W}xYgoIk@fiWAJqUu;r7Ee!S#buP; z{Q`{JofZhcQl2WstH3^RFgcc$m2^Xz>ga!7UNTKMi9`ZEt7vW*SX<}XAUmV93;a=cEo)wusj*b)`dtD24og6DkWT(@o z$NQ`^iHFA}&<+X)bbsWKwH_?Zam`Bqcfcf`%yJvd>42^PN7R5}J>3DS>6-85XMc)a zA^?lH8}Y}Ad{$xJ+smG@&GPWSMXx{ZzeGr^C6g^QQBq;d5zJyahOF?*y@S- zsYI%Hc>MzHV4jmtHliA{AvIY)Dsr2{Gt*lz0U#YWsK0ObG2xy4oc8DQ`pp-5MSH1kv{!g1Q6)^x?^+071kx9Sb@Bo(k`odf#?d%c1dyrSea-yDy zB5X}W45AG@A>th&*d5~`_YQa=S@?Y$dEC~HL^o$V_zSny;iO{8T*EX|rAO8tQPDvq z2}MA>&#bLZTGCXYIw^kf0xjF6GuzLC8q}>*f;Sv3-s`VtRE0E0nNJ=UG1n&{#;lUs zHafgv3}2LWTYeYY&fW;-Hlp|H!Qu0bYzhBNrVm3PTPKRJjA&TR(BGLW#QR-3`qMW^ z5lXVp6r^k{4-lxjk%j`CD1c-hH8jAS!#gMs(rLD@K#`F)hg2geFf=Xjdz|aX>0Wvl z&n+qsnX1OzNG-v|@fGG3vJZZpmsLQ3o0pf5$6Nu(V^T+)yjN6=~YB9qIQyq`32? z>d0-?n|i(I?imW{HKwP?wS6%#T6$4II)&;SV4ybMd^~xgML|{K-l*IniHf`m7u$qR z|KkHUNxqnmb!!2_B;UIZqsP~w+N55HUtXG~9DOyQZ<)OXXkaWYkNd^Fp%B$^^~GX| z+F>yET^xJfK^JK^ydZ2Y>3f_c!16efab8pp1}@HsKz4^m^^jg*_|pR^e#d(1`{+#3 z6mmQq{Ir#2VF6RSroyadmmar?Ry$-dIxi76Ay&S^FB&Eg_h?Ugp-z4Q=CDVDs?_j!E#NcC<+f zy^2X_Dod*9aCuYRsEjCFbVeEzza-%ze`_{ zJ(Rs*M|E?~KKBtjIh~zjRP;Z`E~|F(^q6y@A_ONTO@*Nov_tT)SEr_@i>40ZoVWD|z-MDA0abnAdM5G4V}10X`Rz>!q_3EQm%4tH9eUJiJ+RA~6G zopc$|P_NRfaUWlu#c5z0U};~fEv_0Ebf{AExMSfv6EIR6VQ<+jX~oHKXlRcN zw>`?3{B;m^!O28*Ix2S2jr(wVYO4jnS?>M&)Uwp=W+MZ35q@ex+f<(pSyH`|1aeA# zpjeuWGgw(z(`3chRcpn?5g>s6GZa9u_p$YkNj){!-y1)d&ohQLI}UL=iZ*pluBr^r z%7jmLT0GAW<9X&4(-@)-#t7g%4tErA9<<1H3ImwqOkA+Z&wa!dLjq$9d?WB+`&E|T z`e9+nIEJ;Z057^JdDa3-QIoSH>;GQ-joJLo#|3plGel3jf{~F9(Mx%R%TC}dWbG}+ zhI1$E`zUI0IL&nzUm<-hE_l`ZH|I=t{EG6_j}oztuU6S0Lr&|URiwJRGn?UoIdN&CmHR$HIT;koxnJgS+Ykuvq`&*{GDoxpSG}LG zF>%z?>Sl@7VRASY@LYg1&?vtfBIR-~B&FpoZLcc0kL#&x_M_vC($7Xlm5H5sn~*Ox z%XBd0+;ags$(Z?vDszM=QJaUnS~Oh{>-F1 zd-km#Q(7^VK4AbwwHXZB&y%i1zd|TFUa0C#6~&1MMs4(<_gX;8Nx;jj4P=oMSLB?& zLNA<~yqTM>O8;Z_4Qju3h`8Z8HbyH&ilMfyuC7M%hHqaXR#j0fFTorZa8JhJT@|#am~0^;T+qGp`v_?;7!2K@94GSQ{N+GUg~1LaGQAV} zo&AS7gY}Ji%DUOfX8N%AR3__vEW?2Nh??SA%3-vXb0uq6PEMltHIphevT+VfEk zSRf1#m=-8#>+`fhFVOn|p@dqJW@jl9pgySnzrz{<$jZukEQGUsy!yTT(tX424vl3f zB{@M=F+Q)hJ9Q~72sPz=v}@tps;0&n5o8h2yLKZU>DI7CG|Si6^EY34>9fFQw-YDZUzcOtM54q*l0yaX+JvtwjJH~b% zMWsw~r>^^IE+K9p0399ORVe3MN#Nk(;^OA!m%dx1QC~;ZCNzY{Ge>J);<`-S4+&ip z`&nFDB&8V*H+t?WNI9AO2fMhmb^Vft&ktF%WNOB0;Y`m4+IEukP!ZBuAO_1pZcly4 zOH*z^a4+gde-6=>iOfYCrS}KVflaW_krn0yb9oT&k;kEpmH#p(4J1=I2Jr8|{nP6H zd!Oxgu(K&6_loRHY_)U>8<`@`;1c?s6a9i0}a3q zZPz##n-`?Ie5U$hoSM|R={>z6F9v>b*99ao)V#?2fUP+PAn_FsKdL8(DG$dJJ}NSj zbm z4C&iN3eK@$A%KRaZV(+@ic+(ujS`<5r9e$m5NBll{wV;^00nHs3zQfj$h-VbZ%mmaA6OY!O~~Vkh>odPoLFbgtAIUkfm_5~W%bp9XYKKnR zp{NvU+JPwV&)(fiu<{jbcIA+GoFZ#uByNXp41Z5Jc@ZVcr)zTdzgh=3LBn3*4V`~K zlrcF!58Y&L2msvflRw!3Rpo3V!>?|fI$9DFjmhz{*o+q8@{iC}F4p{S*wPK}R~pw= zA{aN0_ya9C=@^adhvaVeZ;FnOKW)e1`?*}Su&CbA;An9VdT;;9&|iDSJ>K%}qbX{o zXlbus)FO1d7(&m}`uO6@Qcq0Pczk?Tq0vVIx18rdfWn=x( z38y#KRhnhqV224ZNyK)AaEhBM8nwsFLyx8}6tflBNSiACisqG}FWDEC*~$7cYzuDkqsRfx(4p)b&#$7Uj_Pu#K^JNRBB(0({XNv$59XFx{Ecs0K_Q~gLV+N2) z_xwub_bPgan>1J=DvB)uT!)Iet|1ZrN|g$N#u(Y!x(FfjK=){hrIfFWwsQ1lyNl z0?ipk^0M8^%&N|)ISKt!3WlVxyM6D3NW>DJ2J5tBaOCDA)!Mq)oHw@ z&g2Xx9xPE)by2z-Xazh8hc+qjU-*}q&s|bF|2y62Q0OLdtNayq+B0A zBt=fl@P30}t+W*%9i^UAtm^5^Bly;dI&~cQGAC;0g z2T|&lv7b1L1tLu&CcBq#1tkWRe`0pXMh1Hu<;}2)gHVwnw#J)t3Ca>Oe30_PoDi3a zg{|#)c!;e&4$lPNiwo4a7*Rej#=?2#lrhc9<&UR^Umi_KX+(!NROHK*P%_DkYDX)U zhWZCTXdN(n@2n2AmK!Xo09Q^5B)+Q-a~bNl;F#yZ!E99AVo?#2Xzq{$NMR+h*Ey0b zXv6&opq>2v7-NWRd>kb-E>~BV36+QM$1!lvAi@7+SP)~)ssp7cOq=*rZi&zVeFnzb z-jRnh6sfH}ZFF@~OISRL9|juLN?}LV|KnAm*Vu}+_TkwXQ!1l30U`V{o4`_y4t4() zw?N&%wSI}!Y~pT7G1Aa?1(i~cce}cG{6nDz`P#n{(L5!8?ZDNWYgUa=>-2J4m2GLv zaiT}rnKz8Oa+bmy@D6b1&eBE~um=?iwrU01@yY+@siuIf3pGRCmaUU1#l7;xnM!CL zmgS7<`QV(kXSHW(wYP_#s;hw3_AoQWak#CFG-ur|z)QX|f-Va}h0)fy)dS@&Fe5{N zf9iy5p7A4@u3^CCK6^->v(Ko``>;Sa6GV<)OZC;J@xf|SX%D=T(o(v=p^v4FEEvtb z)0B7Tp~KOUJ8nhcE+%Reth|CuOdrpe-kxh+W%KB;ii@mDifO~-q^~!p6-Tev-mb0! z`|t0+b}W6&uVay)V=-R6K?#0G!+Fx=pAexCQIlJVrSy2NIm$9&8<53kfJaJ{l})&J z_m`;&E_LgJz{mQ9Jok~!a!ApJMe~+O+@xi?LW7rkn*Zh5PXs!=jWAniF4p^Jo`E*w ze@K4z`MDuuN6Qqe$MXM@b|wZN%6?TiFW$cu4ByR}8Rr*52lT9QiL$Z%U5Lu7vEP3X z&7o1;0_i%EwNe*Ce2^9lp@Z$f9pd!rRh|Oj-`q++aYYaanQeZ++592}?@LlLUlJui zW}BcVCL;tMsD2!%KK5n~ak(tN(bm>=_O<^fF4lB*2A;0N#ncI9=6>!1kS{hyYf8E+ z+e-Ny%s2(0#YM!#L}1>yE7%xJw53Cp;VEM8~_9lsm&H= zx##LgvW#S`$=6XiTP9os3dvw9RXLu>xU!>r>t*G8jetuh%DHws-Y(ZZk@u(le3=n$ z(x9iu>$UnCowti6EXZS{LusS@F6Ichyt=T$jU!4fvv6*?#w^Y!jdy$0aNVW>OY`Sl z{Hm!#0TFVD)XQ`14Bh+;S`%E8EJ%Y|$hm-9|Gq+3AAF0UNo8C< zZL58MK>TyvchEdj7YWBTK8b!SDQaNYtg)F7U!o(ehhF!KE{znbq*ABg6S#Kj+COvQTz&E(i-yq+i}!we-rUqkj-TDH6sz%XIZ!ZBa zRaKSEX1^KSEVOSe8Ez_|_A(m3hS5(2K+9H?dAz}9*}cK6C(@LK`K4KI`c!!}YoltT z^aPRiKeI&2mW8n~2N8-@mKPcIpNYDM!m(v@?G@M$(tp~ucNMS1l zGxO8pj?~g=ob-}XMunNnu|#Dj{>+TnIq+XdrTgu@se&AC){Ik^3Cn>PK9BV&Lxf;^ z8jqF0qR67C)KlQ?7)vwq1r2K5R794PshpMgx9OTYT}(Y)*4UVyY;^ z?M?(C9(^N^GcBvHn;0z0bz(m2xo{KO`6{C)@60PQ!fh6h%BsAX60|=5=f=WZ!W~ni zC{;_=z;G%Is9_oShM;r($C*Z0pygDjU4GWyOZv9@k=i#HUBLDU;{ies>OiWJeA!LMp{f;wd=*DeHksuqM*hP|EmC_h8iOnt)+2+s@ZRxL zk1JOUy^O9ijIUvV)>p;E!o|}4(1GvSgR8%0A^pAr8656#;9;aqPme1U*}|!><8+M_ ze^qDyBIu03i5?Y0D$3RsL_mDK0olxRKW39?HW_sFZkJ4j0U>wZ>uPBem}k2(!U#Ub zc{2v}Av0)7CRpHexrLJX$=J-99LzGYGt2 z&OO%Q+Ji4-6wX>&&@$WZqnd*am=nYJUhg3+q{mqCl=k~chlXl)`+k0 z#UXL5M$|Q=vGg7rbYOuXDJ8q(Gm;$PQ*4O!ev>1X-8bhR%^H32EO`aL=d`!8s1F#NTL=EIdc4T-=Do55!V1E_b=yj0S~RPHP4#ic?8WQIZV+g7#AuY{;yXwhW^gXcjX^D)2{-IO53h$G1=BXZz~4n={^ zPckUQpEtA5Owq+lm+AorvEBcvpZIxcd|N)=Hy?|$nOPj2-wb`sqJA=~zECBD_eAu3 z_Y3<^=}hH|Kg8yBKpFK}s^NXRM#ppS#>fU5|b!; zkfs#(ApW=cT->+L=aSe|=ee_vYej#@dhd33ZjPMON?7FXMRUv+Apr5&Pr&K|fXwr( z#izX?7kIf{*Yb7>Xu$BqD2fv0RBN&<|G4u_PZg_`jeYoGaE0-gLiYAy_8iw=Eg~^b zNHE%pI8^Zoh4{Ov7<~*ucqhp-ip8@HSfjmr{BjmEf{^qI;PoFg&sDO<3l91z3Ln|9 zHIH&SJ3h#CwOqU1Ac4jV`+WaO=|2sXyKsPIQf*uOQ8uFN0p#+p!+!g6=kPiPZop=w|1d($0PWU;uO zn^DF2P2eWSh~e-5Vsg!-B5N_btJCS}yAkn&5ssRMHx71Qk-acp-hi2lmNtcPIxx<2 z{_*}g_3=lZdo$*PYxtZ_oh z1!Djo?0k9Fk_{Z|fV{V3;6X5%%XU8o2~ed16~8CzLZ)=WR#%*1^4|H$$s5rFZ>;+C zjAW+*)zi+Uuairwq0Y`?UAGCX4cr%Ni68MkemCpLw%kEeSWFFu>0<7En@Y?`p^N9p z?rMofWEZx*Anq+6Dq-~WZ&MsCWD~ZeDD%R^g}K@Jd9HxT_VKMkW6?yDU&vka`m4kA?y)Czxb#)mkY=4y zNKrM%=MP2YS3uK9-Gc?S`4cO zMD|K%-a5aQ4dG zpCLn~TNyetzmhCxZNXP&y_KQ%K~@{7MM@omG#GJ1ZSbi&^jAr6^TxyK=HCJ>Oo^m& zVH)bnZ;EVYSbHrmCsrhO&KxWLAJ=@(Qv0StFl!eHJHC};2q0|zZ1Nbx@gBl~zx0M; zhF|Qi`PZ7JcE-+1Chmo-XIwBL<Au7|sdWJuI!$zMzm3oSK z>9M3QbwU2S2wzoYz0WZMu+6yEstaQ#ykuHlWj5}L3FALv6N=%1?uMvPE-zu4g={Iy zbGj`Buir=D6{1fI#v%=93qmeq6M||Uk^n5#HY%c;KXaEveF)2Ca)vO!wRl|_`FgSI z+UVtm&UmO0V{V384i;9vJ)`igX5r?rbEQAnbtkChkb6oi$8THKnHDhrIoLb;xM~U; zRMV|DsjX|+N9*oY@%Xp~ofmLt;vc}G4R<-=W8Ib(CBveS%*C> zh^=A38U@+(Shez%t}Tj^*r0m+^zv%P&ck5KIZCVU(A?X?Z$8AXQKUwsXP^k9{9eJKn(%CV?p}K)Iq3{>Xj$xgjv)lc|45j5sqplor8E#xct<*gyuP1i=hKjcOn`V>! zUt;{ODBa7f2xV&T%uRe8{a+B(2jb=&y+e1fi@l>%uIfHZR5qT9D3%9u_fYFe^s*{B z#7`Uz4wh<2d33x!x9r8PGe~3<-Td;T*du8qm$lfIl@4Bl3rL*cpw>Rv!Ii#F68^OEHL{--D&OC;{kf(>`x-o9`zi>$frm(Di-UBy8cF%^ z($@Ara-6|`+#Jy z{~M?J?KB=EB>B3Oa)rN0CUh33{h`MlxEOX}l z5YA_|ko{$?%wbliv5yX>F85U;%ZwIvIzrvW$}<)3H?#`PN-hfcJyNgobUMhu1rF9a zM}dGm>|LP@Zm|%eoXk?LdDeDU_XrLi&M2c!)SgRToTv3dL7^!46o<$32{gs_bJhHR z&{`sv*ux*M%mw?@!DJr#G(|IE9&Gn_iF^VA2y2d7m#M83zj1>^(5u|?V)1so_ZYJ& zAbCZ^5gVJO$d_OjFKXr-KVU{6=-T_Gr9gpuHThXY2_DxD?xA21uYEuWiPvTNl66+&`8{oVYDDjoWSr z0^hYF{A;Pv2756D5vYPlgVXo|;_Y9!`j#6`D-wTyuvULomNtv((>QVw$kZ#5V2v-? zivYF}J<7t;!RWw*!PLX-z~I5kz`DT}!_L4W`fD%V-`F4jhzK&X)(IDg0CZB;*SFQx zTddU5sjStV9y>aO%mL2fp1rT%?;}BvA|&|Be*-ZjLFn(#37dyGb%>?baF9nAS-;2V zx)nY)WetG2Ur-FNWc2x@a>c81=1c1vB|bA`j5U6a+xA>zZV zQynyk;v25|v=+8Idwx zhw=Z%fBg5f2+d{r0``0v(s1v>vTHij8F8UazFmTr3QrFKPjZ9WTvAL>kElaP8GS2X zGae^Tlj~+eeGit`cHg|f+=6k@JE4waYcxqIjb(1z>BS^zp>4-jn1#*oPqzU#c(9Pe zJ;xsRVckZo6g5rs-7t-XcPsLe7q!flaHAVhYL|n2en_dw*#J!q9YdXrl!TSWez=}3 zBig#>owS~=!`5c1P08>nhqQvo!NJSP;j&Ux-t-vVL&Xt4^tsoiaWt5j_rMw9JiOG- zAhv4ZvhUZJ+IRcDFP)n(Q`&X?N>xOJ>3eQ zT3(>~9juRp)mVs>>%fYC#RKf(%%-np%FiRu(;=B51Wr!z1%|$_k2rw=b6{HU_?*~^ zuh$8GGH%;A{v-i7cFdaXJ$y0DaTIJSb~C+mhonSk2!<(~p4NQWPuRYX{*%n1FS>Hp z@eudctjRktbDrk%N7Qkxbtg?zM$^7V7uh#E$8VY9*>&eB{6dAL&f(|l)Z60_{~Y>K zuRFvZf;#X$(GArZ@nG=Y4R=Q&}1#aeM5G_QwzzILopbI z8f%GU@5uRRG^Fejw&|N04q!&8$vMzJTd$+6xA{8YA^h!OXGU%X9ip2z!|25F%O$_u zq0BZC*v=R|>KIsfuESCus%34FkQnJ)`%Q#R9;2zF5oAHN#a$joJVIn$@2MBz7~mu~ zMtqG~IdObE#5DCw;*?64_CPmnf+~XOp+x_3i~)&oq5MO zLmTa~8ucMBM;i2)R)B{zRw*+%#c+^&;Ku79wR66pSV7y0_&!;4Vv%2qS86EN^pirR z{gD?|BKxspCTkYSI5)w6-0MFi_)Gw(P@EV=Z0m;T@vIwS6hIGz4L3m^N!HUdM#}om z9sRyo^aNgB;B(fjsT2!n#+wwJ`*Q8qi_-JkfeaKqSVjb>t}aHTvp*?O_;Kr-Nw=mA zokoS>(08b!?>swv^~cT!=<6ZvoHYI=yC3bO#1=dovl{ukTnxu8|KyC{Z%=TM4X^8{ z7ryp2ZxaP7fcpFueuXf1)VAJr2iam)EuN!q{LotVN!a&zxS!f#s<~LtsrilZTRRQi zZ(CElw=_0327W&0Si17KcNrft`RXf@g*}0N!`LZcFMb zJoT6?(F2bAG835`fx)WPD z+T>|kbDwJBS9r*ir?&C*r@2L-GqRsrvI-u)>wkJSk-DL%5>+ZU1F31Re3&9w+?Zll z4iJ8v{qsjB;`Lk?RL)A3;}_)icniMrQxWaw-OqTUuK=iDhIycYlS%oYnM)GHpWwSO zxf>7!b*q4(i^l_eKbo9vuo5Ho5}ZHCHE;$3r1UV)2WSaq)4EIK=6lmGb8jWKv30Vh zypVv1pRTFXZ{w8NwU14_0U@v=GkjqQaW7>^ZjN8q38w(7TjN0%FFoaEDaA#~Q!`IX z1=1b?mlA&`Bm-n5x&>_BlcxYd$l*>(h2&oGvEyj1rtxF%8dA4QmfJ6N>}T?0`rdOYSR7hfE19y6Zl=Mh62^ypiZj5H@y{J}q(PY_vPwSF>oMXB(2=q3D0HwdMmK3@Qg z&U{s#)Tc1&9b^czrpDuXJ#chvbfAhHWLuE znAymYWFdA(g;o>!GgJ!9A`*{?CEc%s!3+Xzzn(|*u% z&DQGb{Nu+CIN&_3)CB6Z$VzeFE$^HnJ+>mF52k}%azeY&!P zxy=^4&TPFA&oWH(io}YLY~oO}^2Gwc(y612^iuXsu;PPtn2mrdO0FymoXR+7(`=o1 z713YHwEi5k`By|C3%$-!){P=#-vFQAd@OB5-*EjcX3%%j%Cht) z%Uafa2q12qRs>c;$Glm{N1B6~Ew(Oy-Y&yCRKKYf%+!!ok=R-S;?)$iboF%>MH${U z5@tgoz+WSW^-h1&M58G1)|3GnUQR3Vw2-^A;eAq9_vu?Jp3oK3Z*X4nztHbXw6 zKkA*uS$G4$71eG1&X>W6IRPak&5s~W^ z$V(~VneH$%w5~s&A{XrLw{0V&INu78*2#qc$wsWlxRMDz%ms7VAi`&i2DCWn-*5k& z$m=sU@&XhitlTP}{F2bJ&yi73IQJF`G>e%odcec5ce2IL>VCUzLtdkd_PuviwX8Wk zZG{8R?&zn4T-BdQxx9MzeL!2DS>dm zZHCSx^;k@Nzo-;>S&V#kXQOGN5;dPG(J;WhfLJEW=h`qiNo|%AYbtrCyKe6oKMjqe zh1P^1QCBLP?C|V6IA(B9*xus;*)|qLheQNMunkvd#s!V$Iyk!4%EjZw+m1%5cziR~ z#%iT;>)qbvx}uyqbyPQ-q1Gfp-9k{%#Li*g(% zQO(mCqrT`#ru6c$M*K5Bmw!3@KNw>Njx0FOo;#0a+!A|geQyv=l{q0>7Q~6+M>?j) zRt64Lp^^^{Etrk%%1~&{D=UV+s}?%bySkB$4(v8cW^GVCVH?@T;Mbz?E1I+PSQ$*c zF>gD&2CECx{Wdqw;N=&zvC0OY_Sj;s&R~3i1Z0aAwIf z&g*WaYt7L@cX&^ZJb7i`-3A#$W8QQ5?kOtkPs^iU@fBXrzS|qm6Wp!hlzQyZ*Dw~B zTz2}G&RIOdFoM!eT^^IY5#g4!tIkGcEmLM|KM4-Kw_=qPb?n?N>%aJ9 zjpyvSj=lWw8I@*Hm#QY@11|BQWC4#d^y7vqNo1?;*eArnB$#T);{gHsA@=O&u3lf) zM2no@60%bJv~5dTmhkt(MNi7Nv`SVS1EcN_7Ds$unQkuGvLt$GOH=whSAEWl%6wEU zX0c{cKg`S;Mpa-zBbWcL1``K`aJZkd(IuhC#SG+;$$>Tqtm~&eiNUsV)xX}s8`U84 zvW27=_Fr~2RX;z*Z%;OeN#9`87h3$~7AnOPgQE4fvH3Hx{=eJu3usUJoG7@i`An$E z=L?RZKEpAY?>pt4Y3}+7WVq*O;p?jnrnO?C^mJ9VJV(8apJ?VaojppNUejF>Psxo; z7iV$9!bVBsGcrR^P}$UvjPrD)WZ|Io941f#@IP90ra+sS-9@?3%M80lQ+rK!0wz3; zEtL(!~b{Up- zy%`ia%M=fbG>_ro4cP>c%uUDv9ft{yfl(=+>zKKYYe?KK2Z#Y$lIl-X`_A<{XDl=X zpPo5>vm(jPkwWMt=!DWyP07I2>>j>jy&}DimN{e7`pKy3{C3VGoSaDEGmkU;{iZ_0 zIPYg#E}9xolbtltD``hx0wV+zwcnH51b}?r_~mTL-@7X+@3=?KO#9$$et&F)cwpKi zgw;xm{}-fx-s-_#=&qf`wMKh;W*s0{wWFq~cy>RT`JEo0n|GmP@xuJP@0jCT0}tDH zW3|tOd0X`kCM{~Cjnpr`jA8mKq;af!ynV%DB#Gr_kNt#Kq<@(Mq`O-WrMhU8R4}dS zut7*Ax{KkK`j7DlXaq$VVR2@_M|G(wJ!T~Vju<IEv3K?{A0%LM}a8U z(-->qe4`f7c(dI2QCIW@YcCJ6eF3RlS=!ME;0?5u)E4)m_-3a>e&oEuP5Y6F>Tr>N zZjpfLKA#y2tEzcaVe8}dx}TjjV$VNW=D<@FKsXPlubAij#W%{VJ)*8-&cV)3i8`SI z3}(woY-(zv!;l5d_-msHVnL0#wg2EfmI@l+#*gd?x?XBI!-bfAGJSh8puw(}i{wdi?K|}Y!Yo%tVp1xsW zb*SZ%@DuTSKfG`n_0RUP;&zLWOkU+l;uy9XNlQ?aEK9K@CKN~U7(ay4kfe@K`9B3Y zkZfgh3p%rIZ7dEzX@2LN2_?Dz-$Mi7)xNBo33-}Ux6WkDX90`G7jhJdt<3=7-m_93eJX^K-)`{NEy7^6?rDnq~%L0jXvS^B2da7!D>8#98|KOMKJw z{82@bc8?`AQ_PD`R^Rb$Xh0SH!-Ka&YD8$7%oU^C(vS69kn!&wjj@2L6kk|q68}H4 z-YTlCwvF1w-HN-ryE_ypt|>0Xy|}v+cW-eg1b3$tihFR^;_kNde*gaW$v(<}gN$Tk zt@Ye8=QVAcLQ^;-uOm}93|Eeazj1HwWF20Nl-KTFx;H+(0(I5o610LI3~dO9L; z#;GT&pf~!nSHxII&nHB)| z6UeV3_t`^JUDns^t2JVEv-tSSmp8_lz3**OX?TR3Qh1Iwwo;SEa1$aS0Ik&2g8QQT zG>Ew(DheqHt}g_`C18J>tMZPmZu0&h?y+rXMkj3h--<~8PYB;S;HJx@e&k5d40G*! z6~RRmQ}IKMShYPOO;cL=qiAdxQ(S`lrn|a~iZ`#fzZ5+mS8~w)Mzw#| zpO0Ur`^w+z?|i<@oH2Gy7+KC^%v)#to(3`o!+;fw<7uhM#cfHdKK3X+4ub;k-rh(S zKOR0Fe@q3nZ3;KWPyjJJdxPGdRchybGqjhrAe{7bvhw`e_qUrV|7!$rnLq-7vREv2 zq`I~i69WU1n0R$}mz$k!Yh$BpVuFc|PDV~FykRn^JFYNhNMMJlqVj9G;t?iCT{qX& z`>bHXJ!4NB6ZEU=99h66quPh({&R8f$5qhB3B?C?n)ZY_ z2f?4`jh?`>a~h7vnDL_DZ;yv+Yvh|BSDntaqDXocqZ?TIpULz=8IT%&TKd-Tv^FH< z7^x&72oACXR3{XHOj5A#2rW zUDau9?@M>*i3>#hcIz)vBILM2V@w*(=zY09p!`yutW>qr9oXe^cr;h8W|$lFxD20= zx~*|m&!pqoRYzoNQ8W$})Cp$Z>gV|h9%W=kMVGAC@g?Hpp*M5LhUSRwz}IHfCUQST z&J+oBKO>O`&dg|Fsi$XVBJM2k@bCmZZvRM2OUuh6$66)F#oY@A+(v9PHK822$>)6@ zQ$&uF)yf7lad|dzpQJlRXjrbd@%^>WOnVkWY~Gpwc0D{@tv`U=c^BZn*&%J+*XK5n zV*l^YHstq^plFu}I6n7%>74Sf%7~NK_jQu}LxnlS`)>=6sI&AT4{7u9f%Odlpi1KN`n9dXPy_}nb^7xn&2 zQxUmznD4})8RM8|TNg833v4q?t-{oW0zyk`>*3yB zaNqsVkeq4fHN=b~$uer_rKw-~N~lkV7(T8}2zF8Xc$u5(`FMY^7q}>2#BfcQ3ms?% zDpM=ZodG4@(7#=Lp^5hqkJc?)nHNA+>pjfi(AHEd)LBJt;*LU< zfsr4Po|cj&M@Ab>UviSa+&&YuQs`7@%E^Y4^dPi{H=Y>(W!Ag`$?bJ^$R7W>b$EWh zF?7K1?bzVpa} zPW!W=0$}}>+0v(DbZ#mVaE>Y6F8sHdbSBx^Ertq{O#mNn=3?@Vry8KI*=CPqWWK?v zz=A!nn%pL18FICJ5jo!{x+9RPBy>VRR!^&i5P(?oU~ts2}b`N^hOM3O_?x2ITc_ zso5e8EEkT3?PxMwO0QwHGi6pZ-{4=RIHmW$5YkX|T2zkosi9mT= zLqc(0UJA|o262}fU&9aE-9^qQLe?L_)BmkutiFNL;z~zf*FL}cnrlp6!ptQAG!I-g zRG-J0C4+3IpMLMn)Iv;2i5)-Anv096+tL1grA)*sV3>xYZ{dVafo-bo^s(PbJp<$m@sQOSDy%_~>ctApyyx|NqEjJ5pMYiN1GaP-Sv8f? zO1kTKiw5rP?ChKqO>=ftbL%TJ2ddegYI!A=>^%P(=Wdx~)cqHKc>|18GxHnk&WDH1 zNa3m9kCxhio{2XfW6mWP;p2>^T0ST~K94L;PM}c#1XE_ z6P8Iu9W1|J1G>U+ew10oaH9@OBNx@$hx`_+XY0FKneY{dgxB?fCpuvqkT@01_W250 zxc`k5k9$3+#Co%AtC6>h`tBVce9G2S%M4NQ!SU*uSeyX!Q1*0ytWE8{(3J_4{(zG^ zNV#l$(&%BLTX*fYtKwQ+oMWjkh+yV%_?NJrprzHp2+0w-W3h?on`O4HsHA~g%=%r^ zf0Xt-f)YnrSH!{Jn#X|En>dv#b`treJXCQ*5<_a33aJop4=Wr}y8T|>JVCgG?(Nx7 zH!;!H*49=3-j$9^@NjqUx^((A!BkU;Rygb^OhX~adCCXk^%3ZSnYKIT)&FD@y0dVV z)m2szcWjGWhLNBM3YNE+PZPLTu(ZarT-zkCNq9M7$ba{)UU(RCR+VhFw;NSGA2haj zVqS0!@>NYmuJVTczoKu*IB2up;q<~3)*f~Xv49B6%cCpAtM4V4LmVRUh6R;DsefLc zvHMnfrrdV1_OIjOp5f7?wM)itv0^He<7}_lFvEL7d!+kDG1C5MBwqg*y9xmdpOZ3g$ zza!bHpF!_qUqi0wP*`rU0`c^}K$edG({d-+AsJ@`!@sa4Jkd|S8>3cGA@M6s7R{uV z!_lJV=H&r^V2X6<$!)A(ko?IvyQUly%UiZ-`7aHckEgT5-Aw@qQd;igb3dQ0`8pc& z2K`oLnzO$QPK3i2HioYj04TrbWR%41{F^WB>8q1&(FA9m2JloxO77d0u$a8&bHpIv zciyZ^be~HE8;_#~Xo___Yc{Qbo6Coqi;>V~zdW}L;~h|f zg-$EC;A#4{Vg4TS4T8j8GQ}hEFQl$36KQ;x)3dFTW?FB_pJS=__`td4rFKIn0AS(x__(u}D9PW= z>#!i{vc|*uAzrZDm;kggCCf6;4RTgllMz6$UVMlza)me!ubj3oT@_t`lC)y0b~@?a zCwWTaA-LKD9wNQ}B9_vINtgK*uGq&gV)gb=NaoC|lG4InDEG!;e>}b!o2HSlZwvl#ruMd@?-fJOGWX zOcU*hKR2vUun(=sjbY12EhnSHqZj#fBthVodV6V%1k&CPbhdJ87BIi8ua%1mgN}!Bp@UZyF>P)rgCNeiA4^d%xCGMIhl6)m>55e7Q( zQt%&SYNurxczF`OY!G2cp$D;Zvo4m=0J#|i;RbhLO+!~hMwG9;ap{)dBkJ8HMHkLY zq=0W{?UCLeSzQ={qU^)vO+gweO%Oyw`z9Kk`HmgU}tp9dDxv?>sAyuwnfA@32$W$3-Bsy=A zZ5djLGgex|pv4iDa4EYCV2_TkzGN}xu(7fZ%g=cE^=u)}@s8JSP)JG~uX~qpoZ7cR zfVr=iV4%^-IW#T>5l_Q<)ZH565-N5>T)Zl_7TSQyasFXuZZA{Mope&lv`Pb+aj$ z#OZ~&LOTcjWaC0Bmwc}_g(6Cl5=sJE1Dc^gnSS@XU~+P@mX;PnHH*kW>G|wZTm#qM zny7D;C`-Gix8<0fgL2|%%?Vt)9KoS=jN6>w;b*`}#$ba_L8;hxqu}OyFaCat2j=F5 zuI8Ju7T)6-tzXiZH83_FeqJUMxh`(^6#0lW1us?RqQ&|iBK#3c=-&Knjt*Fgvb zOPIyV-;|O^0cv^N#>^|C!w-u~dAzlgy4ZPdY2aH}6rrQ7^dIf&93yoeaVA;aSm$ea zAs17I0syyO&fR9&fS+}UJQxb?qT-ICRVx=9oMZ-b6Cn{~zC`XT8n7Y%Ee0Pxfhst@ z^q3lGV8T^{Apb~&1F`c%Wd0KrviJX$2Q#T5A?uGjCx4xsjC-WA>juey!UC7JHm=f= zrVGRd9sC*u6bBFE5=C8@@&?E8N;Mf-(1B+LgzIK5=q@2tj5WRnpY4I;mk-ysiK98z z%=EJZV=hH(Gh5BI;=nRJ{$&p}m}Bfv2eJMmPu8lI28Tz|xpU`wkgh==@-CQf{&Ga( zZSL;x2320-`yIu{U5nomX?hqLV=T(8i}YZJYu~Hd z6AC1$i65IE&dnch6#re-mFDUnYLeGM3x3PjyM@6o^P{))3N+ z305Q}88+W#zIBt4*|Q2(sagsjrB_u)`}^N*e%yW~m`V~cdH>50^g;!yHW)HsSB|~E z_r9JVtF`~!XiXpR5%j)ubar+>ee^tF%BuDP$t0S#<7-9{nX~(B6=OOOOW9D>;uqQf zsXr2bB@+MGPkn%gGA`ie7XbK{8=t;CYMLnjK+yqj-8_1iL!i(9L9Acu9drd2kXYSs zQQBqf5Li7NivRgQ(f;N?QCUH8yQXEP*!z5BEeJ+}ny=cu(PGbDOVhRaKTJuuYA^{} zuRPT&$b?lKN=?2USZw`aYj+Y^CCcAMai3vV%NhMIa-%EBbTe0)!UStU}s9G=(55_=uXV4NNNS@iWVGMrM7A2l^n5~IU zP0cgP#V5|md)-@ZlT9YU7M)1b1Fh2IwNkG1n*Dl(0r5-u$6nYr&0bp3NW1B@B$4Pi zDG;vULzE~Hg%kqDuMhyxem3;yyZiglZ{xug%*@P^|9q8Dqq^jRs|zt~291a&hiewe z*#7?feVe2}qEF}dao5NZ^n6sFe<99(HfRHS`Eid+qW{I=tHp*uqx`y)k>3{ig3#YJ zu`RaHDkgUBf3AAXpL%QU4S(VPO6gYLmCapyq8-FJ=%6 zz-8JH7D;(AwDCyLg%HoCKmdC(4pegrT}m&iuP`{M9f;+qIrL@7zq%sNOS**FKXMg_ zMhDa){X{GC@gy;NHp2j`tEP!;-*r|EZ|RG-(IJA_A6u)^EOl)Sn6m7tkm&G2aAzDq zF=A((G&SVtHjYih;q1adw~#&1eE^!1Oz@%)01B^Do;iq>Sb~sB3j8LiMz)#ie3A%< z7gE_snUx)Xm39%j$ZI_Q`;q^OBX6yaoGAXW=hWePGnmzT>Uh7nH=kHdS_sZFTVEZ4 zEzoH~g-V8ngi#iz8#nzz;g1Q4cKpHv3#;z%ZreM3A;pqp0sTiIw}uLdo8$5MS-2oQ zs;F=yjcGoytAZf7$8hqr^^szcsFA-?cyjlOG zF5Ma-19+5KfR{JQVNawq?3|!j3mN(^cuZL6^cC~iE1qm&SH2o~Cpd(ZJZqM+5klh7 zLIBDLxw3sY$S1O0pyMZP~l(p=f*bp8S1`%1y;T-4Q5Y2iwV+WQ%5 zimiE}I0MkqJ+NSYgh{~^0?$-P{9D*)Z<_sEg_q&8Hmj4fy<5K@d>WY_n+NN7$}?4t>~>YRJY*o zzvHB)%!@?LhLQQni#@db`2Q(L|g&=H|_kNCNK~XO7ji5M5ut{(HI$f#7f^hu(ev zXW<*Lxog-%EjI^2nhgrk|8NlOp5p}dQWUAdgqGs@m@RO>P3!LO?_11gXJ$g=iu-$Z zzhcz&T5{`2P`1wcd;>ueAByn4b>Cot?jJx&{-mx{Ht@odBLo{{O};+Y@TD^UKYySX z#D!{~)g#FAM&tIA!sHZr>AktV-L_?LI}hs;!#l|9VK;OFf=hu?7b;4ZF0^sB4_Wy; z9b8JlJoO6s>?|o9OYo2z^$OAl3ByTs_3iWGr#>0SJzeoiVqr(i(qbb=NGI{(64rYo z!l0~2Ib!16$d|@1$^eP{30$pEN^Z0Aa^ScG)L#f!hA*+IyU`)<%T@57ELO6=*DbT z%NR4{^s#@-NYmhKD|C(8Y;fAn1bBpOw2a-|VOAdf%p@9zAubCdEaegUXA(<(ZcubZ zX8jFo(R#2~Jtye3L;a~i2pAsT5{VHpBW*++cPS=3)LLSx?I;Y<&-=j(35OM{bD0+m zjr%a(e>Df4+IVDfyGKq($f|~U4St(Bh=kWA$f#G6NAC=W-v87GiDc&)^TU3PK{)tg zf$iBq$Ji1~1s)C;h5n&xXhOr|Ef)*=DaBTny08dXtY8h9wApCNn#X2GPPD$H>&r)T zuSmUecsnxe9%-_jxye$dN>rt-M?*l*S2Z*+u)d^~Gnjh#B6I>|$&7}Oc&1yUAl(%_ z5@>^e_CZI|0ddq+6rPg3O_3Bv#JjaqQo!Lc5%jE)lL(P|J0#Es&OR}EDh z&Jr2Y!;6X{Ta+mxR@0#153`7a_l}7psWhcJ>;DVdj`CCnxT4T?_=$iSc+|74E5Q$qoYI8(F#hJp-qv41o$O(q$Yb{I~tg^ za@w|*VH>Y%oJ*=P%Y{;tDcz^DN6Vj(wD`Z(mtwJFGd!_@kx{u@DSc|lm)~x5C=s%K z<~$0LGb|_m*`5OqSgyLfFe|$&jz5oWSkv`Kia#wbM#EGRt0!cQ0gfi{i&g)abHvSy zxnukq!u~59TQoO{jN4gd>z_RCfH2mL_A69UcOo|0MjmuUWP$Kw&X|x4H?tRI!Tsh! zjwM(#9JVP2B1LE=13|5abw31`_DvNdU?9|ayyimUsk4zW$(w4g@8pX07|6pOW+T?B zylYRsIzJ&q%7#DkQ9n^i(Tj>KF>ul@eJrmd=82)Au?2Nk3UW~|O@TRxWHSH3*@M7B zF3D!eTq9(R85%NIk4Ha}(&jJQd|5jN?lz1lBuu_;{(;YVeAAp{#_$>eunbc@LT<&9 zjFunrkmnl2_WXASMX(m4%7JOv9t{e(|06<4%}QJ2x>g_|$%2x|55~qP#U&@+ySvo? z{@$h-{XeI!Mt1+nVmm7||RRs?kpJ+h@*`?s$igH83S)*^|LJ_N^;S`Zr9TSkDz>&hX)? zFLma2sfNTmB$tbKesjOf&oFG<33cA(H~Er|b=f3uDLsR%oU9cf)Y51r2M=0?OLp%l zYq)|js8!&p1Ghq&xVag=&{k2YM3>Dl+`MN-@F>C`Ds=)l$ zL&rk9*-Z+1Hy!#T7^Np}&*#l*EJqzQ%YKWyajD!tmmLn~D^z|8WU{vOd3yK{&GVn+ z7B3U_zh&033ayFvra7n&cJkZi9>V)Le@HLe#19)jt&d!-OYEnO`WkEIan; zOJ#pb8p@Ii;8=fpMG*vJI=!FOZW=$mk4kkOaCa!0oK%iF5yL6*d9`f)&4<#Tfgu;A z8BS2x0rAy6z)RwNhYwW@otT&)A|g_zPn9di!^MTN$5)ugpsq=HD4!hAnq}Uff*vf| zEkTH!fb364lAC30E_HCP2*G;W9s@rBEZK5jj2Mt;U71Koj(7N_ip1oBx|#Fz=^GZf z6YZNf#rA4N=^EO2dpzNXNNr6WzonOGHQI#JGEBfp=cyYVf9MmJoe>*o-co};cD+2s zg8uW|$rSDrJR-89G}*noX5FMCi0FGvQFf}yEIGI8QzaJStt9}qKA|lv(v9z|v926# zi1``xv((DMhHt7r9%>#rMr`sjB|3c2q|_vUPeLc=Wb0ff5N`5Vu}0}3v}BrN5uR^A#nvF>SK(v`VFnm)I@x08X%{rs9bT#5cKLAuF!0YY)g~3!~Rp6 zpBFI)vRy~NIyZUuzpGK1!Zh^vJ@`T*Hz-+jc)01kgVm*o`L&xovcZGWTS-5e z&`M&4M3SYA478Rk+qO)RydL*w?+ZmJw}4nu>jM9X+CW8IGZ} zn$>B=YeOMyXgm?}(_=IloDGhBRt_^rt1Q2R<)7y#RSMBrLAU~5B%pu1E0#sD(95U9 z8Dn!fY|PSqupFVUP@v)J=;NlgS$^zP4%+6;PCpnBQch+OcvO4l9!%4M1?$a*CD6m8r02nTFmKj3378+-v^(bvdyN5)%~eFL@zCEN+E*=^upk? zFnuTEEc>zXlVY$CX(&`h(V5w4#i0%SnsJ{+oSxy9>ykV=ZWe4L%jLV@60KYF)r_7c zTU|m%)vvxs6z*6J?5oE{3NgD?Q$AaADtMl6$b8^4W5X{pMjErNL9cF_yHTbd4U7WC z?e*N#9Ij|eIINho)|OuG{dNtZ{;8etzws_a)H1?buC6F~#VNaLTK1w-wGi#zAgAW+ z&Tp3gE%~Yb=e3juCJI4VvD0>C=cy6_KaH0c^it394Qu9?XJX>3lpYdBj?Qfq{IBO0 z1*T#F`uJr{jODN!ix6%A+3I-l)Q}whckP&wqO9tkh>fDh>9~fci0I%>uhA`;0U1O` zix?Um+pNY2y#FX=(0GgK`+j~PADrixg)X0)iIYV$QJ(c$Khg6&KyRmZW6Fbfu4 zfZQi67-TF;+5cE&{{hQ%!Vp|NX!nu`nC8tkU)|%!cj^(IyxcYl=!iHgEc59D3Tx@ zLqk$;s1=@zUpoxB6%$AaV@}S@SIwfY<(NG!m?PUUJ|v$y$!sj+^kAnF_{00(+CEOO?F|SFbvEteNs2ZSCA`k<>%<` znbQj&Z*BhJLQ6DJmuB%XN1_5tC5sZ~+qHzm=&CYO(TXeCuK;~NIlANWU_jW-PYq!I zldzU_k|GuAqbN1BgtNnf-&FrsRMsG(+_ApaOzT0p$iZ>~^ocDWQq7cBH*G~%MF(Z8 zFEk;8@C@)Gvq{)EpYn4IjMt|nxLCHh)dM_`xm@Vj26*`uU>Z4PRFE}3UD(<=#Uq)XD8@@+`ph! zfKLdy2n=yjtNTMkURirC6`GhIxD=sId1l4}b|nWxoMI}yGM#$Oe;%2bm(146SNi0w4n^MLs24rLko<@MDxolVcft3+> zwU7Dji20I~EBXdz(w82REX9v7@d%_b9Fk4G=z|30+pIh828R`fuuR8=?5T>`CmJlA zcu=xEA;~Io)FJzj!Dq8DiCQ>-(hmOkHJ;C1Nl9sI67|jFyQGjr*AL2y<c8VyJ@=7Or6 zU2H8a!BA(*gf@bxR`8P!#3&Ik%qUyL!Ajq57f0?R0e2*JrvppqO`!s1i=%;p`eMnC zFv>{c_D#NNkH0<|Ar@L3uTgzuiOvr|%yMgL16u18@yics7N3SH;8GPjaW6iSvil zS$tocr0=PF zpn^wNONM$v0K!oKERZP(%#wu_zJtn1^$3d-I(7CaHG5>n5nVc}57%rXMUt(RY790} zo%XeA1-mC?PL2I0VUT1I#9HuW{@X{RGS7>_N*uTpfi*5$GDk8AA!d~{>a=zE9AY!+(a>(?aii+9+2uO;rxRgP zcKHtVun$?t+LN!uTeA4WjMPDwwBk#ro#*{pyPH)eTXAfs&kFePyYxMFGmK<2Ub9`W z4gAb;_JTe#`#?m|@neye80nMyjZqd05S(p_rl16$uqfA9>WMoua{QzoB<*&tUN>Ry zyedJih?(F{n8*u(i(5G2Q)R|^%~1$FQ)&;VxVX}<3+^hG%Dz1bqI!<~**n_g_Zq3% z(gFqqM1&dH7N^fsG`pXL2XdsQN>d|Z1_K^lV5nG<-eDtj@bK{T^z^b%5NHX5sly=a zaJ$p3E7h&!CJVS97W~uU+!0windI0S(;(votsk*9hFvqE(7%y|g;2>3y@l!=e0@BU z!~184-HLHgl_<{`OVr}fFyafBT-S{Tu;G6Q7~y{3u0Y!;Qf=WB3CklUksJSBlGCiC zV`c%BlnS?2l;YC(>P!z%4OJ>ju1hwT2`kpp{w|hA%^X`UPz|z+K$Sfoor|(PXR`y> zX$*bmKY(DN==TMp3{THK(@zR~27HMQ&Y&c7*n#%s;|8n`kjAij`TCO&yR)>#f%! zAoHma>{Q5itpjV_B*@Cn#Kbi@w_0nj%BfD8yc(H`dLZ5H|M%$SN(5=z&eawB7Tuz8 zxlGzKwO*Gs7$6xh#>eOJ^6WY)Yv|5x)b5hMsB{SXh7$#&q0#pB+s!!rYV}cDV>}s; zFxkL;r}_ES(G?RLHdQ?}2lTI;3MlK=rv81)yhR<7xS=+Fmm&;94B^TLpqE6G2WDI% z#+6b{X2o7aa)<}c9}jW>*z!lFI|nOMJKNyZRrD9G{&UZ5bZmTXZdUlFnY_x_htX4g zUuK);9~IzXv8LaA7*4=um?NF(5xf!&DIg^lCS0jOCrzNeeE}?g6i(5MP2KPNuk)_c zmj>3p4QMM7N_&YhRm2(IhTAv|B~r=*E|!>&xHZSk1*w0JyCLnt1=_S2i5exB_pDA< z#1EUhjv_o{hmSBfmHf-Q2+x*3bUO9yC0^}5P#diw5G z0xrDkGp)LHHj=GYkyd<#a`yeYlZWH-Yo2FP-wrfj^Af0VZc>x`sfu}SW->mWCZD{S z8z#iKtpht4)&wjp@OdQGEv<=$=M}!?I+Y~ONqy|E{nvI)}=Rl$#{S@-_*ZK#bK#V7t`5*w{PmLrassg8j zNxtwp=_jMQF95H_jHj8JykhMGe1s6Gd=xnT?}FIzz{MG|NfRe?+phq$KOx6^Yr@SN z1(~WME8&e`OiAFyhog|Nzi|wY(;?0r3lj=$S?_<^NVkj3 zK8LuqT$ggN^Srpde&c@Yg8ge7Q|>BF9t(gfg;IC?r|(z*wwgzQoY=#sXH+)uBoo>yNk6n#Hv=!hrWpl^+ zhrkMQV8a*we;2~7hRY@||4;a5P>2iJA3Y(`dkFkiRnuVPc#*DpXVFAyxon4U&{Y`QL} zqU>2;orAr-D9?`2{_)6w=-ggidqASJ*N8$Lc3p+?=G?htOXJ>!Y9=X3l+Iyiz^}s(c6UCbw`wiVIKAs?8Ikev#8n>iS_R;?#E>gNw zUX0TQM?*s+NQx1Z9})q%%Y}q|UY^IZ?}z^xE?beMODUmxTxEazeqIRXme^Qaf1A7; zkar|Pi5vqPgx}$C&!mv#FE+rb4p4&!*P=_7A^}mvH2TSiq|WlF8B&bOxF(&?Ajr#GvnTJ8m z*bRe(ib+;vCWdS7m?G`B#>pZYi=rl|*sI-eEXLg>{|;l_s~96NFM;7colqm^{8i{O_;!e>P8?ML(pTWS{}r#tZhpM} z(Vy4&2qO!$jt8Xth~fw)3D&HH!f=B7U(*0AXX&{lowj?K`#}dMQdZH;>tUlAXk~dp zX@r+$CJQt!VlBOgw(&Sw8$`&!*wcMxrpAtWsgIh-e`aZr{`-^6U(EYUZ;64Sv5|q% z>BxOAp8NW5`jASVEID>|9K2&9)kQ-4!lso$oq=m*L+*GidddU8#!lr`4v_`WQf*ThX*nvBz}>>?@Yq8Gr0M&|F)01d|( zax+Rhu(j1ubm|E@vG1_sgA4Hqc@2mN!9FEL3Gt{=?5e&!WuGrB=b!%!ak&wmN*co8 zet5X-lvYo3ADT@RLkP_93#T7)_%X%^p`AdaYTM1dDaj^;vl&c)Wue zKp~{X)nrsrGqbvGJ)3yhN`sU_#~RcN+Tv?HsC4Nizc!pa-twun&yHv8j`R* zs)F45pkOvkpmPIn5_oGo8>cCL{;`lh<;6{KhXQa6bg&B4@RSa2w$5|4716)TChN`8 z(ACs5%);L$)@iMCUF;(rMphf+U}8eK{a*F&FZ$O%`@Ckj)c$cMTewOP(48vtv{)Tv ztoR}hdzMW@O^uC%$p5EPhM+z>Hoq2<8rG9$~hL_%H#Oaw+I-Dy-Sl~g#jzIq#-o}q^1<=(usLvD zrJ7?T?tQrx0*hEwR229$W^yhE{55SrPL+O=fpu_>4;U_@f>|?Z}iUVeHoe^&v zaCVs*nuKu`f>#=vxWq(z5uKsx#qC#_O0P(bj{$7@eFhrQ1`>gvV z6+`bRbZ{5)|22Wg{Q(hxf_XSz%k^`+=HtL7JMtWs3n{okE2}iGMGR#hSt}jhyG6`G z8LboO`6Ko$RYGvmb;ieC z?FTu4-=t@DHO}Wb)-nBwZUfX_?%$J2c?fyud$y16nlhyZ%Wqrx_?zz8E^!MAayUg; zW(kYo$Lsm?VYB(eY0vDzf z94S&@zD0O_}lwa=o~B&kixTGH0iS)CUWY7Oq6gP(`~L2_;+ zb~XV?uj%=1s#|Eza8jkr&_?umhl9ke3V|+v4T7qi=L+5(_Q1x7{G*G&0|)+fq_=V- z1B@t(58on`1olJbbWTp^+uK)-`ERdpiQ@Gr%A$|m*>h*B6y!nyGkkkMnID+>f_Lvf zD8jf|)$N;ML1>r?NDVgTJ?Hh(H2TQJxw(j~r3wNwcJboIWXal8)1VD7YH`h`tW$xG zkEF$wnCVIcz&^II^nI~QQ&>I;l#aZy-8qB88*Rbq{S4N5yG{vJ51e3^ucF>)vyrIf zCY)i0Q1~nro2C!9P4nkCqvSjcMeJp?QK)M+f2}`X)%`(k>Jp-dQu!Nll<^g?_)!D{ zK4x(xh=g?DC`EV>dP}MO9g=~uvD7#g%TI((1p2b+lB$F4(IIixkcBldy_qF7$~mv` zwiWzOA@>9%f~AnDh&CYPmg3*2X!}H;Rsk`J6^6Jksy)JhmWJ!Pj?!Dv>6H!Nl7J_K zL_Ex0XrFA(Kc;6WGTplERn0b5C8u+olX!3E~*BT`54wrQL z)48Wz#Lk04Kl=0hX_P6h32BM>4Wdo z92ZOU_?86vT%$#=uD*wo;aoGvuAcx^$Gx1fvj~o<~Yp%jK!FjGhXMu%cQ5@kCcOn+WGXMt!5U@BO2r#|#kA8ApxZ>yFpQ z(_+v|u>@J~>)!$E_wAs!-H-SERSJ`ak5ixwM2Q2{?pc%`SkO|?5wvg{C%%}QGgmNk z!nEtLA;9rdZ|QFuCRBY=<@7i)JiK%c#7yVUc@%hjgZOg@ijM4g=o8fp@#zM65&`!q&GM4E$^h6O?d@ep zu!W1JFQeFUAfNJp*FTE@?D;L;fa|8bT=f?c-{VYF&T*ymyJShL#Hjyb2 z-*<3C51ex^2fZCU$+~1y=zU5N9wFKN&&I6r0B`NuJRo@G*rk7;cgHWU_%C%CsZy4> zhvErqr1cC;4)ubaac$$3_f~KpDuUGEphE;S5dRbt10p5#kEXSWg=_cR* zQ({2&kTD!x*EV347cKwq1=?aZ{lNqKyp zn;y5H)CnNWwvU+Z$+ApOnEYpvL4(`O1Dqif2BM`n6NL<=U--w$(UrC04H?2df61(# zvq#XwrXvx_`T2D)*}e}r+wQ<)kWW?iyUpd@!rcN{zS*rDMgN;A@0T7Qs*=a*EYSw- z#%36+f}ZL!W_Iq}N^dHgyNU{(P4*^fEYAWwh3e1>c&r*I4?CzTNi6%tR)W2j{%{OR zx)@2y;4QJP-0l_)AOn^O5=ywuUG;z8P|{BhGPwOd&+rKuGw=(W6h^{#Tw*4~^$m&1 zuQr6S!G!HYYY;LKia)PDBXvtP!jpPo}mRjyJD6cwBF20n+;`d77MegSRA&FiXBGKazEH2A zW!B%1V-c-NLxfiM`0qIVb_hNmpklAomcCXwThWG^3briuATx)_R%93uF=`^u%7vMO z6_K~{;r21f_-c0&x&qg4r{Po}GeGFvDA~4e3U4pbU zNOug~-JQ~%(kb2F<9p7zzW;O0%$@t*YpuQZw|qaP@_9?V+{4V!n%_UFJwiX-Y#vv5 zv;ZhiFclQbI}Ir;N$^^VoPfQB1UTMA$^G!mM$SqxX_c zr9uOgjn^>tI}MFT$6RidAAf6iKK&i2N*F`bzw;P(iUir@YL?w$)CHcVQiHm=UH`ku z+v$)cW4GX1PN@)}&h$2K;VCLR^Up?|BAHT4-2`uP3T$N0t$${i&4_U9{tmfbZisFu zG{&r4-75(w)w%L#>(GZ_=l-(mo5BOZ*H6#2hfy!6F_O^aUZvucAVa2XJkD%iqadfJ z=pNc*ZTsfO?Bt)Sg5F2-QD#)JlDy^>#dws6n-`y6sD zlbU~X)_xKwcV#xEM9x_35OZx}VPXTe+D`mT@2|7#?bO{+xW*peSLcu@KWfyuBa&ZI zG&#Awy@Qu+aog^9*^ALY?095zy0UM<^1GT{LJ5>9o4(ex$S1&L&L7KdD*gZ`%W9F( zB)kx3CRBnA*k_t=*N=wJZQ`y_P;QfIPpu_-ZMY07y1WAt%a%B-Urm@0iF}lN_+*N& z#?JbBLG95-4t3j72CV@)M>xBt|Dd__nED0@!xKXFf5h$Ifn{jf8 z?-$b0>w27%f*A=>@huC272T0n9EFj8C)2 zKcBR?pAwS}4|=J0{BDU~r@ZbxDF$ie5>^M+J31nxf1sk0cR3~mF0Cdk2Cm`>cjATW z6lh$sl1%_|5l;_sVyYA4y59YwiQqrVf$=|>RHK+IHLYe*u+u*fg1_FAZe(%Dkp8*X zL+!zdK+vS3V=5wa7ZUm|o8W31DZCs8s^%fL)??yU6Ph&r?JgyF_j>$V$H;j`ZwVC6 zcZ1=jG-PNl`@(VMqRFbOs~sF2|K8qWAR_~9ev}OwfBgbpz}8d7Qds{c5ezUF0j$m7 z^Q-e;3WrHJB&vSbt_Mv}tJ~)3@vyx#v{KLH20@Q|0@;#(wGpE|?-3DOR@UT?)YD3< z;8PA(k`8poTOMf|4CqoIKjB|DRuQ=BC+%v#W+%(A8V==oE%-LIdLRjs2G zOrKcx8x)q5iWc)In}u-d_oV!?+CX^!jw}>IPQ!M$rYKyP$Cd=XqLQ12hJ~5oV)+iS zQ^?YMKV60}NCtwiuzS1C)yfrmS?w-#8m}6owH0}usE&0gAIBB91#R2lOYFnV{Q8(P z`EI58dvK^gt9t{D$MQ}1_t`k-(G_)_jw1>Xu@&U+vovj9kMyq$Q4#=61}?uMacL~F zNF|0i1(`3^SZK)%6)@dlYv4bL*7c|m|9hBNZ2^zJyW+0^q;tLh_8gz}e4X|7kmYwd zYV^8I*m*y0=XZB**N#s?@%nkn(qT{D$nARi-B>NtwZb8m_OT8i~ zZjX_REk|ouPL(Ew+f?f#J+3js_a{%pDY!x@2!0jQVrt2BK*wtiGK*h4CNB$LI73o; zY1JGVN%#N)HgkQ7JQ?``IZkRmbysZPU%%Pm#J; zWS_ugJr%KjTp4FNV{Hi&+>^$d(LZ)ap15r-1!2X7(Ns=01yU6bAy)b2S%x8D(Ngb* z=0~gQP%u!!2~yX~)KR}Rb+2azOn-}(DbY~m`nW4%*;ZLZ7@jlonRG>aRMr{C$|*A& zjwd29fH+VA+1GQni6Pot|4MS<<^PxUO`nQ!>HJ(owlG0C^ULgueu`Y&@HAc|#H{&392g}G_!95x3dTXv9& zU;I3cs^z2$2ikP^0cwq;7OK8(&-ZTCE~tuDR*~T>+UcYF94nuxn#!!Ku1g-F

Ym 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 z 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%0DfLRXcjMnU}c{{-80GiR$6? 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 Y8`PTPpv$_7H2?qr07*qoM6N<$f=TAb>i_@% literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-set-layout2.png b/src/designer/src/designer/doc/images/designer-set-layout2.png new file mode 100644 index 0000000000000000000000000000000000000000..df23859d8da976d34dd8cc488ee34c31be14bb9e GIT binary patch literal 7834 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 NIvNJ*A0IxA_&<6|EtdcQ literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-splitter-layout.png b/src/designer/src/designer/doc/images/designer-splitter-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d8d7fce2143da73c553c3674f3e326b35fafa2 GIT binary patch literal 81182 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 Q+yic^idqU)a@L{$3#{J3>i_@% literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-stylesheet-options.png b/src/designer/src/designer/doc/images/designer-stylesheet-options.png new file mode 100644 index 0000000000000000000000000000000000000000..98a4e5d6797c13d86de1c27a811b39dd1ba42c3a GIT binary patch literal 13542 zcmbumWmsHIn=OpHbmLBUW5Hd6(@2m&AXsoGxVvjZaDoJPLa^W#Bsjr6IKhGwB)FXB zd1ubdnVIi9Kh6*KRlD{scGWHGUbPCM)LzTuVo_irARyqrQh=%>ARyYof1EIo;5~$U zb9e{{e3h@DQg6K$kFwGAiRAAF@NpG4m~J{9{nkDTGWJ7bxf$OS_7j*M5{H^{!rE=w ztW9GWY+|C>wTQ4UL)uA049sL|Lc)EhsDoL9x*n`Nt}ZVxJx=h1Zcnbf#hV3RzI-V- z=6B+4d16WAgAtPFrx45-1i}CWgFdHAK_~YbWq!By5*^WK99)O-&U1z9H5WL3>^A6o zbSEP!b&A)#&}6&(yKk9 zGHXiNSgMIMmW)+_*OQ`yTClH&pSh$BoYg(H_impaULQ^0+>WR29(eB^R89#RNR4{) zH|*G2#Ev4=^|A5M;fS$3iA5KEHA{VBwQ`Heq#(cR#lT{T8HG^2nQ5~@g`m)GqxBMm ze5A}&cUHt}N&2nO5I%#U*rDUV*G)7R7R5zYff0+NJ{QKROY(IU;bp2fT~v44q1W%& zaD}L3*7x%3~Z7uT*2i z7z1yhmq98OT-=dDJa(_iMmTo31kf!%)Y$jMDS9Z>?r@3JEq{$Uqnq81EEe*zCPLOA z_W#uRpgyF;(MK0m_lSAH`Dzb1&?iM7u5+azTYiW2+v*Ik=cYF)IAeXx(zmWd@&bjS z;!WNlnV8a-!KJT6WH?$E0`$R)d0vrH&h#@NIVNsjd5weot(b{m{w61zDwAF+{TyZy z@xtW%`{ftl!6rfClcVGySivJ|My6yhdl)aGyw^u30hU>7zYQ2PLtcR=y^;hwbYU8U z4%&a#9RoyDCNus{se~JXV>%j2G}a_UhXSquU9PdK1Hf||X#Nm7WZ)`vJ&&0l`7XWR z;17+?%y(mppHz1&k=0S!j2%CYTs2&XTLe18E22FQhptJhfN?9`5!GtUgf2#dA!-Ab zIO6*T#UDTWtG}66#e&RwDoW8S5&xjjmDB|$J}2}`$+PPot%tYGj)k0THU56;tl?vr2s}Ra5z2N zr;Km*H^bTGNps`zMjPZ!#(v9Li8XZ*17l=V=fhp;M$2ZRcb^&m#?YL6p;`d{DD>8e zzx7-S@F~zRu+V4x9W~S9Kn>vom@X&GuZUAMmwx{v6B{OAJ$_ZSD%PoYW}Cf0xt8sg z_`!%?hU}X34-^4td5^DGrRU3L_AoRO?#o-^_$s$v+<)07BvCFbX951=Q3E*SE#WK6>r8C>EPgOF7<1l-c32 z`KK_QnI#?_Rn&vK+DJVjx5h10b+?3SMc786{Ezq=ntMgVRp!%_GzJs~g!%ay&_Gal zM?;&di#_5i)^EY%aVF>#@xuFTNQ#2FG;SH%Q-p}5#$5e_>-!IpsWJBqcf6MNO1Zn) z;yxC)6U8E)JUdSl?oS3w?v0JpI4T3=d;(w^==7_7G&2ob{Aj=G2VD`aBXyV(J$7O` zb33MZl7AX~RSt2dwdwNmWKAb-?9GA!e@ncL^wqAjK?>bVW_gby#5no$!AF%I9|4Sg z{iW(j-}(eOsTm#E%*8M&G=kcWZ_ZPvR)m4|<$>kQr#JE$xC?XMM-m!CWyj(Mh(NLt z7HI23)q{oWEsl}MwS0xb6)1neFMElqa=3c2ahi{v(Kh3?njnp8M0@L_j(a=zeruun zK2F^2*K=VoG%Q>v8;h9KGzexR-4sULNnepAg;CJvo>-#1Ur6!0gldm~zutbPizM+O zL+CBU(F2ENhY!0(EWUK@RUQJHm4~hr+00? z_RA~ix2fNYA?~H^Pf?=$m%CSN(3tpa6;KipD8Q)NuY~w95Uhc#Y2&-I{c&k4+(Qv@ z`SZi1_C4julL1y$4#QGwssi0p8V7H~=6uOhZ!>;q`_vt9&7?67P_)8#grAHd#fRHp zb`bX_P*~Bx>f_Gvn~Z9?;yLnyt^UrpU@b6&qZc7@caq~2yMKW5HtHJWXBh!HDnNUb z#2}cgL(HX_JQ|*GFk10U41u``$$+F??z==e7*9(PA=zp5^j7iWr{rJjzUO?19A6f@ zk|xfnh+XrtO$9(c()F)BL0Nsc3$MEK1y?hN^@=^UU%GQ(!NQF8bT*6V1cgFNJKCme zBtc2_lPMiizYKP35KUUb6oO|6wdivcmW=r5o(e((f7ZHPg|`~Ix>xZwPzO#np3MlU z&hSxGKl;S%e0Y=bj*c%EcYcPZtc;hX;d1>mOZG!}4(uzS8~}Xwn~Tm8JPY0v@w)1a z@Og>{&g5ymLC&J{733i(8nyyfHT1O3wVbH?1}3xAC-kenfQWb6P7?kCPiYRx6pepz z*s53y?Tk2d-1RqIs_V^jXKFOW5R>fr6l*YyuK=Kkwbh9u1F7#Fl*`^4St9|HHm>2wz_9zK@Vd9$KvOg!tyhTLg%J?FQ2A>L(4MmK0ps<{V z?Z=W^j8Uc@ettP?aoSI}HwmBdMWVpnq|%1(UW8>4lTHm`B&P4taPy)~cWT_0^?Ux7 z0xWcvC4x9+=|ubqeyP)q{5?m{mXMg(LdJvO=Ke$j&SUH=UPP6x-vYt1)hQB5b$7H? zREP6UJ!RCYgvU{!vz2t-V5@3v+MA$Y1*C^KvF$-6WT?Z$$tt#e2IGv=Nl=5GDb}c; z-e#exx??Gqx8V)dHjL7TFN(Z)xu#iCDb+M+@csCb9pat_&K_dZG-`e-^l!{nlJa3< zuV%W&ncO^oPSbT;dJco5Vy#1jn~WrpU|&(rR|3bar?a+K`{LO3$RvAcY(?jJ4zBeE zmNrW&^Yz!d({9lv-|1DdF*C;dZN#Z7AG|(ZCMRZ46sp_e#07Ltui-%HvA~?{MABo? z*x(#EpfL0WotTzfK_#tZlR(B>oSI*;6Xlgt853Q}}q0Z)d z(+MTGTZFDbRZ%KzBHY}DvacC@!6e8AUF5JyDjXL+ck}I}UOVh`xtH70MTE8rs|t~l z2AoXh6JDw22nx~R$omwhk3@auJM!i3{B;u^*FPbnoxPg9>9wFD{NADM_y_e zS<*UIxm8sZDsb-+Y1y`5XX4vKompuTM<{^jYjYq%-R=d;az0GiA{>gqY@^2AquxKN zx;Va#y}(?=9_L`3E$0(U*K+)&vDa(JMmHsDJE0I;o_;`4%F*5Sd92PeFfD3lf92z6 z%3o}+euJdBw`PQgn7Y-F&`ipL2Aei#4Srkuj+UZ;1KURP*GO!y${YJQ7cHmxa7(QB zm8a&i@*d2yE(^IAzZ5pi%9UzTQa!vzCxl^O=bu-cg(uW)NT;JhUqEmD$|>ggE!C^j zhy33X`pU+i$aT616l6OG6lqfz>!}nUFe#?Rf8@}lYkliGlfU>q;$%WPE0;L~&2VMOtceyH)SfhZ z@YU$cnAs-^2R);x)cy|65CnDZx;7`zv@*)@LHjfH5|j$rw;QqdMsu`<#}}C;cl!3@ z-mNsQ?kfTFts`@dPby0;5tnUyi)a$IompQhg>|*<3#xvg*rzxzvJ}p0qRDoy8yZpV3FrHu#)n-9xVZrSh0=r6`C&;Bl0}i2&|T6I0e4)jhy+p69=tfwWv{_8 zfez1|CwF^&KmCD`JeNHy@yGYSR?kA~dv~j&?s(=@hhr0NgQ;}MG6@urL&{J{3^Y)^ zrTacvnSTX1--_%1dWa5HxOG|uJoT(tBR;)!JE)9QI2uVrTv<|fTr0e+tuo8bXzV?MxTbqSJBCYWx2 z7juBll6F-f(s9+i{ZfAaf%csBiL4L**GD=dF}M{^jNml8lo`5Vt-!;T5{cn2S6ABZ^bx|~7}$fFEtZZUVnMEv}LO64r< z0}rdEl?&hotp3XkL7pl0kB&}7L2~XdL;(9j`q*j+-sX86&AVJO52qwi##u8PS+qZ` zcmqH8M~TJX?l-?&5v`e`W%g-|$mZ)N$nwcp32$qA6)vj+{krs+o*!2A4jFn+bw`nh z!&)O(DvVw#tg6-Yq&aQTsC654h+{BnG$oN!;u~0doQ>J~qj2Vl0&p&8+aHy{Tr^B& zU%Ig9QCH7y^}aK+*q%>mfb<`v1nhmk`1zV2vXY%bw4?4!J=ls)V)z;r(b9&2KPq%pmbRC3nGZ~%sF{FOUPiJKdu!GWy{HYpsq5TZvSMZlo@BBQ1d;hOL^RbnbX#C@cDc-KV!45wT#a!Y%k|>q@496sMMCF? zY{<~JSh;q{LOjZGVsZxRKT(k}BiY}b*M>^Z9+29_cV1N9E|c!!!h3_q_0O#0>rpSI1WKxrvK zvR4g5n3W@}nOAIv56k-e4fI6rO{gA=yxaN-jaY504HSEJ!je0bvcDzp{g%Q&pnOxn zxpSRjWano|?DuKPH(f4a9H=1l#Mdko!#G8()S8Ev{P@6l1KdFUxx+T2)){ia$B{l{ zPGmiT5M}-5!i{TfVkYx6+Gj?wO?_wC53UfMqM zOJyglZ!G)tk)iL(h%#~3cay-PB*d&%8po5A*}1Nh&Yz8)@c3KI?ljO)E{L@_2Y%0g zc-=4<&J!g=H+~?iYNdeL-IjoL(434S=4XNPSKCgJ`$!3Mhe4?8I%hLclOPt2)PJgj zqRc_$&5BC~AJk@Cp;}z*q38YHQ+C$l+|P^o$j{WiUw-ggWnpAu0~PfZo^WJovC>E- zzLBo}Er*kIOnE-iIE@e-TVk;a*$XwR+4X*In!jdu^Kl0n*+mDhZ~7Ypm2P?t;wgn` zZY!wJfu<;s*v(z-G4t+E7_AkqC-fwcYFuyq-&dowUO7I6^I-X=55#C<)m$Pim-;KC zu6Lo1clbFSXAK^dMhC|yLeiSQO0~o2dVkI?=;XbSL!TZXR|#22lh$aa-r7WB65Jb! z4o-U&t+C`KE|I3;WLGFG@XMiyjQZq47}Fb1Bx7Z==IB=Oq{@@RT3t+BRv?iX`o~_x zN70Kk8C#R`5KTnBMNp$v@J(wVrC46a7o+TSLw?gXZH5{gXBpFX+R-i^eite&q5Ra= z;OuE3t1!u;RZV8tJ!e8B23X-n>Yb%<|Hzq{p(a_bp|iABc$C}rOdRX`@n&TeWI_f^^K`Bdhp>U~?o(~|wzW9jH#lU{Ds@{ay?jHPrmev zVNiAO^vS`(P5~xQsaFd5as~wakpy%7mNjxA)UIZ00tbCvb~OYPM?H4j1HY;VxyFua zOL}cV+KIO3dN9w7O%Szz@3chEq=BHH%DXD;SA!4~=X>s1^VhvtQ2lhxqGr?|8#hNx zF~j57g5hMJhQt$r6yD8&;gjX8ENF_O5Z=u% z5l6jC6I-u5P?G&^ew!vXtQrCv!GgY!@7{$(RU%QpTVtP(py-SiZR-*>Fk9u1sg0<5 z{6S_AEZ*IlPJ7>Fd|BUU!j&0=^XoOw=_&yCBF1`n@r29 zmOp9NfzY2QiCV!#5FEHjr$CVzOp$Bk1RjwyqolH2pfVQFLwuC~YUG<1AaWjH+=m7} ziFxg^f4c10 zVSdnery($zDuA@OK2@0ifo>f*1Y?Wat@cqOoMrPnC#F72kZ72~*jh)^jwcbLi0M&8Mn0 z2gg+|dU)rOb%Zcb5=91y9gP(OX8gJc<>91XPU4%(-i($JEoZf?>(0n0TypbOQ)|L} z1;^8O->jFka?8_H2g+!+F(Km!i3Iq-ka*b-kG)<9(6h3>52S`|KK(fWDIpDrI+D;ZNIL^F^WIqYOG*JdAsL65*zF?Y=jS)E2W}f)TFSiO@JMQy028#ivpLIMCo1C!;rDspuxEgs zKGlWlM~^Tt1_5$Rk8AyK()cbxLz|t;35DA{fj9RtZN22}VJ)eZ-@v6=W_>hDBn)q8 z>#mf}ambgFM!RNH+uCj@OPM$1y7<6v>FXxL(SahCF*z8>M8fF7*KB5uJey&|u3k98 zU!9^Bm@Q|V=@R}1DOc5|uYF_eM|R1FA6_twf=vxSn)}nQa`vE}B}pXMvXaAKIqgzv zQj+wf@m@!wOc1J}{&=lvRrll(w_Xv9f%qE+m(r1eMlX~fzXiD+5NK(7K$34Owqof& zB$dbic~G3wsH(KP%fUcN!Ggi$J}xT|2GnO{@#pn+$3&RFPX(KTJIlDQxvr5s!eBRd z4G6%c5*GyEXErG)fHWPSSNsoo+sGQ1pj3b60t4e?h~4{=-220U=DzzHf9KellxN9YTWiyhH+V9lQMa<&Yb`%*_$=y^O%6 zoY(~4u`n@Nt+cuyUX9w__J1)DO-yoF{%8~R#Wibxa-$D>=*i947<4OUE)Rd{!$tY_ za2hoIA2&BQD=Ri27y+omLBh})@V8=qLl(705f_An z2(hsAhRzn+YL6fO?0CPI)YthWj{eW9H98(-&kaeGe{Lu3``#)5F#qmNd}ks;t|5{^ zv8_m5s6$0fL{Q!2Ocg`M!i2Q6V^5%|qG*YV0eJ+-UH*uk;vm2!Win7wb^cJ%jTeOh zA+oIw_f7E`25(IZ3UdKmhyhp?RslT`#bog;utQuBfs~y!Sgd`Pfv}01_ii%uS%f@U zvW2NdU%~N0_|!LY8BWX*-J>QJ%85VvHI<#nMS;Fvkh!Lqpznk=GRk0OKJr;4&K}Wt z^XMTY=aTv)wuO;O?m0XFBGkB#CyV=vuL#uLM?{hcW*ZWXM-3RVFe~weCmWuXaS$w< zNo&-}II9rg%Ze0DrZE!AaIT=pLrJ-rp2{5KQ$d3a3BstDpgGOxB`lEt7Gd_t?1ZkW z`d$@!@mYrlBgv=MPNZfZlw$~_rZ`82cw<=Y!$&A!-t>Pl{z8NVH4akXm;j4KVi7kv zp{=d=3TlBdTp?Wd2>8#>Fy3x}&O8Jeh7+SD`iIC3j>)wYaRI?+*olHm;rHdfyOh)+ zMz*KWvE7hUm!12OvCH;v!V+ms-&S6i@L{`u_0`y_>=Mjk^;kM}U7uAM}7)TRzJrZnNZS_NTz#E0Dv=)GCM*6NQvA^-Un z)zZ6-91|v)K%*xod2#%01k_YVo4+!z=)r+WGLS)JYhz2;0X6N6 zC6sE$v0joWETS-?1%Zsqzffg?JmdK!GE-%VftGCjw?B};)l zpDQ+SVYF)-+)d?kt5e=d<;KMXf7F6u9c@ghQs&g}*Q31PQq)Zj_C)b?{;D5%8vHg} zX+6Rh?lmvKB3vaDaE;WNJmFxSGDjti`t z%>=_|h6Z5%V$Y|d3eyUhS8}LjMg?xhk;q9D7aa?t!o5lGkI}jM?C>c*v`y(O=nZ!p z3c|mhl4H;JcglBe=s3b^g%IeV6K?(NBhD;lxM30D@WI0V&o2&7{3zB{<@& zF{Xnlrd@Ce{Ty3q9wPT|fw%C(5@{D>p0D1=HW(Y6&Pz;``@u=h3H&_YK85gawdC*! zDMp5S?q97@!vB=W6PgG>g%Yb7N`F+eWWMJhgrg7&tm~hweh#xF?K?Ui2;)@yH4e_w zfyS~89!$~#71DtzEbKED67s?F#k=ORZH9w;pPk{%DbMBusfA6`k%*p{}qSBq9Nb2N% zqEmwy)Tyi@if=MN<+v6kUFB;Ex#ng*mpPb3ONP>nR2Jl$FX$ha+@p}h%Jh(J%7LQX zptO_p;SVFEzrix^xExxDWPTOI`+WB=JE8m0wJPn!`Dtngm0X%@AtTd>L6h}4mrTb& z(&^WcEu>x7cst(o8rMpnG2hEZnI)O36FOuO%Bewg*kiM&f#JL^+OJZP;kl+@s3KGe z!Uu}{L8%F%=Lr-4;7zm8ri5>XMvIpAy<{qVBz4k~PVk>V_}%+uL9OV90SwtngUXh~l)HV>zN`2AlZd-DJgJuxyf~r(st!rF7A(;9j zh*w{QmRJc*%9OU(oFlniHVlJO-V#R@Q=?-NXXCFg7%CTIPhW!B8S8Kkpb#|2KH#paS%FJdJ)|9 zqCgBd_xjJ^vdi*cfac$C{{xRwcwk|-Zcghn-^mvmT6K)_E_ububzyhJ>Pp9a(qLxR+C6ZM_pTmy>`Qeu`v8Hw~1#t_CW z#iwk6R>du$A)Y)uFx{)m&946E$Mkf9J#ph84n zL6z4(qhmC04J5uAfkg8jKMMduyo=$VIT$z0G9BE3CkR3bAnHqJA%n) zZvlU@_sd%$I*Rjr@f=1wz_NOT5S1#MOs`r<^PeRAL{ZOYg;_t#iP6jhXv=W6%wl>^@TKRo~69gPT4(+z_4ZhZD&7tLKVeXb=)Ac#K0 zLPsb$Jht*`QGwq(c<)J4)ju@)`gj-{x&Dsvo&MSQs$_v9D``P-yh8&%LFf9pS$l?e5fDj7H{y z9mulqKne@zRbT%~3S}GdL4b3!w>Dr?6G7$EU}d2mYTdT)rP~}$ zUyuS`y^fP;ZJ2IHo~=H46gqU7cRwI8x3Vg1d43N6^C=V*DxdX}jzHIw;=s#Tk*A{{ zDRnb8{DA_j2bwlE>aX#>s%O3Jx_g8>=@{hlc%MJ~vb?;i70u`>x@Du)?+CZL(-y6F(KRrm7@`hVI=S@;nd&YDU`=td%Lj zrWxHQG0I8jU(>}vNZWW2XtiOxh`){1gvsxipLOUSqwi|`7Q@fwb0h*i{&vSTur6y? zq`U4-$?7eDY64RZwOSMSC$KWcrl*_7Yi379bYf7p5?A1y)fNUQE@5;$`$SAYP;WI~ zBG%Sq(CReh_&Y{oCvLHI_NAz3mYUp7p@vN)>AA|E^{Ag)kqW}NC3@#%R}xJ~KmlJG zN@}<_5ECmZv?)~fPYZ1YtM@y-A7aFOJs!@6>pzfk-Z~g^IUX0~<@xUI?X^4}qCgL~ zVY}N`n8bEw97*dqaO==RU0hr~{vcgy8WGw*AaVJsb#{tBR$<%f_G_ZpP$FBr<9KeA z75!f8WT{DN5gi!08l>>ZFq=LH-@;~{;v_H=0D=#Z6SJ3gvX<`cM^0Ht1+ur9{Sa~9rLbpl#+(H0^l`y$x7;v{dk4!iaio_7oBuSWvrBPTJ% zAV3mS01I{BoUapom<0H~wO<{4CS1Grdid&pm)hJcBrYQ-cYD0DI!uDAS8KhyHyt?< zWKmZRAI1jtxE@=qt9>)kp?Y#udEDN-9ld=Hp`&wkaS8M0;pIJEZgIZY&0$idDMW;q zHnAQQh_0?~e!l9%eWU$Sqd2I`s8~He3J!5UP@y^&R#t==>!hCW4BrO+Przk?2U%FC zHngzjb$?IZI)gJhY(Bdgi05UvCV08H$~|)#)8cHbKA7TeEh32$LJvyHPPy6bzSWZt zz!z=VUHxPs^;k}8aMoUL>*h8n?mksqA9l`sidcpzSaotaQIm%F!_n;_)L-tSxW=clsBbQ(*NC0e+8o)ZdiyTy#y978l#Yh_kLz-?G{8O1auMsj!8X)cNRF_9k$=npSt)=W(VWp>0+5 zxWfOjOYm@PG1OP!hd*SozMsWB)7PY$U+etl2~JcSWqReS+sd`k_xZO@sM~K#49`_P zZVNYU^;M}eT;rkKX{n;$8XoulTA*KDZ*`XoJmzK7qUGdl(Es9GsgVv^t%A=+a1qW& zmcX@!)$PTqV%6Gf6Qza|r3MTG3mI(#TJFO2zPA#H*wr_j&7S_Fq33}LzK|(fO)^2Y z^)bqMMuMy@PoBZyRJ7t*{F-Ts@*6`WtbZ2wuKgCNPd+JE+$2qWyF&a_(ql2akoD#5 zQM%9G#W&X9Bq%Fq-frHL;2~k4-kc6`pxs#jIyydAZ}e3{B)?YpFew~D5oOF%#E*)^ zO(Mmeo5W3CHe9RdzJHY0b{DPpoO{rtq+L7r4{+RU2u)U{SzyvE!nLjVAsuFfN`zBG zHn(jNJ9{q&3&Jqxg;x3OB@66s&h2kD?H2ytOR4*NN?AKSmaV_Ey}Vk6*w%k&I2Twr zze^Xel$4xo;qP>ck^`p*Z7#GWGHimDtMfX2y)18q0J@)5b&BH+isN(2<4>npOAiR= zcg*ko-Mrg~u^DrPYJV?G1F~ORc{c!~ETl%6w2Em3OmV#^DR}YWGb4rX<1QlxQ(A7r zwr|sKZi{Ahhn5J8?65jp{#2Yy{Qb~hR8;iS)m)(#Dc(ZypZ%yetf*J4O~j~r^%Jn^ zrf)X4`owAJo7d0RCoO@wVl)Hbl) zGi@3~;PMkLD&jL}Odh!M@Ntgkiur8jmcM`hzQBDI{LD;hvHqt5%7dABcvulwWhCT0 zaEIG0$ zlwvBy)A!DKr<#ohInSJRYAxPz$~M1t)S?AfWsFfmeEUiWo~Kwi5MX{V3W*7)1EH_e z3FKy2CJYdU@{;~^@Rz@C5-|2_UDB!}9R&iAyjc8MoX<@yex1Y-n%7cwlROvT7wddmAsN;vY;TBmyo?C1fhf?N@MWfBIqAb> z+ceb#r=7uk`-{n#HIlP5NoY2KT#Oe(>1&$QN0XeqO?7oxEB~nqOy)KBcmLK5|9Y3@ zU|&-hZn3mZD*ck#2-;0Ub`}}9Pr$X_4IE-E{e3Ze?oDLRlZ|!s{ zBuOaG$XSU6lN68&gT7#jyGtAlN8w?LI}&kqIq47+&-BwuaEb`WxElqs0=S<=Fvjp)1t;P=p4Vb2%|%RY^4Lj$WEeuVgVU3NMECrVBOmXM8uDp z!S%)?^y*&ocZ+{uX^tFKV)hX*ryuaM7c2~js?2d@d$US{Q~}vZgCV70h1c01mV;2P zU&^0(HXc`Al5!Y{$8&b3Ej%YuDu{rF#P6niQmoDI@_5zndLl=I310%Y<5SrOMk?>K zKa1PgOk-IhXXAfaqoOcu8?2=r!uL9UR~KNwYAy3-l4fKo5F=F(ddRbh_$1p(@TQ(f^a}e z1%M55PKpq7>`I$nLakd%nF6aWq{GNB`sKDLX5?TBr5KBFku{nXIx=W-p^|<)?CJ2P zb#I``DyaAUuU)QCc`Zz zgVauD1?3QoLP#R5-?YKYO-%&GN$6ny_c=1dw2eBv*WN6+7 zeBqkj=*wh{3TVuhdr7}ye&!0Qn9A#tXX&_eWtub~0n47>Gu?~Akobe5sv7p50Df$N zXUS|AZ)M<`Tw^?S_h}NKmO*=(;RQg$aTryumt z>xN-Fqh;l0l(ZRLc#on4lz#rS-rt$`myM;m)w75>`)`WI|F{MIKP2pE_y0$^gAqp5 zO>m@xx=N$|g2?;$Ujhjcq-En-h#-feU^i0EdkWg20N3|GX-jT&tee~(bM;LBrc@xS zhOH4#-*=^lDy=KfAp*T19ucruD){!fAQaZS%1tHOivt9QuGtd;f|-y~3}9*`WQN$U zxvv4dD1h%-@XhhyVV`Y3;hY$^Oz>MFGlbYvmtRF#a4mnU1x~c#%%!(EmXWm1DLDVuILz8qGXeB%xp|!NEzz^`gGB%l_;FZ+^Ss+vI3^1T`sbhrZlej_|3TOf{$V*D u^xdU_m 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 eM@SA4(fVd61PmO+m&%@jzvl6(C=Q@2e*jv>Kr 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-tab-order-tool.png b/src/designer/src/designer/doc/images/designer-tab-order-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..91992653034f5279d195505278d0d0e4c694f382 GIT binary patch literal 1868 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-widget-final.png b/src/designer/src/designer/doc/images/designer-widget-final.png new file mode 100644 index 0000000000000000000000000000000000000000..a1fd3d3f0f4640aae590087dd3435c6f62fe6b19 GIT binary patch literal 8498 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-widget-initial.png b/src/designer/src/designer/doc/images/designer-widget-initial.png new file mode 100644 index 0000000000000000000000000000000000000000..e3e8f6fe135fce63e78480628f8f4e02e5893558 GIT binary patch literal 14010 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

Sl^m>1#^nL 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-selecting-widgets.png b/src/designer/src/designer/doc/images/designer-selecting-widgets.png new file mode 100644 index 0000000000000000000000000000000000000000..151fda0dd087b635683565af67674cb058dcbd1f GIT binary patch literal 8080 zcmYM3byQUC_x6Vx1`r%TQVVc|a(?+CVZ!A_Z->w0cl$d%YdTIvS)JV<(eJroFi!uN=8J+IvXR!-we}1lJb83j z*kOiaPpzG>@iydB$tmT3S2S{SEoQspLU}1E3G_7XwEr1eLU^PQ7!nfQrDU>|^}i4M z;L?BXKcqr3(Z2>w&&kV^k6d53_6!Q4r)&=kTQojw4jkg%U0!!@wZ_7E-ELEsN8o}$ z3Wx_Q*;S-^dD@7<7n|(Iw>1z5X94RXlv~6%jl>hG3u0CnOoA@7I`Hbukbm8VXU@?y zv>zI?_}BL5@7!C)I4a8Tfi%ATuUB|uvX8Zhs1k&QcqFJx`1PF%ePpx-oa2(T3BxN# zWI`>kdzCajYDqR&yy?(;=gF_4%73Ef_W4f*VG>=U`j{rwIh$xK%$Ur!3(9ORmnmu^ z(7DOd`oU6RvON*6%R0M}#TKz4NKx#!$bUFi=_l|_jUiHjx2l!Jyo8pX9G9P8yfsr{ zO8$pD*5O1AK^}9kK>gTr_36Wvc+2qLBI?tT zc^Nb4cNGU3*V@$5Pd$IHH24h5%cFN^eMN-aKG4`#-_7ML+U@mr_bf&l`e&~hWIu*b z3{(WNbJJm)(7Jc}8yKSJEUlV&ezU(a{(K_F`FrsyJPUXQhE&;c=^~1R8{_k7$OaT+ z(_^iGX!XsOVY+Rl1?=@gv|Z@=2p4pN_|u8!Y5?rca_o^A zSKp1EtZ^r1Md(wr;R644&&O-U^

text diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-3-width.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-3-width.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b852b89f595b232949b04ad320c4f431a5af3c6f GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^7C@}Q2qYMqwOKTPRFtQSV@L(#(*E6?4GBCg{|$AH zs9uWbFt&8KAj+ccI45w2mq4=IjgHp-Y`c9+JoG%DSWkAnx$-Ul38mYzvnTz!C3E;m vanID^la@8S>^1k(OvLry#Z~_MyZ%38|J?72{vu&EAlp1${an^LB{Ts5gGMv8 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-4-td-width.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-4-td-width.htm new file mode 100644 index 0000000..9106572 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-4-td-width.htm @@ -0,0 +1,10 @@ + + + + + + + + + +
+
aaa +
+ bbb bbb bbb bbb bbb bbb bbb bbb bbb bbb \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-4-td-width.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/table-4-td-width.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c1617f85bfe4e3936f3319ebab2fb8554c32362d GIT binary patch literal 202 zcmV;*05$)KP)PD5QWdi3OB%-FH@;hYP>Tcu7|IOl{oaO~`y zUz(-+*mthf^$7DztknzG=N4>)`L+J^zk8y!dw9*jzJ`O~BCjg1tN;K207*qoM6N<$ Eg5PLcKL7v# literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test1.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test1.htm new file mode 100644 index 0000000..eee82c8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test1.htm @@ -0,0 +1,54 @@ + +
+
+
+
+
+
+
+
+
+
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test1.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test1.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2aa261c3b645329edaf99a3b195bf2086f82e58a GIT binary patch literal 2880 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYVC7par149bKgm)9x z-gx$*p@E^h`@#SJ$M=4bWMJTW?CIhdQo;DCpZhXw{lCKiqfDZ~X60S{a|eO>5HTtT6v?R((?U z^Qqt^C91gS*_;XewpLn<3@i&IZtUMG`e(xN1J>6P=BIe{KuV6};m+7Bb;uT07|u|4 zMlMBR?1r_6fOzs^N&eD0bJgyNDf@>a`RPUKVKF@%R+{(oviE$^4vPVBkwKT z8ldRZ!T~I#I|LLHVMP>WMYNM5kMKa0^04yujK_(iOtAXKQmAPEpHCc_15)pRT(nvE z(^hwOQwCtO@5rCs$Cy-dcDiRTbYg?Kmd;fn8P?{s7Wl(z?0qe;G8K*f@QmSv`j15) R?OuUZc)I$ztaD0e0st%#fy)2@ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test10.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test10.htm new file mode 100644 index 0000000..af2a3c0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test10.htm @@ -0,0 +1,19 @@ + + + + Lorem +
a
b
+
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test10.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test10.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c831e42befb365b1933b622abf924270b1dab787 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^V9dk}WbJaw<^WPW0X`wF|Ns97G9Uc^KjGa3a}V7V zAZM1Ri(^OyOJqGaa%>!K1bGhIrPWUtJc`t7Olhsx?3(fqIefB)_E zd9~&KmkY1-{h3zv#L;fS>NAO=9xFE;++SIJ%^_5XDcEzjp3mprhtKanyK(iE^!@EO i*S$|`-+$`vby0IO-$%SB(!GGLWbkzLb6Mw<&;$TW>1Ox< literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test11.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test11.htm new file mode 100644 index 0000000..7506cb8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test11.htm @@ -0,0 +1,14 @@ + + + + Lorem +
+ sqt +
+ amet. +
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test11.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test11.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0ed2c1c5504c768d7ac17272351e28c569731e26 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^^MF`^i5bMw)_e=3I0Jk_T>t<74`fbwH(~RJm~J3z zv8Rh;NCo5D%Yj^n40zZc$R1R#?5w}=zn1@WZPW_?eQy?8F5P-}%T_T_5fL61u^(bW zn|Mp#oK7k82?yiWK#c^gMHaW)xv=vU})7_OhdXXyJzSxSZD zdSBwF;5A+r_nPH&mn5ADFwDO&&-#&AQ22^vO;?Tj7n~L-e7vP{o*inu5VWtsf(U%b?^5@pmP~KUHx3vIVCg!05a2Gi~s-t literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test12.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test12.htm new file mode 100644 index 0000000..d87e354 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test12.htm @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test12.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test12.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6bdd29756a9373d2e3b3796e03a5258460695d04 GIT binary patch literal 1837 zcmV+|2h#Y7P)oCK0`~XY$QP7v$T#Hw1=;+~^7tOe<5&EWyFr|tgB=HHtkLSRogg7z zJ@(Rzenj?t?7`pIa~x-|A;tz9v6=d#TL;TLC(xgnO zun&GG+@#`Dbjp7#E~|G4J!fz#*619v7~NHS*#Q&LPL~r{Frq z<+w}o#V?`l3bHi_8)U1HLLtATG)VC}>dLvQ4%MM7q%1q`xaxAOuC6=uSWaS*cpx|(D?E~R%og=hEi<=Yz z9Z{SagA~F9qf8LgGIX%8LihyXC4`rdt(Ju9-P#zo#1Sge9xB(xAzJFxR%!I>)xUoUb9A z+LPUyVRhNe7<4&ShSzt-w5vW4E4M-9Z$tduA)oG(%|Ag}cPg7Pz}qO0aP6_<`j2?E zokm6dOn0~_gEn?Fb0jzDT0p`ZkiT9)!c|C^At6F!b?bEq%{5s-WCfY7E+9Ba_ARMEJi#^1j^G{BkKm4-Pw~-#JG4wWfp9xUqK5VcN#iV` zOdvtkAn*uLrWvNej%-3gjoCg>i%@zn1F3!xPy(Ugqvb&#SG+=yi`DFLO%R?S&w}W3 zBg91rPY^c9)(oWj(K#WMZG>!>5MDvNL1=^|4Fs};_ym=97D$^&Km*7z zU7NFg;V#9(n}O?I@A@}E?w{?C=fE$L*8R(cUlL1bR1Q%QTDq8@@ImZ}4N3ISE_>z< zY@#uE71AH4SPu(`+GLU5?KDf6hPLzTycr@dA+m(X3y7RSWCf8m1p65qyFsX=Q4wg7 zq{Iy*k|J!pq!5KqX$J_IXi()QMLNWyF##>9Q!qAtS$NXpB=Xs9bP}p&VxD#YMLpVYh ze%Yb{c_5;lLAE2rIVOj3=naq#kC0#Iki`3B34tk+2(hmkOhP&(B3E1kbF}aWY#kwx z2#pX8-UulGdD7{t5#olytsrP!hdg827P$^vj}UhSQt0%@C4}Au3DL$22$`M7ck%+# z&k$WLg4nM)i;6&trG;A{8B)tg$_@<%B+=E?d3KW4)amMCd{E&OHiAL~UU}rA>dGoV z^ulF+kXK@55xwh>y%^NX-p69vduOroDG+%R;=clNd-)QdT#foqEt|LmCTU351wD;# zJqjgQ8e5oaS99g9ya}08xoyuO*rsbpxB((3h|V#&VF8)NaXX|@8wBinrbf4a;~Uhm zlXnDTq&@^4k>b#>?raJ02CjYVacMR|26`02*nzg2gIWH9uPOUOAt5Q451Z--UkUE zfz*UGQ>QF2i1@axJq*HFLC|+6nVP1 + .sub + { + vertical-align: sub; + } + .supper + { + vertical-align: super; + } + + + Hello
My
+ Dear
World +
+
+ + Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet
+ Lorem ipsum dolor + top
3
+ sit amet
+ dolorsit der + Lorem + wery + + ipsud + + asd + + 2lkjsd + bottom + a + top + kjlkds
+ jkkl +
+ sdd +
+ dolor sit amet
+ Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet
+ Lorem ipsum amet + bottom
3
4
+ Lorem ipsum dolor + top
3
+ sit amet
+ Lorem ipsum amet + top
3
4
+ Lorem ipsum dolor + bottom
3
+ sit amet
+ Lorem ipsum dolor + bottom
3
+ sit amet
+ Lorem ipsum dolor + top
3
+ sit amet
+
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test13.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test13.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..bd5a506c5315fc6d08f816be2a2d2ba2745b5a5e GIT binary patch literal 4867 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 Z$float +
table
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test14.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test14.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..fa783444361a691216de714336f6242534e706dd GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^2|%pC2qYMe?soVIq(VJi978G?kDlMidBA|jC6L{> zbZWY=)LGY4vmK|V6)c@0(R9$)YV}W>kl2$?`3}DKF?#LcdCVnef`RbHiQk+3%2?E< rT;W$}+!1z#_w1F}r8igJ-2Z_gi#h7d+>?J)K^A$s`njxgN@xNAjzl!Y literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test15.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test15.htm new file mode 100644 index 0000000..a9e2dfc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test15.htm @@ -0,0 +1,19 @@ + + + + + + + +
+ + + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test15.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test15.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0a624fbd92430f2dac2ea23be4ef1a114ff1e333 GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^H-NZ=kr_zdHOR9DQfvV}A+G;{fPvw4)bq(e5miqY z$B+ufwFVdQ I&MBb@0L8W=8vp + ab + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test16.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test16.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..285039ec80e20c480e290a4a44f81bd2188f2499 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^CO{m7?7r=ATxHAtu8Uu%eLwtyclNAV|NOUW1t0P|aIo&gQNuNNKz4Y# L`njxgN@xNA@_j#x literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test17.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test17.htm new file mode 100644 index 0000000..e5ebd4b --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test17.htm @@ -0,0 +1,3 @@ +
+ Lorem ipsum dolor sit amet +
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test17.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test17.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..76962cff3e31ec79cf2729f21bc01cb514a72420 GIT binary patch literal 244 zcmV2=V(Iapec5;+fu-gr3g@Beqhfa8~l<1Q^{7z)G zZu>ULO8o`nR>oYn8-6$+dncFpsQO+l8UMk^*vN~46i`Z3grd + left-top + right-top + left-bottom + right-bottom + \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test18.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test18.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0d07afb4de0fda893451ce53683d8fe4197ca214 GIT binary patch literal 326 zcmV-M0lEH(P)htl3n;r>q-+2@Yi ziUdA^T}$mE99sr{3j7l=FlokWP7Ii$1c?z}g?tOWF{87lyzKSp_c_WCbUX%XDBzhu z=@c!{=^DIDFhv=v~;r#{%tNOYBWQ9ZPu3MoWfB5wX{H3sKsa=F^CGau) Y0=%(YJne&O+yDRo07*qoM6N<$f=0BBDgXcg literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test19.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test19.htm new file mode 100644 index 0000000..16ecaf4 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test19.htm @@ -0,0 +1,4 @@ +
+ aaa + +
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test19.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test19.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ee3a473e7ec40f5feb346969b1aa14b42b2b86b3 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0y~yV6+3W%b1vfq>h%j7?9!#@CkAK|NlRb$xJY5_^Dj46MG30Gv;9=O{dB<4TgW1@ksbzl3@jltL4Qy`$ia&ApZ+=*1mA5cJ lLxii<2^;l4@$AV4(YWmBwdG%&-z))Hvd$@?2>>%5F_!=U literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test2.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test2.htm new file mode 100644 index 0000000..4dce07d --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test2.htm @@ -0,0 +1,54 @@ + + + litehtml • Fast and lightweight HTML/CSS rendering engine + + + +
+ +
+ +
+
+ + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test2.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test2.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..167fc61161cb9acf32105f10fa631904138a70d6 GIT binary patch literal 718 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKty!3XWa0A9^MI6SfKQ0)|NsBH85lk^H2mLo z5GZ!}@?{2w{{j0}Z((3yD)Dr245?szJN4qk#|k`c>szZ&h;7ilAv=TZo8vo%U-}PK z6q9FL82aR0oABp8Pq(hYn-|=V_<^Q?K*^c=dOz$^S3G-l#+jofM(>DiMVkOeg4uZEwA~p+Fq*->Hn-PyB$yHb51PDe&n8aM}N6f@~3;} zIe&fWjOFq2W_s^blqQ`%ynt6wc6f=)n zDgNvWbqdN9QjFO*NuYAt^?$7?7uB7v-Pxt1%Z`t9{E zXJ)6hde$AjxyHiCJ=N1AWQ(=pHom3LMRwnwn6o=|%LBfLla{7-w|cDa$&2!uY4cr4 zMfFic$9- ztre}Tg^M;QtIj?B;yKsH+uGY)PSjL5mGH^V5wNmQ{NmD~zyHepxQ=_==}vNY71Ny# zO`MSG&M|S;*^?h`El+a!wM;o|>Z9s49}30N!`&|N^xk`89jTzT!P+%>%C)UqH_2X> zb$b26opWcPn~&Dq0-M!uZtq&Ju*6-F&q;yB$UxcGDPh#iJi@ukfPh53sE4*|} zZ%O-vuofSIOML>*Be=L9$~i>|C>gCO)qe8Zw_(CD0Y&BeFO;0CICjohIBT^rzoGyW p2(4vM>}AA_KAJf}(hS3bmimv6I}WG`3g>`CJYD@<);T3K0RXUYIeGv9 literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test20.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test20.htm new file mode 100644 index 0000000..8b6cfbc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test20.htm @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test20.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test20.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1f7ce172dc6fb7ab80528e08bc4f9c6d413ae92c GIT binary patch literal 118 zcmeAS@N?(olHy`uVBq!ia0vp^S|H5C3?z4~RXhQtI0Jk_T>t<7&%gk}a%W~c0tGcZ zT^vIy7@uC)D9FITa=<}3>&8S)OJ)mSt@x#VA97yYny35k%Tvx(3|qFxDo#;imH_Hu N@O1TaS?83{1OQ1lBz*t? literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test21.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test21.htm new file mode 100644 index 0000000..1d7c363 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test21.htm @@ -0,0 +1,13 @@ + + + + +
+ + + + +
+ Lorem ipsum +
+
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test21.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test21.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..50ec2d05dee60bc393a3b55e0f4862d4dddf1619 GIT binary patch literal 227 zcmV<90382`P)2aEO74ZsYsC3N(3Kl#?AAHs!TQyo z+I(JrkMG*%ycX;g@a6gB5njR5Yu0ml^a;m~hv!> + + + + +
+ Lorem ipsum +
+ \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test22.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test22.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..fff0692963ebd255f957c1e8230bfa623629516a GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0y~yV6*|U)tH!pq)OoVJ3xvvz$e7@KM(-f3=FfTGED?h zm7Xq+Ar*{oFB)<+81S?v>d)k{Y*_GprE?kkeNEq`O&t#|cyvEuzo;YM#uIqO?34B$ zf7_jW!O!QN-p4ajoFnH33s0(RulkzRw#l1n-}*k?ea!ou9H-xTv+o}~x~50`{*)m7 vOgjDWx|*0Bhj#yeo4x;&#vXV5u31}|pK_a?$v?k-KFBeiu6{1-oD!M<^3YBg literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test23.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test23.htm new file mode 100644 index 0000000..dc71ab1 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test23.htm @@ -0,0 +1,21 @@ + + + + + + +
+ + + + + +
Pellentesque + Duis rutrum nibh vestibulum finibus luctus. Sed ac gravida urna. Phasellus at est ut augue interdum condimentum. Nunc scelerisque, ligula a feugiat aliquet, est enim pulvinar nunc, at feugiat velit quam vel leo. +
+
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test23.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test23.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..83a7cc7689c68956e90e99dad971fb6d8ffe9b39 GIT binary patch literal 1345 zcmV-H1-|-;P);VjYpd)4R`e!bG;XQHO5WjQL=+?lNO%GS z+LkEP!y4aIR|#mze138ETkIr?YY@cpV;~H&69YciAT~ON{S6Q%%M2ntg5>fU$~nMD zH1o&NmGvZw50@20Q%^v2-S>d-2)+TLp}q?uDbhe%!bl-G3&P~mGoq;{AT7ntfH?O6 z1PA&oh@27h{0gLPJWM+f|2hy>EUiw=%po$X6+~(fL8OKZ>~pS;10?Gltt>|C4r&v6 zUMr4Ptx>BMy-zwzPmY|C0zxaKH-74kJ1MnG-sn97`64;GQQjvVbA#sY95w4zZjzo#cE!u&RK3NE%vhokvpr5dq>J4l4 zKA%J{cfH)>#*Li8vPxHY)AbOh=2$n0endQV9;~vi$9Yb5WzHylT~Sv?e^T8Vg#L_| zcfu2&6g$CZm6tX-EQ9>pcnf6`P2DY4Q|Pg!|EiUfS(pJkcxa9_ZOX#z^RJ#&lS{ZwnBfC^=R1UNy4a29gU1wk9C?E{OW2 zZ9-djZ(8zjO9iBEe+#0f6=b$zcxkIk%=%-HmWUgWa>Thc8PO=e3}ksmW)MHy1$DIt z@gD&3XFyOO@MGiyiW1Am$SCSdn#R;Y(GtYgDwEx9ETa{qRwpfj;+h_+V}gj5*7F&$ zvt|%onFEV~ex+K;2v*B`kb}a!xCpj)!gHeD(i^_Fy4rv1g8McQ^8pa^B#51_otJ|g z`QGWC1NmXnL9QmJKqjMg|43L#hLXd+^wq{6RGF##zX`-Y07;8^bNdM7El*zrVordV zJ5W~ualAo2{uA$Hha{pVW-XC&&&u5`_xn`(ywSaZ1!wSnUuXehi}lxn*r*`<+n7|t zpPUODBqFK{Ui)gJsU>Y2Z=q@d;;Qv`fs8qZ1|p+2uR*lG1%cgFK(KFFJ{C;{aWx`Y zL9}n9%S(_b5h+@NO z$el~>Dj+{bI>^=Jmx28K1=2yTCV#qg>C&Z3m#5@^T_J1e%+61V00000NkvXXu0mjf Dvs-LZ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test24.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test24.htm new file mode 100644 index 0000000..13fd341 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test24.htm @@ -0,0 +1,4 @@ +
+ lorem +
ipsum
+
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test24.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test24.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..9e8ff84400c248f4e337e7556142446ddd2fac3a GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0y~yV6*|U6_}WT + Lorem ipsum dolor sit amets, consectetur. + + +
+ Lorem ipsum dolor sit amets, consectetur. +
+ +
+ Lorem ipsum dolor sit amets, consectetur. +
+ +
+ Lorem ipsum dolor sit amets, consectetur. +
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test25.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test25.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..2d2eef7d4bdf1ac890279c545d6d843c3b50ad46 GIT binary patch literal 2703 zcmcguYgm%m7XG*hVv2}m*=T$wra4YiI$ga%l43GBUei)+QY-B?X-J9+VNc~``c;Nw z-a6x@(LobWVrD>1WK>h0(lkS5O3cELZb)jvAs@%i`EkyVInQ(UkG0p{KlWPdU2Cs* z<$kv*z`|^y82|tlfmBK;03bjBfb}2|#v5UzW2bS$e@6@7U@$JD&HV$EU>*_>{(>S>ObA()?xF zG){~ixLt()J|T7hj=#s<5G3B>*rc2skGf-U_QlG&Y@m$m@`ZB#of8u5jRT|_Au5p5 z*T1lepXjxhl$i%h3A_j<$~lGhqy`L5;7VN-#&8=7itEOPj>&B%T@VS$So}bO95HPY zuo?-109iF`u?@I=CG?XD;V6O^5^TZE&8S@Cvprz;Fpv^~#or4Zu&d(RjJ$Hab$5Zs z7ZjlQTuv0vW$XCx1o?)F=m+?oU+r zD4qy1m%~!N#p3THWnF;P9GqZAjLee)tsc`Dp>ua!f4hp0fmq2*D$V8{uP5c$0;X)Q z))R?4Whb4m5A~1ecBz~Lv9{#6S!!h-%9?y~J`E^-qOyHf>H0h?4%X_3?iIammtCoh z$CW#@AZMZ0E|RYL66~B#M+&sd=FSerw4eYpX~5%C;Yl z(HahWS(Q&R!s|(R@b4^GlA{W9S~uVLW+w}Vxf}aX0}YqKWgqF~4#AL5-73&vm;>dm zrP_x1)P1}jtGmlH1l=%`nXIL*v3A^@Jd^Lwng5I05a^6Z*#7yo0u4jgqwo8S=OUsN zxP3aMu*KcL+rjz$eZizl)Aa|Z@aIFM)u`i9o?zO+eKZYwTaqDwMc8F{J=uHvo#8{a zGZm>+Fj30J)u0n;(?MXS2HO^<6VFa%x(yk^eChO}cV<*}obQ05ldjW!UC}A=@3T`} z9ec#y*;jI+q6Dxzpa_Q!Quz=_Rjbr^(dE#8*RQ5olel>ZZ zOfL^;AMQARrr%aLL>tO#k$M8N5(@?yT z9w02zGT*F z0M3y(dtjTrEwb1+>z!Dv*AKJ5nUqYitpD_ukBrXlmT>coF<|f-pR8JLOexstle4C2 znFbZQnCB9bT5!@O*{6lsJk=m)kx^-gXx|UQqP>yLUm56Fjfw4Jt12`1Hb<&r@=yl+ z_KGDkr!w+H2K=QZH|7l37zwSa=@xYPWGR@C{%U;+A(3nDM&?=G96t*pro9lGL?c_j z`2^Z-#8QT6xoZ5Kj%`M6P??4zi~Dt8dJ3|&4!P8-xgPnFRme!w>ay}S*in$SQC!NI z7rz>msa|&G0o~OiR4w;+)k;}*#r}92G%{h@M0e%J6oZZUs*^S625alZ8ilWrsZN}y zJcmyhkLH@wW|2W2W}qpH6{M?vN15>GeHRjZ9du_F%RX#k%Ip&e5y4=!nR_GhWxQ)Z1if!0 zHBP-b>#<4ed1H!sGhqKO1uEm{cV8S$QAQqK^W(_qANhr4*HDX-5$Q603*JnAQa-5v z;b*R7%dm_2qTxhkg^S*4hC}~sWT+HTFMDj**vn>mp>|SpZfYlc-{;o&AGHy*Udw1U z>@*A!7S8s{NOa(4IMJ0PRe@tK28ZFY%f>Hnf%ET{jgp(9oAy+zh4|>C4sC3$ZxG9p z8m`|tCrASsw6Ef8 zvojQ{acuhL0e@RCgaN%LdWwNhHjSNGXQY|R>FA#1D4Svy40&=4MKJgC){wbOI)F@a zMnftJ{i-!2uP$%(YuCY*r!Zhhmc;ML zFL$)9YL0nBPHFa`_ymhl6H}(_#b=Hh00cL%)P!lv_QVpWIN__I*DU!+m|%|y)VPj1 z`MBjQuOP0dB5#TwyT;*wn!Ij3o9_R}o(dy2ra}w~1DeQ0JjLd|vLp6yx}COuA`nKr z^v<&?XHTWd$a&7-3I>i4wl}=_1S@?kf?e2;rS7pO#^#=yH*YrK#-V41CjW|dZc4{k jNm@TU+#Ub7RWpX>6}$F(-M?S>X_yS$u!+*JejE3n;YoMJ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test26.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test26.htm new file mode 100644 index 0000000..5c81498 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test26.htm @@ -0,0 +1,16 @@ + + +
+ + + + +
+ Duis rutrum nibh vestibulum finibus luctus. Sed ac gravida urna. Phasellus at est ut augue interdum condimentum. Nunc scelerisque, ligula a feugiat aliquet, est enim pulvinar nunc, at feugiat velit quam vel leo. +
+
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test26.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test26.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1dbb335fdf0997ffc90307eb06660e5ede2a3b75 GIT binary patch literal 1250 zcmV<81ReW{P)sr!}^^p(Be!CO_w-Ik~s<%>ofO_4x?C? z=K4B*LW5tjM9#5FU&Jjdu2|wbEZ=UoWoiEf3vtUTeRf!>#}XgL$))si;s@b>N`r)P z5WI<>WO9tf>rR?>)?qa2Cn*tF7`0{bBnF)|*#Aq{!&$Lw{0q;JeXX4_>$qQAbz+3? z#nFA1GnRXYl`1RwJr=S?j3pbEm%&YP#KP#xvJBK=gX^>K757;>*dMVly0VxNh&C+w zh!w_9v+R2%%-3ZNnuCG)mL?Y+{>efuS;((wz0jBKAq8O{TjhP-1wZC~ahvy=4p zr3#4NPWMI@){VG0EWLKgQeE7zF#eduu~cO*jX$O`if?*qE}x;4+39N~Dx|@^%fcy= z#WS|q=d)~CvYQ+2++kd^%!w{zG4Jp_^c#%kLXI)MQeh$A^t|-P%!AW08|}%+<)3_T zzx>e!IZ^!4{rY~^X5U{d-Fbe1&i@@2vO25u^1J$?=~ctm>O(h+i}i-=1=Z=UVZ{!s z_)hGwn%P#Yd8Hq)`gJ{47ze%mlc;~W;zQ5_s0ixO%KDU?r|0T1 zq+?h=S)L)(jDDpK>8q~A@nui6qF!}<_Kc#I7^nZKdP(*jd;y7Ik&Y8cb`qEUj-?GPBh0bxa&F4yRRptFq`9oqeS~%bcdNs%M~uJ3hU)BnuR_`tf2dknpNM7oE3A@l$VT`X#s$k-WTxF!^Eiy& zey?;*@r#?dyoa~2(*M9(x!9lgl<#CkyY19v=23B=R^iKARd4uH+*xVZVYRdHJ${N6 z-(b}~&D!@yr+v;5YbYwA3QG4X4#e1W#Ia=%cp6lL8bpVCIo8nG*l%xNMg5Yu_4uM_ z!Wiwg(xQ>&Eo+`S*g`2&p68IIdqaae$C{k0EKcqAS@5WQCN`?DhCF6aRmB_nwJa}r zO)Hih?JM!wPf1*7J^8)9g36*LVxXkM!FyudGFL4W4H1g`j?l%wJfoHu@)@!eq&z?Pw^tD$C8{I=zSF{j6D`>3+rSy?)n$<5e`N#!;dV~ z>Ql3j8Tl1fj{E0w_OyN+Yv+F6v#&eL!@7?3E#+bTM%GiF@|2s(e~xZ7Grm|X;s5{u M07*qoM6N<$f<;Dw#Q*>R literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test27.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test27.htm new file mode 100644 index 0000000..d527db8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test27.htm @@ -0,0 +1,13 @@ + + + +
+ hello +
+ + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test27.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test27.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..a131a7b7936d7a0a5ed0b9d3aec81b9e9f3f5d5a GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKOPH8}B*V*n?Ld+ +
+ Hello +
+
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test28.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test28.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..7d8a98161fd31d4c77ff721056b19455e9a66585 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^c0g>w#0(^Fd|P}9Nbv;tgt-0(f^7#Uyqf?N`19$s z1CUDcba4!+U_5*2AZM!s56gx4NBb)Zt$WzLZ`1`ju^bodQ3?#VNqzQ>BSrkv>B;X- z{Fkb_`B|)MmPe|;!6tu(SjJt9YNm%4JQEfaP+P)yCn)m%r-#Lv3lA9IcbGPvEh9d$ aOXs?!_Pr^03XTJ9WAJqKb6Mw<&;$TUE=7s} literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test29.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test29.htm new file mode 100644 index 0000000..7c47278 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test29.htm @@ -0,0 +1,48 @@ + + + + +
+ + + + +
+ ipsum +
+
+
+
+ + + + +
+ ipsum +
+
+
+
+ + + + +
+ ipsum +
+
+
+ + + + +
+
+ + + + +
+ ipsum +
+
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test29.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test29.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..1f7c1ed04251717b8c894686688b0b1216926910 GIT binary patch literal 510 zcmeAS@N?(olHy`uVBq!ia0vp^%NQ6KgIJh>tgcHt{{tzB0G|-o|NlWuAijM0@`r|o zwKtwIG%);MP_^Z4{X?LJg`O^sAr*{oub$04Y`}B$R_HB3T3b(GdXD=zwzP9SeQ zLEFJroL*9ye<~;HyI39{(1y*?>1T`2*(J@joBg4DUsvR#>h+0r>mbJcbSj?t^jZ7N zlOcki8{Wm1t>E>%yY8RNg}b_wpY7G$oOw5Thh@#$%CmW>j_iEDw{7R0C5B)iZzd}4 zbEbIq^z>$+3x6t~&Xo}cfzPeaVh!jwUvIkp=Gx8AEYsyT`px(KVI6-|=i}b%2JgAlKp-s$5(1o_v-F?&PT3w8 STQwILiwvHwelF{r5}E*yO!1EZ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test3.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test3.htm new file mode 100644 index 0000000..9d83533 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test3.htm @@ -0,0 +1,76 @@ + +
+
+
+
+
+
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test3.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test3.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d5984604f532f0f022d734fc1317e70c9c6dfdd2 GIT binary patch literal 1392 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYVDe;P28t}Wt<7-_5}A|3AZq zBlrFT1ydL%yqmz#-TmPI|BSaiRSXQQ0-i38Ar*{oE*|7$aui_+3>GjpU^Zm^-0x}7 zWZ$*h^uxWK`o6J1tuT<_eO^WKZQ=qa{ZnTtk@nC<7Qcgae_BClMNuhAL?v z1icPLPDBtDnm{r-UW|^R3EI literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test30.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test30.htm new file mode 100644 index 0000000..72f3b02 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test30.htm @@ -0,0 +1,3 @@ +
+ Lorem ipsum +
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test30.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test30.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..29a0e59bb0b8851dac078f6b7455874eb9f7d4b0 GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^H9%~_#0(@&tGDO?Db4_&5ZC|z|6jg*8ORgxzoG=B zN<3X0Ln;{GUUK9*qQJv?VQ0#wAM6q{cJ^;RQ786yd5(@)&;}1dFYmt!UUDu<``l9O z5BnD$xwZWA>c3Ne{Nt#~WtzI|*o%PQzpK>Wzu!Hj_{)_7#_;d6v-C9PUFoyjn>etk*Wuegx;dl^eJf%Y?ay85}Sb4q9e07-&S^#A|> literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test31.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test31.htm new file mode 100644 index 0000000..fe6c2cc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test31.htm @@ -0,0 +1,7 @@ +
+
+
+
+
+
+
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test31.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test31.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..b720728549191af2c47205197a1b69a43129f20b GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0y~yV4MMDTQD&L$-Q=LbAS|2fKQ0)|NsBj-gwr~(D0$5 zp;G?w4xqS=r;B4q1>@VZfxHX~91MZpQI{rJWhh8X%fDy36Fo`m?F*Bs#XM}y2NMh? q&y+4Tv^<_*Ai=|CJagigo6M?R+S|o%u9*Ndhr!d;&t;ucLK6V7#V&&Y literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test32.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test32.htm new file mode 100644 index 0000000..d8f8218 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test32.htm @@ -0,0 +1,38 @@ + + + + + + +
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
+
+ Lorem ipsum dolor sit +
+
+
+
+ + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test32.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test32.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..18b870382b2f52e9fdc1eaf86eb50e5548d5e914 GIT binary patch literal 2462 zcmV;P31Rk$P)>`X(kOipjc$000S1Nkl(rgPQ=g^%1=MM-DYJ1D3Db88mo}V6bID8^)lXS^gs-x&v1hqz-17b` zwY-p8-bXFF)bbSdRx39)acj*-spB=(c+FRyV!phwJ9bfHPn zc7uAihQyw1HaZWF*a*FjT7=jf8GB!Kuy6XYabE8FEwvm_%S4THn5bo>mXTU_az-t` zqCSie&dIe=<75`pjC(y_?m0BM&ez6iR#H=Hwr!q9*GQe~M6Gj1YHD=Vk(w;FBloEJ zUyYh4yryPWXw=E?i|?4u1+m;xQ#bDQJcsBBjeFfzo%UMZL|s~38^JDhyrC|yq0YF; znnyqy?DlH3Xa?X-rVU1-w>}Ms7M`}|5M~>`h1sA&YZ;=_4c{U(WvsHRIsD)F*D8`cu@!d(E&M z+C66<$}+-K6HUI=AfIVk?H|`@_p;0+vKp_yen2fd)VrSl9JS0m{+?Pkspo6_v@NJZ z(Hd4z(eSX^Wn?P8NfEWeC9h(XX+<~(j>Ix*gR|P zjr8!qUKiGU_BOH|@_jqH5JVG7HjJiBS9j?5|0r#7a#-7C} z`r!E?iRTE_sI|)&*P1m{6VwlXWN>@<#~K>fv`Rx5CCnZiYg`wz2%b;w?Dg+kJotLJ zjkRE}os#$=VD-$bVp)TXizEy0FF@xF8$Em^s zGu5(c+{A3P6cNSBNl)gPz13OHwY8^jjo?JL$8W|%R{cy^y>9mH8^->YntMhoh6?&PU7e^bSP ztlZR(63Xa*zSbc%3gKO9`2n?TP|GH@{%+Sgre56HjY@jpX+gc7yG^~d!jGs?(@Lv~ z*{WVi6>_Uu>8H+J{YBHI=4V|WfjlOVw9QQE1-@THD)_sD~`2vpBl6D zpMw9QmL=oOXVl}1a!M#S?ev4zBV;~@{=|Kn%&aiRIhoWV1d?IC);QxXb*wXL$g}%( zPcl-g%=(d9_NZe$q?S!;d5qf5kgMdXYpZ9DM}m=>a?`>;N9-5WF-_FXXH4jwIwJxQ zIdzZPR;KS`YVrSuRb--0(^)L2n^ybimIu^&lF0~luX*p(;~ur?4ye=Xsd1Zcs3F^; zjz6Q$$RFYAH@J%4-f&Hfa%njv;5>C@z15T>hv*&GhdBf~sm%g8iE}EvPCiv;R-A8{ z#bmT4^|Y4hnAx_ef7a3qtsZQh`m3lv&^q;}s4cq`d7PmFbom%lt>wZarVOEM_c-(iYR_sAZR09-x*x)N(*AJJfPSowd5Lat*ix{eb1qCLm-d9>G7{U!1r z`dCj5qnIzIfiM04xzK_?n@1`3AJR6Pv#g&YMzx^R@Ah>ez8}Hd(`v1Olvr`{FXW`_S(F1b<^lby@ryINho7aDWYjX z4Vub+No`)1)XAyMpGL^_J=D6_%A`bgn^x4#;!)~mO;%m^JIRtvu+5F;b zsxS4qOFd=GbxT{q2Xv3xuH~Cq`t;Oy+6{GF=JPM1{)*PAKS&+QOl#w6naIBux3%5% zP%y8IH{UEvv!$GV)7z-!hB{n4>mN|F8XZ&1pQDzMdTRUBw^{a&P-|T(s7c6vtV=vq z);?6U67`|nDqez~8m(bh-D%7;dmF)itcawhrpVVrHn%r`MMKB|M zppHL&Y3{j_+U=QVs(p>~UP^7vvugb1DZaCH>W@-?LF?2XqE2(oJU1c6!}P6r`8%lP zJE&!sTFyp|+NHIR5y2XnBFC(UQmEsYnsZTu@{%I!u@t<;#^@uz^F`0&bP)nFIiQxa zQYXo^Q6nZ6)Qo$^${DHgb83mV)VP_Q`UTV(xkB>@NCPs7W|6^iNVrl58;qo$iF(=6 zhtWFqDcS+`iCd@c)Sdb)ty6dEPJNcvsXKM2K1=J=ow`$>r8Vj=Yn{4N|Gm_`_10T& cy?sIZ5B?|e3**wjKL7v#07*qoM6N<$f*Ar0F#rGn literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test33.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test33.htm new file mode 100644 index 0000000..d47eb23 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test33.htm @@ -0,0 +1,5 @@ + + + + +
Lorem ipsum dolor
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test33.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test33.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d1bee94be59374650d8554776e7833aa7e9bb4b2 GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^3xHUI5lAouhMzG7Qd2!$978G?&tBTdcSu2i?SY+h z?2H;VChq2q>#wvNUAx|4@6nUhu8(zjSX|c|hU#BjsPdQH$u93_<1@9CsW)ntKKNic zU3=kk+4+f5EN6=6d^(gdK@u#X<{QfX!$wyvyw;fwp{ig}4 zEy;FliNB#O&8xOHwmb0coViz3R{s9Kl4O literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test34.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test34.htm new file mode 100644 index 0000000..3458673 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test34.htm @@ -0,0 +1,14 @@ + + + + + +
Should be on right side
+
+ + + + +
Should be on left side
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test34.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test34.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..fd4dbab0a4a8106bce6521bb68fc38c2cbd13c71 GIT binary patch literal 409 zcmV;K0cQS*P)vI0004CNkl1pw6b9fNql0O#5b)w5Bsqb61s@?ni?T#l2x#e0?5h|$w3BoN9l^y*0^+?!O4^1t z-dLCQO;e81Gyb(sIgd`*(-@OKk6p@!dLqhHxsSM&fNOL^x(e#HMBrU?q(Db@U zY0eQ02$CbM)oUwTi?+qLm0h%GSGFR46V2FKOU?Xx*=xqp9QF+y?p(M|^vZ zcCQz-2sqZAYE1A7ceh=i5gtMnEtje~*j?>VORW zq0|R12?@D(MC6E0Hhnlc37H>l4Kc!1=z2szI(HSM|AiouBVCc#V%!z;-DD18GMVo( z@i3!BM6OiLn_l?^{Mlg-T-CIQR1uNw*dxo#;cmvakF*H?ya|Sz_=m5#B9CwcMC4CJ zR)lorjVq=w?xqEGy6$IoCFGwXqsv8$M5u%$ + .vector-menu-tabs-legacy { + padding: 5px; + background-color: lightblue; + display: inline-block; + } + .vector-menu-tabs-legacy ul { + list-style: none; + margin: 0; + padding: 0; + } + .vector-menu-tabs-legacy li { + background-color: lightcoral; + float: left; + display: block; + margin: 0; + padding: 5px; + white-space: nowrap; + } + + + diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test35.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test35.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..9ab5b44d878d5d6f5d5700a348fc431f6aeee575 GIT binary patch literal 250 zcmV)3L0sR + #mw-head { + position: absolute; + top: 0; + left: 0; + background-color: lightgray; + } + #right-navigation { + display: inline-block; + margin-top: 2.5em; + } + .vector-menu-tabs-legacy { + float: left; + height: 2.5em; + padding-left: 1px; + } + .vector-menu-tabs-legacy ul { + float: left; + list-style: none; + margin: 0; + padding: 0; + } + .vector-menu-tabs-legacy li { + background-color: lightblue; + float: left; + display: block; + margin: 0; + padding: 5px; + line-height: 1.125em; + white-space: nowrap; + } + .vector-search-box { + float: left; + margin-right: 5px; + margin-left: 5px; + background-color: lightgreen; + } + .vector-search-box-inner { + width: 200px; + height: 100%; + } + .vector-search-box-input { + width: 100%; + height: 30px; + box-sizing: border-box; + border: 1px solid #a2a9b1; + border-radius: 2px; + padding: 5px; + } + + +
+
+ + + +
+
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test36.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test36.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ef7f915aa6beca57298d7f63dc0a7ea3dd636bea GIT binary patch literal 477 zcmeAS@N?(olHy`uVBq!ia0y~yV7v%qN3bx1NYx8GKuRdUC&cyg<;(y7|6hCK*`k#j zC%l^g6rL`rTnkj*=IP=XQo;E4%6s281_CS}_JlLOWN5su=()J{ZyOFD zY(MSxShD5Q@$EY^3r7GU0ShFM1RVo)}TEBUE*FJoiyV# zj&frY?DqPfpH~?>?KbDmwl8lVzpPviGGOVIP%WjqpPkRJd#XSE6D}$0@tuA9KgGC9 zciEm#`KP*=$#Tz!$!*o_)9?FL+54OGP853^)pTXi;if-(FH{GfY7btx?zhL+n`Lii zZPa}gz43KUMNGo?ExYu;%<_1;cU$lBx7T{x8%?&omgi!-^g{FOtsT{_rH?Zeue{3* znjF2mpUY=f^d7Uu#asSdKYnRTfLY$#y_2_je%rg?Y;tVD^k?(+FPV7Ad4H{lD6q|( zyLaQcdwV{bO)!^U`pr@EZcO_74Gy;>(+ec8Exq9SN^pL;*Gu8KJ9no~$btk;>-1k~ uaxdSu#5h;^Ap#}U?4^|_j4SME&zO65!#lQ$V}-zoVeoYIb6Mw<&;$VW_~S4D literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test37.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test37.htm new file mode 100644 index 0000000..a6f7221 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test37.htm @@ -0,0 +1,46 @@ + + +
+
a
+
s
+
+
+
d
+
f
+
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test37.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test37.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2b7c4dd5fc1533fdaf07ee655bdd137a61c69 GIT binary patch literal 792 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKzpyX^$%&Hj|A7>LfKQ0)|NkK7<;$1}E9l{OS`xVXjP1g}OBYZ$BHm3rTe!VV8K1NdUFR?0R{g{JVwV16`mVT>)yk3 z;%AD}H+D#vGj^Ib#vEhVv{y1^>oM6BOLkz0g93}?#5-dAH;?a0d38w+lLH5$b*UM70n;~wr>mdKI;Vst0JmrsxBvhE literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test38.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test38.htm new file mode 100644 index 0000000..a2eb9c3 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test38.htm @@ -0,0 +1,33 @@ + + +
    +
  • item1
  • +
  • item2
  • +
  • item3
  • +
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test38.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test38.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..0abfc11b5f11b3aa8544d6894aa62bc7e629f36e GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^yMS1Ui5W;v4b}DmQk(%kA+G=b{|7QByqmCjLrgc2 zwZYTHF{Fa=?PNo~Lk2vqa}QcO7jgOpvVG$@cPR6g^8sFo7n%n + .container + { + display: flex; + background-color: yellow; + border-width: 5px; + border-style: solid; + border-color: darkblue; + padding: 10px; + margin: 10px; + } + .container > div + { + background-color: cyan; + border-width: 5px; + border-style: solid; + border-color: darkcyan; + padding: 5px; + margin: 5px; + } + + +
+
block number 1
+
block number 2
+
hello my dear friend
+
+
+
block number 1
+
block number 2
+
hello my dear friend
+
+
+
block number 1
+
block number 2
+
hello my dear friend
+
+
+
block number 1
+
block number 2
+
hello my dear friend
+
diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test39.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test39.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..5d518ffb8ab3fa08211f5ff41d3996df3a877866 GIT binary patch literal 2850 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 +.clear +{ + clear: both; +} +.caption +{ + background-color: lightblue; + border: 5px solid darkred; + height: 40px; +} +.version +{ + background-color: lightcoral; + border: 5px solid darkblue; + height: 30px; + width: 100px; + float: right; +} +.info +{ + float: right; + background-color: lightcyan; + border: 5px solid darkgreen; + height: 400px; + width: 200px; +} +.mainblock +{ + height: 600px; + background-color: lightsalmon; + border: 5px solid darkorange; +} + + +
+
+
+
+
+
+ diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test4.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test4.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..310c3609f66115ec707db519d151c39dfc4611d7 GIT binary patch literal 1718 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU|Pb$3>4wwa8m?Q(g8jpuK)l4?`B|Fd*c}c zL-&V&RywH)E-6cj2J{`Bo!oZUXNpCU#b6C@N3 zK@w*a@AC^A^})Cxb;>Gk9zCQ8%yix+VAKWCQO3E@0j7JMqWv9X+wKXaQ2Sj@ytnUO z_VT^WJ1(zID4r%OWz!|$kdx(T;4qur96#NmS$Ex+ za~zt)+c9IYqI`9U2Me%ZGd#2ZHK#;n#Do~xkok&+ZAT=8l0AAx8MF_CO}oCmn8Cpy z!IF^hclEm-#uFdv3nwh)=`>V1HbKHU35hY&S;GJ(SP86q*zI&3_$(aa&9?(N44$rj JF6*2UngGnfB)$Lu literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test5.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test5.htm new file mode 100644 index 0000000..2e3efee --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test5.htm @@ -0,0 +1,31 @@ + + +
+
+
+
+ diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test5.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test5.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..276ecda5d389626f3e343c9fde682f56562621b8 GIT binary patch literal 320 zcmeAS@N?(olHy`uVBq!ia0y~yVAcY%pD{55N%z#5Y(R=9z$e7@|NsAMZ#-jQ=>E{q z!02({Gf@1!r;B4q1>@WEhJs9n3=9X&J)7-kDKOc0JSaRc;g$a6Sk`iOpfV8HFzt5I z9+S$SujQT1m6A*0_#8Gee>ht ztGRJ|u2g^xhU+n_-r2vSeY%Riud)7l +a +{ + text-decoration: none; +} +a:hover +{ + text-decoration: underline; +} + + + + oops + + Hello +
    +
  • item1
  • +
  • item2
  • +
  • item3
  • +
+ hhh +
    +
  • item1-1
  • +
  • item1-2
  • +
  • item1-3
  • +
+ World
+
+ diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test6.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test6.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..819fe6be31a7f84df86e849be5088128f09d730b GIT binary patch literal 785 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKA22Zk$u_xHmw*&!fKQ0)|Ns9#G&C?U{FkbW zOkiMOI_&A<7*fIb_Ns5+Yb}Ac#3zc{j$7s3cin8<%$0xWvyhN(%<4T;UOp*#ea25` zYIjZl)cJ{fnS6l8S)BfpWXtKaYtxy}wY&43TkdR=cIpun<_^9(!TsU{_l@&j&M&Z< z_s##?rVNkBHR)BxOLUl90<<-|-@J2nvI)8A6+N9)c+f#dO{q9G3^;dCx?*3}S zA*rU)BgzD{00=JlX8-xOb>5RD6JFRZQI)y>#L^`q+-XL{jjz`(3EYjEQ{;3))W&%U zpU@#j#p{BVtBzO(Zk@-%@L=%^+eIB~)h#t!CVy4QmsW7ORo1oIpz!x|TbJ!(%jYyb zH~wUt618@@rJ_rg-^HpK!C@*-p8V|i!}YPog>T|=uQ`)LUVAby*md1g?%ETta$caU zcB?!a$HZ-OA8GmA`B%w*aqUsfDb!sIPkaHG1uGMur~} zRsCP?fBTjCI@6D%Qy1uUll+&8z#zL72K4;K4S8!HSc1LZC$K*KNqNT=&Vfd$`IMf%WcB)`JKEa6slJUagX1Xy{+~-Qz!RqYW?=Cae%&bbIw)b#OeO8)~9b zWvjg3dC9JL!IibHciIbHD6ZrGEug6NDWtP_(W^gBEKo0M&x~KA?&uLN*1IGBbCT^z zO-1vT7`I5dr`h{+`|nmw+U|TxDc7~@yXem>#oru?o3@)uDyGP*x|}~Dpm@8!Kj>L? k;s0y1KxS|_0gYt)Z11sSk@(LFhI)_%p00i_>zopr0B!?WX#fBK literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test7.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test7.htm new file mode 100644 index 0000000..ec532ea --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test7.htm @@ -0,0 +1,19 @@ + +
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test7.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test7.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..ebf17da118f797ee9778871d0944846654eb9b46 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0y~yV7vole`8_>l8n9gnt&8%fKQ0)|Ns9PQWz$@n{c10 zmklV`<>}%WQo;E4tRZiMg8*xw)SrtD9Dfv;LXr|D|7JDWp8xA)w2J2>F#5IIHdFhU zsnyQdyPqY?roJh;SD4u_aLX-R(-qqKwHjR_&02|!md Nc)I$ztaD0e0symYSiS%N literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test8.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test8.htm new file mode 100644 index 0000000..8d68ac8 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test8.htm @@ -0,0 +1,32 @@ + +
    +
  • Item1
  • +
  • Item2
  • +
  • Item3
  • +
+
    +
  1. Item1
  2. +
  3. Item2
  4. +
  5. Item3
  6. +
+
+ Item1 + Item2 + hello + Item3 +
\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test8.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test8.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..c331ca5d6820587c9be1b34d938b9496a7b07532 GIT binary patch literal 558 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OKA20$5hRfXcZx|RDzk0ejhEy=Vy}I$@It78& zho=L0RRscPZ*EMD;AvDemB?jubBjsQ{_$-7X5&{kTRunzRQxY(S9EIO5L9xR*LLOF z?nQ#KC*C!#iBKu)6*_p_bi%aD(GwmtP6=0dY$P{H%%n+H>Au^$c^pzZ=l}Y{bfl{2 z;KYwt=F4?>`%B4bW`1_y65BYB1)}Q4WO;$(^FsKKB&r`|*5v&E@MFhUmO9Ce&S1+r z1Qb81yHu~w)pJOZcbLsB5Xl?4@PvhWuh7QhrOLX?w<`cOWw^*raILmg(G6g+bFu5` z`0BF2W7~2&d-$S@2()?D*Cmb<&sJ@6oS0;*8K4s3&m_{wKapSg%}IZt z>?+%@qWdN{CX^dZWN>@duFv_Wd?mBeHQ_g@E&M=F1Kr`&a$>%~+r8U%GEA&x0J+CB zrB&(i922i2a@Tve%(>UZ!oOsJV5x)H+piP0I0*g__-O#t->7tR-mgD3Pd3eq33#$) z`M(w=J=@G{li6I&UY}%h?fSaJVPb+UC&=OZdZ+Ar&)4y2o?f_0mzLT7n(=PII z%6U}sDs4UVU+)djM5BLqZ#`pjIi`EXZ2EHL>kf;KaCcp)R<{iO;m6i=_oNGx#7y~p pDj)7I&iv9*DR9urnR;o_%LVO(G~Ec)I$ztaD0e0su+N=(Yd= literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test9.htm b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test9.htm new file mode 100644 index 0000000..34d34e0 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test9.htm @@ -0,0 +1,2 @@ +

BIG WORDS.
WORDS
MORE
AAA
+
MORE WORDS.

diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test9.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/test9.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..6f661fcb7cea7d51c7209caacaecce420401f48d GIT binary patch literal 698 zcmV;r0!96aP)tp7g-#j0Stk38gKPl4R&qbIZNTgVZ5TQNskqD3Bdm^I4S+mpl)y}Y4i2c**B6?m$ zXd#wU^I3>zOU(iCNxZXw?!8OlBM3IPY8=ORR+ab2> zNZmF+004lS^0M+Yx(S~2F3^9%n^tB6Dq zB41-(vWRA1c4;qR{EAtK!bC<_0g-n~vxn!0(N)}lGJaE(a3fKY$bL7viW`t(4q3vD z6p<)JWRW4V?9yJs_!YAdMTtC(E&?Ja9KP=kB1FpgO^>6CfQUqquzv_5M8w<(M7D;Q zg(ybk?Fzp}7xr0RujKDW7xv$Z{0JBE{6sFNzd!^c|0;5)jkn$gr6yA&XFN-h$<$v7 zTQp6ErHHj>A~ohH5`>6M_f))f?;?zQ#QJ(8_cbz#XOC2onMCL#D-mar$yE_*dq?kl zMW$o36Gl0fBBt~OEks(4)b^#>If`6PLj)oaktXu + .test + { + font-size: 1em; + } + .test:before + { + content: "\007B\002A["; + font-size: 2em; + } + .test:after + { + content: "]\002A\007D"; + font-size: 2em; + } + + +Hello \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/text-before-after.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/text-before-after.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..118a608d469bdc7e0af2fafffef8bb41b54f7574 GIT binary patch literal 232 zcmVP)Y@3!>q zgu|dsKTUgq5YJwnVdgV<7YK;qvJ3Cr0U}&>=G#EcZ{rN06FV}HtY9*etTa +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pretium aenean pharetra magna ac placerat. Augue lacus viverra vitae congue. Enim tortor at auctor urna nunc id cursus. Lobortis elementum nibh tellus molestie nunc non blandit massa. Donec ultrices tincidunt arcu non sodales neque sodales ut. Et netus et malesuada fames. Nibh cras pulvinar mattis nunc sed blandit libero. Faucibus turpis in eu mi bibendum neque egestas. Odio facilisis mauris sit amet massa vitae. Massa tempor nec feugiat nisl pretium fusce id velit. +

+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pretium aenean pharetra magna ac placerat. Augue lacus viverra vitae congue. Enim tortor at auctor urna nunc id cursus. Lobortis elementum nibh tellus molestie nunc non blandit massa. Donec ultrices tincidunt arcu non sodales neque sodales ut. Et netus et malesuada fames. Nibh cras pulvinar mattis nunc sed blandit libero. Faucibus turpis in eu mi bibendum neque egestas. Odio facilisis mauris sit amet massa vitae. Massa tempor nec feugiat nisl pretium fusce id velit. +

+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pretium aenean pharetra magna ac placerat. Augue lacus viverra vitae congue. Enim tortor at auctor urna nunc id cursus. Lobortis elementum nibh tellus molestie nunc non blandit massa. Donec ultrices tincidunt arcu non sodales neque sodales ut. Et netus et malesuada fames. Nibh cras pulvinar mattis nunc sed blandit libero. Faucibus turpis in eu mi bibendum neque egestas. Odio facilisis mauris sit amet massa vitae. Massa tempor nec feugiat nisl pretium fusce id velit. +

\ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/text-justify.htm.png b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render/text-justify.htm.png new file mode 100644 index 0000000000000000000000000000000000000000..d9482f4fab1031b517c54253e049f18f8873ac1c GIT binary patch literal 10626 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 N1F_OlLR+okhX%uJc z#kV5647>B|5_yQnqCvK5B$jE!g;4HIxN`qe?tkLaDLb(L|A$NYIG=D>MObtN59pOm zizrX;0NrA3W@~e)_1d=@Q;e>u>6!F{_s@h)*D)Du6wXag!W3aEz3(yS)#Xsz1|afE z8B}aZfJ%8Ovjh0+v&u-PthoP)mmL33Uh4VZcnQy90|kM{3nu6Zn)}Q2i_4rei)}Jsf1>Eeyb54pk8&G>GTb*S8tM zi9cuk>O+1BX)%tNtq9<|4$jW~d)p5)U)cwvoXDn)Y6Oy<1VnJ=M_1n>fqBV1@5 zFL%2Y+`BjYJ2(S>_xJNh)t+{pbp*kea`?DkTLrKDm>~RoDqT~RA71*smB8aAK|6X& z1_kEzim@#i7&uxLEt&2gexHUzDvw8MYvTM!Ar;@OB#o}%kg89fguIsr`8C5r7L)Za ztG>hjI-Ty)UET1+Dl6X0JpvD*j6q0tE5+VzSjI0_d~;*}+vq8ZN^-|_{CW|kZP>;a zcYb}tb!I)sXFALUMfLIJ_ne4pNu-djNuy?5L%OqGj=Rc731{J(gNEeeV{}ysyF$A~ zQzco?9P2;%=glVhIY}hf;Um!rBTh{m_&5Xs)(Wn8SxtoL;S6-(wr3Nz|CEz&b$_$? z(P5-Q^D!g4MqzR#zein3EIWA#2DSDq&B8QyGC15d=f<+_^S|p$BZ_;(7}XqBk{AVY zPN6{D4Yt@<0X(qSpD8s)-J5NHRdW>!uC`n&H~)+&`=@o^#Oh|MNfJmS57O;||1d8{ zq|27^ZYrx}S?lIqd-d;P z+p*pOHnR<^VTH&2TU_g3O-0}k9oeypNJ~p56n?2 z3fHR_uSMZ6Y!=E_<={i6@?i4(EXb-bLk){~;{#GNBJ&F$GDwjZRE5Ky$CEhwgl3-& zhMG0_>R3iau9g8>U)9D-roR4*YM5bC{Gz+YvXKx zqU44yTcI_|Dn*1X77KV4@|Dr?KkW@nh+6(m%Vl{oTv-;0PC0t?u={5U2COrq8y_;= z6Y|)|>Z$lJyJpAaF0gw$BC-JG`pN%QS3eg9MRq>ap3;6(Q6ii-(1CU^iV(WWb=?v8 zmSQ4`#ruD7(sLp^5t9~C=Vq%|}u(RDMZoO;R^itw! zhA9z2bv)q$bN3lukQnJ)sBA$X?l$J-2P(Xp#hsL+`n39_Cg=>6rMNs^p1_}-&+_(f z=B>ZIYpp>@k4NY;@=52oJ}5d3{M$kDZqlIYg}uQ&A)n5*&q6gBG$z80WAyd23%Mtj z7V*xN>iUdjm5s|YeM+NkP8S2u+Q~k91S_4H=48pFFn0VQ+Nr}gKfaU3QYCtMdZd1~ z|95T)=eBBv6QciOC1lsLZ8f=II6g!r{N}5UAXVD;5B-0?oF+cOrUX5@WkRUiaD(l6 zp)Cc`A-AF>E9RSvqNSf{|BMwGr#D-V6cxz#N0$wLPL58q7f@}=yRcDAP`rmnNVHjM z>o|@t>6&RDqTw!>K?3_=L7d&B z+(?}L;wvF+ds|;IbxU8dco_?G?R;-IhdhWZ8KzQ7D6)?(m! zzt6@jtrzl4+n)fdQ7R4-7*yblQt=g1&>@7LM*LelnxII>AD3%g!cBK{`*;n8rOOYW z98U(Na(R+)l46d@rkM1T@M_9WZO54)csK7)=zZ^7G?=7hN^0{=QfQ3w9_o~Rakxg` zI3R1Fmg!y@HyvcQx5_JUqeGO{Go7$MPSmWyyd&#XgJ9w>H#(#)1RtlI+4+6b8lT80 zc_l7Kt!QkE04vj)et8RHe)#bES5@#W#Tm9Je8A);VfJfhcSSEdg>hy`RpvxYWPqAg z)IHYC4uT&j1|B)lR%aibdxA~BHtjXWnXx)DJMK`n6m`6DO$pMyV_XyFe|Qwwt_YK} Xqk}$}@wQ%Hqr_8J)mHhAFbn=)sbx({ literal 0 HcmV?d00001 diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render_test.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render_test.cpp new file mode 100644 index 0000000..4c5c7aa --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/render_test.cpp @@ -0,0 +1,108 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#ifdef _WIN32 + #include "dirent.h" +#else + #include +#endif +#include "../containers/test/test_container.h" +#include "../containers/test/Bitmap.h" +using namespace std; + +vector find_htm_files(); +void test(string filename); + +const char* test_dir = "../test/render"; // ctest is run from litehtml/build + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +using render_test = testing::TestWithParam; + +TEST_P(render_test, _) +{ + test(string(test_dir) + "/" + GetParam()); +} + +INSTANTIATE_TEST_SUITE_P(, render_test, testing::ValuesIn(find_htm_files())); +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +void error(const char* msg) { puts(msg); exit(1); } + +void read_dir(const string& subdir, vector& files) +{ + string full_path = string(test_dir) + "/" + subdir; + DIR* dir = opendir(full_path.c_str()); + if (!dir) error(full_path.c_str()); + while (dirent* ent = readdir(dir)) + { + string name = ent->d_name; + if (ent->d_type == DT_DIR) + { + if(name != "." && name != ".." && name[0] != '-') + { + read_dir(subdir + "/" + name, files); + } + } else if (ent->d_type == DT_REG) + { + if (name[0] != '-' && name.size() > 4 && + (name.substr(name.size() - 4) == ".htm" || name.substr(name.size() - 5) == ".html")) + files.push_back(subdir + "/" + name); + } + } + closedir(dir); +} + +vector find_htm_files() +{ + vector ret; + read_dir("", ret); + sort(ret.begin(), ret.end()); + return ret; +} + +string readfile(string filename) +{ + stringstream ss; + ifstream(filename) >> ss.rdbuf(); + return ss.str(); +} + +Bitmap draw(document::ptr doc, int width, int height) +{ + Bitmap bmp(width, height); + position clip(0, 0, width, height); + + doc->draw((uint_ptr)&bmp, 0, 0, &clip); + + bmp.resize(width, height); + + return bmp; +} + +void test(string filename) +{ + string html = readfile(filename); + + int width = 800, height = 1600; // image will be cropped to content_width/content_height + auto last_slash_pos = filename.find_last_of('/'); + string base_path; + if(last_slash_pos != string::npos) + { + base_path = filename.substr(0, last_slash_pos); + } else + { + base_path = test_dir; + } + test_container container(width, height, base_path); + + auto doc = document::createFromString(html.c_str(), &container); + doc->render(width); + Bitmap bmp = draw(doc, doc->content_width(), doc->content_height()); + + Bitmap good(filename + ".png"); + if (bmp != good) + { + bmp.save(filename + "-FAILED.png"); + ASSERT_TRUE(false); + } +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/tstring_view_test.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/tstring_view_test.cpp new file mode 100644 index 0000000..026e792 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/tstring_view_test.cpp @@ -0,0 +1,77 @@ +// Copyright (C) 2020-2021 Primate Labs Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the names of the copyright holders nor the names of their +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "litehtml/tstring_view.h" + +#include + +#include + +using namespace litehtml; + +TEST(TStringViewTest, DefaultConstructor) +{ + tstring_view view; + + EXPECT_EQ(nullptr, view.data()); + EXPECT_EQ(0, view.size()); + EXPECT_TRUE(view.empty()); +} + +TEST(TStringViewTest, Constructor) +{ + constexpr size_t offset = 5; + constexpr size_t length = 10; + + string string = "the quick brown fox jumps over the lazy dog"; + tstring_view view(string.data() + offset, length); + + EXPECT_EQ(string.data() + offset, view.data()); + EXPECT_EQ(length, view.size()); + EXPECT_FALSE(view.empty()); + + for (size_t i = 0; i < view.size(); i++) { + EXPECT_EQ(string[offset + i], view[i]); + } +} + +TEST(TStringViewTest, RangeForLoop) +{ + constexpr size_t offset = 5; + constexpr size_t length = 10; + + string string = "the quick brown fox jumps over the lazy dog"; + tstring_view view(string.data() + offset, length); + + for (auto c : view) { + // TODO: How can we automatically (rather than manually) verify the + // iterator is working properly here? + std::cout << c << std::endl; + } +} diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/url_path_test.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/url_path_test.cpp new file mode 100644 index 0000000..72349cc --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/url_path_test.cpp @@ -0,0 +1,150 @@ +// Copyright (C) 2020-2021 Primate Labs Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the names of the copyright holders nor the names of their +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "litehtml/url_path.h" + +#include +#include +#include + +#include + +using namespace litehtml; + +namespace { + +struct url_path_testcase { + string base; + string path; + string expected; +}; + +} // namespace + +TEST(URLPathTest, Absolute) +{ + std::vector> testcases = { + { "", false }, + { "a", false }, + { "a/", false }, + { "a/b", false }, + { "a/b/", false }, + { "a/b/c", false }, + { "a/b/c/", false }, + + { "/", true }, + { "/a", true }, + { "/a/", true }, + { "/a/b", true }, + { "/a/b/", true }, + { "/a/b/c", true }, + { "/a/b/c/", true }, + }; + + for (auto& testcase : testcases) { + EXPECT_EQ(testcase.second, is_url_path_absolute(testcase.first)); + } +} + +TEST(URLPathTest, DirectoryName) +{ + std::vector> testcases = { + { "", "." }, + { "a", "." }, + { "a/", "a/" }, + { "a/b", "a/" }, + { "a/b/", "a/b/" }, + { "a/b/c", "a/b/" }, + { "a/b/c/", "a/b/c/" }, + + { "/", "/" }, + { "/a", "/" }, + { "/a/", "/a/" }, + { "/a/b", "/a/" }, + { "/a/b/", "/a/b/" }, + { "/a/b/c", "/a/b/" }, + { "/a/b/c/", "/a/b/c/" }, + }; + + for (auto& testcase : testcases) { + EXPECT_EQ(testcase.second, url_path_directory_name(testcase.first)); + } +} + +TEST(URLPathTest, BaseName) +{ + std::vector> testcases = { + { "", "" }, + { "a", "a" }, + { "a/", "" }, + { "a/b", "b" }, + { "a/b/", "" }, + { "a/b/c", "c" }, + { "a/b/c/", "" }, + + { "/", "" }, + { "/a", "a" }, + { "/a/", "" }, + { "/a/b", "b" }, + { "/a/b/", "" }, + { "/a/b/c", "c" }, + { "/a/b/c/", "" }, + }; + + for (auto& testcase : testcases) { + EXPECT_EQ(testcase.second, url_path_base_name(testcase.first)); + } +} + +TEST(URLPathTest, Append) +{ + std::vector testcases = { + { "", "a", "a" }, + { "/", "a", "/a" }, + { "/a", "", "/a" }, + { "/a", "b", "/a/b" }, + }; + + for (auto& testcase : testcases) { + EXPECT_EQ(testcase.expected, url_path_append(testcase.base, testcase.path)); + } +} + +TEST(URLPathTest, Resolve) +{ + std::vector testcases = { + { "/", "a", "/a" }, + { "/a", "b", "/b" }, + { "/a", "/b", "/b" }, + }; + + for (auto& testcase : testcases) { + EXPECT_EQ(testcase.expected, url_path_resolve(testcase.base, testcase.path)); + } +} \ No newline at end of file diff --git a/src/assistant/qlitehtml/src/3rdparty/litehtml/test/url_test.cpp b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/url_test.cpp new file mode 100644 index 0000000..f98ec5f --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/litehtml/test/url_test.cpp @@ -0,0 +1,206 @@ +// Copyright (C) 2020-2021 Primate Labs Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the names of the copyright holders nor the names of their +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "litehtml/url.h" + +#include + +#include + +using namespace litehtml; + +namespace { + +struct url_parse_testcase { + string str; + string scheme; + string authority; + string path; + string query; + string fragment; +}; + +struct url_resolve_testcase { + string base; + string reference; + string expected; +}; + +} // namespace + +TEST(URLTest, DefaultConstructor) +{ + url u; + + EXPECT_TRUE(u.scheme().empty()); + EXPECT_TRUE(u.authority().empty()); + EXPECT_TRUE(u.path().empty()); + EXPECT_TRUE(u.query().empty()); + EXPECT_TRUE(u.fragment().empty()); +} + +TEST(URLTest, Parse) +{ + std::vector testcases = { + + // Example from RFC 3986 that includes a scheme, an authority, a path, + // a query, and a fragment. + { "foo://example.com:8042/over/there?name=ferret#nose", + "foo", "example.com:8042", "/over/there", "name=ferret", "nose" }, + + // Example from RFC 3986 that only includes a scheme and a path. + { "urn:example:animal:ferret:nose", + "urn", "", "example:animal:ferret:nose", "", "" }, + + { "http://www.litehtml.com/", + "http", "www.litehtml.com", "/", "", "" }, + + { "https://www.slashdot.org/", + "https", "www.slashdot.org", "/", "", "" }, + + { "https://www.slashdot.org", + "https", "www.slashdot.org", "", "", "" }, + + { "https://news.slashdot.org/story/21/09/24/2157247/", + "https", "news.slashdot.org", "/story/21/09/24/2157247/", "", "" }, + + { "https://www.cbc.ca/news/politics/spavor-kovrig-return-1.6189516", + "https", "www.cbc.ca", "/news/politics/spavor-kovrig-return-1.6189516", "", "" }, + + { "https://twitter.com/geekbench/status/1412433598200823810", + "https", "twitter.com", "/geekbench/status/1412433598200823810", "", "" }, + + { "https://browser.geekbench.com/v5/cpu/search?q=ryzen", + "https", "browser.geekbench.com", "/v5/cpu/search", "q=ryzen", "" }, + + { "https://datatracker.ietf.org/doc/html/rfc3986#section-2.2", + "https", "datatracker.ietf.org", "/doc/html/rfc3986", "", "section-2.2" }, + + { "file:///home/litehtml/build/hipster.html", + "file", "", "/home/litehtml/build/hipster.html" }, + + { "/home/litehtml/Projects/litehtml/build/hipster.html", + "", "", "/home/litehtml/Projects/litehtml/build/hipster.html" }, + }; + + for (auto& testcase : testcases) { + url u(testcase.str); + + EXPECT_EQ(testcase.scheme, u.scheme()); + EXPECT_EQ(testcase.authority, u.authority()); + EXPECT_EQ(testcase.path, u.path()); + EXPECT_EQ(testcase.query, u.query()); + EXPECT_EQ(testcase.fragment, u.fragment()); + } +} + +TEST(URLTest, Build) +{ + std::vector testcases = { + + // Example from RFC 3986 that includes a scheme, an authority, a path, + // a query, and a fragment. + { "foo://example.com:8042/over/there?name=ferret#nose", + "foo", "example.com:8042", "/over/there", "name=ferret", "nose" }, + + // Example from RFC 3986 that only includes a scheme and a path. + { "urn:example:animal:ferret:nose", + "urn", "", "example:animal:ferret:nose", "", "" }, + + { "http://www.litehtml.com/", + "http", "www.litehtml.com", "/", "", "" }, + + { "https://www.slashdot.org/", + "https", "www.slashdot.org", "/", "", "" }, + + { "https://www.slashdot.org", + "https", "www.slashdot.org", "", "", "" }, + + { "https://news.slashdot.org/story/21/09/24/2157247/", + "https", "news.slashdot.org", "/story/21/09/24/2157247/", "", "" }, + + { "https://www.cbc.ca/news/politics/spavor-kovrig-return-1.6189516", + "https", "www.cbc.ca", "/news/politics/spavor-kovrig-return-1.6189516", "", "" }, + + { "https://twitter.com/geekbench/status/1412433598200823810", + "https", "twitter.com", "/geekbench/status/1412433598200823810", "", "" }, + + { "https://browser.geekbench.com/v5/cpu/search?q=ryzen", + "https", "browser.geekbench.com", "/v5/cpu/search", "q=ryzen", "" }, + + { "https://datatracker.ietf.org/doc/html/rfc3986#section-2.2", + "https", "datatracker.ietf.org", "/doc/html/rfc3986", "", "section-2.2" }, + + // Disabled since the url class does not regenerate the same URL for + // this test case (it does not emit the double slash at the start of + // the authority). How do we determine which schemes require the double + // slash and which ones do not? + + // { "file:///home/litehtml/build/hipster.html", + // "file", "", "/home/litehtml/build/hipster.html" }, + + { "/home/litehtml/Projects/litehtml/build/hipster.html", + "", "", "/home/litehtml/Projects/litehtml/build/hipster.html" }, + }; + + for (auto& testcase : testcases) { + url u(testcase.scheme, + testcase.authority, + testcase.path, + testcase.query, + testcase.fragment); + EXPECT_EQ(testcase.str, u.str()); + } + +} + +TEST(URLTest, Resolve) +{ + std::vector testcases = { + { "https://www.twitter.com/", "/foo", + "https://www.twitter.com/foo" }, + + { "https://www.twitter.com/", "https://www.facebook.com/", + "https://www.facebook.com/" }, + + { "https://www.example.com/index.html", "about.html", + "https://www.example.com/about.html" }, + }; + + for (auto& testcase : testcases) { + url u = resolve(url(testcase.base), url(testcase.reference)); + url expected(testcase.expected); + + EXPECT_EQ(expected.scheme(), u.scheme()); + EXPECT_EQ(expected.authority(), u.authority()); + EXPECT_EQ(expected.path(), u.path()); + EXPECT_EQ(expected.query(), u.query()); + EXPECT_EQ(expected.fragment(), u.fragment()); + } +} diff --git a/src/assistant/qlitehtml/src/3rdparty/qt_attribution.json b/src/assistant/qlitehtml/src/3rdparty/qt_attribution.json new file mode 100644 index 0000000..8a363d2 --- /dev/null +++ b/src/assistant/qlitehtml/src/3rdparty/qt_attribution.json @@ -0,0 +1,39 @@ +[ + { + "Id": "litehtml-gumbo", + "Name": "Gumbo", + "QDocModule": "qtassistant", + "QtParts": ["tools"], + "QtUsage": "Used in Qt Assistant.", + "Path": "litehtml/src/gumbo", + "Description": "Gumbo is an implementation of the HTML5 parsing algorithm implemented as a pure C99 library with no outside dependencies.", + "Homepage": "https://github.com/google/gumbo-parser", + "PURL": [ + "pkg:github/google/gumbo-parser@$" + ], + "Comment": "No relevant CPE found", + "Version": "0.10.1", + "LicenseId": "Apache-2.0 AND MIT", + "License": "Apache License 2.0 AND MIT", + "CopyrightFile": "GUMBO-AUTHORS.txt" + }, + { + "Id": "litehtml", + "Name": "litehtml", + "QDocModule": "qtassistant", + "QtParts": ["tools"], + "QtUsage": "Used in Qt Assistant.", + "Path": "litehtml", + "Description": "litehtml is the lightweight HTML rendering engine with CSS2/CSS3 support.", + "Homepage": "https://github.com/litehtml/litehtml", + "PURL": [ + "pkg:github/litehtml/litehtml@v$" + ], + "Comment": "No relevant CPE found", + "Version": "0.9", + "LicenseId": "BSD-3-Clause", + "License": "BSD 3-Clause \"New\" or \"Revised\" License", + "LicenseFile": "litehtml/LICENSE", + "Copyright": "Copyright (c) 2013, Yuri Kobets (tordex)" + } +] diff --git a/src/assistant/qlitehtml/src/CMakeLists.txt b/src/assistant/qlitehtml/src/CMakeLists.txt new file mode 100644 index 0000000..6400a38 --- /dev/null +++ b/src/assistant/qlitehtml/src/CMakeLists.txt @@ -0,0 +1,175 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# set the following variables before adding as subdirectory into a project +# QLITEHTML_BIN_PATH - relative install path for result DLLs +# QLITEHTML_LIBRARY_PATH - relative install path for result dynamic libraries +# QLITEHTML_LIBRARY_ARCHIVE_PATH - relative install path for result archives (.lib, static lib), +# defaults to QLITEHTML_LIBRARY_PATH +# QLITEHTML_EXPORT - export name for qlitehtml +# QLITEHTML_DEVEL_COMPONENT - component name for development installation +# QLITEHTML_DEVEL_EXCLUDE_FROM_ALL - if development component should not be installed by default +# QLITEHTML_HEADER_PATH - relative install path for development headers +# QLITEHTML_LIBRARY_TYPE - SHARED or STATIC, defaults to SHARED +set(QLITEHTML_VERSION ${PROJECT_VERSION}) +set(QLITEHTML_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(QLITEHTML_VERSION_COMPAT ${QLITEHTML_VERSION} CACHE STRING "qlitehtml compat version number.") + +option(QLITEHTML_USE_SYSTEM_LITEHTML "Uses litehtml from the system if available" OFF) +if(QLITEHTML_USE_SYSTEM_LITEHTML) + find_package(litehtml QUIET) +endif() +if(NOT TARGET litehtml AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/litehtml/CMakeLists.txt) + set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # make it possible to override LITEHTML_BUILD_TESTING + set(ORIG_FPIC ${CMAKE_POSITION_INDEPENDENT_CODE}) + set(ORIG_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}) + if (WIN32) + set(LITEHTML_UTF8 ON CACHE BOOL "") + endif() + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + set(BUILD_SHARED_LIBS OFF) + set(LITEHTML_BUILD_TESTING OFF) + + add_subdirectory(3rdparty/litehtml EXCLUDE_FROM_ALL) + + # suppress compiler warnings from litehtml + set_target_properties( + litehtml + PROPERTIES + QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + ) + set_target_properties( + gumbo + PROPERTIES + QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON + ) + + # suppress compiler warnings from litehtml headers by making them "system" + # include directories + get_target_property(litehtml_includedirs litehtml INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(litehtml SYSTEM INTERFACE ${litehtml_includedirs}) + + set(CMAKE_POSITION_INDEPENDENT_CODE "${ORIG_FPIC}") + set(BUILD_SHARED_LIBS ${ORIG_BUILD_SHARED_LIBS}) + # force optimized litehtml even in debug + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + # except for windows + if (NOT WIN32) + target_compile_options(gumbo PRIVATE -O2) + target_compile_options(litehtml PRIVATE -O2) + endif() + endif() +endif() + +# TODO error if litehtml was not found? + +if(TARGET litehtml) + set(PUBLIC_HEADERS + container_qpainter.h + container_qpainter_p.h + qlitehtml_global.h + qlitehtmlwidget.h) + if(NOT QLITEHTML_LIBRARY_TYPE) + set(QLITEHTML_LIBRARY_TYPE SHARED) + endif() + if(NOT QLITEHTML_LIBRARY_ARCHIVE_PATH) + set(QLITEHTML_LIBRARY_ARCHIVE_PATH "${QLITEHTML_LIBRARY_PATH}") + endif() + + add_library(qlitehtml ${QLITEHTML_LIBRARY_TYPE} ${PUBLIC_HEADERS} container_qpainter.cpp qlitehtmlwidget.cpp) + + target_include_directories(qlitehtml PUBLIC $) + target_link_libraries(qlitehtml PUBLIC Qt${QT_VERSION_MAJOR}::Widgets PRIVATE litehtml) + target_compile_definitions(qlitehtml PRIVATE + QT_NO_JAVA_STYLE_ITERATORS + QT_NO_CAST_TO_ASCII QT_RESTRICTED_CAST_FROM_ASCII + QT_USE_QSTRINGBUILDER) + if("${QLITEHTML_LIBRARY_TYPE}" STREQUAL "SHARED") + target_compile_definitions(qlitehtml PRIVATE QLITEHTML_LIBRARY) + else() + target_compile_definitions(qlitehtml PUBLIC QLITEHTML_STATIC_LIBRARY) + endif() + if(TARGET Qt${QT_VERSION_MAJOR}::PrintSupport AND QT_FEATURE_printer) + target_link_libraries(qlitehtml PRIVATE Qt${QT_VERSION_MAJOR}::PrintSupport) + target_compile_definitions(qlitehtml PRIVATE QLITEHTML_HAS_QPRINTER=1) + else() + target_compile_definitions(qlitehtml PRIVATE QLITEHTML_HAS_QPRINTER=0) + endif() + if (WIN32) + target_compile_definitions(qlitehtml PRIVATE UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS) + if (NOT BUILD_WITH_PCH) + # Windows 8 0x0602 + target_compile_definitions(qlitehtml PRIVATE WINVER=0x0602 _WIN32_WINNT=0x0602 + WIN32_LEAN_AND_MEAN) + endif() + endif() + + set_target_properties(qlitehtml PROPERTIES + SOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + VERSION "${QLITEHTML_VERSION}" + SOVERSION "${QLITEHTML_VERSION_MAJOR}" + MACHO_CURRENT_VERSION "${QLITEHTML_VERSION}" + MACHO_COMPATIBILITY_VERSION "${QLITEHTML_VERSION_COMPAT}" + CXX_EXTENSIONS OFF + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON) + + if(WIN32) + set_target_properties(qlitehtml PROPERTIES + SUFFIX "${QLITEHTML_VERSION_MAJOR}${CMAKE_${QLITEHTML_LIBRARY_TYPE}_LIBRARY_SUFFIX}" + PREFIX "" + IMPORT_SUFFIX "${QLITEHTML_VERSION_MAJOR}${CMAKE_IMPORT_LIBRARY_SUFFIX}" + IMPORT_PREFIX "") + endif() + + if(DEFINED QLITEHTML_BIN_PATH AND DEFINED QLITEHTML_LIBRARY_PATH) + set(_EXPORT) + if(DEFINED QLITEHTML_EXPORT) + set(_EXPORT EXPORT ${QLITEHTML_EXPORT}) + add_library(${QLITEHTML_EXPORT}::qlitehtml ALIAS qlitehtml) + endif() + + set(_DEVEL_COMPONENT) + set(_NAMELINK) + if(DEFINED QLITEHTML_DEVEL_COMPONENT) + set(_EXCLUDE) + if(QLITEHTML_DEVEL_EXCLUDE_FROM_ALL) + set(_EXCLUDE EXCLUDE_FROM_ALL) + endif() + set(_DEVEL_COMPONENT COMPONENT ${QLITEHTML_DEVEL_COMPONENT} ${_EXCLUDE}) + set(_NAMELINK NAMELINK_SKIP) + install(TARGETS qlitehtml + LIBRARY + DESTINATION "${QLITEHTML_LIBRARY_PATH}" + NAMELINK_ONLY + ${_DEVEL_COMPONENT}) + if(DEFINED QLITEHTML_HEADER_PATH) + install( + FILES ${PUBLIC_HEADERS} + DESTINATION ${QLITEHTML_HEADER_PATH} + ${_DEVEL_COMPONENT}) + target_include_directories(qlitehtml PUBLIC $) + endif() + endif() + + install(TARGETS qlitehtml + ${_EXPORT} + RUNTIME + DESTINATION "${QLITEHTML_BIN_PATH}" + OPTIONAL + LIBRARY + DESTINATION "${QLITEHTML_LIBRARY_PATH}" + ${_NAMELINK} + OPTIONAL + OBJECTS + DESTINATION "${QLITEHTML_LIBRARY_PATH}" + ${_DEVEL_COMPONENT} + OPTIONAL + ARCHIVE + DESTINATION "${QLITEHTML_LIBRARY_ARCHIVE_PATH}" + ${_DEVEL_COMPONENT} + OPTIONAL) + endif() + +endif() diff --git a/src/assistant/qlitehtml/src/container_qpainter.cpp b/src/assistant/qlitehtml/src/container_qpainter.cpp new file mode 100644 index 0000000..00d77d7 --- /dev/null +++ b/src/assistant/qlitehtml/src/container_qpainter.cpp @@ -0,0 +1,1479 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "container_qpainter.h" +#include "container_qpainter_p.h" + +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +const int kDragDistance = 5; + +using Font = QFont; +using Context = QPainter; + +#ifdef Q_STATIC_LOGGING_CATEGORY +Q_STATIC_LOGGING_CATEGORY(log, "qlitehtml", QtCriticalMsg) +#else +static Q_LOGGING_CATEGORY(log, "qlitehtml", QtCriticalMsg) +#endif + +static QFont toQFont(litehtml::uint_ptr hFont) +{ + return *reinterpret_cast(hFont); +} + +static QPainter *toQPainter(litehtml::uint_ptr hdc) +{ + return reinterpret_cast(hdc); +} + +static QRect toQRect(litehtml::position position) +{ + return {position.x, position.y, position.width, position.height}; +} + +static bool isVisible(const litehtml::element::ptr &element) +{ + // TODO render_item::is_visible() would also take m_skip into account, so this might be wrong + return element->css().get_display() != litehtml::display_none + && element->css().get_visibility() == litehtml::visibility_visible; +} + +static litehtml::elements_list path(const litehtml::element::ptr &element) +{ + litehtml::elements_list result; + litehtml::element::ptr current = element; + while (current) { + result.push_back(current); + current = current->parent(); + } + std::reverse(std::begin(result), std::end(result)); + return result; +} + +// +static std::tuple +getCommonParent(const litehtml::elements_list &a, const litehtml::elements_list &b) +{ + litehtml::element::ptr parent; + auto ait = a.cbegin(); + auto bit = b.cbegin(); + while (ait != a.cend() && bit != b.cend()) { + if (*ait != *bit) + return {parent, *ait, *bit}; + parent = *ait; + ++ait; + ++bit; + } + return {parent, + (ait != a.cend() ? *ait : litehtml::element::ptr()), + (bit != b.cend() ? *bit : litehtml::element::ptr())}; +} + +static std::pair getStartAndEnd(const Selection::Element &a, + const Selection::Element &b) +{ + if (a.element == b.element) { + if (a.index <= b.index) + return {a, b}; + return {b, a}; + } + const litehtml::elements_list aPath = path(a.element); + const litehtml::elements_list bPath = path(b.element); + litehtml::element::ptr commonParent; + litehtml::element::ptr aBranch; + litehtml::element::ptr bBranch; + std::tie(commonParent, aBranch, bBranch) = getCommonParent(aPath, bPath); + if (!commonParent) { + qWarning() << "internal error: litehtml elements do not have common parent"; + return {a, b}; + } + if (commonParent == a.element) + return {a, a}; // 'a' already contains 'b' + if (commonParent == b.element) + return {b, b}; + // find out if a or b is first in the child sub-trees of commonParent + for (const litehtml::element::ptr &child : commonParent->children()) { + if (child == aBranch) + return {a, b}; + if (child == bBranch) + return {b, a}; + } + qWarning() << "internal error: failed to find out order of litehtml elements"; + return {a, b}; +} + +// 1) stops right away if element == stop, otherwise stops whenever stop element is encountered +// 2) moves down the first children from element until there is none anymore +static litehtml::element::ptr firstLeaf(const litehtml::element::ptr &element, + const litehtml::element::ptr &stop) +{ + if (element == stop) + return element; + litehtml::element::ptr current = element; + while (current != stop && !current->children().empty()) + current = current->children().front(); + return current; +} + +// 1) stops right away if element == stop, otherwise stops whenever stop element is encountered +// 2) starts at next sibling (up the hierarchy chain) if possible, otherwise root +// 3) returns first leaf of the element found in 2 +static litehtml::element::ptr nextLeaf(const litehtml::element::ptr &element, + const litehtml::element::ptr &stop) +{ + if (element == stop) + return element; + litehtml::element::ptr current = element; + if (!current->is_root()) { + // find next sibling + const litehtml::element::ptr parent = current->parent(); + const litehtml::elements_list &children = parent->children(); + auto childIt = std::find_if(children.cbegin(), + children.cend(), + [¤t](const litehtml::element::ptr &e) { + return e == current; + }); + if (childIt == children.cend()) { + qWarning() << "internal error: filed to find litehtml child element in parent"; + return stop; + } + ++childIt; + if (childIt == children.cend()) // no sibling, move up + return nextLeaf(parent, stop); + current = *childIt; + } + return firstLeaf(current, stop); +} + +static Selection::Element selectionDetails(const litehtml::element::ptr &element, + const QString &text, + const QPoint &pos) +{ + // shortcut, which _might_ not really be correct + if (!element->children().empty()) + return {element, -1, -1}; // everything selected + const QFont &font = toQFont(element->css().get_font()); + const QFontMetrics fm(font); + int previous = 0; + for (int i = 0; i < text.size(); ++i) { + const int width = fm.size(0, text.left(i + 1)).width(); + if ((width + previous) / 2 >= pos.x()) + return {element, i, previous}; + previous = width; + } + return {element, int(text.size()), previous}; +} + +// Returns whether the intended child was found and stop. +// Does a depth-first iteration over elements that "pos" is inside, and executes +// \a action with them. If \a action returns \c true, the iteration is stopped. +static bool deepest_child_at_point(const litehtml::element::ptr &element, + const QPoint &pos, + const QPoint &viewportPos, + const std::function &action, + int level = 0) +{ + // TODO are there elements for which we should take viewportPos into account instead? + // E.g. fixed position elements? + if (!element) + return false /*continue iterating*/; + const QRect placement = toQRect(element->get_placement()); + // Do not continue down elements that do not cover the position. Exceptions: + // - elements with 0 size (includes anchors and other weird elements) + // - html and body, which for some reason have size == viewport size + if (!placement.size().isEmpty() && element->tag() != litehtml::_html_ + && element->tag() != litehtml::_body_ && !placement.contains(pos)) { + return false /*continue iterating*/; + } + // qDebug() << qPrintable(QString(level * 2, ' ')) << element->dump_get_name() << placement << pos; + + const litehtml::elements_list &children = element->children(); + for (auto it = children.cbegin(); it != children.cend(); ++it) { + if (deepest_child_at_point(*it, pos, viewportPos, action, level + 1)) + return true; + } + if (placement.contains(pos)) + return action(element); + return false /*continue iterating*/; +} + +static Selection::Element selection_element_at_point(const litehtml::element::ptr &element, + const QPoint &pos, + const QPoint &viewportPos, + Selection::Mode mode) +{ + Selection::Element result; + deepest_child_at_point(element, + pos, + viewportPos, + [mode, &result, &pos](const litehtml::element::ptr &element) { + const QRect placement = toQRect(element->get_placement()); + std::string text; + element->get_text(text); + if (!text.empty()) { + result = mode == Selection::Mode::Free + ? selectionDetails(element, + QString::fromStdString(text), + pos - placement.topLeft()) + : Selection::Element({element, -1, -1}); + return true; + } + return false; /*continue*/ + }); + return result; +} + +// CSS: 400 == normal, 700 == bold. +// Qt5: 50 == normal, 75 == bold +// Qt6: == CSS +static QFont::Weight cssWeightToQtWeight(int cssWeight) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QFont::Weight(cssWeight); +#else + if (cssWeight <= 400) + return QFont::Weight(cssWeight * 50 / 400); + if (cssWeight >= 700) + return QFont::Weight(75 + (cssWeight - 700) * 25 / 300); + return QFont::Weight(50 + (cssWeight - 400) * 25 / 300); +#endif +} + +static QFont::Style toQFontStyle(litehtml::font_style style) +{ + switch (style) { + case litehtml::font_style_normal: + return QFont::StyleNormal; + case litehtml::font_style_italic: + return QFont::StyleItalic; + } + // should not happen + qWarning(log) << "Unknown litehtml font style:" << style; + return QFont::StyleNormal; +} + +static QColor toQColor(const litehtml::web_color &color) +{ + return {color.red, color.green, color.blue, color.alpha}; +} + +static Qt::PenStyle borderPenStyle(litehtml::border_style style) +{ + switch (style) { + case litehtml::border_style_dotted: + return Qt::DotLine; + case litehtml::border_style_dashed: + return Qt::DashLine; + case litehtml::border_style_solid: + return Qt::SolidLine; + default: + qWarning(log) << "Unsupported border style:" << style; + } + return Qt::SolidLine; +} + +static QPen borderPen(const litehtml::border &border) +{ + return {toQColor(border.color), qreal(border.width), borderPenStyle(border.style)}; +} + +static QCursor toQCursor(const QString &c) +{ + if (c == "alias") + return {Qt::PointingHandCursor}; // ??? + if (c == "all-scroll") + return {Qt::SizeAllCursor}; + if (c == "auto") + return {Qt::ArrowCursor}; // ??? + if (c == "cell") + return {Qt::UpArrowCursor}; + if (c == "context-menu") + return {Qt::ArrowCursor}; // ??? + if (c == "col-resize") + return {Qt::SplitHCursor}; + if (c == "copy") + return {Qt::DragCopyCursor}; + if (c == "crosshair") + return {Qt::CrossCursor}; + if (c == "default") + return {Qt::ArrowCursor}; + if (c == "e-resize") + return {Qt::SizeHorCursor}; // ??? + if (c == "ew-resize") + return {Qt::SizeHorCursor}; + if (c == "grab") + return {Qt::OpenHandCursor}; + if (c == "grabbing") + return {Qt::ClosedHandCursor}; + if (c == "help") + return {Qt::WhatsThisCursor}; + if (c == "move") + return {Qt::SizeAllCursor}; + if (c == "n-resize") + return {Qt::SizeVerCursor}; // ??? + if (c == "ne-resize") + return {Qt::SizeBDiagCursor}; // ??? + if (c == "nesw-resize") + return {Qt::SizeBDiagCursor}; + if (c == "ns-resize") + return {Qt::SizeVerCursor}; + if (c == "nw-resize") + return {Qt::SizeFDiagCursor}; // ??? + if (c == "nwse-resize") + return {Qt::SizeFDiagCursor}; + if (c == "no-drop") + return {Qt::ForbiddenCursor}; + if (c == "none") + return {Qt::BlankCursor}; + if (c == "not-allowed") + return {Qt::ForbiddenCursor}; + if (c == "pointer") + return {Qt::PointingHandCursor}; + if (c == "progress") + return {Qt::BusyCursor}; + if (c == "row-resize") + return {Qt::SplitVCursor}; + if (c == "s-resize") + return {Qt::SizeVerCursor}; // ??? + if (c == "se-resize") + return {Qt::SizeFDiagCursor}; // ??? + if (c == "sw-resize") + return {Qt::SizeBDiagCursor}; // ??? + if (c == "text") + return {Qt::IBeamCursor}; + if (c == "url") + return {Qt::ArrowCursor}; // ??? + if (c == "w-resize") + return {Qt::SizeHorCursor}; // ??? + if (c == "wait") + return {Qt::BusyCursor}; + if (c == "zoom-in") + return {Qt::ArrowCursor}; // ??? + qWarning(log) << QString("unknown cursor property \"%1\"").arg(c).toUtf8().constData(); + return {Qt::ArrowCursor}; +} + +bool Selection::isValid() const +{ + return !selection.isEmpty(); +} + +void Selection::update() +{ + const auto addElement = [this](const Selection::Element &element, + const Selection::Element &end = {}) { + std::string elemText; + element.element->get_text(elemText); + const QString textStr = QString::fromStdString(elemText); + if (!textStr.isEmpty()) { + QRect rect = toQRect(element.element->get_placement()).adjusted(-1, -1, 1, 1); + if (element.index < 0) { // fully selected + text += textStr; + } else if (end.element) { // select from element "to end" + if (element.element == end.element) { + // end.index is guaranteed to be >= element.index by caller, same for x + text += textStr.mid(element.index, end.index - element.index); + const int left = rect.left(); + rect.setLeft(left + element.x); + rect.setRight(left + end.x); + } else { + text += textStr.mid(element.index); + rect.setLeft(rect.left() + element.x); + } + } else { // select from start of element + text += textStr.left(element.index); + rect.setRight(rect.left() + element.x); + } + selection.append(rect); + } + }; + + if (startElem.element && endElem.element) { + // Edge cases: + // start and end elements could be reversed or children of each other + Selection::Element start; + Selection::Element end; + std::tie(start, end) = getStartAndEnd(startElem, endElem); + + selection.clear(); + text.clear(); + + // Treats start element as a leaf even if it isn't, because it already contains all its + // children + addElement(start, end); + if (start.element != end.element) { + litehtml::element::ptr current = start.element; + do { + current = nextLeaf(current, end.element); + if (current == end.element) + addElement(end); + else + addElement({current, -1, -1}); + } while (current != end.element); + } + } else { + selection = {}; + text.clear(); + } +#if QT_CONFIG(clipboard) + QClipboard *cb = QGuiApplication::clipboard(); + if (cb->supportsSelection()) + cb->setText(text, QClipboard::Selection); +#endif +} + +QRect Selection::boundingRect() const +{ + QRect rect; + for (const QRect &r : selection) + rect = rect.united(r); + return rect; +} + +DocumentContainer::DocumentContainer() + : d(new DocumentContainerPrivate) +{} + +DocumentContainer::~DocumentContainer() = default; + +litehtml::uint_ptr DocumentContainerPrivate::create_font(const char *faceName, + int size, + int weight, + litehtml::font_style italic, + unsigned int decoration, + litehtml::font_metrics *fm) +{ + const QStringList splitNames = QString::fromUtf8(faceName).split(',', Qt::SkipEmptyParts); + QStringList familyNames; + std::transform(splitNames.cbegin(), + splitNames.cend(), + std::back_inserter(familyNames), + [this](const QString &s) { + // clean whitespace and quotes + QString name = s.trimmed(); + if (name.startsWith('\"')) + name = name.mid(1); + if (name.endsWith('\"')) + name.chop(1); + const QString lowerName = name.toLower(); + if (lowerName == "serif") + return serifFont(); + if (lowerName == "sans-serif") + return sansSerifFont(); + if (lowerName == "monospace") + return monospaceFont(); + return name; + }); + auto font = new QFont(); + font->setFamilies(familyNames); + font->setPixelSize(size); + font->setWeight(cssWeightToQtWeight(weight)); + font->setStyle(toQFontStyle(italic)); + font->setStyleStrategy(m_antialias ? QFont::PreferAntialias : QFont::NoAntialias); + if (decoration == litehtml::font_decoration_underline) + font->setUnderline(true); + if (decoration == litehtml::font_decoration_overline) + font->setOverline(true); + if (decoration == litehtml::font_decoration_linethrough) + font->setStrikeOut(true); + if (fm) { + const QFontMetrics metrics(*font); + fm->height = metrics.height(); + fm->ascent = metrics.ascent(); + fm->descent = metrics.descent(); + fm->x_height = metrics.xHeight(); + fm->draw_spaces = true; + } + return reinterpret_cast(font); +} + +void DocumentContainerPrivate::delete_font(litehtml::uint_ptr hFont) +{ + auto font = reinterpret_cast(hFont); + delete font; +} + +int DocumentContainerPrivate::text_width(const char *text, litehtml::uint_ptr hFont) +{ + const QFontMetrics fm(toQFont(hFont)); + return fm.horizontalAdvance(QString::fromUtf8(text)); +} + +void DocumentContainerPrivate::draw_text(litehtml::uint_ptr hdc, + const char *text, + litehtml::uint_ptr hFont, + litehtml::web_color color, + const litehtml::position &pos) +{ + auto painter = toQPainter(hdc); + painter->setFont(toQFont(hFont)); + painter->setPen(toQColor(color)); + painter->drawText(toQRect(pos), 0, QString::fromUtf8(text)); +} + +int DocumentContainerPrivate::pt_to_px(int pt) const +{ + // magic factor of 11/12 to account for differences to webengine/webkit + return m_paintDevice->physicalDpiY() * pt * 11 / m_paintDevice->logicalDpiY() / 12; +} + +int DocumentContainerPrivate::get_default_font_size() const +{ + return m_defaultFont.pointSize(); +} + +const char *DocumentContainerPrivate::get_default_font_name() const +{ + return m_defaultFontFamilyName.constData(); +} + +void DocumentContainerPrivate::draw_list_marker(litehtml::uint_ptr hdc, + const litehtml::list_marker &marker) +{ + auto painter = toQPainter(hdc); + if (marker.image.empty()) { + if (marker.marker_type == litehtml::list_style_type_square) { + painter->setPen(Qt::NoPen); + painter->setBrush(toQColor(marker.color)); + painter->drawRect(toQRect(marker.pos)); + } else if (marker.marker_type == litehtml::list_style_type_disc) { + painter->setPen(Qt::NoPen); + painter->setBrush(toQColor(marker.color)); + painter->drawEllipse(toQRect(marker.pos)); + } else if (marker.marker_type == litehtml::list_style_type_circle) { + painter->setPen(toQColor(marker.color)); + painter->setBrush(Qt::NoBrush); + painter->drawEllipse(toQRect(marker.pos)); + } else { + // TODO we do not get information about index and font for e.g. decimal / roman + // at least draw a bullet + painter->setPen(Qt::NoPen); + painter->setBrush(toQColor(marker.color)); + painter->drawEllipse(toQRect(marker.pos)); + qWarning(log) << "list marker of type" << marker.marker_type << "not supported"; + } + } else { + const QPixmap pixmap = getPixmap(QString::fromStdString(marker.image), + QString::fromStdString(marker.baseurl)); + painter->drawPixmap(toQRect(marker.pos), pixmap); + } +} + +void DocumentContainerPrivate::load_image(const char *src, const char *baseurl, bool redraw_on_ready) +{ + const auto qtSrc = QString::fromUtf8(src); + const auto qtBaseUrl = QString::fromUtf8(baseurl); + Q_UNUSED(redraw_on_ready) + qDebug(log) << "load_image:" << QString("src = \"%1\";").arg(qtSrc).toUtf8().constData() + << QString("base = \"%1\"").arg(qtBaseUrl).toUtf8().constData(); + const QUrl url = resolveUrl(qtSrc, qtBaseUrl); + if (m_pixmaps.contains(url)) + return; + + QPixmap pixmap; + pixmap.loadFromData(m_dataCallback(url)); + m_pixmaps.insert(url, pixmap); +} + +void DocumentContainerPrivate::get_image_size(const char *src, + const char *baseurl, + litehtml::size &sz) +{ + const auto qtSrc = QString::fromUtf8(src); + const auto qtBaseUrl = QString::fromUtf8(baseurl); + if (qtSrc.isEmpty()) // for some reason that happens + return; + qDebug(log) << "get_image_size:" << QString("src = \"%1\";").arg(qtSrc).toUtf8().constData() + << QString("base = \"%1\"").arg(qtBaseUrl).toUtf8().constData(); + const QPixmap pm = getPixmap(qtSrc, qtBaseUrl); + sz.width = pm.width(); + sz.height = pm.height(); +} + +void DocumentContainerPrivate::drawSelection(QPainter *painter, const QRect &clip) const +{ + painter->save(); + painter->setClipRect(clip, Qt::IntersectClip); + for (const QRect &r : m_selection.selection) { + const QRect clientRect = r.translated(-m_scrollPosition); + const QPalette palette = m_paletteCallback(); + painter->fillRect(clientRect, palette.brush(QPalette::Highlight)); + } + painter->restore(); +} + +static bool isInBody(const litehtml::element::ptr &e) +{ + litehtml::element::ptr current = e; + while (current && QString::fromUtf8(current->get_tagName()).toLower() != "body") + current = current->parent(); + return (bool)current; +} + +void DocumentContainerPrivate::buildIndex() +{ + m_index.elementToIndex.clear(); + m_index.indexToElement.clear(); + m_index.text.clear(); + + int index = 0; + bool inBody = false; + litehtml::element::ptr current = firstLeaf(m_document->root(), nullptr); + while (current != m_document->root()) { + m_index.elementToIndex.insert({current, index}); + if (!inBody) + inBody = isInBody(current); + if (inBody && isVisible(current)) { + std::string text; + current->get_text(text); + if (!text.empty()) { + m_index.indexToElement.push_back({index, current}); + const QString str = QString::fromStdString(text); + m_index.text += str; + index += str.size(); + } + } + current = nextLeaf(current, m_document->root()); + } +} + +void DocumentContainerPrivate::updateSelection() +{ + const QString oldText = m_selection.text; + m_selection.update(); + if (!m_clipboardCallback) + return; + + const QString newText = m_selection.text; + if (oldText.isEmpty() && !newText.isEmpty()) + m_clipboardCallback(true); + else if (!oldText.isEmpty() && newText.isEmpty()) + m_clipboardCallback(false); +} + +void DocumentContainerPrivate::clearSelection() +{ + const QString oldText = m_selection.text; + m_selection = {}; + if (!m_clipboardCallback) + return; + + if (!oldText.isEmpty()) + m_clipboardCallback(false); +} + +void DocumentContainerPrivate::draw_background(litehtml::uint_ptr hdc, + const std::vector &bgs) +{ + auto painter = toQPainter(hdc); + const QRegion initialClipRegion = painter->clipRegion(); + const Qt::ClipOperation initialClipOperation + = initialClipRegion.isEmpty() ? Qt::ReplaceClip : Qt::IntersectClip; + painter->save(); + for (const litehtml::background_paint &bg : bgs) { + if (bg.is_root) { + // TODO ? + break; + } + if (!initialClipRegion.isEmpty()) + painter->setClipRegion(initialClipRegion); + painter->setClipRect(toQRect(bg.clip_box), initialClipOperation); + const QRegion horizontalMiddle(QRect(bg.border_box.x, + bg.border_box.y + bg.border_radius.top_left_y, + bg.border_box.width, + bg.border_box.height - bg.border_radius.top_left_y + - bg.border_radius.bottom_left_y)); + const QRegion horizontalTop( + QRect(bg.border_box.x + bg.border_radius.top_left_x, + bg.border_box.y, + bg.border_box.width - bg.border_radius.top_left_x - bg.border_radius.top_right_x, + bg.border_radius.top_left_y)); + const QRegion horizontalBottom(QRect(bg.border_box.x + bg.border_radius.bottom_left_x, + bg.border_box.bottom() - bg.border_radius.bottom_left_y, + bg.border_box.width - bg.border_radius.bottom_left_x + - bg.border_radius.bottom_right_x, + bg.border_radius.bottom_left_y)); + const QRegion topLeft(QRect(bg.border_box.left(), + bg.border_box.top(), + 2 * bg.border_radius.top_left_x, + 2 * bg.border_radius.top_left_y), + QRegion::Ellipse); + const QRegion topRight(QRect(bg.border_box.right() - 2 * bg.border_radius.top_right_x, + bg.border_box.top(), + 2 * bg.border_radius.top_right_x, + 2 * bg.border_radius.top_right_y), + QRegion::Ellipse); + const QRegion bottomLeft(QRect(bg.border_box.left(), + bg.border_box.bottom() - 2 * bg.border_radius.bottom_left_y, + 2 * bg.border_radius.bottom_left_x, + 2 * bg.border_radius.bottom_left_y), + QRegion::Ellipse); + const QRegion bottomRight(QRect(bg.border_box.right() - 2 * bg.border_radius.bottom_right_x, + bg.border_box.bottom() - 2 * bg.border_radius.bottom_right_y, + 2 * bg.border_radius.bottom_right_x, + 2 * bg.border_radius.bottom_right_y), + QRegion::Ellipse); + const QRegion clipRegion = horizontalMiddle.united(horizontalTop) + .united(horizontalBottom) + .united(topLeft) + .united(topRight) + .united(bottomLeft) + .united(bottomRight); + painter->setClipRegion(clipRegion, Qt::IntersectClip); + painter->setPen(Qt::NoPen); + painter->setBrush(toQColor(bg.color)); + painter->drawRect(bg.border_box.x, + bg.border_box.y, + bg.border_box.width, + bg.border_box.height); + drawSelection(painter, toQRect(bg.border_box)); + if (!bg.image.empty()) { + const QPixmap pixmap = getPixmap(QString::fromStdString(bg.image), + QString::fromStdString(bg.baseurl)); + if (bg.repeat == litehtml::background_repeat_no_repeat) { + painter->drawPixmap(QRect(bg.position_x, + bg.position_y, + bg.image_size.width, + bg.image_size.height), + pixmap); + } else if (bg.repeat == litehtml::background_repeat_repeat_x) { + if (bg.image_size.width > 0) { + int x = bg.border_box.left(); + while (x <= bg.border_box.right()) { + painter->drawPixmap(QRect(x, + bg.border_box.top(), + bg.image_size.width, + bg.image_size.height), + pixmap); + x += bg.image_size.width; + } + } + } else { + qWarning(log) << "unsupported background repeat" << bg.repeat; + } + } + } + painter->restore(); +} + +void DocumentContainerPrivate::draw_borders(litehtml::uint_ptr hdc, + const litehtml::borders &borders, + const litehtml::position &draw_pos, + bool root) +{ + Q_UNUSED(root) + // TODO: special border styles + auto painter = toQPainter(hdc); + if (borders.top.style != litehtml::border_style_none + && borders.top.style != litehtml::border_style_hidden) { + painter->setPen(borderPen(borders.top)); + painter->drawLine(draw_pos.left() + borders.radius.top_left_x, + draw_pos.top(), + draw_pos.right() - borders.radius.top_right_x, + draw_pos.top()); + painter->drawArc(draw_pos.left(), + draw_pos.top(), + 2 * borders.radius.top_left_x, + 2 * borders.radius.top_left_y, + 90 * 16, + 90 * 16); + painter->drawArc(draw_pos.right() - 2 * borders.radius.top_right_x, + draw_pos.top(), + 2 * borders.radius.top_right_x, + 2 * borders.radius.top_right_y, + 0, + 90 * 16); + } + if (borders.bottom.style != litehtml::border_style_none + && borders.bottom.style != litehtml::border_style_hidden) { + painter->setPen(borderPen(borders.bottom)); + painter->drawLine(draw_pos.left() + borders.radius.bottom_left_x, + draw_pos.bottom(), + draw_pos.right() - borders.radius.bottom_right_x, + draw_pos.bottom()); + painter->drawArc(draw_pos.left(), + draw_pos.bottom() - 2 * borders.radius.bottom_left_y, + 2 * borders.radius.bottom_left_x, + 2 * borders.radius.bottom_left_y, + 180 * 16, + 90 * 16); + painter->drawArc(draw_pos.right() - 2 * borders.radius.bottom_right_x, + draw_pos.bottom() - 2 * borders.radius.bottom_right_y, + 2 * borders.radius.bottom_right_x, + 2 * borders.radius.bottom_right_y, + 270 * 16, + 90 * 16); + } + if (borders.left.style != litehtml::border_style_none + && borders.left.style != litehtml::border_style_hidden) { + painter->setPen(borderPen(borders.left)); + painter->drawLine(draw_pos.left(), + draw_pos.top() + borders.radius.top_left_y, + draw_pos.left(), + draw_pos.bottom() - borders.radius.bottom_left_y); + } + if (borders.right.style != litehtml::border_style_none + && borders.right.style != litehtml::border_style_hidden) { + painter->setPen(borderPen(borders.right)); + painter->drawLine(draw_pos.right(), + draw_pos.top() + borders.radius.top_right_y, + draw_pos.right(), + draw_pos.bottom() - borders.radius.bottom_right_y); + } +} + +void DocumentContainerPrivate::set_caption(const char *caption) +{ + m_caption = QString::fromUtf8(caption); +} + +void DocumentContainerPrivate::set_base_url(const char *base_url) +{ + m_baseUrl = QString::fromUtf8(base_url); +} + +void DocumentContainerPrivate::link(const std::shared_ptr &doc, + const litehtml::element::ptr &el) +{ + // TODO + qDebug(log) << "link"; + Q_UNUSED(doc) + Q_UNUSED(el) +} + +void DocumentContainerPrivate::on_anchor_click(const char *url, const litehtml::element::ptr &el) +{ + Q_UNUSED(el) + if (!m_blockLinks) + m_linkCallback(resolveUrl(QString::fromUtf8(url), m_baseUrl)); +} + +void DocumentContainerPrivate::set_cursor(const char *cursor) +{ + m_cursorCallback(toQCursor(QString::fromUtf8(cursor))); +} + +void DocumentContainerPrivate::transform_text(std::string &text, litehtml::text_transform tt) +{ + // TODO + qDebug(log) << "transform_text"; + Q_UNUSED(text) + Q_UNUSED(tt) +} + +void DocumentContainerPrivate::import_css(std::string &text, + const std::string &url, + std::string &baseurl) +{ + const QUrl actualUrl = resolveUrl(QString::fromStdString(url), QString::fromStdString(baseurl)); + const QString urlString = actualUrl.toString(QUrl::None); + const int lastSlash = urlString.lastIndexOf('/'); + baseurl = urlString.left(lastSlash).toStdString(); + text = QString::fromUtf8(m_dataCallback(actualUrl)).toStdString(); +} + +void DocumentContainerPrivate::set_clip(const litehtml::position &pos, + const litehtml::border_radiuses &bdr_radius) +{ + // TODO + qDebug(log) << "set_clip"; + Q_UNUSED(pos) + Q_UNUSED(bdr_radius) +} + +void DocumentContainerPrivate::del_clip() +{ + // TODO + qDebug(log) << "del_clip"; +} + +void DocumentContainerPrivate::get_client_rect(litehtml::position &client) const +{ + client = {m_clientRect.x(), m_clientRect.y(), m_clientRect.width(), m_clientRect.height()}; +} + +std::shared_ptr DocumentContainerPrivate::create_element( + const char *tag_name, + const litehtml::string_map &attributes, + const std::shared_ptr &doc) +{ + // TODO + qDebug(log) << "create_element" << QString::fromUtf8(tag_name); + Q_UNUSED(attributes) + Q_UNUSED(doc) + return {}; +} + +void DocumentContainerPrivate::get_media_features(litehtml::media_features &media) const +{ + media.type = mediaType; + qDebug(log) << "get_media_features"; +} + +void DocumentContainerPrivate::get_language(std::string &language, std::string &culture) const +{ + // TODO + qDebug(log) << "get_language"; + Q_UNUSED(language) + Q_UNUSED(culture) +} + +void DocumentContainer::setPaintDevice(QPaintDevice *paintDevice) +{ + d->m_paintDevice = paintDevice; +} + +void DocumentContainer::setScrollPosition(const QPoint &pos) +{ + d->m_scrollPosition = pos; +} + +void DocumentContainer::setDocument(const QByteArray &data, DocumentContainerContext *context) +{ + d->m_pixmaps.clear(); + d->clearSelection(); + d->m_document = litehtml::document::createFromString(data.constData(), + d.get(), + context->d->masterCss.toUtf8().constData()); + d->buildIndex(); +} + +bool DocumentContainer::hasDocument() const +{ + return d->m_document.get(); +} + +void DocumentContainer::setBaseUrl(const QString &url) +{ + d->set_base_url(url.toUtf8().constData()); +} + +QString DocumentContainer::baseUrl() const +{ + return d->m_baseUrl; +} + +void DocumentContainer::render(int width, int height) +{ + d->m_clientRect = {0, 0, width, height}; + if (!d->m_document) + return; + d->m_document->render(width); + d->updateSelection(); +} + +void DocumentContainer::draw(QPainter *painter, const QRect &clip) +{ + d->drawSelection(painter, clip); + const QPoint pos = -d->m_scrollPosition; + const litehtml::position clipRect = {clip.x(), clip.y(), clip.width(), clip.height()}; + d->m_document->draw(reinterpret_cast(painter), pos.x(), pos.y(), &clipRect); +} + +int DocumentContainer::documentWidth() const +{ + return d->m_document->width(); +} + +int DocumentContainer::documentHeight() const +{ + return d->m_document->height(); +} + +int DocumentContainer::anchorY(const QString &anchorName) const +{ + litehtml::element::ptr element = d->m_document->root()->select_one( + QString("#%1").arg(anchorName).toStdString()); + if (!element) { + element = d->m_document->root()->select_one(QString("[name=%1]").arg(anchorName).toStdString()); + } + if (!element) + return -1; + while (element) { + if (element->get_placement().y > 0) + return element->get_placement().y; + element = element->parent(); + } + return 0; +} + +static litehtml::media_type fromQt(const DocumentContainer::MediaType mt) +{ + using MT = DocumentContainer::MediaType; + switch (mt) + { + case MT::None: + return litehtml::media_type_none; + case MT::All: + return litehtml::media_type_all; + case MT::Screen: + return litehtml::media_type_screen; + case MT::Print: + return litehtml::media_type_print; + case MT::Braille: + return litehtml::media_type_braille; + case MT::Embossed: + return litehtml::media_type_embossed; + case MT::Handheld: + return litehtml::media_type_handheld; + case MT::Projection: + return litehtml::media_type_projection; + case MT::Speech: + return litehtml::media_type_speech; + case MT::TTY: + return litehtml::media_type_tty; + case MT::TV: + return litehtml::media_type_tv; + } + Q_UNREACHABLE(); +} + +void DocumentContainer::setMediaType(MediaType mt) +{ + d->mediaType = fromQt(mt); +} + +QVector DocumentContainer::mousePressEvent(const QPoint &documentPos, + const QPoint &viewportPos, + Qt::MouseButton button) +{ + if (!d->m_document || button != Qt::LeftButton) + return {}; + QVector redrawRects; + // selection + if (d->m_selection.isValid()) + redrawRects.append(d->m_selection.boundingRect()); + d->clearSelection(); + d->m_selection.selectionStartDocumentPos = documentPos; + d->m_selection.startElem = selection_element_at_point(d->m_document->root(), + documentPos, + viewportPos, + d->m_selection.mode); + // post to litehtml + litehtml::position::vector redrawBoxes; + if (d->m_document->on_lbutton_down( + documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) { + for (const litehtml::position &box : redrawBoxes) + redrawRects.append(toQRect(box)); + } + return redrawRects; +} + +QVector DocumentContainer::mouseMoveEvent(const QPoint &documentPos, + const QPoint &viewportPos) +{ + if (!d->m_document) + return {}; + QVector redrawRects; + // selection + if (d->m_selection.isSelecting + || (!d->m_selection.selectionStartDocumentPos.isNull() + && (d->m_selection.selectionStartDocumentPos - documentPos).manhattanLength() >= kDragDistance + && d->m_selection.startElem.element)) { + const Selection::Element element = selection_element_at_point(d->m_document->root(), + documentPos, + viewportPos, + d->m_selection.mode); + if (element.element) { + redrawRects.append( + d->m_selection.boundingRect() /*.adjusted(-1, -1, +1, +1)*/); // redraw old selection area + d->m_selection.endElem = element; + d->updateSelection(); + redrawRects.append(d->m_selection.boundingRect()); + } + d->m_selection.isSelecting = true; + } + litehtml::position::vector redrawBoxes; + if (d->m_document->on_mouse_over( + documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) { + for (const litehtml::position &box : redrawBoxes) + redrawRects.append(toQRect(box)); + } + return redrawRects; +} + +QVector DocumentContainer::mouseReleaseEvent(const QPoint &documentPos, + const QPoint &viewportPos, + Qt::MouseButton button) +{ + if (!d->m_document || button != Qt::LeftButton) + return {}; + QVector redrawRects; + // selection + d->m_selection.isSelecting = false; + d->m_selection.selectionStartDocumentPos = {}; + if (d->m_selection.isValid()) + d->m_blockLinks = true; + else + d->clearSelection(); + litehtml::position::vector redrawBoxes; + if (d->m_document->on_lbutton_up( + documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) { + for (const litehtml::position &box : redrawBoxes) + redrawRects.append(toQRect(box)); + } + d->m_blockLinks = false; + return redrawRects; +} + +QVector DocumentContainer::mouseDoubleClickEvent(const QPoint &documentPos, + const QPoint &viewportPos, + Qt::MouseButton button) +{ + if (!d->m_document || button != Qt::LeftButton) + return {}; + QVector redrawRects; + d->clearSelection(); + d->m_selection.mode = Selection::Mode::Word; + const Selection::Element element = selection_element_at_point(d->m_document->root(), + documentPos, + viewportPos, + d->m_selection.mode); + if (element.element) { + d->m_selection.startElem = element; + d->m_selection.endElem = d->m_selection.startElem; + d->m_selection.isSelecting = true; + d->updateSelection(); + if (d->m_selection.isValid()) + redrawRects.append(d->m_selection.boundingRect()); + } else { + if (d->m_selection.isValid()) + redrawRects.append(d->m_selection.boundingRect()); + d->clearSelection(); + } + return redrawRects; +} + +QVector DocumentContainer::leaveEvent() +{ + if (!d->m_document) + return {}; + litehtml::position::vector redrawBoxes; + if (d->m_document->on_mouse_leave(redrawBoxes)) { + QVector redrawRects; + for (const litehtml::position &box : redrawBoxes) + redrawRects.append(toQRect(box)); + return redrawRects; + } + return {}; +} + +QUrl DocumentContainer::linkAt(const QPoint &documentPos, const QPoint &viewportPos) +{ + if (!d->m_document) + return {}; + const char *href = nullptr; + deepest_child_at_point(d->m_document->root(), + documentPos, + viewportPos, + [&href](const litehtml::element::ptr &e) { + const litehtml::element::ptr parent = e->parent(); + if (parent && parent->tag() == litehtml::_a_) { + href = parent->get_attr("href"); + if (href) + return true; + } + return false; /*continue*/ + }); + if (href) + return d->resolveUrl(QString::fromUtf8(href), d->m_baseUrl); + return {}; +} + +QString DocumentContainer::caption() const +{ + return d->m_caption; +} + +QString DocumentContainer::selectedText() const +{ + return d->m_selection.text; +} + +void DocumentContainer::findText(const QString &text, + QTextDocument::FindFlags flags, + bool incremental, + bool *wrapped, + bool *success, + QVector *oldSelection, + QVector *newSelection) +{ + if (success) + *success = false; + if (oldSelection) + oldSelection->clear(); + if (newSelection) + newSelection->clear(); + if (!d->m_document) + return; + const bool backward = flags & QTextDocument::FindBackward; + int startIndex = backward ? -1 : 0; + if (d->m_selection.startElem.element && d->m_selection.endElem.element) { // selection + // poor-man's incremental search starts at beginning of selection, + // non-incremental at end (forward search) or beginning (backward search) + Selection::Element start; + Selection::Element end; + std::tie(start, end) = getStartAndEnd(d->m_selection.startElem, d->m_selection.endElem); + Selection::Element searchStart; + if (incremental || backward) { + if (start.index < 0) // fully selected + searchStart = {firstLeaf(start.element, nullptr), 0, -1}; + else + searchStart = start; + } else { + if (end.index < 0) // fully selected + searchStart = {nextLeaf(end.element, nullptr), 0, -1}; + else + searchStart = end; + } + const auto findInIndex = d->m_index.elementToIndex.find(searchStart.element); + if (findInIndex == std::end(d->m_index.elementToIndex)) { + qWarning() << "internal error: cannot find litehmtl element in index"; + return; + } + startIndex = findInIndex->second + searchStart.index; + if (backward) + --startIndex; + } + + const auto fillXPos = [](const Selection::Element &e) { + std::string ttext; + e.element->get_text(ttext); + const QString text = QString::fromStdString(ttext); + const auto fontPtr = e.element->css().get_font(); + if (!fontPtr) + return e; + const QFont &font = toQFont(fontPtr); + const QFontMetrics fm(font); + return Selection::Element{e.element, e.index, fm.size(0, text.left(e.index)).width()}; + }; + + QString term = QRegularExpression::escape(text); + if (flags & QTextDocument::FindWholeWords) + term = QString("\\b%1\\b").arg(term); + const QRegularExpression::PatternOptions patternOptions + = (flags & QTextDocument::FindCaseSensitively) ? QRegularExpression::NoPatternOption + : QRegularExpression::CaseInsensitiveOption; + const QRegularExpression expression(term, patternOptions); + + int foundIndex = backward ? d->m_index.text.lastIndexOf(expression, startIndex) + : d->m_index.text.indexOf(expression, startIndex); + if (foundIndex < 0) { // wrap + foundIndex = backward ? d->m_index.text.lastIndexOf(expression) + : d->m_index.text.indexOf(expression); + if (wrapped && foundIndex >= 0) + *wrapped = true; + } + if (foundIndex >= 0) { + const Index::Entry startEntry = d->m_index.findElement(foundIndex); + const Index::Entry endEntry = d->m_index.findElement(foundIndex + text.size()); + if (!startEntry.second || !endEntry.second) { + qWarning() << "internal error: search ended up with nullptr elements"; + return; + } + if (oldSelection) + *oldSelection = d->m_selection.selection; + d->clearSelection(); + d->m_selection.startElem = fillXPos({startEntry.second, foundIndex - startEntry.first, -1}); + d->m_selection.endElem = fillXPos( + {endEntry.second, int(foundIndex + text.size() - endEntry.first), -1}); + d->updateSelection(); + if (newSelection) + *newSelection = d->m_selection.selection; + if (success) + *success = true; + return; + } + return; +} + +void DocumentContainer::setDefaultFont(const QFont &font) +{ + d->m_defaultFont = font; + d->m_defaultFontFamilyName = d->m_defaultFont.family().toUtf8(); + // Since font family name and size are read only once, when parsing html, + // we need to trigger the reparse of this info. + if (d->m_document && d->m_document->root()) { + d->m_document->root()->refresh_styles(); + d->m_document->root()->compute_styles(); + } +} + +QFont DocumentContainer::defaultFont() const +{ + return d->m_defaultFont; +} + +void DocumentContainer::setAntialias(bool on) +{ + d->m_antialias = on; +} + +bool DocumentContainer::antialias() const +{ + return d->m_antialias; +} + +void DocumentContainer::setDataCallback(const DocumentContainer::DataCallback &callback) +{ + d->m_dataCallback = callback; +} + +DocumentContainer::DataCallback DocumentContainer::dataCallback() const +{ + return d->m_dataCallback; +} + +void DocumentContainer::setCursorCallback(const DocumentContainer::CursorCallback &callback) +{ + d->m_cursorCallback = callback; +} + +void DocumentContainer::setLinkCallback(const DocumentContainer::LinkCallback &callback) +{ + d->m_linkCallback = callback; +} + +void DocumentContainer::setPaletteCallback(const DocumentContainer::PaletteCallback &callback) +{ + d->m_paletteCallback = callback; +} + +DocumentContainer::PaletteCallback DocumentContainer::paletteCallback() const +{ + return d->m_paletteCallback; +} + +void DocumentContainer::setClipboardCallback(const DocumentContainer::ClipboardCallback &callback) +{ + d->m_clipboardCallback = callback; +} + +static litehtml::element::ptr elementForY(int y, const litehtml::element::ptr &element) +{ + if (!element) + return {}; + if (element->get_placement().y >= y) + return element; + for (const litehtml::element::ptr &child : element->children()) { + litehtml::element::ptr result = elementForY(y, child); + if (result) + return result; + } + return {}; +} + +static litehtml::element::ptr elementForY(int y, const litehtml::document::ptr &document) +{ + if (!document) + return {}; + + return elementForY(y, document->root()); +} + +int DocumentContainer::withFixedElementPosition(int y, const std::function &action) +{ + const litehtml::element::ptr element = elementForY(y, d->m_document); + action(); + if (element) + return element->get_placement().y; + return -1; +} + +QPixmap DocumentContainerPrivate::getPixmap(const QString &imageUrl, const QString &baseUrl) +{ + const QUrl url = resolveUrl(imageUrl, baseUrl); + if (!m_pixmaps.contains(url)) { + qWarning(log) << "draw_background: pixmap not loaded for" << url; + return {}; + } + return m_pixmaps.value(url); +} + +QString DocumentContainerPrivate::serifFont() const +{ + // TODO make configurable + return {"Times New Roman"}; +} + +QString DocumentContainerPrivate::sansSerifFont() const +{ + // TODO make configurable + return {"Arial"}; +} + +QString DocumentContainerPrivate::monospaceFont() const +{ + // TODO make configurable + return {"Courier"}; +} + +QUrl DocumentContainerPrivate::resolveUrl(const QString &url, const QString &baseUrl) const +{ + // several cases: + // full url: "https://foo.bar/blah.css" + // relative path: "foo/bar.css" + // server relative path: "/foo/bar.css" + // net path: "//foo.bar/blah.css" + // fragment only: "#foo-fragment" + const QUrl qurl = QUrl::fromEncoded(url.toUtf8()); + if (qurl.scheme().isEmpty()) { + if (url.startsWith('#')) // leave alone if just a fragment + return qurl; + const QUrl pageBaseUrl = QUrl(baseUrl.isEmpty() ? m_baseUrl : baseUrl); + if (url.startsWith("//")) // net path + return QUrl(pageBaseUrl.scheme() + ":" + url); + QUrl serverUrl = QUrl(pageBaseUrl); + serverUrl.setPath(""); + const QString actualBaseUrl = url.startsWith('/') + ? serverUrl.toString(QUrl::FullyEncoded) + : pageBaseUrl.toString(QUrl::FullyEncoded); + QUrl resolvedUrl(actualBaseUrl + '/' + url); + resolvedUrl.setPath(resolvedUrl.path(QUrl::FullyEncoded | QUrl::NormalizePathSegments), QUrl::TolerantMode); + return resolvedUrl; + } + return qurl; +} + +Index::Entry Index::findElement(int index) const +{ + const auto upper = std::upper_bound(std::begin(indexToElement), + std::end(indexToElement), + Entry{index, {}}, + [](const Entry &a, const Entry &b) { + return a.first < b.first; + }); + if (upper == std::begin(indexToElement)) // should not happen for index >= 0 + return {-1, {}}; + return *(upper - 1); +} + +DocumentContainerContext::DocumentContainerContext() + : d(new DocumentContainerContextPrivate) +{} + +DocumentContainerContext::~DocumentContainerContext() = default; + +void DocumentContainerContext::setMasterStyleSheet(const QString &css) +{ + d->masterCss = css; +} diff --git a/src/assistant/qlitehtml/src/container_qpainter.h b/src/assistant/qlitehtml/src/container_qpainter.h new file mode 100644 index 0000000..7e9c798 --- /dev/null +++ b/src/assistant/qlitehtml/src/container_qpainter.h @@ -0,0 +1,127 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qlitehtml_global.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class DocumentContainerPrivate; +class DocumentContainerContextPrivate; + +class QLITEHTML_EXPORT DocumentContainerContext +{ +public: + DocumentContainerContext(); + ~DocumentContainerContext(); + + void setMasterStyleSheet(const QString &css); + +private: + std::unique_ptr d; + + friend class DocumentContainer; + friend class DocumentContainerPrivate; +}; + +class QLITEHTML_EXPORT DocumentContainer +{ +public: + DocumentContainer(); + virtual ~DocumentContainer(); + +public: // outside API + void setPaintDevice(QPaintDevice *paintDevice); + void setDocument(const QByteArray &data, DocumentContainerContext *context); + bool hasDocument() const; + void setBaseUrl(const QString &url); + QString baseUrl() const; + void setScrollPosition(const QPoint &pos); + void render(int width, int height); + void draw(QPainter *painter, const QRect &clip); + int documentWidth() const; + int documentHeight() const; + int anchorY(const QString &anchorName) const; + + enum class MediaType + { + None, + All, + Screen, + Print, + Braille, + Embossed, + Handheld, + Projection, + Speech, + TTY, + TV + }; + + void setMediaType(MediaType t); + + // these return areas to redraw in document space + QVector mousePressEvent(const QPoint &documentPos, + const QPoint &viewportPos, + Qt::MouseButton button); + QVector mouseMoveEvent(const QPoint &documentPos, const QPoint &viewportPos); + QVector mouseReleaseEvent(const QPoint &documentPos, + const QPoint &viewportPos, + Qt::MouseButton button); + QVector mouseDoubleClickEvent(const QPoint &documentPos, + const QPoint &viewportPos, + Qt::MouseButton button); + QVector leaveEvent(); + + QUrl linkAt(const QPoint &documentPos, const QPoint &viewportPos); + + QString caption() const; + QString selectedText() const; + + void findText(const QString &text, + QTextDocument::FindFlags flags, + bool incremental, + bool *wrapped, + bool *success, + QVector *oldSelection, + QVector *newSelection); + + void setDefaultFont(const QFont &font); + QFont defaultFont() const; + void setAntialias(bool on); + bool antialias() const; + + using DataCallback = std::function; + void setDataCallback(const DataCallback &callback); + DataCallback dataCallback() const; + + using CursorCallback = std::function; + void setCursorCallback(const CursorCallback &callback); + + using LinkCallback = std::function; + void setLinkCallback(const LinkCallback &callback); + + using PaletteCallback = std::function; + void setPaletteCallback(const PaletteCallback &callback); + PaletteCallback paletteCallback() const; + + using ClipboardCallback = std::function; + void setClipboardCallback(const ClipboardCallback &callback); + + int withFixedElementPosition(int y, const std::function &action); + +private: + std::unique_ptr d; +}; diff --git a/src/assistant/qlitehtml/src/container_qpainter_p.h b/src/assistant/qlitehtml/src/container_qpainter_p.h new file mode 100644 index 0000000..23c55b4 --- /dev/null +++ b/src/assistant/qlitehtml/src/container_qpainter_p.h @@ -0,0 +1,141 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "container_qpainter.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +class Selection +{ +public: + struct Element + { + litehtml::element::ptr element; + int index = -1; + int x = -1; + }; + + enum class Mode { Free, Word }; + + bool isValid() const; + + void update(); + QRect boundingRect() const; + + Element startElem; + Element endElem; + QVector selection; + QString text; + + QPoint selectionStartDocumentPos; + Mode mode = Mode::Free; + bool isSelecting = false; +}; + +struct Index +{ + QString text; + // only contains leaf elements + std::unordered_map elementToIndex; + + using Entry = std::pair; + std::vector indexToElement; + + Entry findElement(int index) const; +}; + +class DocumentContainerPrivate final : public litehtml::document_container +{ +public: // document_container API + litehtml::uint_ptr create_font(const char *faceName, + int size, + int weight, + litehtml::font_style italic, + unsigned int decoration, + litehtml::font_metrics *fm) override; + void delete_font(litehtml::uint_ptr hFont) override; + int text_width(const char *text, litehtml::uint_ptr hFont) override; + void draw_text(litehtml::uint_ptr hdc, + const char *text, + litehtml::uint_ptr hFont, + litehtml::web_color color, + const litehtml::position &pos) override; + int pt_to_px(int pt) const override; + int get_default_font_size() const override; + const char *get_default_font_name() const override; + void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) override; + void load_image(const char *src, const char *baseurl, bool redraw_on_ready) override; + void get_image_size(const char *src, const char *baseurl, litehtml::size &sz) override; + void draw_background(litehtml::uint_ptr hdc, + const std::vector &bgs) override; + void draw_borders(litehtml::uint_ptr hdc, + const litehtml::borders &borders, + const litehtml::position &draw_pos, + bool root) override; + void set_caption(const char *caption) override; + void set_base_url(const char *base_url) override; + void link(const std::shared_ptr &doc, + const litehtml::element::ptr &el) override; + void on_anchor_click(const char *url, const litehtml::element::ptr &el) override; + void set_cursor(const char *cursor) override; + void transform_text(std::string &text, litehtml::text_transform tt) override; + void import_css(std::string &text, const std::string &url, std::string &baseurl) override; + void set_clip(const litehtml::position &pos, + const litehtml::border_radiuses &bdr_radius) override; + void del_clip() override; + void get_client_rect(litehtml::position &client) const override; + std::shared_ptr create_element( + const char *tag_name, + const litehtml::string_map &attributes, + const std::shared_ptr &doc) override; + void get_media_features(litehtml::media_features &media) const override; + void get_language(std::string &language, std::string &culture) const override; + + QPixmap getPixmap(const QString &imageUrl, const QString &baseUrl); + QString serifFont() const; + QString sansSerifFont() const; + QString monospaceFont() const; + QUrl resolveUrl(const QString &url, const QString &baseUrl) const; + void drawSelection(QPainter *painter, const QRect &clip) const; + void buildIndex(); + void updateSelection(); + void clearSelection(); + + QPaintDevice *m_paintDevice = nullptr; + litehtml::document::ptr m_document; + litehtml::media_type mediaType = litehtml::media_type_screen; + Index m_index; + QString m_baseUrl; + QRect m_clientRect; + QPoint m_scrollPosition; + QString m_caption; + QFont m_defaultFont = QFont(sansSerifFont(), 16); + QByteArray m_defaultFontFamilyName = m_defaultFont.family().toUtf8(); + bool m_antialias = true; + QHash m_pixmaps; + Selection m_selection; + DocumentContainer::DataCallback m_dataCallback; + DocumentContainer::CursorCallback m_cursorCallback; + DocumentContainer::LinkCallback m_linkCallback; + DocumentContainer::PaletteCallback m_paletteCallback; + DocumentContainer::ClipboardCallback m_clipboardCallback; + bool m_blockLinks = false; +}; + +class DocumentContainerContextPrivate +{ +public: + QString masterCss; +}; diff --git a/src/assistant/qlitehtml/src/qlitehtml.pri b/src/assistant/qlitehtml/src/qlitehtml.pri new file mode 100644 index 0000000..a4b6918 --- /dev/null +++ b/src/assistant/qlitehtml/src/qlitehtml.pri @@ -0,0 +1,164 @@ +exists($$PWD/3rdparty/litehtml/CMakeLists.txt) { + LH_SRC = $$PWD/3rdparty/litehtml + LH_HDR = $$LH_SRC/include/litehtml + GB_SRC = $$PWD/3rdparty/litehtml/src/gumbo + GB_HDR = $$GB_SRC/include/gumbo + + # gumbo + SOURCES += \ + $$GB_SRC/attribute.c \ + $$GB_SRC/char_ref.c \ + $$GB_SRC/error.c \ + $$GB_SRC/parser.c \ + $$GB_SRC/string_buffer.c \ + $$GB_SRC/string_piece.c \ + $$GB_SRC/tag.c \ + $$GB_SRC/tokenizer.c \ + $$GB_SRC/utf8.c \ + $$GB_SRC/util.c \ + $$GB_SRC/vector.c + + HEADERS += \ + $$GB_SRC/include//gumbo.h \ + $$GB_HDR/attribute.h \ + $$GB_HDR/char_ref.h \ + $$GB_HDR/error.h \ + $$GB_HDR/insertion_mode.h \ + $$GB_HDR/parser.h \ + $$GB_HDR/string_buffer.h \ + $$GB_HDR/string_piece.h \ + $$GB_HDR/tag_enum.h \ + $$GB_HDR/tag_gperf.h \ + $$GB_HDR/tag_sizes.h \ + $$GB_HDR/tag_strings.h \ + $$GB_HDR/token_type.h \ + $$GB_HDR/tokenizer.h \ + $$GB_HDR/tokenizer_states.h \ + $$GB_HDR/utf8.h \ + $$GB_HDR/util.h \ + $$GB_HDR/vector.h + + INCLUDEPATH *= $$GB_SRC/include $$GB_HDR + + win32 { + HEADERS += \ + $$GB_SRC/visualc/include/strings.h + INCLUDEPATH *= $$GB_SRC/visualc/include + } + + # litehtml + SOURCES += \ + $$LH_SRC/src/background.cpp \ + $$LH_SRC/src/box.cpp \ + $$LH_SRC/src/context.cpp \ + $$LH_SRC/src/css_length.cpp \ + $$LH_SRC/src/css_selector.cpp \ + $$LH_SRC/src/document.cpp \ + $$LH_SRC/src/el_anchor.cpp \ + $$LH_SRC/src/el_base.cpp \ + $$LH_SRC/src/el_before_after.cpp \ + $$LH_SRC/src/el_body.cpp \ + $$LH_SRC/src/el_break.cpp \ + $$LH_SRC/src/el_cdata.cpp \ + $$LH_SRC/src/el_comment.cpp \ + $$LH_SRC/src/el_div.cpp \ + $$LH_SRC/src/element.cpp \ + $$LH_SRC/src/el_font.cpp \ + $$LH_SRC/src/el_image.cpp \ + $$LH_SRC/src/el_li.cpp \ + $$LH_SRC/src/el_link.cpp \ + $$LH_SRC/src/el_para.cpp \ + $$LH_SRC/src/el_script.cpp \ + $$LH_SRC/src/el_space.cpp \ + $$LH_SRC/src/el_style.cpp \ + $$LH_SRC/src/el_table.cpp \ + $$LH_SRC/src/el_td.cpp \ + $$LH_SRC/src/el_text.cpp \ + $$LH_SRC/src/el_title.cpp \ + $$LH_SRC/src/el_tr.cpp \ + $$LH_SRC/src/html.cpp \ + $$LH_SRC/src/html_tag.cpp \ + $$LH_SRC/src/iterators.cpp \ + $$LH_SRC/src/media_query.cpp \ + $$LH_SRC/src/num_cvt.cpp \ + $$LH_SRC/src/style.cpp \ + $$LH_SRC/src/stylesheet.cpp \ + $$LH_SRC/src/table.cpp \ + $$LH_SRC/src/utf8_strings.cpp \ + $$LH_SRC/src/web_color.cpp + + HEADERS += \ + $$LH_SRC/include/litehtml.h \ + $$LH_HDR/attributes.h \ + $$LH_HDR/background.h \ + $$LH_HDR/borders.h \ + $$LH_HDR/box.h \ + $$LH_HDR/context.h \ + $$LH_HDR/css_length.h \ + $$LH_HDR/css_margins.h \ + $$LH_HDR/css_offsets.h \ + $$LH_HDR/css_position.h \ + $$LH_HDR/css_selector.h \ + $$LH_HDR/document.h \ + $$LH_HDR/el_anchor.h \ + $$LH_HDR/el_base.h \ + $$LH_HDR/el_before_after.h \ + $$LH_HDR/el_body.h \ + $$LH_HDR/el_break.h \ + $$LH_HDR/el_cdata.h \ + $$LH_HDR/el_comment.h \ + $$LH_HDR/el_div.h \ + $$LH_HDR/el_font.h \ + $$LH_HDR/el_image.h \ + $$LH_HDR/el_li.h \ + $$LH_HDR/el_link.h \ + $$LH_HDR/el_para.h \ + $$LH_HDR/el_script.h \ + $$LH_HDR/el_space.h \ + $$LH_HDR/el_style.h \ + $$LH_HDR/el_table.h \ + $$LH_HDR/el_td.h \ + $$LH_HDR/el_text.h \ + $$LH_HDR/el_title.h \ + $$LH_HDR/el_tr.h \ + $$LH_HDR/element.h \ + $$LH_HDR/html.h \ + $$LH_HDR/html_tag.h \ + $$LH_HDR/iterators.h \ + $$LH_HDR/media_query.h \ + $$LH_HDR/num_cvt.h \ + $$LH_HDR/os_types.h \ + $$LH_HDR/style.h \ + $$LH_HDR/stylesheet.h \ + $$LH_HDR/table.h \ + $$LH_HDR/types.h \ + $$LH_HDR/utf8_strings.h \ + $$LH_HDR/web_color.h + + INCLUDEPATH *= $$LH_SRC/include $$LH_HDR + + # litehtml without optimization is not fun + QMAKE_CFLAGS_DEBUG += -O2 + QMAKE_CXXFLAGS_DEBUG += -O2 +} else { + INCLUDEPATH *= $$LITEHTML_INSTALL_DIR/include $$LITEHTML_INSTALL_DIR/include/litehtml + LITEHTML_LIB_DIR = $$LITEHTML_INSTALL_DIR/lib + LIBS += -L$$LITEHTML_LIB_DIR -llitehtml -lgumbo + + win32: PRE_TARGETDEPS += $$LITEHTML_LIB_DIR/litehtml.lib $$LITEHTML_LIB_DIR/gumbo.lib + else:unix: PRE_TARGETDEPS += $$LITEHTML_LIB_DIR/liblitehtml.a $$LITEHTML_LIB_DIR/libgumbo.a +} + +HEADERS += \ + $$PWD/container_qpainter.h \ + $$PWD/container_qpainter_p.h \ + $$PWD/qlitehtmlwidget.h + +SOURCES += \ + $$PWD/container_qpainter.cpp \ + $$PWD/qlitehtmlwidget.cpp + +INCLUDEPATH *= $$PWD +win32: DEFINES += LITEHTML_UTF8 + +DEFINES *= QLITEHTML_STATIC_LIBRARY diff --git a/src/assistant/qlitehtml/src/qlitehtml.qbs b/src/assistant/qlitehtml/src/qlitehtml.qbs new file mode 100644 index 0000000..677bcbe --- /dev/null +++ b/src/assistant/qlitehtml/src/qlitehtml.qbs @@ -0,0 +1,248 @@ +import qbs.File +import qbs.FileInfo + +Product { + type: buildLib ? ["staticlibrary"] : undefined + + Depends { name: "cpp" } + Depends { name: "qtc" } + + property bool useExternalLib: qtc.litehtmlInstallDir + property bool buildLib: !useExternalLib && File.exists(path + "/3rdparty/litehtml/CMakeLists.txt") + condition: useExternalLib || buildLib + + property string gumboSrcDir: path + "/3rdparty/litehtml/src/gumbo" + property string gumboHeaderDir: gumboSrcDir + "/include/gumbo" + property string litehtmlHeaderDir: path + "/3rdparty/litehtml/include/litehtml" + property string mainHeaderDir: litehtmlHeaderDir + '/..' + property stringList sharedDefines: { + var defines = ["QLITEHTML_STATIC_LIBRARY"]; + if (qbs.targetOS.contains("windows")) + defines.push("LITEHTML_UTF8"); + return defines; + } + + cpp.defines: sharedDefines + cpp.includePaths: { + var paths = [gumboHeaderDir, gumboHeaderDir + '/..', litehtmlHeaderDir, mainHeaderDir]; + if (qbs.targetOS.contains("windows")) + paths.push(gumboSrcDir + "/visualc/include"); + return paths; + } + cpp.optimization: "fast" + cpp.warningLevel: "none" + cpp.cxxLanguageVersion: "c++14" + + Export { + Depends { name: "cpp" } + Group { + name: "litehtml/Qt glue" + cpp.warningLevel: "none" + files: [ + "container_qpainter.cpp", + "container_qpainter.h", + "container_qpainter_p.h", + "qlitehtmlwidget.cpp", + "qlitehtmlwidget.h", + ] + } + + Properties { + condition: product.useExternalLib + cpp.dynamicLibraries: ["litehtml", "gumbo"] + cpp.includePaths: [ + FileInfo.joinPaths(qtc.litehtmlInstallDir, "include"), + FileInfo.joinPaths(qtc.litehtmlInstallDir, "include", "litehtml"), + ] + cpp.libraryPaths: FileInfo.joinPaths(qtc.litehtmlInstallDir, "lib") + } + Properties { + condition: exportingProduct.buildLib + cpp.defines: exportingProduct.sharedDefines + cpp.includePaths: [exportingProduct.mainHeaderDir, path] + } + } + + Group { + condition: buildLib + name: "gumbo sources" + prefix: gumboSrcDir + '/' + files: [ + "attribute.c", + "char_ref.c", + "error.c", + "parser.c", + "string_buffer.c", + "string_piece.c", + "tag.c", + "tokenizer.c", + "utf8.c", + "util.c", + "vector.c", + ] + } + + Group { + condition: buildLib + name: "gumbo headers" + prefix: gumboHeaderDir + '/' + files: [ + "../gumbo.h", + "attribute.h", + "char_ref.h", + "error.h", + "insertion_mode.h", + "parser.h", + "string_buffer.h", + "string_piece.h", + "tag_enum.h", + "tag_gperf.h", + "tag_sizes.h", + "tag_strings.h", + "token_type.h", + "tokenizer.h", + "tokenizer_states.h", + "utf8.h", + "util.h", + "vector.h", + ] + + Group { + name: "gumbo Windows headers" + condition: qbs.targetOS.contains("windows") + files: "../../visualc/include/strings.h" + } + } + + Group { + condition: buildLib + name: "litehtml sources" + prefix: litehtmlHeaderDir + "/../../src/" + files: [ + "codepoint.cpp", + "css_borders.cpp", + "css_length.cpp", + "css_properties.cpp", + "css_selector.cpp", + "document.cpp", + "document_container.cpp", + "el_anchor.cpp", + "el_base.cpp", + "el_before_after.cpp", + "el_body.cpp", + "el_break.cpp", + "el_cdata.cpp", + "el_comment.cpp", + "el_div.cpp", + "element.cpp", + "el_font.cpp", + "el_image.cpp", + "el_link.cpp", + "el_para.cpp", + "el_script.cpp", + "el_space.cpp", + "el_style.cpp", + "el_table.cpp", + "el_td.cpp", + "el_text.cpp", + "el_title.cpp", + "el_tr.cpp", + "flex_item.cpp", + "flex_line.cpp", + "formatting_context.cpp", + "html.cpp", + "html_tag.cpp", + "iterators.cpp", + "line_box.cpp", + "media_query.cpp", + "num_cvt.cpp", + "render_block.cpp", + "render_block_context.cpp", + "render_flex.cpp", + "render_image.cpp", + "render_inline_context.cpp", + "render_item.cpp", + "render_table.cpp", + "string_id.cpp", + "strtod.cpp", + "style.cpp", + "stylesheet.cpp", + "table.cpp", + "tstring_view.cpp", + "url.cpp", + "url_path.cpp", + "utf8_strings.cpp", + "web_color.cpp", + ] + } + + Group { + condition: buildLib + name: "litehtml headers" + prefix: litehtmlHeaderDir + '/' + files: [ + "../litehtml.h", + "background.h", + "borders.h", + "codepoint.h", + "css_length.h", + "css_margins.h", + "css_offsets.h", + "css_position.h", + "css_properties.h", + "css_selector.h", + "document.h", + "document_container.h", + "el_anchor.h", + "el_base.h", + "el_before_after.h", + "el_body.h", + "el_break.h", + "el_cdata.h", + "el_comment.h", + "el_div.h", + "el_font.h", + "el_image.h", + "el_link.h", + "el_para.h", + "el_script.h", + "el_space.h", + "el_style.h", + "el_table.h", + "el_td.h", + "el_text.h", + "el_title.h", + "el_tr.h", + "element.h", + "flex_item.h", + "flex_line.h", + "formatting_context.h", + "html.h", + "html_tag.h", + "iterators.h", + "line_box.h", + "master_css.h", + "media_query.h", + "num_cvt.h", + "os_types.h", + "render_block.h", + "render_block_context.h", + "render_flex.h", + "render_image.h", + "render_inline.h", + "render_inline_context.h", + "render_item.h", + "render_table.h", + "string_id.h", + "style.h", + "stylesheet.h", + "table.h", + "tstring_view.h", + "types.h", + "url.h", + "url_path.h", + "utf8_strings.h", + "web_color.h", + ] + } +} diff --git a/src/assistant/qlitehtml/src/qlitehtml_global.h b/src/assistant/qlitehtml/src/qlitehtml_global.h new file mode 100644 index 0000000..5daa6c1 --- /dev/null +++ b/src/assistant/qlitehtml/src/qlitehtml_global.h @@ -0,0 +1,14 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#if defined(QLITEHTML_LIBRARY) +# define QLITEHTML_EXPORT Q_DECL_EXPORT +#elif defined(QLITEHTML_STATIC_LIBRARY) // Abuse single files for manual tests +# define QLITEHTML_EXPORT +#else +# define QLITEHTML_EXPORT Q_DECL_IMPORT +#endif diff --git a/src/assistant/qlitehtml/src/qlitehtmlwidget.cpp b/src/assistant/qlitehtml/src/qlitehtmlwidget.cpp new file mode 100644 index 0000000..6128ffa --- /dev/null +++ b/src/assistant/qlitehtml/src/qlitehtmlwidget.cpp @@ -0,0 +1,789 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qlitehtmlwidget.h" + +#include "container_qpainter.h" + +#include +#include +#include +#include +#include + +#if QLITEHTML_HAS_QPRINTER +#include +#endif + +const int kScrollBarStep = 40; + +// copied from include/litehtml/master_css.h +const char master_css[] = R"##( +html { + display: block; + position: relative; +} + +head { + display: none +} + +meta { + display: none +} + +title { + display: none +} + +link { + display: none +} + +style { + display: none +} + +script { + display: none +} + +body { + display:block; + margin:8px; +} + +p { + display:block; + margin-top:1em; + margin-bottom:1em; +} + +b, strong { + display:inline; + font-weight:bold; +} + +i, em, cite { + display:inline; + font-style:italic; +} + +ins, u { + text-decoration:underline +} + +del, s, strike { + text-decoration:line-through +} + +center +{ + text-align:center; + display:block; +} + +a:link +{ + text-decoration: underline; + color: #00f; + cursor: pointer; +} + +h1, h2, h3, h4, h5, h6, div { + display:block; +} + +h1 { + font-weight:bold; + margin-top:0.67em; + margin-bottom:0.67em; + font-size: 2em; +} + +h2 { + font-weight:bold; + margin-top:0.83em; + margin-bottom:0.83em; + font-size: 1.5em; +} + +h3 { + font-weight:bold; + margin-top:1em; + margin-bottom:1em; + font-size:1.17em; +} + +h4 { + font-weight:bold; + margin-top:1.33em; + margin-bottom:1.33em +} + +h5 { + font-weight:bold; + margin-top:1.67em; + margin-bottom:1.67em; + font-size:.83em; +} + +h6 { + font-weight:bold; + margin-top:2.33em; + margin-bottom:2.33em; + font-size:.67em; +} + +br { + display:inline-block; +} + +br[clear="all"] +{ + clear:both; +} + +br[clear="left"] +{ + clear:left; +} + +br[clear="right"] +{ + clear:right; +} + +span { + display:inline +} + +img { + display: inline-block; +} + +img[align="right"] +{ + float: right; +} + +img[align="left"] +{ + float: left; +} + +hr { + display: block; + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-left: auto; + margin-right: auto; + border-style: inset; + border-width: 1px +} + + +/***************** TABLES ********************/ + +table { + display: table; + border-collapse: separate; + border-spacing: 2px; + border-top-color:gray; + border-left-color:gray; + border-bottom-color:black; + border-right-color:black; + font-size: medium; + font-weight: normal; + font-style: normal; +} + +tbody, tfoot, thead { + display:table-row-group; + vertical-align:middle; +} + +tr { + display: table-row; + vertical-align: inherit; + border-color: inherit; +} + +td, th { + display: table-cell; + vertical-align: inherit; + border-width:1px; + padding:1px; +} + +th { + font-weight: bold; +} + +table[border] { + border-style:solid; +} + +table[border|=0] { + border-style:none; +} + +table[border] td, table[border] th { + border-style:solid; + border-top-color:black; + border-left-color:black; + border-bottom-color:gray; + border-right-color:gray; +} + +table[border|=0] td, table[border|=0] th { + border-style:none; +} + +table[align=left] { + float: left; +} + +table[align=right] { + float: right; +} + +table[align=center] { + margin-left: auto; + margin-right: auto; +} + +caption { + display: table-caption; +} + +td[nowrap], th[nowrap] { + white-space:nowrap; +} + +tt, code, kbd, samp { + font-family: monospace +} + +pre, xmp, plaintext, listing { + display: block; + font-family: monospace; + white-space: pre; + margin: 1em 0 +} + +/***************** LISTS ********************/ + +ul, menu, dir { + display: block; + list-style-type: disc; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; + padding-left: 40px +} + +ol { + display: block; + list-style-type: decimal; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; + padding-left: 40px +} + +li { + display: list-item; +} + +ul ul, ol ul { + list-style-type: circle; +} + +ol ol ul, ol ul ul, ul ol ul, ul ul ul { + list-style-type: square; +} + +dd { + display: block; + margin-left: 40px; +} + +dl { + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 0; + margin-right: 0; +} + +dt { + display: block; +} + +ol ul, ul ol, ul ul, ol ol { + margin-top: 0; + margin-bottom: 0 +} + +blockquote { + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 40px; + margin-right: 40px; +} + +/*********** FORM ELEMENTS ************/ + +form { + display: block; + margin-top: 0em; +} + +option { + display: none; +} + +input, textarea, keygen, select, button, isindex { + margin: 0em; + color: initial; + line-height: normal; + text-transform: none; + text-indent: 0; + text-shadow: none; + display: inline-block; +} +input[type="hidden"] { + display: none; +} + + +article, aside, footer, header, hgroup, nav, section +{ + display: block; +} + +sub { + vertical-align: sub; + font-size: smaller; +} + +sup { + vertical-align: super; + font-size: smaller; +} + +figure { + display: block; + margin-top: 1em; + margin-bottom: 1em; + margin-left: 40px; + margin-right: 40px; +} + +figcaption { + display: block; +} + +)##"; + +class QLiteHtmlWidgetPrivate +{ +public: + QString html; + DocumentContainerContext context; + QUrl url; + DocumentContainer documentContainer; + qreal zoomFactor = 1; + QUrl lastHighlightedLink; +}; + +QLiteHtmlWidget::QLiteHtmlWidget(QWidget *parent) + : QAbstractScrollArea(parent) + , d(new QLiteHtmlWidgetPrivate) +{ + setMouseTracking(true); + horizontalScrollBar()->setSingleStep(kScrollBarStep); + verticalScrollBar()->setSingleStep(kScrollBarStep); + + d->documentContainer.setCursorCallback([this](const QCursor &c) { viewport()->setCursor(c); }); + d->documentContainer.setPaletteCallback([this] { return palette(); }); + d->documentContainer.setLinkCallback([this](const QUrl &url) { + QUrl fullUrl = url; + if (url.isRelative() && url.path(QUrl::FullyEncoded).isEmpty()) { // fragment/anchor only + fullUrl = d->url; + fullUrl.setFragment(url.fragment(QUrl::FullyEncoded)); + } + // delay because document may not be changed directly during this callback + QMetaObject::invokeMethod(this, [this, fullUrl] { emit linkClicked(fullUrl); }, + Qt::QueuedConnection); + }); + d->documentContainer.setClipboardCallback([this](bool yes) { emit copyAvailable(yes); }); + + // TODO adapt mastercss to palette (default text & background color) + d->context.setMasterStyleSheet(master_css); +} + +QLiteHtmlWidget::~QLiteHtmlWidget() +{ + delete d; +} + +void QLiteHtmlWidget::setUrl(const QUrl &url) +{ + d->url = url; + QUrl baseUrl = url; + baseUrl.setFragment({}); + const QString path = baseUrl.path(QUrl::FullyEncoded); + const int lastSlash = path.lastIndexOf('/'); + const QString basePath = lastSlash >= 0 ? path.left(lastSlash) : QString(); + baseUrl.setPath(basePath); + d->documentContainer.setBaseUrl(baseUrl.toString(QUrl::FullyEncoded)); + QMetaObject::invokeMethod(this, [this] { updateHightlightedLink(); }, + Qt::QueuedConnection); +} + +QUrl QLiteHtmlWidget::url() const +{ + return d->url; +} + +void QLiteHtmlWidget::setHtml(const QString &content) +{ + d->html = content; + d->documentContainer.setPaintDevice(viewport()); + d->documentContainer.setDocument(content.toUtf8(), &d->context); + verticalScrollBar()->setValue(0); + horizontalScrollBar()->setValue(0); + render(); + QMetaObject::invokeMethod(this, [this] { updateHightlightedLink(); }, + Qt::QueuedConnection); +} + +QString QLiteHtmlWidget::html() const +{ + return d->html; +} + +QString QLiteHtmlWidget::title() const +{ + return d->documentContainer.caption(); +} + +void QLiteHtmlWidget::setZoomFactor(qreal scale) +{ + Q_ASSERT(scale != 0); + d->zoomFactor = scale; + withFixedTextPosition([this] { render(); }); +} + +qreal QLiteHtmlWidget::zoomFactor() const +{ + return d->zoomFactor; +} + +bool QLiteHtmlWidget::findText(const QString &text, + QTextDocument::FindFlags flags, + bool incremental, + bool *wrapped) +{ + bool success = false; + QVector oldSelection; + QVector newSelection; + d->documentContainer + .findText(text, flags, incremental, wrapped, &success, &oldSelection, &newSelection); + // scroll to search result position and/or redraw as necessary + QRect newSelectionCombined; + for (const QRect &r : std::as_const(newSelection)) + newSelectionCombined = newSelectionCombined.united(r); + QScrollBar *vBar = verticalScrollBar(); + const int top = newSelectionCombined.top(); + const int bottom = newSelectionCombined.bottom() - toVirtual(viewport()->size()).height(); + if (success && top < vBar->value() && vBar->minimum() <= top) { + vBar->setValue(top); + } else if (success && vBar->value() < bottom && bottom <= vBar->maximum()) { + vBar->setValue(bottom); + } else { + viewport()->update(fromVirtual(newSelectionCombined.translated(-scrollPosition()))); + for (const QRect &r : std::as_const(oldSelection)) + viewport()->update(fromVirtual(r.translated(-scrollPosition()))); + } + return success; +} + +void QLiteHtmlWidget::setDefaultFont(const QFont &font) +{ + withFixedTextPosition([this, &font] { + d->documentContainer.setDefaultFont(font); + render(); + }); +} + +QFont QLiteHtmlWidget::defaultFont() const +{ + return d->documentContainer.defaultFont(); +} + +void QLiteHtmlWidget::setAntialias(bool on) +{ + withFixedTextPosition([this, on] { + d->documentContainer.setAntialias(on); + // force litehtml to recreate fonts + setHtml(d->html); + }); +} + +void QLiteHtmlWidget::scrollToAnchor(const QString &name) +{ + if (!d->documentContainer.hasDocument()) + return; + horizontalScrollBar()->setValue(0); + if (name.isEmpty()) { + verticalScrollBar()->setValue(0); + return; + } + const int y = d->documentContainer.anchorY(name); + if (y >= 0) + verticalScrollBar()->setValue(std::min(y, verticalScrollBar()->maximum())); +} + +void QLiteHtmlWidget::setResourceHandler(const QLiteHtmlWidget::ResourceHandler &handler) +{ + d->documentContainer.setDataCallback(handler); +} + +QString QLiteHtmlWidget::selectedText() const +{ + return d->documentContainer.selectedText(); +} + +void QLiteHtmlWidget::print(QPrinter *printer) +{ +#if QLITEHTML_HAS_QPRINTER + QPainter painter; + if (!painter.begin(printer)) + return; + + DocumentContainer dc; + dc.setDataCallback(d->documentContainer.dataCallback()); + dc.setPaletteCallback(d->documentContainer.paletteCallback()); + dc.setDefaultFont(d->documentContainer.defaultFont()); + dc.setPaintDevice(printer); + dc.setBaseUrl(d->documentContainer.baseUrl()); + dc.setMediaType(DocumentContainer::MediaType::Print); + dc.setDocument(d->html.toUtf8(), &d->context); + + const QRect pageRect = printer->pageRect(QPrinter::DevicePixel).toRect(); + dc.render(pageRect.width(), pageRect.height()); + + QRect drawRect = pageRect; + drawRect.moveTo(0, 0); + painter.setClipping(true); + painter.setClipRect(drawRect); + + QPoint scrollPosition(0, 0); + while (true) { + dc.setScrollPosition(scrollPosition); + dc.draw(&painter, drawRect); + scrollPosition.ry() += drawRect.height(); + if (scrollPosition.y() < dc.documentHeight()) + printer->newPage(); + else + break; + } + + painter.end(); +#else + Q_UNUSED(printer); +#endif +} + +void QLiteHtmlWidget::paintEvent(QPaintEvent *event) +{ + if (!d->documentContainer.hasDocument()) + return; + d->documentContainer.setScrollPosition(scrollPosition()); + QPainter p(viewport()); + p.setWorldTransform(QTransform().scale(d->zoomFactor, d->zoomFactor)); + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + p.setRenderHint(QPainter::Antialiasing, true); + d->documentContainer.draw(&p, toVirtual(event->rect())); +} + +void QLiteHtmlWidget::resizeEvent(QResizeEvent *event) +{ + withFixedTextPosition([this, event] { + QAbstractScrollArea::resizeEvent(event); + render(); + }); +} + +void QLiteHtmlWidget::mouseMoveEvent(QMouseEvent *event) +{ + QPoint viewportPos; + QPoint pos; + htmlPos(event->pos(), &viewportPos, &pos); + const QVector areas = d->documentContainer.mouseMoveEvent(pos, viewportPos); + for (const QRect &r : areas) + viewport()->update(fromVirtual(r.translated(-scrollPosition()))); + + updateHightlightedLink(); +} + +void QLiteHtmlWidget::mousePressEvent(QMouseEvent *event) +{ + QPoint viewportPos; + QPoint pos; + htmlPos(event->pos(), &viewportPos, &pos); + const QVector areas = d->documentContainer.mousePressEvent(pos, viewportPos, event->button()); + for (const QRect &r : areas) + viewport()->update(fromVirtual(r.translated(-scrollPosition()))); +} + +void QLiteHtmlWidget::mouseReleaseEvent(QMouseEvent *event) +{ + QPoint viewportPos; + QPoint pos; + htmlPos(event->pos(), &viewportPos, &pos); + const QVector areas = d->documentContainer.mouseReleaseEvent(pos, viewportPos, event->button()); + for (const QRect &r : areas) + viewport()->update(fromVirtual(r.translated(-scrollPosition()))); +} + +void QLiteHtmlWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + QPoint viewportPos; + QPoint pos; + htmlPos(event->pos(), &viewportPos, &pos); + const QVector areas = d->documentContainer.mouseDoubleClickEvent(pos, viewportPos, event->button()); + for (const QRect &r : areas) { + viewport()->update(fromVirtual(r.translated(-scrollPosition()))); + } +} + +void QLiteHtmlWidget::leaveEvent(QEvent *event) +{ + Q_UNUSED(event) + const QVector areas = d->documentContainer.leaveEvent(); + for (const QRect &r : areas) + viewport()->update(fromVirtual(r.translated(-scrollPosition()))); + setHightlightedLink(QUrl()); +} + +void QLiteHtmlWidget::contextMenuEvent(QContextMenuEvent *event) +{ + QPoint viewportPos; + QPoint pos; + htmlPos(event->pos(), &viewportPos, &pos); + emit contextMenuRequested(event->pos(), d->documentContainer.linkAt(pos, viewportPos)); +} + +static QAbstractSlider::SliderAction getSliderAction(int key) +{ + if (key == Qt::Key_Home) + return QAbstractSlider::SliderToMinimum; + if (key == Qt::Key_End) + return QAbstractSlider::SliderToMaximum; + if (key == Qt::Key_PageUp) + return QAbstractSlider::SliderPageStepSub; + if (key == Qt::Key_PageDown) + return QAbstractSlider::SliderPageStepAdd; + return QAbstractSlider::SliderNoAction; +} + +void QLiteHtmlWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::KeypadModifier) { + const QAbstractSlider::SliderAction sliderAction = getSliderAction(event->key()); + if (sliderAction != QAbstractSlider::SliderNoAction) { + verticalScrollBar()->triggerAction(sliderAction); + event->accept(); + return; + } + } + + QAbstractScrollArea::keyPressEvent(event); +} + +void QLiteHtmlWidget::updateHightlightedLink() +{ + QPoint viewportPos; + QPoint pos; + htmlPos(mapFromGlobal(QCursor::pos()), &viewportPos, &pos); + setHightlightedLink(d->documentContainer.linkAt(pos, viewportPos)); +} + +void QLiteHtmlWidget::setHightlightedLink(const QUrl &url) +{ + if (d->lastHighlightedLink == url) + return; + d->lastHighlightedLink = url; + emit linkHighlighted(d->lastHighlightedLink); +} + +void QLiteHtmlWidget::withFixedTextPosition(const std::function &action) +{ + // remember element to which to scroll after re-rendering + QPoint viewportPos; + QPoint pos; + htmlPos({}, &viewportPos, &pos); // top-left + const int y = d->documentContainer.withFixedElementPosition(pos.y(), action); + if (y >= 0) + verticalScrollBar()->setValue(std::min(y, verticalScrollBar()->maximum())); +} + +void QLiteHtmlWidget::render() +{ + if (!d->documentContainer.hasDocument()) + return; + const int fullWidth = width() / d->zoomFactor; + const QSize vViewportSize = toVirtual(viewport()->size()); + const int scrollbarWidth = style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, this); + const int w = fullWidth - scrollbarWidth - 2; + d->documentContainer.render(w, vViewportSize.height()); + // scroll bars reflect virtual/scaled size of html document + horizontalScrollBar()->setPageStep(vViewportSize.width()); + horizontalScrollBar()->setRange(0, std::max(0, d->documentContainer.documentWidth() - w)); + verticalScrollBar()->setPageStep(vViewportSize.height()); + verticalScrollBar() + ->setRange(0, std::max(0, d->documentContainer.documentHeight() - vViewportSize.height())); + viewport()->update(); +} + +QPoint QLiteHtmlWidget::scrollPosition() const +{ + return {horizontalScrollBar()->value(), verticalScrollBar()->value()}; +} + +void QLiteHtmlWidget::htmlPos(const QPoint &pos, QPoint *viewportPos, QPoint *htmlPos) const +{ + *viewportPos = toVirtual(viewport()->mapFromParent(pos)); + *htmlPos = *viewportPos + scrollPosition(); +} + +QPoint QLiteHtmlWidget::toVirtual(const QPoint &p) const +{ + return {int(p.x() / d->zoomFactor), int(p.y() / d->zoomFactor)}; +} + +QSize QLiteHtmlWidget::toVirtual(const QSize &s) const +{ + return {int(s.width() / d->zoomFactor), int(s.height() / d->zoomFactor)}; +} + +QRect QLiteHtmlWidget::toVirtual(const QRect &r) const +{ + return {toVirtual(r.topLeft()), toVirtual(r.size())}; +} + +QRect QLiteHtmlWidget::fromVirtual(const QRect &r) const +{ + const QPoint tl{int(r.x() * d->zoomFactor), int(r.y() * d->zoomFactor)}; + // round size up, and add one since the topleft point was rounded down + const QSize s{int(r.width() * d->zoomFactor + 0.5) + 1, + int(r.height() * d->zoomFactor + 0.5) + 1}; + return {tl, s}; +} diff --git a/src/assistant/qlitehtml/src/qlitehtmlwidget.h b/src/assistant/qlitehtml/src/qlitehtmlwidget.h new file mode 100644 index 0000000..4e496a3 --- /dev/null +++ b/src/assistant/qlitehtml/src/qlitehtmlwidget.h @@ -0,0 +1,83 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qlitehtml_global.h" + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QPrinter) + +#include + +class QLiteHtmlWidgetPrivate; + +class QLITEHTML_EXPORT QLiteHtmlWidget : public QAbstractScrollArea +{ + Q_OBJECT +public: + explicit QLiteHtmlWidget(QWidget *parent = nullptr); + ~QLiteHtmlWidget() override; + + // declaring the getters Q_INVOKABLE to make them Squish-testable + void setUrl(const QUrl &url); + Q_INVOKABLE QUrl url() const; + void setHtml(const QString &content); + Q_INVOKABLE QString html() const; + Q_INVOKABLE QString title() const; + + void setZoomFactor(qreal scale); + qreal zoomFactor() const; + + bool findText(const QString &text, + QTextDocument::FindFlags flags, + bool incremental, + bool *wrapped = nullptr); + + void setDefaultFont(const QFont &font); + QFont defaultFont() const; + void setAntialias(bool on); + + void scrollToAnchor(const QString &name); + + using ResourceHandler = std::function; + void setResourceHandler(const ResourceHandler &handler); + + // declaring this Q_INVOKABLE to make it Squish-testable + Q_INVOKABLE QString selectedText() const; + + void print(QPrinter *printer); + +signals: + void linkClicked(const QUrl &url); + void linkHighlighted(const QUrl &url); + void copyAvailable(bool available); + void contextMenuRequested(const QPoint &pos, const QUrl &url); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void leaveEvent(QEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +private: + void updateHightlightedLink(); + void setHightlightedLink(const QUrl &url); + void withFixedTextPosition(const std::function &action); + void render(); + QPoint scrollPosition() const; + void htmlPos(const QPoint &pos, QPoint *viewportPos, QPoint *htmlPos) const; + QPoint toVirtual(const QPoint &p) const; + QSize toVirtual(const QSize &s) const; + QRect toVirtual(const QRect &r) const; + QRect fromVirtual(const QRect &r) const; + + QLiteHtmlWidgetPrivate *d; +}; diff --git a/src/assistant/qlitehtml/tests/manual/browser/CMakeLists.txt b/src/assistant/qlitehtml/tests/manual/browser/CMakeLists.txt new file mode 100644 index 0000000..bdee01e --- /dev/null +++ b/src/assistant/qlitehtml/tests/manual/browser/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +find_package(${Qt} COMPONENTS Widgets Network REQUIRED) + +add_executable(qlitehtmlbrowser + MACOSX_BUNDLE + main.cpp) + +target_link_libraries(qlitehtmlbrowser qlitehtml ${Qt}::Widgets ${Qt}::Network) diff --git a/src/assistant/qlitehtml/tests/manual/browser/main.cpp b/src/assistant/qlitehtml/tests/manual/browser/main.cpp new file mode 100644 index 0000000..4b65480 --- /dev/null +++ b/src/assistant/qlitehtml/tests/manual/browser/main.cpp @@ -0,0 +1,150 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_STATIC_LOGGING_CATEGORY +Q_STATIC_LOGGING_CATEGORY(log, "qlitehtml.browser") +#else +static Q_LOGGING_CATEGORY(log, "qlitehtml.browser") +#endif + +class BrowserWindow : public QWidget +{ + Q_OBJECT + +public: + BrowserWindow(); + +private: + QNetworkAccessManager m_nam; +}; + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + BrowserWindow w; + w.show(); + return app.exec(); +} + +BrowserWindow::BrowserWindow() +{ + auto vlayout = new QVBoxLayout; + vlayout->setContentsMargins(0, 0, 0, 0); + setLayout(vlayout); + +#if QT_CONFIG(clipboard) + auto menuBar = new QMenuBar; + vlayout->addWidget(menuBar); + auto editMenu = menuBar->addMenu(tr("Edit")); + auto copyAction = editMenu->addAction(tr("Copy")); + copyAction->setShortcut(QKeySequence::Copy); + copyAction->setEnabled(false); + + auto toolBar = new QToolBar; + vlayout->addWidget(toolBar); + toolBar->addAction(copyAction); +#endif + + auto centerWidget = new QWidget; + auto centerLayout = new QVBoxLayout; + centerWidget->setLayout(centerLayout); + vlayout->addWidget(centerWidget, 10); + auto urlLayout = new QHBoxLayout; + urlLayout->addWidget(new QLabel(tr("URL:"))); + auto urlInput = new QLineEdit; + urlLayout->addWidget(urlInput); + auto browseButton = new QPushButton(tr("Browse...")); + urlLayout->addWidget(browseButton); + + centerLayout->addLayout(urlLayout); + auto htmlWidget = new QLiteHtmlWidget; + centerLayout->addWidget(htmlWidget); + + auto statusBar = new QStatusBar; + vlayout->addWidget(statusBar); + + resize(1000, 650); + + htmlWidget->setResourceHandler([this](const QUrl &url) { + // create blocking request + // TODO implement asynchronous requests in container_qpainter + QEventLoop loop; + QByteArray data; + qCDebug(log) << "Resource requested:" << url; + QNetworkReply *reply = m_nam.get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, this, [&data, &loop, reply] { + qCDebug(log) << "Resource finished:" << reply->url() << reply->error(); + if (reply->error() == QNetworkReply::NoError) + data = reply->readAll(); + reply->deleteLater(); + loop.exit(); + }); + loop.exec(QEventLoop::ExcludeUserInputEvents); + return data; + }); + connect(htmlWidget, &QLiteHtmlWidget::linkHighlighted, statusBar, [statusBar](const QUrl &url) { + statusBar->showMessage(url.toString()); + }); +#if QT_CONFIG(clipboard) + connect(htmlWidget, &QLiteHtmlWidget::copyAvailable, copyAction, &QAction::setEnabled); + connect(copyAction, &QAction::triggered, htmlWidget, [htmlWidget] { + QGuiApplication::clipboard()->setText(htmlWidget->selectedText()); + }); +#endif + + const auto loadUrl = [this, htmlWidget, urlInput, browseButton] { + urlInput->setEnabled(false); + browseButton->setEnabled(false); + const QUrl url = QUrl(urlInput->text().trimmed()); + qCDebug(log) << "Url requested:" << url; + QNetworkReply *reply = m_nam.get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, this, [htmlWidget, urlInput, browseButton, reply] { + qCDebug(log) << "Url finished:" << reply->url() << reply->error(); + if (reply->error() == QNetworkReply::NoError) { + const QByteArray data = reply->readAll(); + htmlWidget->setUrl(reply->url()); + htmlWidget->setHtml(QString::fromUtf8(data)); + } + urlInput->setEnabled(true); + browseButton->setEnabled(true); + reply->deleteLater(); + }); + }; + connect(urlInput, &QLineEdit::returnPressed, this, loadUrl); + connect(browseButton, &QPushButton::clicked, this, [this, urlInput, loadUrl] { + const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Open File")); + if (url.isValid()) { + urlInput->setText(url.toString()); + loadUrl(); + } + }); + + QAction *action; + action = new QAction(tr("Enter location")); + action->setShortcut({"Ctrl+L"}); + connect(action, &QAction::triggered, [urlInput] { urlInput->setFocus(); }); + addAction(action); +} + +#include "main.moc" diff --git a/src/assistant/shared/collectionconfiguration.cpp b/src/assistant/shared/collectionconfiguration.cpp new file mode 100644 index 0000000..8caf39d --- /dev/null +++ b/src/assistant/shared/collectionconfiguration.cpp @@ -0,0 +1,295 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "collectionconfiguration.h" + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + const QString AboutIconKey("AboutIcon"_L1); + const QString AboutImagesKey("AboutImages"_L1); + const QString AboutMenuTextsKey("AboutMenuTexts"_L1); + const QString AboutTextsKey("AboutTexts"_L1); + const QString ApplicationIconKey("ApplicationIcon"_L1); + const QString CacheDirKey("CacheDirectory"_L1); + const QString CacheDirRelativeToCollectionKey("CacheDirRelativeToCollection"_L1); + const QString CreationTimeKey("CreationTime"_L1); + const QString DefaultHomePageKey("defaultHomepage"_L1); + const QString EnableAddressBarKey("EnableAddressBar"_L1); + const QString EnableDocManagerKey("EnableDocumentationManager"_L1); + const QString EnableFilterKey("EnableFilterFunctionality"_L1); + const QString HideAddressBarKey("HideAddressBar"_L1); + const QString FilterToolbarHiddenKey("HideFilterFunctionality"_L1); + const QString LastPageKey("LastTabPage"_L1); + const QString LastRegisterTime("LastRegisterTime"_L1); + const QString LastShownPagesKey("LastShownPages"_L1); + const QString LastZoomFactorsKey( +#if defined(BROWSER_QTWEBKIT) + "LastPagesZoomWebView"_L1 +#else + "LastPagesZoomTextBrowser"_L1 +#endif + ); + const QString WindowTitleKey("WindowTitle"_L1); + const QString FullTextSearchFallbackKey("FullTextSearchFallback"_L1); +} // anonymous namespace + +const QString CollectionConfiguration::DefaultZoomFactor("0.0"_L1); +const QString CollectionConfiguration::ListSeparator("|"_L1); + +uint CollectionConfiguration::creationTime(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(CreationTimeKey, 0).toUInt(); +} + +void CollectionConfiguration::setCreationTime(QHelpEngineCore &helpEngine, uint time) +{ + helpEngine.setCustomValue(CreationTimeKey, time); +} + +const QString CollectionConfiguration::windowTitle(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(WindowTitleKey).toString(); +} + +void CollectionConfiguration::setWindowTitle(QHelpEngineCore &helpEngine, + const QString &windowTitle) +{ + helpEngine.setCustomValue(WindowTitleKey, windowTitle); +} + +bool CollectionConfiguration::filterFunctionalityEnabled(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(EnableFilterKey, true).toBool(); +} + +void CollectionConfiguration::setFilterFunctionalityEnabled(QHelpEngineCore &helpEngine, + bool enabled) +{ + helpEngine.setCustomValue(EnableFilterKey, enabled); +} + +bool CollectionConfiguration::filterToolbarVisible(const QHelpEngineCore &helpEngine) +{ + return !helpEngine.customValue(FilterToolbarHiddenKey, true).toBool(); +} + +void CollectionConfiguration::setFilterToolbarVisible(QHelpEngineCore &helpEngine, + bool visible) +{ + helpEngine.setCustomValue(FilterToolbarHiddenKey, !visible); +} + +bool CollectionConfiguration::addressBarEnabled(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(EnableAddressBarKey, true).toBool(); +} + +void CollectionConfiguration::setAddressBarEnabled(QHelpEngineCore &helpEngine, + bool enabled) +{ + helpEngine.setCustomValue(EnableAddressBarKey, enabled); +} + +bool CollectionConfiguration::addressBarVisible(const QHelpEngineCore &helpEngine) +{ + return !helpEngine.customValue(HideAddressBarKey, true).toBool(); +} + +void CollectionConfiguration::setAddressBarVisible(QHelpEngineCore &helpEngine, + bool visible) +{ + helpEngine.setCustomValue(HideAddressBarKey, !visible); +} + +const QString CollectionConfiguration::cacheDir(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(CacheDirKey).toString(); +} + +bool CollectionConfiguration::cacheDirIsRelativeToCollection(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(CacheDirRelativeToCollectionKey).toBool(); +} + +void CollectionConfiguration::setCacheDir(QHelpEngineCore &helpEngine, + const QString &cacheDir, bool relativeToCollection) +{ + helpEngine.setCustomValue(CacheDirKey, cacheDir); + helpEngine.setCustomValue(CacheDirRelativeToCollectionKey, + relativeToCollection); +} + +bool CollectionConfiguration::documentationManagerEnabled(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(EnableDocManagerKey, true).toBool(); +} + +void CollectionConfiguration::setDocumentationManagerEnabled(QHelpEngineCore &helpEngine, + bool enabled) +{ + helpEngine.setCustomValue(EnableDocManagerKey, enabled); +} + +const QByteArray CollectionConfiguration::applicationIcon(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(ApplicationIconKey).toByteArray(); +} + +void CollectionConfiguration::setApplicationIcon(QHelpEngineCore &helpEngine, + const QByteArray &icon) +{ + helpEngine.setCustomValue(ApplicationIconKey, icon); +} + +const QByteArray CollectionConfiguration::aboutMenuTexts(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(AboutMenuTextsKey).toByteArray(); +} + +void CollectionConfiguration::setAboutMenuTexts(QHelpEngineCore &helpEngine, + const QByteArray &texts) +{ + helpEngine.setCustomValue(AboutMenuTextsKey, texts); +} + +const QByteArray CollectionConfiguration::aboutIcon(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(AboutIconKey).toByteArray(); +} + +void CollectionConfiguration::setAboutIcon(QHelpEngineCore &helpEngine, + const QByteArray &icon) +{ + helpEngine.setCustomValue(AboutIconKey, icon); +} + +const QByteArray CollectionConfiguration::aboutTexts(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(AboutTextsKey).toByteArray(); +} + +void CollectionConfiguration::setAboutTexts(QHelpEngineCore &helpEngine, + const QByteArray &texts) +{ + helpEngine.setCustomValue(AboutTextsKey, texts); +} + +const QByteArray CollectionConfiguration::aboutImages(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(AboutImagesKey).toByteArray(); +} + +void CollectionConfiguration::setAboutImages(QHelpEngineCore &helpEngine, + const QByteArray &images) +{ + helpEngine.setCustomValue(AboutImagesKey, images); +} + +const QString CollectionConfiguration::defaultHomePage(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(DefaultHomePageKey, "help"_L1).toString(); +} + +void CollectionConfiguration::setDefaultHomePage(QHelpEngineCore &helpEngine, + const QString &page) +{ + helpEngine.setCustomValue(DefaultHomePageKey, page); +} + +const QStringList CollectionConfiguration::lastShownPages(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(LastShownPagesKey).toString(). + split(ListSeparator, Qt::SkipEmptyParts); +} + +void CollectionConfiguration::setLastShownPages(QHelpEngineCore &helpEngine, + const QStringList &lastShownPages) +{ + helpEngine.setCustomValue(LastShownPagesKey, + lastShownPages.join(ListSeparator)); +} + +const QStringList CollectionConfiguration::lastZoomFactors(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(LastZoomFactorsKey).toString(). + split(ListSeparator, Qt::SkipEmptyParts); +} + +void CollectionConfiguration::setLastZoomFactors(QHelpEngineCore &helpEngine, + const QStringList &lastZoomFactors) +{ + helpEngine.setCustomValue(LastZoomFactorsKey, + lastZoomFactors.join(ListSeparator)); +} + +int CollectionConfiguration::lastTabPage(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(LastPageKey, 1).toInt(); +} + +void CollectionConfiguration::setLastTabPage(QHelpEngineCore &helpEngine, + int lastPage) +{ + helpEngine.setCustomValue(LastPageKey, lastPage); +} + +const QDateTime CollectionConfiguration::lastRegisterTime(const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(LastRegisterTime, QDateTime()).toDateTime(); +} + +void CollectionConfiguration::updateLastRegisterTime(QHelpEngineCore &helpEngine, QDateTime dt) +{ + helpEngine.setCustomValue(LastRegisterTime, dt); +} + +void CollectionConfiguration::updateLastRegisterTime(QHelpEngineCore &helpEngine) +{ + updateLastRegisterTime(helpEngine, QDateTime::currentDateTime()); +} + +bool CollectionConfiguration::isNewer(const QHelpEngineCore &newer, + const QHelpEngineCore &older) +{ + return creationTime(newer) > creationTime(older); +} + +void CollectionConfiguration::copyConfiguration(const QHelpEngineCore &source, + QHelpEngineCore &target) +{ + setCreationTime(target, creationTime(source)); + setWindowTitle(target, windowTitle(source)); + target.setCurrentFilter(source.currentFilter()); + setCacheDir(target, cacheDir(source), cacheDirIsRelativeToCollection(source)); + setFilterFunctionalityEnabled(target, filterFunctionalityEnabled(source)); + setFilterToolbarVisible(target, filterToolbarVisible(source)); + setAddressBarEnabled(target, addressBarEnabled(source)); + setAddressBarVisible(target, addressBarVisible(source)); + setDocumentationManagerEnabled(target, documentationManagerEnabled(source)); + setApplicationIcon(target, applicationIcon(source)); + setAboutMenuTexts(target, aboutMenuTexts(source)); + setAboutIcon(target, aboutIcon(source)); + setAboutTexts(target, aboutTexts(source)); + setAboutImages(target, aboutImages(source)); + setDefaultHomePage(target, defaultHomePage(source)); + setFullTextSearchFallbackEnabled(target, fullTextSearchFallbackEnabled(source)); +} + +bool CollectionConfiguration:: fullTextSearchFallbackEnabled( + const QHelpEngineCore &helpEngine) +{ + return helpEngine.customValue(FullTextSearchFallbackKey, false).toBool(); +} + +void CollectionConfiguration::setFullTextSearchFallbackEnabled( + QHelpEngineCore &helpEngine, bool on) +{ + helpEngine.setCustomValue(FullTextSearchFallbackKey, on); +} + +QT_END_NAMESPACE diff --git a/src/assistant/shared/collectionconfiguration.h b/src/assistant/shared/collectionconfiguration.h new file mode 100644 index 0000000..854da02 --- /dev/null +++ b/src/assistant/shared/collectionconfiguration.h @@ -0,0 +1,112 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef COLLECTIONCONFIGURATION_H +#define COLLECTIONCONFIGURATION_H + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QHelpEngineCore; + +class CollectionConfiguration +{ +public: + static const QString windowTitle(const QHelpEngineCore &helpEngine); + static void setWindowTitle(QHelpEngineCore &helpEngine, + const QString &windowTitle); + + static const QString cacheDir(const QHelpEngineCore &helpEngine); + static bool cacheDirIsRelativeToCollection(const QHelpEngineCore &helpEngine); + static void setCacheDir(QHelpEngineCore &helpEngine, + const QString &cacheDir, bool relativeToCollection); + + static uint creationTime(const QHelpEngineCore &helpEngine); + static void setCreationTime(QHelpEngineCore &helpEngine, uint time); + + static bool filterFunctionalityEnabled(const QHelpEngineCore &helpEngine); + static void setFilterFunctionalityEnabled(QHelpEngineCore &helpEngine, + bool enabled); + + static bool filterToolbarVisible(const QHelpEngineCore &helpEngine); + static void setFilterToolbarVisible(QHelpEngineCore &helpEngine, + bool visible); + + static bool addressBarEnabled(const QHelpEngineCore &helpEngine); + static void setAddressBarEnabled(QHelpEngineCore &helpEngine, bool enabled); + + static bool addressBarVisible(const QHelpEngineCore &helpEngine); + static void setAddressBarVisible(QHelpEngineCore &helpEngine, bool visible); + + + static bool documentationManagerEnabled(const QHelpEngineCore &helpEngine); + static void setDocumentationManagerEnabled(QHelpEngineCore &helpEngine, + bool enabled); + + static const QByteArray applicationIcon(const QHelpEngineCore &helpEngine); + static void setApplicationIcon(QHelpEngineCore &helpEngine, + const QByteArray &icon); + + // TODO: Encapsulate encoding from/to QByteArray here + static const QByteArray aboutMenuTexts(const QHelpEngineCore &helpEngine); + static void setAboutMenuTexts(QHelpEngineCore &helpEngine, + const QByteArray &texts); + + static const QByteArray aboutIcon(const QHelpEngineCore &helpEngine); + static void setAboutIcon(QHelpEngineCore &helpEngine, + const QByteArray &icon); + + // TODO: Encapsulate encoding from/to QByteArray here + static const QByteArray aboutTexts(const QHelpEngineCore &helpEngine); + static void setAboutTexts(QHelpEngineCore &helpEngine, + const QByteArray &texts); + + static const QByteArray aboutImages(const QHelpEngineCore &helpEngine); + static void setAboutImages(QHelpEngineCore &helpEngine, + const QByteArray &images); + + static const QString defaultHomePage(const QHelpEngineCore &helpEngine); + static void setDefaultHomePage(QHelpEngineCore &helpEngine, + const QString &page); + + // TODO: Don't allow last pages and zoom factors to be set in isolation + // Perhaps also fill up missing elements automatically or assert. + static const QStringList lastShownPages(const QHelpEngineCore &helpEngine); + static void setLastShownPages(QHelpEngineCore &helpEngine, + const QStringList &lastShownPages); + static const QStringList lastZoomFactors(const QHelpEngineCore &helpEngine); + static void setLastZoomFactors(QHelpEngineCore &helPEngine, + const QStringList &lastZoomFactors); + + static int lastTabPage(const QHelpEngineCore &helpEngine); + static void setLastTabPage(QHelpEngineCore &helpEngine, int lastPage); + + static bool isNewer(const QHelpEngineCore &newer, + const QHelpEngineCore &older); + static void copyConfiguration(const QHelpEngineCore &source, + QHelpEngineCore &target); + + /* + * Note that this only reflects register actions caused by the + * "-register" command line switch, not GUI or remote control actions. + */ + static const QDateTime lastRegisterTime(const QHelpEngineCore &helpEngine); + static void updateLastRegisterTime(QHelpEngineCore &helpEngine, QDateTime dt); + static void updateLastRegisterTime(QHelpEngineCore &helpEngine); + + static bool fullTextSearchFallbackEnabled(const QHelpEngineCore &helpEngine); + static void setFullTextSearchFallbackEnabled(QHelpEngineCore &helpEngine, + bool on); + + static const QString DefaultZoomFactor; + static const QString ListSeparator; +}; + +QT_END_NAMESPACE + +#endif // COLLECTIONCONFIGURATION_H diff --git a/src/designer/CMakeLists.txt b/src/designer/CMakeLists.txt new file mode 100644 index 0000000..b4deaf9 --- /dev/null +++ b/src/designer/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_FEATURE_designer) + return() +endif() +add_subdirectory(src) diff --git a/src/designer/data/README b/src/designer/data/README new file mode 100644 index 0000000..5b05ffa --- /dev/null +++ b/src/designer/data/README @@ -0,0 +1,8 @@ +You may generate ui4.h and ui4.cpp parser files by using the generate_ui.py script +or manually by invoking the xsltproc command: + +xsltproc generate_header.xsl ui4.xsd > ui4.h +xsltproc generate_impl.xsl ui4.xsd > ui4.cpp + +Remember to update uic sources in qtbase module accordingly, +adapting the license. diff --git a/src/designer/data/generate_header.xsl b/src/designer/data/generate_header.xsl new file mode 100644 index 0000000..c2b12df --- /dev/null +++ b/src/designer/data/generate_header.xsl @@ -0,0 +1,482 @@ + +]> + + + + + + + + + + + + + class + + ;&endl; + + + + + + + + + + + + // child element accessors&endl; + + + + + + + + + + + + + + + + + + + enum Kind { Unknown = 0 + + + + + + + + + + + + + , + + + };&endl; + inline Kind kind() const { return m_kind; }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + inline + + element + + () const { return m_ + + ; }&endl; + + + + + takeElement + + ();&endl; + + + void setElement + + ( + + a);&endl; + + + inline bool hasElement + + () const { return m_children & + + ; }&endl; + void clearElement + + ();&endl; + + &endl; + + + + + + + + + + + + + + &endl; // child element data&endl; + Kind m_kind = Unknown;&endl; + + + &endl; // child element data&endl; + uint m_children = 0;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + m_ + + + + + + + = 0 + + + = 0.0 + + + = false + + + + + = nullptr + + + + + ;&endl; + + + + &endl; enum Child {&endl; + + + + + + + + + + + + = + + + + + , + + &endl; + + + };&endl; + + + + + + + + + + + + + // attribute accessors&endl; + + + + + + + + + + + + + + + + + + + + + + + + + inline bool hasAttribute + + () const { return m_has_attr_ + + ; }&endl; + + inline + + attribute + + () const { return m_attr_ + + ; }&endl; + + inline void setAttribute + + ( + + a) { m_attr_ + + = a; m_has_attr_ + + = true; }&endl; + + inline void clearAttribute + + () { m_has_attr_ + + = false; }&endl;&endl; + + + + + + + + + + + class QDESIGNER_UILIB_EXPORT + + {&endl; Q_DISABLE_COPY_MOVE( + + )&endl; + public:&endl; + + + () = default;&endl; + ~ + + ();&endl;&endl; + + void read(QXmlStreamReader &reader);&endl; + void write(QXmlStreamWriter &writer, const QString &tagName = QString()) const;&endl;&endl; + + + inline QString text() const { return m_text; }&endl; + inline void setText(const QString &s) { m_text = s; }&endl;&endl; + + + + + + + + + + + private:&endl; + + + QString m_text;&endl;&endl; + + + + void clear();&endl;&endl; + + + + + + + // attribute data&endl; + + + + + + + + + + + + + + + m_attr_ + + + + + = 0 + + + = 0.0 + + + = false + + + + ;&endl; + bool m_has_attr_ + + = false;&endl; + + &endl; + + + + + + + + };&endl;&endl; + + + + + + +@LICENSE@ +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Widgets Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT! + +#ifndef UI4_H +#define UI4_H + +#include <qlist.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qxmlstream.h> +#include <qglobal.h> + +QT_BEGIN_NAMESPACE + +#define QDESIGNER_UILIB_EXTERN Q_DECL_EXPORT +#define QDESIGNER_UILIB_IMPORT Q_DECL_IMPORT + +#if defined(QT_DESIGNER_STATIC) || defined(QT_UIC) || defined(QT_UIC3) +# define QDESIGNER_UILIB_EXPORT +#elif defined(QDESIGNER_UILIB_LIBRARY) +# define QDESIGNER_UILIB_EXPORT QDESIGNER_UILIB_EXTERN +#else +# define QDESIGNER_UILIB_EXPORT QDESIGNER_UILIB_IMPORT +#endif + +#ifndef QDESIGNER_UILIB_EXPORT +# define QDESIGNER_UILIB_EXPORT +#endif + +#ifdef QFORMINTERNAL_NAMESPACE +namespace QFormInternal +{ +#endif + + + + &endl; + /*******************************************************************************&endl; + ** Forward declarations&endl; + */&endl;&endl; + + + + + + + + &endl; + /*******************************************************************************&endl; + ** Declarations&endl; + */&endl;&endl; + + + + + + + +#ifdef QFORMINTERNAL_NAMESPACE +} +#endif + +QT_END_NAMESPACE + +#endif // UI4_H + + + diff --git a/src/designer/data/generate_impl.xsl b/src/designer/data/generate_impl.xsl new file mode 100644 index 0000000..7ce02f4 --- /dev/null +++ b/src/designer/data/generate_impl.xsl @@ -0,0 +1,845 @@ + +]> + + + + + + + + + + + + + + + + + + + + + m_ + + = 0;&endl; + + + m_ + + = 0.0;&endl; + + + m_ + + = false;&endl; + + + + + m_ + + = nullptr;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + qDeleteAll(m_ + + );&endl; + + m_ + + .clear();&endl; + + + + delete m_ + + ;&endl; + + + + + + + + + + + + ::~ + + () + + + + + + + + + + + + &endl;{&endl; + + }&endl;&endl; + + + = default;&endl;&endl; + + + + + + + + + + + + void + ::clear()&endl; + {&endl; + + + + + + + + + + + &endl; + + + + m_kind = Unknown;&endl;&endl; + + + + + m_children = 0;&endl; + + + + + + + + + + }&endl;&endl; + + + + + + + + u" + + "_s + + + + + + + + + const QXmlStreamAttributes &attributes = reader.attributes();&endl; + for (const QXmlStreamAttribute &attribute : attributes) {&endl; + const auto name = attribute.name();&endl; + + + + + + + + + + + + + + + + + attribute.value() + + + + + if (name == + + + + ) {&endl; + setAttribute + + ( + + );&endl; + continue;&endl; + }&endl; + + + reader.raiseError("Unexpected attribute "_L1 + name);&endl; + }&endl; + &endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if (!tag.compare( + + + + , Qt::CaseInsensitive)) {&endl; + + + + qWarning("Omitting deprecated element < + + >.");&endl; + reader.skipCurrentElement();&endl; + + + + + + + + + + setElement + + ( + + );&endl; + + + + + + + + + + m_ + + .append( + + );&endl; + + + auto + *v = new Dom + + ();&endl; + v->read(reader);&endl; + setElement + + (v);&endl; + + + auto + *v = new Dom + + ();&endl; + v->read(reader);&endl; + m_ + + .append(v);&endl; + + + continue;&endl; + }&endl; + + + + + + + + void + + ::read(QXmlStreamReader &reader)&endl; + + {&endl; + + + + + + while (!reader.hasError()) {&endl; + switch (reader.readNext()) {&endl; + case QXmlStreamReader::StartElement : {&endl; + const auto tag = reader.name();&endl; + + + + + + + + reader.raiseError("Unexpected element "_L1 + tag);&endl; + }&endl; + break;&endl; + case QXmlStreamReader::EndElement :&endl; + return;&endl; + + + + case QXmlStreamReader::Characters :&endl; + if (!reader.isWhitespace())&endl; + m_text.append(reader.text().toString());&endl; + break;&endl; + + + default :&endl; + break;&endl; + }&endl; + }&endl; + }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + if (hasAttribute + + ())&endl; + writer.writeAttribute( + + + + + , + + + + + + + );&endl;&endl; + + + + + + + + switch (kind()) {&endl; + + + + + + + + + + + + + + + + + + + + + + + + case + + :&endl; + + + + + + + + + + writer.writeTextElement( + + + + , + + );&endl; + + + + + + + + + if (m_ + + != nullptr)&endl; + m_ + + ->write(writer, + + + + );&endl; + + + break;&endl; + &endl; + + + default:&endl; + break;&endl; + }&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + for ( + + v : m_ + + )&endl; + + + v->write(writer, + + + + );&endl; + + + + + + + + + + writer.writeTextElement( + + + + , + + );&endl; + + + &endl; + + + if (m_children & + + )&endl; + + + m_ + + ->write(writer, + + + + );&endl; + + + + + + + + + writer.writeTextElement( + + + + , + + );&endl; + + + &endl; + + + + + + + + + + + + + + + void + + ::write(QXmlStreamWriter &writer, const QString &tagName) const&endl; + {&endl; + + writer.writeStartElement(tagName.isEmpty() ? QStringLiteral(" + + ") : tagName.toLower());&endl;&endl; + + + + + + + + + + + + + + + + + + + + if (!m_text.isEmpty())&endl; + writer.writeCharacters(m_text);&endl;&endl; + + + writer.writeEndElement();&endl; + }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ::takeElement + + ()&endl;{&endl; + + + a = m_ + + ;&endl; + m_ + + = nullptr;&endl; + + m_children ^= + + ;&endl; + + return a;&endl; + }&endl;&endl; + + + void + + ::setElement + + ( + + a)&endl; + {&endl; + + + clear();&endl; + m_kind = + + ;&endl; + + + delete + m_ + + ;&endl; + + + + m_children |= + + ;&endl; + + m_ + + = a;&endl; + }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + void + + ::clearElement + + ()&endl; + {&endl; + + delete m_ + + ;&endl; + m_ + + = nullptr;&endl; + + m_children &= ~ + + ;&endl; + }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@LICENSE@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT! + + + #include "@HEADER@"&endl; + &endl; + &endl; + QT_BEGIN_NAMESPACE&endl; + &endl;using namespace Qt::StringLiterals;&endl;&endl; + + #ifdef QFORMINTERNAL_NAMESPACE&endl; + using namespace QFormInternal;&endl; + #endif&endl; + &endl; + + /*******************************************************************************&endl; + ** Implementations&endl; + */&endl;&endl; + + + + + + + QT_END_NAMESPACE&endl; + + &endl; + + + diff --git a/src/designer/data/generate_shared.xsl b/src/designer/data/generate_shared.xsl new file mode 100644 index 0000000..d1ca2b9 --- /dev/null +++ b/src/designer/data/generate_shared.xsl @@ -0,0 +1,349 @@ + +]> + + + + + + + exportMacro + layoutDefault + layoutFunction + pixmapFunction + customWidgets + tabStops + tabStop + buttonGroups + exportMacro + actionGroup + buttonGroup + customWidget + sizeHint + addPageMethod + sizePolicy + horData + verData + rowSpan + colSpan + addAction + zOrder + startX + startY + endX + endY + centralX + centralY + focalX + focalY + widgetData + coordinateMode + brushStyle + colorRole + pointSize + strikeOut + styleStrategy + hintingPreference + fontWeight + hSizeType + vSizeType + horStretch + verStretch + normalOff + normalOn + disabledOff + disabledOn + activeOff + activeOn + selectedOff + selectedOn + cursorShape + iconSet + stringList + dateTime + pointF + rectF + sizeF + longLong + UInt + uLongLong + rowStretch + columnStretch + rowMinimumHeight + columnMinimumWidth + extraComment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + .toInt() + + + + .toFloat() + + + + .toDouble() + + + + == u"true"_s + + + + .toLongLong() + + + + .toUInt() + + + + .toULongLong() + + ### BZZZZT! ### + + + + + + + + + + .toString() + + + + + + + + + + + + + + + + + + + QString::number( + + ) + + + QString::number( + + ) + + + QString::number( + + ) + + + QString::number( + + ) + + + QString::number( + + , 'f', 8) + + + QString::number( + + , 'f', 15) + + + ( + + ? u"true"_s : u"false"_s) + + ### BZZZZT! ### + + + + + + + + value + + + value + value + value + value + value + value + value + value + pointer + + + + + + + + + + + + QStringList + QList<int> + QList<float> + QList<double> + QList<bool> + QList<qlonglong> + QList<uint> + QList<qulonglong> + QList<Dom *> + + + + + QString + int + float + double + bool + qlonglong + uint + qulonglong + Dom + + + + + + + + + + + + QStringList + QList<int> + QList<float> + QList<double> + QList<bool> + QList<qlonglong> + QList<uint> + QList<qulonglong> + QList<Dom *> + + + + + QString + int + float + double + bool + qlonglong + uint + qulonglong + Dom * + + + + + + + + + + + + const QStringList & + const QList<int> & + const QList<float> & + const QList<double> & + const QList<bool> & + const QList<qlonglong> & + const QList<uint> & + const QList<qulonglong> & + const QList<Dom *> & + + + + + const QString & + int + float + double + bool + qlonglong + uint + qulonglong + Dom * + + + + + + + diff --git a/src/designer/data/generate_ui.py b/src/designer/data/generate_ui.py new file mode 100755 index 0000000..05472c7 --- /dev/null +++ b/src/designer/data/generate_ui.py @@ -0,0 +1,96 @@ +#!/usr/bin/python3 + +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import subprocess + +from argparse import ArgumentParser, RawTextHelpFormatter +from pathlib import Path +from tempfile import NamedTemporaryFile + + +DESCRIPTION = """ +Usage: generate_ui.py + +Generates the source files ui4.cpp, ui4.h used in the uic tool, the QtUiTools library and +Qt Widgets Designer from the XML schema used for .ui files. + +Requires xalan. +""" + + +opt_delete_temp_files = True + + +def read_cpp_license(path): + """Read out the license from a C++ source""" + result = "" + for line in path.read_text().splitlines(): + result += line + "\n" + if 'SPDX-License-Identifier' in line: + break + return result + + +def replace_xsl_keys(xsl_source_file, license, ui_header_name=None): + """Replace special keys in XSL files and return a handle to temporary file""" + xsl = xsl_source_file.read_text() + xsl = xsl.replace("@LICENSE@", license) + if ui_header_name: + xsl = xsl.replace("@HEADER@", ui_header_name) + + result = NamedTemporaryFile(mode='w', suffix='.xsl', + dir=Path.cwd(), + delete=opt_delete_temp_files) + result.write(xsl) + return result + + +def run_xslt(source, sheet, target): + """Run xalan.""" + cmd = ['xalan', '-in', os.fspath(source), '-xsl', os.fspath(sheet), + '-out', os.fspath(target)] + subprocess.check_call(cmd) + + +if __name__ == '__main__': + argument_parser = ArgumentParser(description=DESCRIPTION, + formatter_class=RawTextHelpFormatter) + argument_parser.add_argument('--keep', '-k', action='store_true', + help='Keep temporary files') + options = argument_parser.parse_args() + opt_delete_temp_files = not options.keep + + # Generate uilib header and source. + xml_dir = Path(__file__).parent.resolve() + ui4_xsd = xml_dir / 'ui4.xsd' + + designer_dir = xml_dir.parent + uilib_dir = designer_dir / "src" / "lib" / "uilib" + uilib_impl = uilib_dir / 'ui4.cpp' + license = read_cpp_license(uilib_impl) + + print("Running XSLT processor for uilib header...\n") + header_xsl_source = xml_dir / 'generate_header.xsl' + header_xsl = replace_xsl_keys(header_xsl_source, license) + run_xslt(ui4_xsd, header_xsl.name, uilib_dir / 'ui4_p.h') + + print("Running XSLT processor for uilib source...\n") + impl_xsl_source = xml_dir / 'generate_impl.xsl' + impl_xsl = replace_xsl_keys(impl_xsl_source, license, 'ui4_p.h') + run_xslt(ui4_xsd, impl_xsl.name, uilib_impl) + + # uic: Header is called 'ui4.h' instead of 'ui4_p.h' + uic_dir = designer_dir.parents[2] / "qtbase" / "src" / "tools" / "uic" + uic_impl = uic_dir / 'ui4.cpp' + license = read_cpp_license(uic_impl) + print("Running XSLT processor for uic header...\n") + header_xsl = replace_xsl_keys(header_xsl_source, license) + run_xslt(ui4_xsd, header_xsl.name, uic_dir / 'ui4.h') + print("Running XSLT processor for uic source...\n") + impl_xsl = replace_xsl_keys(impl_xsl_source, license, 'ui4.h') + run_xslt(ui4_xsd, impl_xsl.name, uic_impl) + + subprocess.call(['git', 'diff']) diff --git a/src/designer/data/ui3.xsd b/src/designer/data/ui3.xsd new file mode 100644 index 0000000..5bf417d --- /dev/null +++ b/src/designer/data/ui3.xsd @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/designer/data/ui4.xsd b/src/designer/data/ui4.xsd new file mode 100644 index 0000000..0063f47 --- /dev/null +++ b/src/designer/data/ui4.xsd @@ -0,0 +1,557 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/designer/src/CMakeLists.txt b/src/designer/src/CMakeLists.txt new file mode 100644 index 0000000..00d2a48 --- /dev/null +++ b/src/designer/src/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_exclude_tool_directories_from_default_target( + lib + components + designer + plugins +) + +if(QT_FEATURE_process) + add_subdirectory(lib) + add_subdirectory(components) + add_subdirectory(designer) +endif() +if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_process) + add_subdirectory(plugins) +endif() diff --git a/src/designer/src/components/CMakeLists.txt b/src/designer/src/components/CMakeLists.txt new file mode 100644 index 0000000..ec6a9a8 --- /dev/null +++ b/src/designer/src/components/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(lib) diff --git a/src/designer/src/components/buddyeditor/buddyeditor.cpp b/src/designer/src/components/buddyeditor/buddyeditor.cpp new file mode 100644 index 0000000..452d337 --- /dev/null +++ b/src/designer/src/components/buddyeditor/buddyeditor.cpp @@ -0,0 +1,398 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "buddyeditor.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto buddyPropertyC = "buddy"_L1; + +static bool canBeBuddy(QWidget *w, QDesignerFormWindowInterface *form) +{ + if (qobject_cast(w) || qobject_cast(w)) + return false; + if (w == form->mainContainer() || w->isHidden() ) + return false; + + QExtensionManager *ext = form->core()->extensionManager(); + if (QDesignerPropertySheetExtension *sheet = qt_extension(ext, w)) { + const int index = sheet->indexOf(u"focusPolicy"_s); + if (index != -1) { + bool ok = false; + const Qt::FocusPolicy q = static_cast(qdesigner_internal::Utils::valueOf(sheet->property(index), &ok)); + // Refuse No-focus unless the widget is promoted. + return (ok && q != Qt::NoFocus) || qdesigner_internal::isPromoted(form->core(), w); + } + } + return false; +} + +static QString buddy(QLabel *label, QDesignerFormEditorInterface *core) +{ + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), label); + if (sheet == nullptr) + return QString(); + const int prop_idx = sheet->indexOf(buddyPropertyC); + if (prop_idx == -1) + return QString(); + return sheet->property(prop_idx).toString(); +} + +namespace qdesigner_internal { + +/******************************************************************************* +** BuddyEditor +*/ + +BuddyEditor::BuddyEditor(QDesignerFormWindowInterface *form, QWidget *parent) : + ConnectionEdit(parent, form), + m_formWindow(form), + m_updating(false) +{ +} + + +QWidget *BuddyEditor::widgetAt(const QPoint &pos) const +{ + QWidget *w = ConnectionEdit::widgetAt(pos); + + while (w != nullptr && !m_formWindow->isManaged(w)) + w = w->parentWidget(); + if (!w) + return w; + + if (state() == Editing) { + QLabel *label = qobject_cast(w); + if (label == nullptr) + return nullptr; + const int cnt = connectionCount(); + for (int i = 0; i < cnt; ++i) { + Connection *con = connection(i); + if (con->widget(EndPoint::Source) == w) + return nullptr; + } + } else { + if (!canBeBuddy(w, m_formWindow)) + return nullptr; + } + + return w; +} + +Connection *BuddyEditor::createConnection(QWidget *source, QWidget *destination) +{ + return new Connection(this, source, destination); +} + +QDesignerFormWindowInterface *BuddyEditor::formWindow() const +{ + return m_formWindow; +} + +void BuddyEditor::updateBackground() +{ + if (m_updating || background() == nullptr) + return; + ConnectionEdit::updateBackground(); + + m_updating = true; + QList newList; + const auto label_list = background()->findChildren(); + for (QLabel *label : label_list) { + const QString buddy_name = buddy(label, m_formWindow->core()); + if (buddy_name.isEmpty()) + continue; + + const QWidgetList targets = background()->findChildren(buddy_name); + if (targets.isEmpty()) + continue; + + const auto wit = std::find_if(targets.cbegin(), targets.cend(), + [] (const QWidget *w) { return !w->isHidden(); }); + if (wit == targets.cend()) + continue; + + Connection *con = new Connection(this); + con->setEndPoint(EndPoint::Source, label, widgetRect(label).center()); + con->setEndPoint(EndPoint::Target, *wit, widgetRect(*wit).center()); + newList.append(con); + } + + QList toRemove; + + const int c = connectionCount(); + for (int i = 0; i < c; i++) { + Connection *con = connection(i); + QObject *source = con->object(EndPoint::Source); + QObject *target = con->object(EndPoint::Target); + const bool found = + std::any_of(newList.cbegin(), newList.cend(), + [source, target] (const Connection *nc) + { return nc->object(EndPoint::Source) == source && nc->object(EndPoint::Target) == target; }); + if (!found) + toRemove.append(con); + } + if (!toRemove.isEmpty()) { + DeleteConnectionsCommand command(this, toRemove); + command.redo(); + for (Connection *con : std::as_const(toRemove)) + delete takeConnection(con); + } + + for (Connection *newConn : std::as_const(newList)) { + bool found = false; + const int c = connectionCount(); + for (int i = 0; i < c; i++) { + Connection *con = connection(i); + if (con->object(EndPoint::Source) == newConn->object(EndPoint::Source) && + con->object(EndPoint::Target) == newConn->object(EndPoint::Target)) { + found = true; + break; + } + } + if (found) { + delete newConn; + } else { + AddConnectionCommand command(this, newConn); + command.redo(); + } + } + m_updating = false; +} + +void BuddyEditor::setBackground(QWidget *background) +{ + clear(); + ConnectionEdit::setBackground(background); + if (background == nullptr) + return; + + const auto label_list = background->findChildren(); + for (QLabel *label : label_list) { + const QString buddy_name = buddy(label, m_formWindow->core()); + if (buddy_name.isEmpty()) + continue; + QWidget *target = background->findChild(buddy_name); + if (target == nullptr) + continue; + + Connection *con = new Connection(this); + con->setEndPoint(EndPoint::Source, label, widgetRect(label).center()); + con->setEndPoint(EndPoint::Target, target, widgetRect(target).center()); + addConnection(con); + } +} + +static QUndoCommand *createBuddyCommand(QDesignerFormWindowInterface *fw, QLabel *label, QWidget *buddy) +{ + SetPropertyCommand *command = new SetPropertyCommand(fw); + command->init(label, buddyPropertyC, buddy->objectName()); + command->setText(BuddyEditor::tr("Add buddy")); + return command; +} + +void BuddyEditor::endConnection(QWidget *target, const QPoint &pos) +{ + Connection *tmp_con = newlyAddedConnection(); + Q_ASSERT(tmp_con != nullptr); + + tmp_con->setEndPoint(EndPoint::Target, target, pos); + + QWidget *source = tmp_con->widget(EndPoint::Source); + Q_ASSERT(source != nullptr); + Q_ASSERT(target != nullptr); + setEnabled(false); + Connection *new_con = createConnection(source, target); + setEnabled(true); + if (new_con != nullptr) { + new_con->setEndPoint(EndPoint::Source, source, tmp_con->endPointPos(EndPoint::Source)); + new_con->setEndPoint(EndPoint::Target, target, tmp_con->endPointPos(EndPoint::Target)); + + selectNone(); + addConnection(new_con); + QLabel *source = qobject_cast(new_con->widget(EndPoint::Source)); + QWidget *target = new_con->widget(EndPoint::Target); + if (source) { + undoStack()->push(createBuddyCommand(m_formWindow, source, target)); + } else { + qDebug("BuddyEditor::endConnection(): not a label"); + } + setSelected(new_con, true); + } + + clearNewlyAddedConnection(); + findObjectsUnderMouse(mapFromGlobal(QCursor::pos())); +} + +void BuddyEditor::widgetRemoved(QWidget *widget) +{ + QWidgetList child_list = widget->findChildren(); + child_list.prepend(widget); + + ConnectionSet remove_set; + for (QWidget *w : std::as_const(child_list)) { + const ConnectionList &cl = connectionList(); + for (Connection *con : cl) { + if (con->widget(EndPoint::Source) == w || con->widget(EndPoint::Target) == w) + remove_set.insert(con, con); + } + } + + if (!remove_set.isEmpty()) { + undoStack()->beginMacro(tr("Remove buddies")); + for (Connection *con : std::as_const(remove_set)) { + setSelected(con, false); + con->update(); + QWidget *source = con->widget(EndPoint::Source); + if (qobject_cast(source) == 0) { + qDebug("BuddyConnection::widgetRemoved(): not a label"); + } else { + ResetPropertyCommand *command = new ResetPropertyCommand(formWindow()); + command->init(source, buddyPropertyC); + undoStack()->push(command); + } + delete takeConnection(con); + } + undoStack()->endMacro(); + } +} + +void BuddyEditor::deleteSelected() +{ + const ConnectionSet selectedConnections = selection(); // want copy for unselect + if (selectedConnections.isEmpty()) + return; + + undoStack()->beginMacro(tr("Remove %n buddies", nullptr, selectedConnections.size())); + for (Connection *con : selectedConnections) { + setSelected(con, false); + con->update(); + QWidget *source = con->widget(EndPoint::Source); + if (qobject_cast(source) == 0) { + qDebug("BuddyConnection::deleteSelected(): not a label"); + } else { + ResetPropertyCommand *command = new ResetPropertyCommand(formWindow()); + command->init(source, buddyPropertyC); + undoStack()->push(command); + } + delete takeConnection(con); + } + undoStack()->endMacro(); +} + +void BuddyEditor::autoBuddy() +{ + // Any labels? + auto labelList = background()->findChildren(); + if (labelList.isEmpty()) + return; + // Find already used buddies + QWidgetList usedBuddies; + const ConnectionList &beforeConnections = connectionList(); + for (const Connection *c : beforeConnections) + usedBuddies.push_back(c->widget(EndPoint::Target)); + // Find potential new buddies, keep lists in sync + QWidgetList buddies; + for (auto it = labelList.begin(); it != labelList.end(); ) { + QLabel *label = *it; + QWidget *newBuddy = nullptr; + if (m_formWindow->isManaged(label)) { + const QString buddy_name = buddy(label, m_formWindow->core()); + if (buddy_name.isEmpty()) + newBuddy = findBuddy(label, usedBuddies); + } + if (newBuddy) { + buddies.push_back(newBuddy); + usedBuddies.push_back(newBuddy); + ++it; + } else { + it = labelList.erase(it); + } + } + // Add the list in one go. + if (labelList.isEmpty()) + return; + const auto count = labelList.size(); + Q_ASSERT(count == buddies.size()); + undoStack()->beginMacro(tr("Add %n buddies", nullptr, count)); + for (qsizetype i = 0; i < count; ++i) + undoStack()->push(createBuddyCommand(m_formWindow, labelList.at(i), buddies.at(i))); + undoStack()->endMacro(); + // Now select all new ones + const ConnectionList &connections = connectionList(); + for (Connection *con : connections) + setSelected(con, buddies.contains(con->widget(EndPoint::Target))); +} + +// Geometrically find a potential buddy for label by checking neighbouring children of parent +QWidget *BuddyEditor::findBuddy(QLabel *l, const QWidgetList &existingBuddies) const +{ + enum { DeltaX = 5 }; + const QWidget *parent = l->parentWidget(); + // Try to find next managed neighbour on horizontal line + const QRect geom = l->geometry(); + const int y = geom.center().y(); + QWidget *neighbour = nullptr; + switch (l->layoutDirection()) { + case Qt::LayoutDirectionAuto: + case Qt::LeftToRight: { // Walk right to find next managed neighbour + const int xEnd = parent->size().width(); + for (int x = geom.right() + 1; x < xEnd; x += DeltaX) + if (QWidget *c = parent->childAt (x, y)) + if (m_formWindow->isManaged(c)) { + neighbour = c; + break; + } + } + break; + case Qt::RightToLeft: // Walk left to find next managed neighbour + for (int x = geom.x() - 1; x >= 0; x -= DeltaX) + if (QWidget *c = parent->childAt (x, y)) + if (m_formWindow->isManaged(c)) { + neighbour = c; + break; + } + break; + } + if (neighbour && !existingBuddies.contains(neighbour) && canBeBuddy(neighbour, m_formWindow)) + return neighbour; + + return nullptr; +} + +void BuddyEditor::createContextMenu(QMenu &menu) +{ + QAction *autoAction = menu.addAction(tr("Set automatically")); + connect(autoAction, &QAction::triggered, this, &BuddyEditor::autoBuddy); + menu.addSeparator(); + ConnectionEdit::createContextMenu(menu); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/buddyeditor/buddyeditor.h b/src/designer/src/components/buddyeditor/buddyeditor.h new file mode 100644 index 0000000..d844b99 --- /dev/null +++ b/src/designer/src/components/buddyeditor/buddyeditor.h @@ -0,0 +1,54 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUDDYEDITOR_H +#define BUDDYEDITOR_H + +#include "buddyeditor_global.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +class QLabel; + +namespace qdesigner_internal { + +class QT_BUDDYEDITOR_EXPORT BuddyEditor : public ConnectionEdit +{ + Q_OBJECT + +public: + BuddyEditor(QDesignerFormWindowInterface *form, QWidget *parent); + + QDesignerFormWindowInterface *formWindow() const; + void setBackground(QWidget *background) override; + void deleteSelected() override; + +public slots: + void updateBackground() override; + void widgetRemoved(QWidget *w) override; + void autoBuddy(); + +protected: + QWidget *widgetAt(const QPoint &pos) const override; + Connection *createConnection(QWidget *source, QWidget *destination) override; + void endConnection(QWidget *target, const QPoint &pos) override; + void createContextMenu(QMenu &menu) override; + +private: + QWidget *findBuddy(QLabel *l, const QWidgetList &existingBuddies) const; + + QPointer m_formWindow; + bool m_updating; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/components/buddyeditor/buddyeditor.json b/src/designer/src/components/buddyeditor/buddyeditor.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/designer/src/components/buddyeditor/buddyeditor.json @@ -0,0 +1 @@ +{} diff --git a/src/designer/src/components/buddyeditor/buddyeditor_global.h b/src/designer/src/components/buddyeditor/buddyeditor_global.h new file mode 100644 index 0000000..1085e40 --- /dev/null +++ b/src/designer/src/components/buddyeditor/buddyeditor_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUDDYEDITOR_GLOBAL_H +#define BUDDYEDITOR_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_BUDDYEDITOR_LIBRARY +# define QT_BUDDYEDITOR_EXPORT +#else +# define QT_BUDDYEDITOR_EXPORT +#endif +#else +#define QT_BUDDYEDITOR_EXPORT +#endif + +#endif // BUDDYEDITOR_GLOBAL_H diff --git a/src/designer/src/components/buddyeditor/buddyeditor_plugin.cpp b/src/designer/src/components/buddyeditor/buddyeditor_plugin.cpp new file mode 100644 index 0000000..6b919e4 --- /dev/null +++ b/src/designer/src/components/buddyeditor/buddyeditor_plugin.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include "buddyeditor_plugin.h" +#include "buddyeditor_tool.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +BuddyEditorPlugin::BuddyEditorPlugin() = default; + +BuddyEditorPlugin::~BuddyEditorPlugin() = default; + +bool BuddyEditorPlugin::isInitialized() const +{ + return m_initialized; +} + +void BuddyEditorPlugin::initialize(QDesignerFormEditorInterface *core) +{ + Q_ASSERT(!isInitialized()); + + m_action = new QAction(tr("Edit Buddies"), this); + m_action->setObjectName(u"__qt_edit_buddies_action"_s); + m_action->setIcon(createIconSet("buddytool.png"_L1)); + m_action->setEnabled(false); + + setParent(core); + m_core = core; + m_initialized = true; + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowAdded, + this, &BuddyEditorPlugin::addFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowRemoved, + this, &BuddyEditorPlugin::removeFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &BuddyEditorPlugin::activeFormWindowChanged); +} + +QDesignerFormEditorInterface *BuddyEditorPlugin::core() const +{ + return m_core; +} + +void BuddyEditorPlugin::addFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == false); + + BuddyEditorTool *tool = new BuddyEditorTool(formWindow, this); + m_tools[formWindow] = tool; + connect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + formWindow->registerTool(tool); +} + +void BuddyEditorPlugin::removeFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == true); + + BuddyEditorTool *tool = m_tools.value(formWindow); + m_tools.remove(formWindow); + disconnect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + // ### FIXME disable the tool + + delete tool; +} + +QAction *BuddyEditorPlugin::action() const +{ + return m_action; +} + +void BuddyEditorPlugin::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) +{ + m_action->setEnabled(formWindow != nullptr); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/buddyeditor/buddyeditor_plugin.h b/src/designer/src/components/buddyeditor/buddyeditor_plugin.h new file mode 100644 index 0000000..1e38f0e --- /dev/null +++ b/src/designer/src/components/buddyeditor/buddyeditor_plugin.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUDDYEDITOR_PLUGIN_H +#define BUDDYEDITOR_PLUGIN_H + +#include "buddyeditor_global.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class BuddyEditorTool; + +class QT_BUDDYEDITOR_EXPORT BuddyEditorPlugin: public QObject, public QDesignerFormEditorPluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerFormEditorPluginInterface" FILE "buddyeditor.json") + Q_INTERFACES(QDesignerFormEditorPluginInterface) +public: + BuddyEditorPlugin(); + ~BuddyEditorPlugin() override; + + bool isInitialized() const override; + void initialize(QDesignerFormEditorInterface *core) override; + QAction *action() const override; + + QDesignerFormEditorInterface *core() const override; + +public slots: + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + +private slots: + void addFormWindow(QDesignerFormWindowInterface *formWindow); + void removeFormWindow(QDesignerFormWindowInterface *formWindow); + +private: + QPointer m_core; + QHash m_tools; + bool m_initialized = false; + QAction *m_action = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // BUDDYEDITOR_PLUGIN_H diff --git a/src/designer/src/components/buddyeditor/buddyeditor_tool.cpp b/src/designer/src/components/buddyeditor/buddyeditor_tool.cpp new file mode 100644 index 0000000..0ed4e7e --- /dev/null +++ b/src/designer/src/components/buddyeditor/buddyeditor_tool.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "buddyeditor_tool.h" +#include "buddyeditor.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +BuddyEditorTool::BuddyEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent) + : QDesignerFormWindowToolInterface(parent), + m_formWindow(formWindow), + m_action(new QAction(tr("Edit Buddies"), this)) +{ +} + +BuddyEditorTool::~BuddyEditorTool() = default; + +QDesignerFormEditorInterface *BuddyEditorTool::core() const +{ + return m_formWindow->core(); +} + +QDesignerFormWindowInterface *BuddyEditorTool::formWindow() const +{ + return m_formWindow; +} + +bool BuddyEditorTool::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + Q_UNUSED(widget); + Q_UNUSED(managedWidget); + Q_UNUSED(event); + + return false; +} + +QWidget *BuddyEditorTool::editor() const +{ + if (!m_editor) { + Q_ASSERT(formWindow() != nullptr); + m_editor = new BuddyEditor(formWindow(), nullptr); + connect(formWindow(), &QDesignerFormWindowInterface::mainContainerChanged, + m_editor.data(), &BuddyEditor::setBackground); + connect(formWindow(), &QDesignerFormWindowInterface::changed, + m_editor.data(), &BuddyEditor::updateBackground); + } + + return m_editor; +} + +void BuddyEditorTool::activated() +{ + m_editor->enableUpdateBackground(true); +} + +void BuddyEditorTool::deactivated() +{ + m_editor->enableUpdateBackground(false); +} + +QAction *BuddyEditorTool::action() const +{ + return m_action; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/buddyeditor/buddyeditor_tool.h b/src/designer/src/components/buddyeditor/buddyeditor_tool.h new file mode 100644 index 0000000..136711e --- /dev/null +++ b/src/designer/src/components/buddyeditor/buddyeditor_tool.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUDDYEDITOR_TOOL_H +#define BUDDYEDITOR_TOOL_H + +#include "buddyeditor_global.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class BuddyEditor; + +class QT_BUDDYEDITOR_EXPORT BuddyEditorTool: public QDesignerFormWindowToolInterface +{ + Q_OBJECT +public: + explicit BuddyEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent = nullptr); + ~BuddyEditorTool() override; + + QDesignerFormEditorInterface *core() const override; + QDesignerFormWindowInterface *formWindow() const override; + + QWidget *editor() const override; + QAction *action() const override; + + void activated() override; + void deactivated() override; + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) override; + +private: + QDesignerFormWindowInterface *m_formWindow; + mutable QPointer m_editor; + QAction *m_action; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // BUDDYEDITOR_TOOL_H diff --git a/src/designer/src/components/formeditor/default_actionprovider.cpp b/src/designer/src/components/formeditor/default_actionprovider.cpp new file mode 100644 index 0000000..8903918 --- /dev/null +++ b/src/designer/src/components/formeditor/default_actionprovider.cpp @@ -0,0 +1,171 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "default_actionprovider.h" +#include "invisible_widget_p.h" +#include "qdesigner_toolbar_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ------------ ActionProviderBase: +// Draws the drag indicator when dragging an action over a widget +// that receives action Dnd, such as ToolBar, Menu or MenuBar. +ActionProviderBase::ActionProviderBase(QWidget *widget) : + m_indicator(new InvisibleWidget(widget)) +{ + Q_ASSERT(widget != nullptr); + + m_indicator->setAutoFillBackground(true); + m_indicator->setBackgroundRole(QPalette::Window); + + QPalette p; + p.setColor(m_indicator->backgroundRole(), Qt::red); + m_indicator->setPalette(p); + m_indicator->hide(); +} + +enum { indicatorSize = 2 }; + +// Position an indicator horizontally over the rectangle, indicating +// 'Insert before' (left or right according to layout direction) +static inline QRect horizontalIndicatorRect(const QRect &rect, Qt::LayoutDirection layoutDirection) +{ + // Position right? + QRect rc = QRect(rect.x(), 0, indicatorSize, rect.height() - 1); + if (layoutDirection == Qt::RightToLeft) + rc.moveLeft(rc.x() + rect.width() - indicatorSize); + return rc; +} + +// Position an indicator vertically over the rectangle, indicating 'Insert before' (top) +static inline QRect verticalIndicatorRect(const QRect &rect) +{ + return QRect(0, rect.top(), rect.width() - 1, indicatorSize); +} + +// Determine the geometry of the indicator by retrieving +// the action under mouse and positioning the bar within its geometry. +QRect ActionProviderBase::indicatorGeometry(const QPoint &pos, Qt::LayoutDirection layoutDirection) const +{ + QAction *action = actionAt(pos); + if (!action) + return QRect(); + QRect rc = actionGeometry(action); + return orientation() == Qt::Horizontal ? horizontalIndicatorRect(rc, layoutDirection) : verticalIndicatorRect(rc); +} + +// Adjust the indicator while dragging. (-1,1) is called to finish a DND operation +void ActionProviderBase::adjustIndicator(const QPoint &pos) +{ + if (pos == QPoint(-1, -1)) { + m_indicator->hide(); + return; + } + const QRect ig = indicatorGeometry(pos, m_indicator->layoutDirection()); + if (ig.isValid()) { + m_indicator->setGeometry(ig); + QPalette p = m_indicator->palette(); + if (p.color(m_indicator->backgroundRole()) != Qt::red) { + p.setColor(m_indicator->backgroundRole(), Qt::red); + m_indicator->setPalette(p); + } + m_indicator->show(); + m_indicator->raise(); + } else { + m_indicator->hide(); + } +} + +// ------------- QToolBarActionProvider +QToolBarActionProvider::QToolBarActionProvider(QToolBar *widget, QObject *parent) : + QObject(parent), + ActionProviderBase(widget), + m_widget(widget) +{ +} + +QRect QToolBarActionProvider::actionGeometry(QAction *action) const +{ + return m_widget->actionGeometry(action); +} + +QAction *QToolBarActionProvider::actionAt(const QPoint &pos) const +{ + return ToolBarEventFilter::actionAt(m_widget, pos); +} + +Qt::Orientation QToolBarActionProvider::orientation() const +{ + return m_widget->orientation(); +} + +QRect QToolBarActionProvider::indicatorGeometry(const QPoint &pos, Qt::LayoutDirection layoutDirection) const +{ + const QRect actionRect = ActionProviderBase::indicatorGeometry(pos, layoutDirection); + if (actionRect.isValid()) + return actionRect; + // Toolbar differs in that is has no dummy placeholder to 'insert before' + // when intending to append. Check the free area. + const QRect freeArea = ToolBarEventFilter::freeArea(m_widget); + if (!freeArea.contains(pos)) + return QRect(); + return orientation() == Qt::Horizontal ? horizontalIndicatorRect(freeArea, layoutDirection) : verticalIndicatorRect(freeArea); +} + +// ------------- QMenuBarActionProvider +QMenuBarActionProvider::QMenuBarActionProvider(QMenuBar *widget, QObject *parent) : + QObject(parent), + ActionProviderBase(widget), + m_widget(widget) +{ +} + +QRect QMenuBarActionProvider::actionGeometry(QAction *action) const +{ + return m_widget->actionGeometry(action); +} + +QAction *QMenuBarActionProvider::actionAt(const QPoint &pos) const +{ + return m_widget->actionAt(pos); +} + +Qt::Orientation QMenuBarActionProvider::orientation() const +{ + return Qt::Horizontal; +} + +// ------------- QMenuActionProvider +QMenuActionProvider::QMenuActionProvider(QMenu *widget, QObject *parent) : + QObject(parent), + ActionProviderBase(widget), + m_widget(widget) +{ +} + +QRect QMenuActionProvider::actionGeometry(QAction *action) const +{ + return m_widget->actionGeometry(action); +} + +QAction *QMenuActionProvider::actionAt(const QPoint &pos) const +{ + return m_widget->actionAt(pos); +} + +Qt::Orientation QMenuActionProvider::orientation() const +{ + return Qt::Vertical; +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/default_actionprovider.h b/src/designer/src/components/formeditor/default_actionprovider.h new file mode 100644 index 0000000..88e7fab --- /dev/null +++ b/src/designer/src/components/formeditor/default_actionprovider.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEFAULT_ACTIONPROVIDER_H +#define DEFAULT_ACTIONPROVIDER_H + +#include "formeditor_global.h" +#include "actionprovider_p.h" +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class FormWindow; + +class QT_FORMEDITOR_EXPORT ActionProviderBase: public QDesignerActionProviderExtension +{ +protected: + explicit ActionProviderBase(QWidget *widget); + +public: + void adjustIndicator(const QPoint &pos) override; + virtual Qt::Orientation orientation() const = 0; + +protected: + virtual QRect indicatorGeometry(const QPoint &pos, Qt::LayoutDirection layoutDirection) const; + +private: + QWidget *m_indicator; +}; + +class QT_FORMEDITOR_EXPORT QToolBarActionProvider: public QObject, public ActionProviderBase +{ + Q_OBJECT + Q_INTERFACES(QDesignerActionProviderExtension) +public: + explicit QToolBarActionProvider(QToolBar *widget, QObject *parent = nullptr); + + QRect actionGeometry(QAction *action) const override; + QAction *actionAt(const QPoint &pos) const override; + Qt::Orientation orientation() const override; + +protected: + QRect indicatorGeometry(const QPoint &pos, Qt::LayoutDirection layoutDirection) const override; + +private: + QToolBar *m_widget; +}; + +class QT_FORMEDITOR_EXPORT QMenuBarActionProvider: public QObject, public ActionProviderBase +{ + Q_OBJECT + Q_INTERFACES(QDesignerActionProviderExtension) +public: + explicit QMenuBarActionProvider(QMenuBar *widget, QObject *parent = nullptr); + + QRect actionGeometry(QAction *action) const override; + QAction *actionAt(const QPoint &pos) const override; + Qt::Orientation orientation() const override; + +private: + QMenuBar *m_widget; +}; + +class QT_FORMEDITOR_EXPORT QMenuActionProvider: public QObject, public ActionProviderBase +{ + Q_OBJECT + Q_INTERFACES(QDesignerActionProviderExtension) +public: + explicit QMenuActionProvider(QMenu *widget, QObject *parent = nullptr); + + QRect actionGeometry(QAction *action) const override; + QAction *actionAt(const QPoint &pos) const override; + Qt::Orientation orientation() const override; + +private: + QMenu *m_widget; +}; + +using QToolBarActionProviderFactory = ExtensionFactory; +using QMenuBarActionProviderFactory = ExtensionFactory; +using QMenuActionProviderFactory = ExtensionFactory; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DEFAULT_ACTIONPROVIDER_H diff --git a/src/designer/src/components/formeditor/default_container.cpp b/src/designer/src/components/formeditor/default_container.cpp new file mode 100644 index 0000000..ea68b9b --- /dev/null +++ b/src/designer/src/components/formeditor/default_container.cpp @@ -0,0 +1,137 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "default_container.h" +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +template +static inline void setCurrentContainerIndex(int index, Container *container) +{ + const bool blocked = container->signalsBlocked(); + container->blockSignals(true); + container->setCurrentIndex(index); + container->blockSignals(blocked); +} + +static inline void ensureNoParent(QWidget *widget) +{ + if (widget->parentWidget()) + widget->setParent(nullptr); +} + +static constexpr auto PageLabel = "Page"_L1; + +namespace qdesigner_internal { + +// --------- QStackedWidgetContainer +QStackedWidgetContainer::QStackedWidgetContainer(QStackedWidget *widget, QObject *parent) : + QObject(parent), + m_widget(widget) +{ +} + +void QStackedWidgetContainer::setCurrentIndex(int index) +{ + setCurrentContainerIndex(index, m_widget); +} + +void QStackedWidgetContainer::addWidget(QWidget *widget) +{ + ensureNoParent(widget); + m_widget->addWidget(widget); +} + +void QStackedWidgetContainer::insertWidget(int index, QWidget *widget) +{ + ensureNoParent(widget); + m_widget->insertWidget(index, widget); +} + +void QStackedWidgetContainer::remove(int index) +{ + m_widget->removeWidget(widget(index)); +} + +// --------- QTabWidgetContainer +QTabWidgetContainer::QTabWidgetContainer(QTabWidget *widget, QObject *parent) : + QObject(parent), + m_widget(widget) +{ +} + +void QTabWidgetContainer::setCurrentIndex(int index) +{ + setCurrentContainerIndex(index, m_widget); +} + +void QTabWidgetContainer::addWidget(QWidget *widget) +{ + ensureNoParent(widget); + m_widget->addTab(widget, QString::fromUtf8(PageLabel)); +} + +void QTabWidgetContainer::insertWidget(int index, QWidget *widget) +{ + ensureNoParent(widget); + m_widget->insertTab(index, widget, QString::fromUtf8(PageLabel)); +} + +void QTabWidgetContainer::remove(int index) +{ + m_widget->removeTab(index); +} + +// ------------------- QToolBoxContainer +QToolBoxContainer::QToolBoxContainer(QToolBox *widget, QObject *parent) : + QObject(parent), + m_widget(widget) +{ +} + +void QToolBoxContainer::setCurrentIndex(int index) +{ + setCurrentContainerIndex(index, m_widget); +} + +void QToolBoxContainer::addWidget(QWidget *widget) +{ + ensureNoParent(widget); + m_widget->addItem(widget, QString::fromUtf8(PageLabel)); +} + +void QToolBoxContainer::insertWidget(int index, QWidget *widget) +{ + ensureNoParent(widget); + m_widget->insertItem(index, widget, QString::fromUtf8(PageLabel)); +} + +void QToolBoxContainer::remove(int index) +{ + m_widget->removeItem(index); +} + +// ------------------- QScrollAreaContainer +// We pass on active=true only if there are no children yet. +// If there are children, it is a legacy custom widget QScrollArea that has an internal, +// unmanaged child, in which case we deactivate the extension (otherwise we crash). +// The child will then not show up in the task menu + +QScrollAreaContainer::QScrollAreaContainer(QScrollArea *widget, QObject *parent) : + QObject(parent), + SingleChildContainer(widget, widget->widget() == nullptr) +{ +} +// ------------------- QDockWidgetContainer +QDockWidgetContainer::QDockWidgetContainer(QDockWidget *widget, QObject *parent) : + QObject(parent), + SingleChildContainer(widget) +{ +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/default_container.h b/src/designer/src/components/formeditor/default_container.h new file mode 100644 index 0000000..a22abad --- /dev/null +++ b/src/designer/src/components/formeditor/default_container.h @@ -0,0 +1,184 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEFAULT_CONTAINER_H +#define DEFAULT_CONTAINER_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ------------ QStackedWidgetContainer +class QStackedWidgetContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QStackedWidgetContainer(QStackedWidget *widget, QObject *parent = nullptr); + + int count() const override { return m_widget->count(); } + QWidget *widget(int index) const override { return m_widget->widget(index); } + + int currentIndex() const override { return m_widget->currentIndex(); } + void setCurrentIndex(int index) override; + + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QStackedWidget *m_widget; +}; + +// ------------ QTabWidgetContainer +class QTabWidgetContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QTabWidgetContainer(QTabWidget *widget, QObject *parent = nullptr); + + int count() const override { return m_widget->count(); } + QWidget *widget(int index) const override { return m_widget->widget(index); } + + int currentIndex() const override { return m_widget->currentIndex(); } + void setCurrentIndex(int index) override; + + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QTabWidget *m_widget; +}; + +// ------------ QToolBoxContainer +class QToolBoxContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QToolBoxContainer(QToolBox *widget, QObject *parent = nullptr); + + int count() const override { return m_widget->count(); } + QWidget *widget(int index) const override { return m_widget->widget(index); } + + int currentIndex() const override { return m_widget->currentIndex(); } + void setCurrentIndex(int index) override; + + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QToolBox *m_widget; +}; + +// ------------ SingleChildContainer: +// Template for containers that have a single child widget using widget()/setWidget(). + +template +class SingleChildContainer: public QDesignerContainerExtension +{ +protected: + explicit SingleChildContainer(Container *widget, bool active = true); +public: + int count() const override; + QWidget *widget(int index) const override; + int currentIndex() const override; + void setCurrentIndex(int /*index*/) override {} + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + void remove(int /*index*/) override {} + + bool canAddWidget() const override { return false; } + bool canRemove(int) const override { return false; } + +private: + const bool m_active; + Container *m_container; +}; + +template +SingleChildContainer::SingleChildContainer(Container *widget, bool active) : + m_active(active), + m_container(widget) +{ +} + +template +int SingleChildContainer::count() const +{ + return m_active && m_container->widget() ? 1 : 0; +} + +template +QWidget *SingleChildContainer::widget(int /* index */) const +{ + return m_container->widget(); +} + +template +int SingleChildContainer::currentIndex() const +{ + return m_active && m_container->widget() ? 0 : -1; +} + +template +void SingleChildContainer::addWidget(QWidget *widget) +{ + Q_ASSERT(m_container->widget() == nullptr); + widget->setParent(m_container); + m_container->setWidget(widget); +} + +template +void SingleChildContainer::insertWidget(int /* index */, QWidget *widget) +{ + addWidget(widget); +} + +// ------------ QScrollAreaContainer +class QScrollAreaContainer: public QObject, public SingleChildContainer +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QScrollAreaContainer(QScrollArea *widget, QObject *parent = nullptr); +}; + +// --------------- QDockWidgetContainer +class QDockWidgetContainer: public QObject, public SingleChildContainer +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QDockWidgetContainer(QDockWidget *widget, QObject *parent = nullptr); +}; + +using QDesignerStackedWidgetContainerFactory = ExtensionFactory; +using QDesignerTabWidgetContainerFactory = ExtensionFactory; +using QDesignerToolBoxContainerFactory = ExtensionFactory; +using QScrollAreaContainerFactory = ExtensionFactory; +using QDockWidgetContainerFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DEFAULT_CONTAINER_H diff --git a/src/designer/src/components/formeditor/default_layoutdecoration.cpp b/src/designer/src/components/formeditor/default_layoutdecoration.cpp new file mode 100644 index 0000000..171bd0f --- /dev/null +++ b/src/designer/src/components/formeditor/default_layoutdecoration.cpp @@ -0,0 +1,41 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "default_layoutdecoration.h" +#include "qlayout_widget_p.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ---- QDesignerLayoutDecorationFactory ---- +QDesignerLayoutDecorationFactory::QDesignerLayoutDecorationFactory(QExtensionManager *parent) + : QExtensionFactory(parent) +{ +} + +QObject *QDesignerLayoutDecorationFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const +{ + if (!object->isWidgetType() || iid != Q_TYPEID(QDesignerLayoutDecorationExtension)) + return nullptr; + + QWidget *widget = qobject_cast(object); + + if (const QLayoutWidget *layoutWidget = qobject_cast(widget)) + return QLayoutSupport::createLayoutSupport(layoutWidget->formWindow(), widget, parent); + + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(widget)) + if (LayoutInfo::managedLayout(fw->core(), widget)) + return QLayoutSupport::createLayoutSupport(fw, widget, parent); + + return nullptr; +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/default_layoutdecoration.h b/src/designer/src/components/formeditor/default_layoutdecoration.h new file mode 100644 index 0000000..b9cf593 --- /dev/null +++ b/src/designer/src/components/formeditor/default_layoutdecoration.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEFAULT_LAYOUTDECORATION_H +#define DEFAULT_LAYOUTDECORATION_H + +#include "formeditor_global.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { +class QDesignerLayoutDecorationFactory: public QExtensionFactory +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionFactory) +public: + explicit QDesignerLayoutDecorationFactory(QExtensionManager *parent = nullptr); + +protected: + QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DEFAULT_LAYOUTDECORATION_H diff --git a/src/designer/src/components/formeditor/defaultbrushes.xml b/src/designer/src/components/formeditor/defaultbrushes.xml new file mode 100644 index 0000000..88035c3 --- /dev/null +++ b/src/designer/src/components/formeditor/defaultbrushes.xml @@ -0,0 +1,542 @@ + + + + + + + 0 + 0 + 255 + + + + + 0 + 0 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + + + 0 + 0 + 0 + + + + + 0 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 255 + 0 + + + + + 255 + 255 + 0 + + + + + + + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + + + + + + + 60 + 160 + 0 + + + + + 60 + 160 + 0 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + + + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 5 + 0 + 70 + + + + + 5 + 0 + 70 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 255 + 0 + + + + + 255 + 255 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 255 + + + + + + + 0 + 255 + 255 + + + + + + + 0 + 255 + 0 + + + + + + + 255 + 0 + 255 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 0 + + + + diff --git a/src/designer/src/components/formeditor/deviceprofiledialog.cpp b/src/designer/src/components/formeditor/deviceprofiledialog.cpp new file mode 100644 index 0000000..188e24c --- /dev/null +++ b/src/designer/src/components/formeditor/deviceprofiledialog.cpp @@ -0,0 +1,172 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "deviceprofiledialog.h" +#include "ui_deviceprofiledialog.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto profileExtensionC = "qdp"_L1; + +static inline QString fileFilter() +{ + return qdesigner_internal::DeviceProfileDialog::tr("Device Profiles (*.%1)").arg(profileExtensionC); +} + +// Populate a combo with a sequence of integers, also set them as data. +template + static void populateNumericCombo(IntIterator i1, IntIterator i2, QComboBox *cb) +{ + QString s; + for ( ; i1 != i2 ; ++i1) { + const int n = *i1; + s.setNum(n); + cb->addItem(s, QVariant(n)); + } +} + +namespace qdesigner_internal { + +DeviceProfileDialog::DeviceProfileDialog(QDesignerDialogGuiInterface *dlgGui, QWidget *parent) : + QDialog(parent), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::DeviceProfileDialog), + m_dlgGui(dlgGui) +{ + setModal(true); + m_ui->setupUi(this); + + const auto standardFontSizes = QFontDatabase::standardSizes(); + populateNumericCombo(standardFontSizes.constBegin(), standardFontSizes.constEnd(), m_ui->m_systemFontSizeCombo); + + // 288pt observed on macOS. + const int maxPointSize = qMax(288, standardFontSizes.constLast()); + m_ui->m_systemFontSizeCombo->setValidator(new QIntValidator(1, maxPointSize, + m_ui->m_systemFontSizeCombo)); + + // Styles + const QStringList styles = QStyleFactory::keys(); + m_ui->m_styleCombo->addItem(tr("Default"), QVariant(QString())); + for (const auto &s : styles) + m_ui->m_styleCombo->addItem(s, s); + + connect(m_ui->m_nameLineEdit, &QLineEdit::textChanged, this, &DeviceProfileDialog::nameChanged); + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(m_ui->buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, + this, &QDialog::accept); + // Note that Load/Save emit accepted() of the button box.. + connect(m_ui->buttonBox->button(QDialogButtonBox::Save), &QAbstractButton::clicked, + this, &DeviceProfileDialog::save); + connect(m_ui->buttonBox->button(QDialogButtonBox::Open), &QAbstractButton::clicked, + this, &DeviceProfileDialog::open); +} + +DeviceProfileDialog::~DeviceProfileDialog() +{ + delete m_ui; +} + +DeviceProfile DeviceProfileDialog::deviceProfile() const +{ + DeviceProfile rc; + rc.setName(m_ui->m_nameLineEdit->text()); + rc.setFontFamily(m_ui->m_systemFontComboBox->currentFont().family()); + rc.setFontPointSize(m_ui->m_systemFontSizeCombo->itemData(m_ui->m_systemFontSizeCombo->currentIndex()).toInt()); + + int dpiX, dpiY; + m_ui->m_dpiChooser->getDPI(&dpiX, &dpiY); + rc.setDpiX(dpiX); + rc.setDpiY(dpiY); + + rc.setStyle(m_ui->m_styleCombo->itemData(m_ui->m_styleCombo->currentIndex()).toString()); + + return rc; +} + +void DeviceProfileDialog::setDeviceProfile(const DeviceProfile &s) +{ + m_ui->m_nameLineEdit->setText(s.name()); + m_ui->m_systemFontComboBox->setCurrentFont(QFont(s.fontFamily())); + const int fontSizeIndex = m_ui->m_systemFontSizeCombo->findData(QVariant(s.fontPointSize())); + m_ui->m_systemFontSizeCombo->setCurrentIndex(fontSizeIndex != -1 ? fontSizeIndex : 0); + m_ui->m_dpiChooser->setDPI(s.dpiX(), s.dpiY()); + const int styleIndex = m_ui->m_styleCombo->findData(s.style()); + m_ui->m_styleCombo->setCurrentIndex(styleIndex != -1 ? styleIndex : 0); +} + +void DeviceProfileDialog::setOkButtonEnabled(bool v) +{ + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(v); +} + +bool DeviceProfileDialog::showDialog(const QStringList &existingNames) +{ + m_existingNames = existingNames; + m_ui->m_nameLineEdit->setFocus(Qt::OtherFocusReason); + nameChanged(m_ui->m_nameLineEdit->text()); + return exec() == Accepted; +} + +void DeviceProfileDialog::nameChanged(const QString &name) +{ + const bool invalid = name.isEmpty() || m_existingNames.indexOf(name) != -1; + setOkButtonEnabled(!invalid); +} + +void DeviceProfileDialog::save() +{ + QString fn = m_dlgGui->getSaveFileName(this, tr("Save Profile"), QString(), fileFilter()); + if (fn.isEmpty()) + return; + if (QFileInfo(fn).completeSuffix().isEmpty()) + fn += u'.' + profileExtensionC; + + QFile file(fn); + if (!file.open(QIODevice::WriteOnly|QIODevice::Text)) { + critical(tr("Save Profile - Error"), tr("Unable to open the file '%1' for writing: %2").arg(fn, file.errorString())); + return; + } + file.write(deviceProfile().toXml().toUtf8()); +} + +void DeviceProfileDialog::open() +{ + const QString fn = m_dlgGui->getOpenFileName(this, tr("Open profile"), QString(), fileFilter()); + if (fn.isEmpty()) + return; + + QFile file(fn); + if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) { + critical(tr("Open Profile - Error"), tr("Unable to open the file '%1' for reading: %2").arg(fn, file.errorString())); + return; + } + QString errorMessage; + DeviceProfile newSettings; + if (!newSettings.fromXml(QString::fromUtf8(file.readAll()), &errorMessage)) { + critical(tr("Open Profile - Error"), tr("'%1' is not a valid profile: %2").arg(fn, errorMessage)); + return; + } + setDeviceProfile(newSettings); +} + +void DeviceProfileDialog::critical(const QString &title, const QString &msg) +{ + m_dlgGui->message(this, QDesignerDialogGuiInterface::OtherMessage, QMessageBox::Critical, title, msg); +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/deviceprofiledialog.h b/src/designer/src/components/formeditor/deviceprofiledialog.h new file mode 100644 index 0000000..34a9fe5 --- /dev/null +++ b/src/designer/src/components/formeditor/deviceprofiledialog.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef SYSTEMSETTINGSDIALOG_H +#define SYSTEMSETTINGSDIALOG_H + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace Ui { + class DeviceProfileDialog; +} + +class QDesignerDialogGuiInterface; + +class QDialogButtonBox; + +namespace qdesigner_internal { + +class DeviceProfile; + +/* DeviceProfileDialog: Widget to edit system settings for embedded design */ + +class DeviceProfileDialog : public QDialog +{ + Q_DISABLE_COPY_MOVE(DeviceProfileDialog) + Q_OBJECT +public: + explicit DeviceProfileDialog(QDesignerDialogGuiInterface *dlgGui, QWidget *parent = nullptr); + ~DeviceProfileDialog(); + + DeviceProfile deviceProfile() const; + void setDeviceProfile(const DeviceProfile &s); + + bool showDialog(const QStringList &existingNames); + +private slots: + void setOkButtonEnabled(bool); + void nameChanged(const QString &name); + void save(); + void open() override; + +private: + void critical(const QString &title, const QString &msg); + Ui::DeviceProfileDialog *m_ui; + QDesignerDialogGuiInterface *m_dlgGui; + QStringList m_existingNames; +}; +} + +QT_END_NAMESPACE + +#endif // SYSTEMSETTINGSDIALOG_H diff --git a/src/designer/src/components/formeditor/deviceprofiledialog.ui b/src/designer/src/components/formeditor/deviceprofiledialog.ui new file mode 100644 index 0000000..1671d97 --- /dev/null +++ b/src/designer/src/components/formeditor/deviceprofiledialog.ui @@ -0,0 +1,112 @@ + + + DeviceProfileDialog + + + + 0 + 0 + 348 + 209 + + + + + + + + + + &Family + + + m_systemFontComboBox + + + + + + + + + + &Point Size + + + m_systemFontSizeCombo + + + + + + + true + + + + + + + Style + + + m_styleCombo + + + + + + + + + + Device DPI + + + + + + + + + + Name + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Open|QDialogButtonBox::Save + + + + + + + + qdesigner_internal::DPI_Chooser + QWidget +
dpi_chooser.h
+ 1 +
+
+ + m_nameLineEdit + m_systemFontComboBox + m_systemFontSizeCombo + m_styleCombo + buttonBox + + + +
diff --git a/src/designer/src/components/formeditor/dpi_chooser.cpp b/src/designer/src/components/formeditor/dpi_chooser.cpp new file mode 100644 index 0000000..5a34f12 --- /dev/null +++ b/src/designer/src/components/formeditor/dpi_chooser.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "dpi_chooser.h" + +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +enum { minDPI = 50, maxDPI = 400 }; + +namespace qdesigner_internal { + +// Entry struct for predefined values +struct DPI_Entry { + int dpiX; + int dpiY; + const char *description; +}; + +const struct DPI_Entry dpiEntries[] = { + //: Embedded device standard screen resolution + { 96, 96, QT_TRANSLATE_NOOP("DPI_Chooser", "Standard (96 x 96)") }, + //: Embedded device screen resolution + { 179, 185, QT_TRANSLATE_NOOP("DPI_Chooser", "Greenphone (179 x 185)") }, + //: Embedded device high definition screen resolution + { 192, 192, QT_TRANSLATE_NOOP("DPI_Chooser", "High (192 x 192)") } +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(const struct qdesigner_internal::DPI_Entry*); + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ------------- DPI_Chooser + +DPI_Chooser::DPI_Chooser(QWidget *parent) : + QWidget(parent), + m_systemEntry(new DPI_Entry), + m_predefinedCombo(new QComboBox), + m_dpiXSpinBox(new QSpinBox), + m_dpiYSpinBox(new QSpinBox) +{ + // Predefined settings: System + DeviceProfile::systemResolution(&(m_systemEntry->dpiX), &(m_systemEntry->dpiY)); + m_systemEntry->description = nullptr; + const struct DPI_Entry *systemEntry = m_systemEntry; + //: System resolution + m_predefinedCombo->addItem(tr("System (%1 x %2)").arg(m_systemEntry->dpiX).arg(m_systemEntry->dpiY), QVariant::fromValue(systemEntry)); + // Devices. Exclude the system values as not to duplicate the entries + for (const DPI_Entry &e : dpiEntries) { + if (e.dpiX != m_systemEntry->dpiX || e.dpiY != m_systemEntry->dpiY) + m_predefinedCombo->addItem(tr(e.description), QVariant::fromValue(&e)); + } + m_predefinedCombo->addItem(tr("User defined")); + + setFocusProxy(m_predefinedCombo); + m_predefinedCombo->setEditable(false); + m_predefinedCombo->setCurrentIndex(0); + connect(m_predefinedCombo, &QComboBox::currentIndexChanged, + this, &DPI_Chooser::syncSpinBoxes); + // top row with predefined settings + QVBoxLayout *vBoxLayout = new QVBoxLayout; + vBoxLayout->setContentsMargins(QMargins()); + vBoxLayout->addWidget(m_predefinedCombo); + // Spin box row + QHBoxLayout *hBoxLayout = new QHBoxLayout; + hBoxLayout->setContentsMargins(QMargins()); + + m_dpiXSpinBox->setMinimum(minDPI); + m_dpiXSpinBox->setMaximum(maxDPI); + hBoxLayout->addWidget(m_dpiXSpinBox); + //: DPI X/Y separator + hBoxLayout->addWidget(new QLabel(tr(" x "))); + + m_dpiYSpinBox->setMinimum(minDPI); + m_dpiYSpinBox->setMaximum(maxDPI); + hBoxLayout->addWidget(m_dpiYSpinBox); + + hBoxLayout->addStretch(); + vBoxLayout->addLayout(hBoxLayout); + setLayout(vBoxLayout); + + syncSpinBoxes(); +} + +DPI_Chooser::~DPI_Chooser() +{ + delete m_systemEntry; +} + +void DPI_Chooser::getDPI(int *dpiX, int *dpiY) const +{ + *dpiX = m_dpiXSpinBox->value(); + *dpiY = m_dpiYSpinBox->value(); +} + +void DPI_Chooser::setDPI(int dpiX, int dpiY) +{ + // Default to system if it is something weird + const bool valid = dpiX >= minDPI && dpiX <= maxDPI && dpiY >= minDPI && dpiY <= maxDPI; + if (!valid) { + m_predefinedCombo->setCurrentIndex(0); + return; + } + // Try to find the values among the predefined settings + const int count = m_predefinedCombo->count(); + int predefinedIndex = -1; + for (int i = 0; i < count; i++) { + const QVariant data = m_predefinedCombo->itemData(i); + if (data.metaType().id() != QMetaType::UnknownType) { + const struct DPI_Entry *entry = qvariant_cast(data); + if (entry->dpiX == dpiX && entry->dpiY == dpiY) { + predefinedIndex = i; + break; + } + } + } + if (predefinedIndex != -1) { + m_predefinedCombo->setCurrentIndex(predefinedIndex); // triggers syncSpinBoxes() + } else { + setUserDefinedValues(dpiX, dpiY); + } +} + +void DPI_Chooser::setUserDefinedValues(int dpiX, int dpiY) +{ + const bool blocked = m_predefinedCombo->blockSignals(true); + m_predefinedCombo->setCurrentIndex(m_predefinedCombo->count() - 1); + m_predefinedCombo->blockSignals(blocked); + + m_dpiXSpinBox->setEnabled(true); + m_dpiYSpinBox->setEnabled(true); + m_dpiXSpinBox->setValue(dpiX); + m_dpiYSpinBox->setValue(dpiY); +} + +void DPI_Chooser::syncSpinBoxes() +{ + const int predefIdx = m_predefinedCombo->currentIndex(); + const QVariant data = m_predefinedCombo->itemData(predefIdx); + + // Predefined mode in which spin boxes are disabled or user defined? + const bool userSetting = data.metaType().id() == QMetaType::UnknownType; + m_dpiXSpinBox->setEnabled(userSetting); + m_dpiYSpinBox->setEnabled(userSetting); + + if (!userSetting) { + const struct DPI_Entry *entry = qvariant_cast(data); + m_dpiXSpinBox->setValue(entry->dpiX); + m_dpiYSpinBox->setValue(entry->dpiY); + } +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/dpi_chooser.h b/src/designer/src/components/formeditor/dpi_chooser.h new file mode 100644 index 0000000..ba8af37 --- /dev/null +++ b/src/designer/src/components/formeditor/dpi_chooser.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DPICHOOSER_H +#define DPICHOOSER_H + +#include + +QT_BEGIN_NAMESPACE + +class QSpinBox; +class QComboBox; + +namespace qdesigner_internal { + +struct DPI_Entry; + +/* Let the user choose a DPI settings */ +class DPI_Chooser : public QWidget { + Q_DISABLE_COPY_MOVE(DPI_Chooser) + Q_OBJECT + +public: + explicit DPI_Chooser(QWidget *parent = nullptr); + ~DPI_Chooser(); + + void getDPI(int *dpiX, int *dpiY) const; + void setDPI(int dpiX, int dpiY); + +private slots: + void syncSpinBoxes(); + +private: + void setUserDefinedValues(int dpiX, int dpiY); + + struct DPI_Entry *m_systemEntry; + QComboBox *m_predefinedCombo; + QSpinBox *m_dpiXSpinBox; + QSpinBox *m_dpiYSpinBox; +}; +} + +QT_END_NAMESPACE + +#endif // DPICHOOSER_H diff --git a/src/designer/src/components/formeditor/embeddedoptionspage.cpp b/src/designer/src/components/formeditor/embeddedoptionspage.cpp new file mode 100644 index 0000000..3d9b7f9 --- /dev/null +++ b/src/designer/src/components/formeditor/embeddedoptionspage.cpp @@ -0,0 +1,420 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "embeddedoptionspage.h" +#include "deviceprofiledialog.h" +#include "widgetfactory_p.h" +#include "formwindowmanager.h" + +#include +#include +#include +#include +#include + + +// SDK +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +using DeviceProfileList = QList; + +enum { profileComboIndexOffset = 1 }; + +// Sort by name. Used by template, do not make it static! +bool deviceProfileLessThan(const DeviceProfile &d1, const DeviceProfile &d2) +{ + return d1.name().toLower() < d2.name().toLower(); +} + +static bool ask(QWidget *parent, + QDesignerDialogGuiInterface *dlgui, + const QString &title, + const QString &what) +{ + return dlgui->message(parent, QDesignerDialogGuiInterface::OtherMessage, + QMessageBox::Question, title, what, + QMessageBox::Yes|QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; +} + +// ------------ EmbeddedOptionsControlPrivate +class EmbeddedOptionsControlPrivate { + Q_DISABLE_COPY_MOVE(EmbeddedOptionsControlPrivate) +public: + EmbeddedOptionsControlPrivate(QDesignerFormEditorInterface *core); + void init(EmbeddedOptionsControl *q); + + bool isDirty() const { return m_dirty; } + + void loadSettings(); + void saveSettings(); + void slotAdd(); + void slotEdit(); + void slotDelete(); + void slotProfileIndexChanged(int); + +private: + QStringList existingProfileNames() const; + void sortAndPopulateProfileCombo(); + void updateState(); + void updateDescriptionLabel(); + + QDesignerFormEditorInterface *m_core; + QComboBox *m_profileCombo; + QToolButton *m_addButton; + QToolButton *m_editButton; + QToolButton *m_deleteButton; + QLabel *m_descriptionLabel; + + DeviceProfileList m_sortedProfiles; + EmbeddedOptionsControl *m_q = nullptr; + QSet m_usedProfiles; + bool m_dirty = false; +}; + +EmbeddedOptionsControlPrivate::EmbeddedOptionsControlPrivate(QDesignerFormEditorInterface *core) : + m_core(core), + m_profileCombo(new QComboBox), + m_addButton(new QToolButton), + m_editButton(new QToolButton), + m_deleteButton(new QToolButton), + m_descriptionLabel(new QLabel) +{ + m_descriptionLabel->setMinimumHeight(80); + // Determine used profiles to lock them + const QDesignerFormWindowManagerInterface *fwm = core->formWindowManager(); + if (const int fwCount = fwm->formWindowCount()) { + for (int i = 0; i < fwCount; i++) + if (const FormWindowBase *fwb = qobject_cast(fwm->formWindow(i))) { + const QString deviceProfileName = fwb->deviceProfileName(); + if (!deviceProfileName.isEmpty()) + m_usedProfiles.insert(deviceProfileName); + } + } +} + +void EmbeddedOptionsControlPrivate::init(EmbeddedOptionsControl *q) +{ + m_q = q; + QVBoxLayout *vLayout = new QVBoxLayout; + QHBoxLayout *hLayout = new QHBoxLayout; + m_profileCombo->setMinimumWidth(200); + m_profileCombo->setEditable(false); + hLayout->addWidget(m_profileCombo); + m_profileCombo->addItem(EmbeddedOptionsControl::tr("None")); + EmbeddedOptionsControl::connect(m_profileCombo, &QComboBox::currentIndexChanged, + m_q, &EmbeddedOptionsControl::slotProfileIndexChanged); + + m_addButton->setIcon(createIconSet("plus.png"_L1)); + m_addButton->setToolTip(EmbeddedOptionsControl::tr("Add a profile")); + EmbeddedOptionsControl::connect(m_addButton, &QAbstractButton::clicked, + m_q, &EmbeddedOptionsControl::slotAdd); + hLayout->addWidget(m_addButton); + + EmbeddedOptionsControl::connect(m_editButton, &QAbstractButton::clicked, + m_q, &EmbeddedOptionsControl::slotEdit); + m_editButton->setIcon(createIconSet("edit.png"_L1)); + m_editButton->setToolTip(EmbeddedOptionsControl::tr("Edit the selected profile")); + hLayout->addWidget(m_editButton); + + m_deleteButton->setIcon(createIconSet("minus.png"_L1)); + m_deleteButton->setToolTip(EmbeddedOptionsControl::tr("Delete the selected profile")); + EmbeddedOptionsControl::connect(m_deleteButton, &QAbstractButton::clicked, + m_q, &EmbeddedOptionsControl::slotDelete); + hLayout->addWidget(m_deleteButton); + + hLayout->addStretch(); + vLayout->addLayout(hLayout); + vLayout->addWidget(m_descriptionLabel); + m_q->setLayout(vLayout); +} + +QStringList EmbeddedOptionsControlPrivate::existingProfileNames() const +{ + QStringList rc; + for (const auto &dp : m_sortedProfiles) + rc.append(dp.name()); + return rc; +} + +void EmbeddedOptionsControlPrivate::slotAdd() +{ + DeviceProfileDialog dlg(m_core->dialogGui(), m_q); + dlg.setWindowTitle(EmbeddedOptionsControl::tr("Add Profile")); + // Create a new profile with a new, unique name + DeviceProfile settings; + settings.fromSystem(); + dlg.setDeviceProfile(settings); + + const QStringList names = existingProfileNames(); + const QString newNamePrefix = EmbeddedOptionsControl::tr("New profile"); + QString newName = newNamePrefix; + for (int i = 2; names.contains(newName); i++) { + newName = newNamePrefix; + newName += QString::number(i); + } + + settings.setName(newName); + dlg.setDeviceProfile(settings); + if (dlg.showDialog(names)) { + const DeviceProfile newProfile = dlg.deviceProfile(); + m_sortedProfiles.push_back(newProfile); + // Maintain sorted order + sortAndPopulateProfileCombo(); + const int index = m_profileCombo->findText(newProfile.name()); + m_profileCombo->setCurrentIndex(index); + m_dirty = true; + } +} + +void EmbeddedOptionsControlPrivate::slotEdit() +{ + const int index = m_profileCombo->currentIndex() - profileComboIndexOffset; + if (index < 0) + return; + + // Edit the profile, compile a list of existing names + // excluding current one. re-insert if changed, + // re-sort if name changed. + const DeviceProfile oldProfile = m_sortedProfiles.at(index); + const QString oldName = oldProfile.name(); + QStringList names = existingProfileNames(); + names.removeAll(oldName); + + DeviceProfileDialog dlg(m_core->dialogGui(), m_q); + dlg.setWindowTitle(EmbeddedOptionsControl::tr("Edit Profile")); + dlg.setDeviceProfile(oldProfile); + if (dlg.showDialog(names)) { + const DeviceProfile newProfile = dlg.deviceProfile(); + if (newProfile != oldProfile) { + m_dirty = true; + m_sortedProfiles[index] = newProfile; + if (newProfile.name() != oldName) { + sortAndPopulateProfileCombo(); + const int index = m_profileCombo->findText(newProfile.name()); + m_profileCombo->setCurrentIndex(index); + } else { + updateDescriptionLabel(); + } + + } + } +} + +void EmbeddedOptionsControlPrivate::slotDelete() +{ + const int index = m_profileCombo->currentIndex() - profileComboIndexOffset; + if (index < 0) + return; + const QString name = m_sortedProfiles.at(index).name(); + if (ask(m_q, m_core->dialogGui(), + EmbeddedOptionsControl::tr("Delete Profile"), + EmbeddedOptionsControl::tr("Would you like to delete the profile '%1'?").arg(name))) { + m_profileCombo->setCurrentIndex(0); + m_sortedProfiles.removeAt(index); + m_profileCombo->removeItem(index + profileComboIndexOffset); + m_dirty = true; + } +} + +void EmbeddedOptionsControlPrivate::sortAndPopulateProfileCombo() +{ + // Clear items until only "None" is left + for (int i = m_profileCombo->count() - 1; i > 0; i--) + m_profileCombo->removeItem(i); + if (!m_sortedProfiles.isEmpty()) { + std::sort(m_sortedProfiles.begin(), m_sortedProfiles.end(), deviceProfileLessThan); + m_profileCombo->addItems(existingProfileNames()); + } +} + +void EmbeddedOptionsControlPrivate::loadSettings() +{ + const QDesignerSharedSettings settings(m_core); + m_sortedProfiles = settings.deviceProfiles(); + sortAndPopulateProfileCombo(); + // Index: 0 is "None" + const int settingsIndex = settings.currentDeviceProfileIndex(); + const int profileIndex = settingsIndex >= 0 && settingsIndex < m_sortedProfiles.size() ? settingsIndex + profileComboIndexOffset : 0; + m_profileCombo->setCurrentIndex(profileIndex); + updateState(); + m_dirty = false; +} + +void EmbeddedOptionsControlPrivate::saveSettings() +{ + QDesignerSharedSettings settings(m_core); + settings.setDeviceProfiles(m_sortedProfiles); + // Index: 0 is "None" + settings.setCurrentDeviceProfileIndex(m_profileCombo->currentIndex() - profileComboIndexOffset); + m_dirty = false; +} + +//: Format embedded device profile description +static const char *descriptionFormat = QT_TRANSLATE_NOOP("EmbeddedOptionsControl", +"" +"" +"" +"" +"" +"
Font%1, %2
Style%3
Resolution%4 x %5
" +""); + +static inline QString description(const DeviceProfile& p) +{ + QString styleName = p.style(); + if (styleName.isEmpty()) + styleName = EmbeddedOptionsControl::tr("Default"); + return EmbeddedOptionsControl::tr(descriptionFormat). + arg(p.fontFamily()).arg(p.fontPointSize()).arg(styleName).arg(p.dpiX()).arg(p.dpiY()); +} + +void EmbeddedOptionsControlPrivate::updateDescriptionLabel() +{ + const int profileIndex = m_profileCombo->currentIndex() - profileComboIndexOffset; + if (profileIndex >= 0) { + m_descriptionLabel->setText(description(m_sortedProfiles.at(profileIndex))); + } else { + m_descriptionLabel->clear(); + } +} + +void EmbeddedOptionsControlPrivate::updateState() +{ + const int profileIndex = m_profileCombo->currentIndex() - profileComboIndexOffset; + // Allow for changing/deleting only if it is not in use + bool modifyEnabled = false; + if (profileIndex >= 0) + modifyEnabled = !m_usedProfiles.contains(m_sortedProfiles.at(profileIndex).name()); + m_editButton->setEnabled(modifyEnabled); + m_deleteButton->setEnabled(modifyEnabled); + updateDescriptionLabel(); +} + +void EmbeddedOptionsControlPrivate::slotProfileIndexChanged(int) +{ + updateState(); + m_dirty = true; +} + +// ------------- EmbeddedOptionsControl +EmbeddedOptionsControl::EmbeddedOptionsControl(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_d(new EmbeddedOptionsControlPrivate(core)) +{ + m_d->init(this); +} + +EmbeddedOptionsControl::~EmbeddedOptionsControl() +{ + delete m_d; +} + +void EmbeddedOptionsControl::slotAdd() +{ + m_d->slotAdd(); +} + +void EmbeddedOptionsControl::slotEdit() +{ + m_d->slotEdit(); +} + +void EmbeddedOptionsControl::slotDelete() +{ + m_d->slotDelete(); +} + +void EmbeddedOptionsControl::loadSettings() +{ + m_d->loadSettings(); +} + +void EmbeddedOptionsControl::saveSettings() +{ + m_d->saveSettings(); +} + +void EmbeddedOptionsControl::slotProfileIndexChanged(int i) +{ + m_d->slotProfileIndexChanged(i); +} + +bool EmbeddedOptionsControl::isDirty() const +{ + return m_d->isDirty(); +} + +// EmbeddedOptionsPage: +EmbeddedOptionsPage::EmbeddedOptionsPage(QDesignerFormEditorInterface *core) : + m_core(core) +{ +} + +QString EmbeddedOptionsPage::name() const +{ + //: Tab in preferences dialog + return QCoreApplication::translate("EmbeddedOptionsPage", "Embedded Design"); +} + +QWidget *EmbeddedOptionsPage::createPage(QWidget *parent) +{ + QWidget *optionsWidget = new QWidget(parent); + + QVBoxLayout *optionsVLayout = new QVBoxLayout(); + + //: EmbeddedOptionsControl group box" + QGroupBox *gb = new QGroupBox(QCoreApplication::translate("EmbeddedOptionsPage", "Device Profiles")); + QVBoxLayout *gbVLayout = new QVBoxLayout(); + m_embeddedOptionsControl = new EmbeddedOptionsControl(m_core); + m_embeddedOptionsControl->loadSettings(); + gbVLayout->addWidget(m_embeddedOptionsControl); + gb->setLayout(gbVLayout); + optionsVLayout->addWidget(gb); + + optionsVLayout->addStretch(1); + + // Outer layout to give it horizontal stretch + QHBoxLayout *optionsHLayout = new QHBoxLayout(); + optionsHLayout->addLayout(optionsVLayout); + optionsHLayout->addStretch(1); + optionsWidget->setLayout(optionsHLayout); + return optionsWidget; +} + +void EmbeddedOptionsPage::apply() +{ + if (!m_embeddedOptionsControl || !m_embeddedOptionsControl->isDirty()) + return; + + m_embeddedOptionsControl->saveSettings(); + if (FormWindowManager *fw = qobject_cast(m_core->formWindowManager())) + fw->deviceProfilesChanged(); +} + +void EmbeddedOptionsPage::finish() +{ +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/embeddedoptionspage.h b/src/designer/src/components/formeditor/embeddedoptionspage.h new file mode 100644 index 0000000..d1d2d0f --- /dev/null +++ b/src/designer/src/components/formeditor/embeddedoptionspage.h @@ -0,0 +1,67 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EMBEDDEDOPTIONSPAGE_H +#define EMBEDDEDOPTIONSPAGE_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class EmbeddedOptionsControlPrivate; + +/* EmbeddedOptions Control. Presents the user with a list of embedded + * device profiles he can modify/add/delete. */ +class EmbeddedOptionsControl : public QWidget { + Q_DISABLE_COPY_MOVE(EmbeddedOptionsControl) + Q_OBJECT +public: + explicit EmbeddedOptionsControl(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~EmbeddedOptionsControl(); + + bool isDirty() const; + +public slots: + void loadSettings(); + void saveSettings(); + +private slots: + void slotAdd(); + void slotEdit(); + void slotDelete(); + void slotProfileIndexChanged(int); + +private: + friend class EmbeddedOptionsControlPrivate; + + EmbeddedOptionsControlPrivate *m_d; +}; + +// EmbeddedOptionsPage +class EmbeddedOptionsPage : public QDesignerOptionsPageInterface +{ + Q_DISABLE_COPY_MOVE(EmbeddedOptionsPage) +public: + explicit EmbeddedOptionsPage(QDesignerFormEditorInterface *core); + + QString name() const override; + QWidget *createPage(QWidget *parent) override; + void finish() override; + void apply() override; + +private: + QDesignerFormEditorInterface *m_core; + QPointer m_embeddedOptionsControl; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // EMBEDDEDOPTIONSPAGE_H diff --git a/src/designer/src/components/formeditor/formeditor.cpp b/src/designer/src/components/formeditor/formeditor.cpp new file mode 100644 index 0000000..4d92608 --- /dev/null +++ b/src/designer/src/components/formeditor/formeditor.cpp @@ -0,0 +1,158 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formeditor.h" +#include "formeditor_optionspage.h" +#include "embeddedoptionspage.h" +#include "templateoptionspage.h" +#include "metadatabase_p.h" +#include "widgetdatabase_p.h" +#include "widgetfactory_p.h" +#include "formwindowmanager.h" +#include "qmainwindow_container.h" +#include "qmdiarea_container.h" +#include "qwizard_container.h" +#include "default_container.h" +#include "default_layoutdecoration.h" +#include "default_actionprovider.h" +#include "qlayoutwidget_propertysheet.h" +#include "spacer_propertysheet.h" +#include "line_propertysheet.h" +#include "layout_propertysheet.h" +#include "qdesigner_dockwidget_p.h" +#include "qdesigner_stackedbox_p.h" +#include "qdesigner_toolbox_p.h" +#include "qdesigner_tabwidget_p.h" +#include "qtresourcemodel_p.h" +#include "itemview_propertysheet.h" + +// sdk +#include +#include +// shared +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +FormEditor::FormEditor(QObject *parent) : FormEditor(QStringList{}, parent) +{ +} + +FormEditor::FormEditor(const QStringList &pluginPaths, + QObject *parent) + : QDesignerFormEditorInterface(parent) +{ + setIntrospection(new QDesignerIntrospection); + setDialogGui(new DialogGui); + auto *pluginManager = new QDesignerPluginManager(pluginPaths, this); + setPluginManager(pluginManager); + + WidgetDataBase *widgetDatabase = new WidgetDataBase(this, this); + setWidgetDataBase(widgetDatabase); + + MetaDataBase *metaDataBase = new MetaDataBase(this, this); + setMetaDataBase(metaDataBase); + + WidgetFactory *widgetFactory = new WidgetFactory(this, this); + setWidgetFactory(widgetFactory); + + FormWindowManager *formWindowManager = new FormWindowManager(this, this); + setFormManager(formWindowManager); + connect(formWindowManager, &QDesignerFormWindowManagerInterface::formWindowAdded, + widgetFactory, &WidgetFactory::formWindowAdded); + connect(formWindowManager, &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + widgetFactory, &WidgetFactory::activeFormWindowChanged); + + QExtensionManager *mgr = new QExtensionManager(this); + const QString containerExtensionId = Q_TYPEID(QDesignerContainerExtension); + + QDesignerStackedWidgetContainerFactory::registerExtension(mgr, containerExtensionId); + QDesignerTabWidgetContainerFactory::registerExtension(mgr, containerExtensionId); + QDesignerToolBoxContainerFactory::registerExtension(mgr, containerExtensionId); + QMainWindowContainerFactory::registerExtension(mgr, containerExtensionId); + QDockWidgetContainerFactory::registerExtension(mgr, containerExtensionId); + QScrollAreaContainerFactory::registerExtension(mgr, containerExtensionId); + QMdiAreaContainerFactory::registerExtension(mgr, containerExtensionId); + QWizardContainerFactory::registerExtension(mgr, containerExtensionId); + + mgr->registerExtensions(new QDesignerLayoutDecorationFactory(mgr), + Q_TYPEID(QDesignerLayoutDecorationExtension)); + + const QString actionProviderExtensionId = Q_TYPEID(QDesignerActionProviderExtension); + QToolBarActionProviderFactory::registerExtension(mgr, actionProviderExtensionId); + QMenuBarActionProviderFactory::registerExtension(mgr, actionProviderExtensionId); + QMenuActionProviderFactory::registerExtension(mgr, actionProviderExtensionId); + + QDesignerDefaultPropertySheetFactory::registerExtension(mgr); + QDockWidgetPropertySheetFactory::registerExtension(mgr); + QLayoutWidgetPropertySheetFactory::registerExtension(mgr); + SpacerPropertySheetFactory::registerExtension(mgr); + LinePropertySheetFactory::registerExtension(mgr); + LayoutPropertySheetFactory::registerExtension(mgr); + QStackedWidgetPropertySheetFactory::registerExtension(mgr); + QToolBoxWidgetPropertySheetFactory::registerExtension(mgr); + QTabWidgetPropertySheetFactory::registerExtension(mgr); + QMdiAreaPropertySheetFactory::registerExtension(mgr); + QWizardPagePropertySheetFactory::registerExtension(mgr); + QWizardPropertySheetFactory::registerExtension(mgr); + + QTreeViewPropertySheetFactory::registerExtension(mgr); + QTableViewPropertySheetFactory::registerExtension(mgr); + + QDesignerTaskMenuFactory::registerExtension(mgr, u"QDesignerInternalTaskMenuExtension"_s); + + mgr->registerExtensions(new QDesignerMemberSheetFactory(mgr), + Q_TYPEID(QDesignerMemberSheetExtension)); + + setExtensionManager(mgr); + + setPromotion(new QDesignerPromotion(this)); + + QtResourceModel *resourceModel = new QtResourceModel(this); + setResourceModel(resourceModel); + connect(resourceModel, &QtResourceModel::qrcFileModifiedExternally, + this, &FormEditor::slotQrcFileChangedExternally); + + QList optionsPages; + optionsPages << new TemplateOptionsPage(this) << new FormEditorOptionsPage(this) << new EmbeddedOptionsPage(this); + setOptionsPages(optionsPages); + + setSettingsManager(new QDesignerQSettings()); +} + +FormEditor::~FormEditor() = default; + +void FormEditor::slotQrcFileChangedExternally(const QString &path) +{ + if (!integration()) + return; + + QDesignerIntegration::ResourceFileWatcherBehaviour behaviour = integration()->resourceFileWatcherBehaviour(); + if (behaviour == QDesignerIntegration::NoResourceFileWatcher) + return; + if (behaviour == QDesignerIntegration::PromptToReloadResourceFile) { + QMessageBox::StandardButton button = dialogGui()->message(topLevel(), QDesignerDialogGuiInterface::FileChangedMessage, QMessageBox::Warning, + tr("Resource File Changed"), + tr("The file \"%1\" has changed outside Designer. Do you want to reload it?").arg(path), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (button != QMessageBox::Yes) + return; + } + + resourceModel()->reload(path); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/formeditor.h b/src/designer/src/components/formeditor/formeditor.h new file mode 100644 index 0000000..483bd6b --- /dev/null +++ b/src/designer/src/components/formeditor/formeditor.h @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMEDITOR_H +#define FORMEDITOR_H + +#include "formeditor_global.h" + +#include + +QT_BEGIN_NAMESPACE + +class QObject; + +namespace qdesigner_internal { + +class QT_FORMEDITOR_EXPORT FormEditor: public QDesignerFormEditorInterface +{ + Q_OBJECT +public: + FormEditor(QObject *parent = nullptr); + FormEditor(const QStringList &pluginPaths, + QObject *parent = nullptr); + ~FormEditor() override; +public slots: + void slotQrcFileChangedExternally(const QString &path); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMEDITOR_H diff --git a/src/designer/src/components/formeditor/formeditor_global.h b/src/designer/src/components/formeditor/formeditor_global.h new file mode 100644 index 0000000..37af044 --- /dev/null +++ b/src/designer/src/components/formeditor/formeditor_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMEDITOR_GLOBAL_H +#define FORMEDITOR_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_FORMEDITOR_LIBRARY +# define QT_FORMEDITOR_EXPORT +#else +# define QT_FORMEDITOR_EXPORT +#endif +#else +#define QT_FORMEDITOR_EXPORT +#endif + +#endif // FORMEDITOR_GLOBAL_H diff --git a/src/designer/src/components/formeditor/formeditor_optionspage.cpp b/src/designer/src/components/formeditor/formeditor_optionspage.cpp new file mode 100644 index 0000000..ffd93f2 --- /dev/null +++ b/src/designer/src/components/formeditor/formeditor_optionspage.cpp @@ -0,0 +1,175 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formeditor_optionspage.h" + +// shared +#include "formwindowbase_p.h" +#include "gridpanel_p.h" +#include "grid_p.h" +#include "previewconfigurationwidget_p.h" +#include "shared_settings_p.h" +#include "zoomwidget_p.h" +#include + +// SDK +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Zoom, currently for preview only +class ZoomSettingsWidget : public QGroupBox { + Q_DISABLE_COPY_MOVE(ZoomSettingsWidget) +public: + explicit ZoomSettingsWidget(QWidget *parent = nullptr); + + void fromSettings(const QDesignerSharedSettings &s); + void toSettings(QDesignerSharedSettings &s) const; + +private: + QComboBox *m_zoomCombo; +}; + +ZoomSettingsWidget::ZoomSettingsWidget(QWidget *parent) : + QGroupBox(parent), + m_zoomCombo(new QComboBox) +{ + m_zoomCombo->setEditable(false); + const QList &zoomValues = ZoomMenu::zoomValues(); + for (int z : zoomValues) { + //: Zoom percentage + m_zoomCombo->addItem(QCoreApplication::translate("FormEditorOptionsPage", "%1 %").arg(z), QVariant(z)); + } + + // Layout + setCheckable(true); + setTitle(QCoreApplication::translate("FormEditorOptionsPage", "Preview Zoom")); + QFormLayout *lt = new QFormLayout; + lt->addRow(QCoreApplication::translate("FormEditorOptionsPage", "Default Zoom"), m_zoomCombo); + setLayout(lt); +} + +void ZoomSettingsWidget::fromSettings(const QDesignerSharedSettings &s) +{ + setChecked(s.zoomEnabled()); + const int idx = m_zoomCombo->findData(QVariant(s.zoom())); + m_zoomCombo->setCurrentIndex(qMax(0, idx)); +} + +void ZoomSettingsWidget::toSettings(QDesignerSharedSettings &s) const +{ + s.setZoomEnabled(isChecked()); + const int zoom = m_zoomCombo->itemData(m_zoomCombo->currentIndex()).toInt(); + s.setZoom(zoom); +} + + + +// FormEditorOptionsPage: +FormEditorOptionsPage::FormEditorOptionsPage(QDesignerFormEditorInterface *core) + : m_core(core) +{ +} + +QString FormEditorOptionsPage::name() const +{ + //: Tab in preferences dialog + return QCoreApplication::translate("FormEditorOptionsPage", "Forms"); +} + +QWidget *FormEditorOptionsPage::createPage(QWidget *parent) +{ + QWidget *optionsWidget = new QWidget(parent); + + const QDesignerSharedSettings settings(m_core); + m_previewConf = new PreviewConfigurationWidget(m_core); + m_zoomSettingsWidget = new ZoomSettingsWidget; + m_zoomSettingsWidget->fromSettings(settings); + + m_defaultGridConf = new GridPanel(); + m_defaultGridConf->setTitle(QCoreApplication::translate("FormEditorOptionsPage", "Default Grid")); + m_defaultGridConf->setGrid(settings.defaultGrid()); + + const QString namingTitle = + QCoreApplication::translate("FormEditorOptionsPage", "Object Naming Convention"); + QGroupBox *namingGroupBox = new QGroupBox(namingTitle); + const QString namingToolTip = + QCoreApplication::translate("FormEditorOptionsPage", + "Naming convention used for generating action object names from their text"); + namingGroupBox->setToolTip(namingToolTip); + QHBoxLayout *namingHLayout = new QHBoxLayout(namingGroupBox); + m_namingComboBox = new QComboBox; + m_namingComboBox->setToolTip(namingToolTip); + QStringList items; // matching ActionEditor::NamingMode + items << QCoreApplication::translate("FormEditorOptionsPage", "Camel Case") + << QCoreApplication::translate("FormEditorOptionsPage", "Underscore"); + m_namingComboBox->addItems(items); + m_namingComboBox->setCurrentIndex(settings.objectNamingMode()); + namingHLayout->addWidget(m_namingComboBox.data()); + + QVBoxLayout *optionsVLayout = new QVBoxLayout(); + optionsVLayout->addWidget(m_defaultGridConf); + optionsVLayout->addWidget(m_previewConf); + optionsVLayout->addWidget(m_zoomSettingsWidget); + optionsVLayout->addWidget(namingGroupBox); + optionsVLayout->addStretch(1); + + // Outer layout to give it horizontal stretch + QHBoxLayout *optionsHLayout = new QHBoxLayout(); + optionsHLayout->addLayout(optionsVLayout); + optionsHLayout->addStretch(1); + optionsWidget->setLayout(optionsHLayout); + + return optionsWidget; +} + +void FormEditorOptionsPage::apply() +{ + QDesignerSharedSettings settings(m_core); + if (m_defaultGridConf) { + const Grid defaultGrid = m_defaultGridConf->grid(); + settings.setDefaultGrid(defaultGrid); + + FormWindowBase::setDefaultDesignerGrid(defaultGrid); + // Update grid settings in all existing form windows + QDesignerFormWindowManagerInterface *fwm = m_core->formWindowManager(); + if (const int numWindows = fwm->formWindowCount()) { + for (int i = 0; i < numWindows; i++) + if (qdesigner_internal::FormWindowBase *fwb + = qobject_cast( fwm->formWindow(i))) + if (!fwb->hasFormGrid()) + fwb->setDesignerGrid(defaultGrid); + } + } + if (m_previewConf) { + m_previewConf->saveState(); + } + + if (m_zoomSettingsWidget) + m_zoomSettingsWidget->toSettings(settings); + + if (m_namingComboBox) { + const ObjectNamingMode namingMode + = static_cast(m_namingComboBox->currentIndex()); + settings.setObjectNamingMode(namingMode); + ActionEditor::setObjectNamingMode(namingMode); + } +} + +void FormEditorOptionsPage::finish() +{ +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/formeditor_optionspage.h b/src/designer/src/components/formeditor/formeditor_optionspage.h new file mode 100644 index 0000000..69758d2 --- /dev/null +++ b/src/designer/src/components/formeditor/formeditor_optionspage.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMEDITOR_OPTIONSPAGE_H +#define FORMEDITOR_OPTIONSPAGE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QComboBox; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class PreviewConfigurationWidget; +class GridPanel; +class ZoomSettingsWidget; + +class FormEditorOptionsPage : public QDesignerOptionsPageInterface +{ +public: + explicit FormEditorOptionsPage(QDesignerFormEditorInterface *core); + + QString name() const override; + QWidget *createPage(QWidget *parent) override; + void apply() override; + void finish() override; + +private: + QDesignerFormEditorInterface *m_core; + QPointer m_previewConf; + QPointer m_defaultGridConf; + QPointer m_zoomSettingsWidget; + QPointer m_namingComboBox; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMEDITOR_OPTIONSPAGE_H diff --git a/src/designer/src/components/formeditor/formwindow.cpp b/src/designer/src/components/formeditor/formwindow.cpp new file mode 100644 index 0000000..cf3e9dc --- /dev/null +++ b/src/designer/src/components/formeditor/formwindow.cpp @@ -0,0 +1,2943 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindow.h" +#include "formeditor.h" +#include "formwindow_dnditem.h" +#include "formwindow_widgetstack.h" +#include "formwindowcursor.h" +#include "formwindowmanager.h" +#include "tool_widgeteditor.h" +#include "widgetselection.h" +#include "qtresourcemodel_p.h" +#include "widgetfactory_p.h" + +// shared +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#if QT_CONFIG(clipboard) +# include +#endif +#include +#include + +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QWidget*) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { +class BlockSelection +{ + Q_DISABLE_COPY_MOVE(BlockSelection) +public: + BlockSelection(qdesigner_internal::FormWindow *fw) + : m_formWindow(fw), + m_blocked(m_formWindow->blockSelectionChanged(true)) + { + } + + ~BlockSelection() + { + if (m_formWindow) + m_formWindow->blockSelectionChanged(m_blocked); + } + +private: + QPointer m_formWindow; + const bool m_blocked; +}; + +enum { debugFormWindow = 0 }; +} + +namespace qdesigner_internal { + +// ------------------------ FormWindow::Selection +// Maintains a pool of WidgetSelections to be used for selected widgets. + +class FormWindow::Selection +{ + Q_DISABLE_COPY_MOVE(Selection) +public: + Selection(); + ~Selection(); + + // Clear + void clear(); + + // Also clear out the pool. Call if reparenting of the main container occurs. + void clearSelectionPool(); + + void repaintSelection(QWidget *w); + void repaintSelection(); + + bool isWidgetSelected(QWidget *w) const; + QWidgetList selectedWidgets() const; + + WidgetSelection *addWidget(FormWindow* fw, QWidget *w); + // remove widget, return new current widget or 0 + QWidget* removeWidget(QWidget *w); + + void raiseList(const QWidgetList& l); + void raiseWidget(QWidget *w); + + void updateGeometry(QWidget *w); + + void hide(QWidget *w); + void show(QWidget *w); + +private: + + using SelectionPool = QList; + SelectionPool m_selectionPool; + + QHash m_usedSelections; +}; + +FormWindow::Selection::Selection() = default; + +FormWindow::Selection::~Selection() +{ + clearSelectionPool(); +} + +void FormWindow::Selection::clear() +{ + if (!m_usedSelections.isEmpty()) { + for (auto it = m_usedSelections.begin(), mend = m_usedSelections.end(); it != mend; ++it) + it.value()->setWidget(nullptr); + m_usedSelections.clear(); + } +} + +void FormWindow::Selection::clearSelectionPool() +{ + clear(); + qDeleteAll(m_selectionPool); + m_selectionPool.clear(); +} + +WidgetSelection *FormWindow::Selection::addWidget(FormWindow* fw, QWidget *w) +{ + WidgetSelection *rc = m_usedSelections.value(w); + if (rc != nullptr) { + rc->show(); + rc->updateActive(); + return rc; + } + // find a free one in the pool + for (auto *s : std::as_const(m_selectionPool)) { + if (!s->isUsed()) { + rc = s; + break; + } + } + + if (rc == nullptr) { + rc = new WidgetSelection(fw); + m_selectionPool.push_back(rc); + } + + m_usedSelections.insert(w, rc); + rc->setWidget(w); + return rc; +} + +QWidget* FormWindow::Selection::removeWidget(QWidget *w) +{ + WidgetSelection *s = m_usedSelections.value(w); + if (!s) + return w; + + s->setWidget(nullptr); + m_usedSelections.remove(w); + + if (m_usedSelections.isEmpty()) + return nullptr; + + return (*m_usedSelections.begin())->widget(); +} + +void FormWindow::Selection::repaintSelection(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) + s->update(); +} + +void FormWindow::Selection::repaintSelection() +{ + for (auto it = m_usedSelections.begin(), mend = m_usedSelections.end(); it != mend; ++it) + it.value()->update(); +} + +bool FormWindow::Selection::isWidgetSelected(QWidget *w) const{ + return m_usedSelections.contains(w); +} + +QWidgetList FormWindow::Selection::selectedWidgets() const +{ + return m_usedSelections.keys(); +} + +void FormWindow::Selection::raiseList(const QWidgetList& l) +{ + for (auto it = m_usedSelections.constBegin(), mend = m_usedSelections.constEnd(); it != mend; ++it) { + WidgetSelection *w = it.value(); + if (l.contains(w->widget())) + w->show(); + } +} + +void FormWindow::Selection::raiseWidget(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) + s->show(); +} + +void FormWindow::Selection::updateGeometry(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) { + s->updateGeometry(); + } +} + +void FormWindow::Selection::hide(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) + s->hide(); +} + +void FormWindow::Selection::show(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) + s->show(); +} + +// ------------------------ FormWindow +FormWindow::FormWindow(FormEditor *core, QWidget *parent, Qt::WindowFlags flags) : + FormWindowBase(core, parent, flags), + m_mouseState(NoMouseState), + m_core(core), + m_selection(new Selection), + m_widgetStack(new FormWindowWidgetStack(this)), + m_contextMenuPosition(-1, -1) +{ + // Apply settings to formcontainer + deviceProfile().apply(core, m_widgetStack->formContainer(), qdesigner_internal::DeviceProfile::ApplyFormParent); + + setLayout(m_widgetStack->layout()); + init(); + + m_cursor = new FormWindowCursor(this, this); + + core->formWindowManager()->addFormWindow(this); + + setDirty(false); + setAcceptDrops(true); +} + +FormWindow::~FormWindow() +{ + Q_ASSERT(core() != nullptr); + Q_ASSERT(core()->metaDataBase() != nullptr); + Q_ASSERT(core()->formWindowManager() != nullptr); + + core()->formWindowManager()->removeFormWindow(this); + core()->metaDataBase()->remove(this); + + const QWidgetList &l = widgets(); + for (QWidget *w : l) + core()->metaDataBase()->remove(w); + + m_widgetStack = nullptr; + m_rubberBand = nullptr; + if (resourceSet()) + core()->resourceModel()->removeResourceSet(resourceSet()); + delete m_selection; + + if (FormWindowManager *manager = qobject_cast (core()->formWindowManager())) + manager->undoGroup()->removeStack(&m_undoStack); + m_undoStack.disconnect(); +} + +QDesignerFormEditorInterface *FormWindow::core() const +{ + return m_core; +} + +QDesignerFormWindowCursorInterface *FormWindow::cursor() const +{ + return m_cursor; +} + +void FormWindow::updateWidgets() +{ + if (!m_mainContainer) + return; +} + +int FormWindow::widgetDepth(const QWidget *w) +{ + int d = -1; + while (w && !w->isWindow()) { + d++; + w = w->parentWidget(); + } + + return d; +} + +bool FormWindow::isChildOf(const QWidget *c, const QWidget *p) +{ + while (c) { + if (c == p) + return true; + c = c->parentWidget(); + } + return false; +} + +void FormWindow::setCursorToAll(const QCursor &c, QWidget *start) +{ +#if QT_CONFIG(cursor) + start->setCursor(c); + const QWidgetList widgets = start->findChildren(); + for (QWidget *widget : widgets) { + if (!qobject_cast(widget)) { + widget->setCursor(c); + } + } +#endif +} + +void FormWindow::init() +{ + if (FormWindowManager *manager = qobject_cast (core()->formWindowManager())) { + manager->undoGroup()->addStack(&m_undoStack); + } + + m_blockSelectionChanged = false; + + m_defaultMargin = INT_MIN; + m_defaultSpacing = INT_MIN; + + connect(m_widgetStack, &FormWindowWidgetStack::currentToolChanged, + this, &QDesignerFormWindowInterface::toolChanged); + + m_selectionChangedTimer = new QTimer(this); + m_selectionChangedTimer->setSingleShot(true); + connect(m_selectionChangedTimer, &QTimer::timeout, this, + &FormWindow::selectionChangedTimerDone); + + m_checkSelectionTimer = new QTimer(this); + m_checkSelectionTimer->setSingleShot(true); + connect(m_checkSelectionTimer, &QTimer::timeout, + this, &FormWindow::checkSelectionNow); + + m_geometryChangedTimer = new QTimer(this); + m_geometryChangedTimer->setSingleShot(true); + connect(m_geometryChangedTimer, &QTimer::timeout, + this, &QDesignerFormWindowInterface::geometryChanged); + + m_rubberBand = nullptr; + + setFocusPolicy(Qt::StrongFocus); + + m_mainContainer = nullptr; + m_currentWidget = nullptr; + + connect(&m_undoStack, &QUndoStack::indexChanged, + this, &QDesignerFormWindowInterface::changed); + connect(&m_undoStack, &QUndoStack::cleanChanged, + this, &FormWindow::slotCleanChanged); + connect(this, &QDesignerFormWindowInterface::changed, + this, &FormWindow::checkSelection); + + core()->metaDataBase()->add(this); + + initializeCoreTools(); + + QAction *a = new QAction(this); + a->setText(tr("Edit contents")); + a->setShortcut(tr("F2")); + connect(a, &QAction::triggered, this, &FormWindow::editContents); + addAction(a); +} + +QWidget *FormWindow::mainContainer() const +{ + return m_mainContainer; +} + + +void FormWindow::clearMainContainer() +{ + if (m_mainContainer) { + setCurrentTool(0); + m_widgetStack->setMainContainer(nullptr); + core()->metaDataBase()->remove(m_mainContainer); + unmanageWidget(m_mainContainer); + delete m_mainContainer; + m_mainContainer = nullptr; + } +} + +void FormWindow::setMainContainer(QWidget *w) +{ + if (w == m_mainContainer) { + // nothing to do + return; + } + + clearMainContainer(); + + m_mainContainer = w; + const QSize sz = m_mainContainer->size(); + + m_widgetStack->setMainContainer(m_mainContainer); + m_widgetStack->setCurrentTool(m_widgetEditor); + + setCurrentWidget(m_mainContainer); + manageWidget(m_mainContainer); + + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), m_mainContainer)) { + sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true); + sheet->setVisible(sheet->indexOf(u"windowIcon"_s), true); + sheet->setVisible(sheet->indexOf(u"windowModality"_s), true); + sheet->setVisible(sheet->indexOf(u"windowOpacity"_s), true); + sheet->setVisible(sheet->indexOf(u"windowFilePath"_s), true); + // ### generalize + } + + m_mainContainer->setFocusPolicy(Qt::StrongFocus); + m_mainContainer->resize(sz); + + emit mainContainerChanged(m_mainContainer); +} + +QWidget *FormWindow::findTargetContainer(QWidget *widget) const +{ + Q_ASSERT(widget); + + while (QWidget *parentWidget = widget->parentWidget()) { + if (LayoutInfo::layoutType(m_core, parentWidget) == LayoutInfo::NoLayout && isManaged(widget)) + return widget; + + widget = parentWidget; + } + + return mainContainer(); +} + +static inline void clearObjectInspectorSelection(const QDesignerFormEditorInterface *core) +{ + if (QDesignerObjectInspector *oi = qobject_cast(core->objectInspector())) + oi->clearSelection(); +} + +// Find a parent of a desired selection state +static QWidget *findSelectedParent(QDesignerFormWindowInterface *fw, const QWidget *w, bool selected) +{ + const QDesignerFormWindowCursorInterface *cursor = fw->cursor(); + QWidget *mainContainer = fw->mainContainer(); + for (QWidget *p = w->parentWidget(); p && p != mainContainer; p = p->parentWidget()) + if (fw->isManaged(p)) + if (cursor->isWidgetSelected(p) == selected) + return p; + return nullptr; +} + +// Mouse modifiers. + +enum MouseFlags { ToggleSelectionModifier = 0x1, CycleParentModifier=0x2, CopyDragModifier=0x4 }; + +static inline unsigned mouseFlags(Qt::KeyboardModifiers mod) +{ + switch (mod) { + case Qt::ShiftModifier: + return CycleParentModifier; + break; +#ifdef Q_OS_MACOS + case Qt::AltModifier: // "Alt" or "option" key on Mac means copy + return CopyDragModifier; +#endif + case Qt::ControlModifier: + return CopyDragModifier|ToggleSelectionModifier; + break; + default: + break; + } + return 0; +} + +// Handle the click selection: Do toggling/cycling +// of parents according to the modifiers. +void FormWindow::handleClickSelection(QWidget *managedWidget, unsigned mouseMode) +{ + const bool sameWidget = managedWidget == m_lastClickedWidget; + m_lastClickedWidget = managedWidget; + + const bool selected = isWidgetSelected(managedWidget); + if (debugFormWindow) + qDebug() << "handleClickSelection" << managedWidget << " same=" << sameWidget << " mouse= " << mouseMode << " selected=" << selected; + + // // toggle selection state of widget + if (mouseMode & ToggleSelectionModifier) { + selectWidget(managedWidget, !selected); + return; + } + + QWidget *selectionCandidate = nullptr; + // Hierarchy cycling: If the same widget clicked again: Attempt to cycle + // trough the hierarchy. Find the next currently selected parent + if (sameWidget && (mouseMode & CycleParentModifier)) + if (QWidget *currentlySelectedParent = selected ? managedWidget : findSelectedParent(this, managedWidget, true)) + selectionCandidate = findSelectedParent(this, currentlySelectedParent, false); + // Not the same widget, list wrapped over or there was no unselected parent + if (!selectionCandidate && !selected) + selectionCandidate = managedWidget; + + if (selectionCandidate) + selectSingleWidget(selectionCandidate); +} + +void FormWindow::selectSingleWidget(QWidget *w) +{ + clearSelection(false); + selectWidget(w, true); + raiseChildSelections(w); +} + +bool FormWindow::handleMousePressEvent(QWidget * widget, QWidget *managedWidget, QMouseEvent *e) +{ + m_mouseState = NoMouseState; + m_startPos = QPoint(); + e->accept(); + + BlockSelection blocker(this); + + if (core()->formWindowManager()->activeFormWindow() != this) + core()->formWindowManager()->setActiveFormWindow(this); + + const Qt::MouseButtons buttons = e->buttons(); + if (buttons != Qt::LeftButton && buttons != Qt::MiddleButton) + return true; + + m_startPos = mapFromGlobal(e->globalPosition().toPoint()); + + if (debugFormWindow) + qDebug() << "handleMousePressEvent:" << widget << ',' << managedWidget; + + if (buttons == Qt::MiddleButton || isMainContainer(managedWidget)) { // press was on the formwindow + clearObjectInspectorSelection(m_core); // We might have a toolbar or non-widget selected in the object inspector. + clearSelection(false); + + m_mouseState = MouseDrawRubber; + m_currRect = QRect(); + startRectDraw(mapFromGlobal(e->globalPosition().toPoint()), this, Rubber); + return true; + } + if (buttons != Qt::LeftButton) + return true; + + const unsigned mouseMode = mouseFlags(e->modifiers()); + + /* Normally, we want to be able to click /select-on-press to drag away + * the widget in the next step. However, in the case of a widget which + * itself or whose parent is selected, we defer the selection to the + * release event. + * This is to prevent children from being dragged away from layouts + * when their layouts are selected and one wants to move the layout. + * Note that toggle selection is only deferred if the widget is already + * selected, so, it is still possible to just Ctrl+Click and CopyDrag. */ + const bool deferSelection = isWidgetSelected(managedWidget) || findSelectedParent(this, managedWidget, true); + if (deferSelection) { + m_mouseState = MouseDeferredSelection; + } else { + // Cycle the parent unless we explicitly want toggle + const unsigned effectiveMouseMode = (mouseMode & ToggleSelectionModifier) ? mouseMode : static_cast(CycleParentModifier); + handleClickSelection(managedWidget, effectiveMouseMode); + } + return true; +} + +// We can drag widget in managed layouts except splitter. +static bool canDragWidgetInLayout(const QDesignerFormEditorInterface *core, QWidget *w) +{ + bool managed; + const LayoutInfo::Type type = LayoutInfo::laidoutWidgetType(core ,w, &managed); + if (!managed) + return false; + switch (type) { + case LayoutInfo::NoLayout: + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: + return false; + default: + break; + } + return true; +} + +bool FormWindow::handleMouseMoveEvent(QWidget *, QWidget *, QMouseEvent *e) +{ + e->accept(); + if (m_startPos.isNull()) + return true; + + const QPoint pos = mapFromGlobal(e->globalPosition().toPoint()); + + switch (m_mouseState) { + case MouseDrawRubber: // Rubber band with left/middle mouse + continueRectDraw(pos, this, Rubber); + return true; + case MouseMoveDrag: // Spurious move event after drag started? + return true; + default: + break; + } + + if (e->buttons() != Qt::LeftButton) + return true; + + const bool canStartDrag = (m_startPos - pos).manhattanLength() > QApplication::startDragDistance(); + + if (!canStartDrag) // nothing to do + return true; + + m_mouseState = MouseMoveDrag; + const bool blocked = blockSelectionChanged(true); + + QWidgetList sel = selectedWidgets(); + const QWidgetList originalSelection = sel; + simplifySelection(&sel); + + QSet widget_set; + + for (QWidget *child : std::as_const(sel)) { // Move parent layout or container? + QWidget *current = child; + + bool done = false; + while (!isMainContainer(current) && !done) { + if (!isManaged(current)) { + current = current->parentWidget(); + continue; + } + if (LayoutInfo::isWidgetLaidout(core(), current)) { + // Go up to parent of layout if shift pressed, else do that only for splitters + if (!canDragWidgetInLayout(core(), current)) { + current = current->parentWidget(); + continue; + } + } + done = true; + } + + if (current == mainContainer()) + continue; + + widget_set.insert(current); + } + + sel = widget_set.values(); + QDesignerFormWindowCursorInterface *c = cursor(); + QWidget *current = c->current(); + if (sel.contains(current)) { + sel.removeAll(current); + sel.prepend(current); + } + + QList item_list; + const QPoint globalPos = mapToGlobal(m_startPos); + const QDesignerDnDItemInterface::DropType dropType = (mouseFlags(e->modifiers()) & CopyDragModifier) ? + QDesignerDnDItemInterface::CopyDrop : QDesignerDnDItemInterface::MoveDrop; + for (QWidget *widget : std::as_const(sel)) { + item_list.append(new FormWindowDnDItem(dropType, this, widget, globalPos)); + if (dropType == QDesignerDnDItemInterface::MoveDrop) { + m_selection->hide(widget); + widget->hide(); + } + } + + // In case when we have reduced the selection (by calling simplifySelection() + // beforehand) we still need to hide selection handles for children widgets + for (auto *widget : originalSelection) + m_selection->hide(widget); + + blockSelectionChanged(blocked); + + if (!sel.isEmpty()) // reshow selection? + if (QDesignerMimeData::execDrag(item_list, core()->topLevel()) == Qt::IgnoreAction && dropType == QDesignerDnDItemInterface::MoveDrop) + for (QWidget *widget : std::as_const(sel)) + m_selection->show(widget); + + m_startPos = QPoint(); + + return true; +} + +bool FormWindow::handleMouseReleaseEvent(QWidget *w, QWidget *mw, QMouseEvent *e) +{ + const MouseState oldState = m_mouseState; + m_mouseState = NoMouseState; + + if (debugFormWindow) + qDebug() << "handleMouseeleaseEvent:" << w << ',' << mw << "state=" << oldState; + + if (oldState == MouseDoubleClicked) + return true; + + e->accept(); + + switch (oldState) { + case MouseDrawRubber: { // we were drawing a rubber selection + endRectDraw(); // get rid of the rectangle + const bool blocked = blockSelectionChanged(true); + selectWidgets(); // select widgets which intersect the rect + blockSelectionChanged(blocked); + } + break; + // Deferred select: Select the child here unless the parent was moved. + case MouseDeferredSelection: + handleClickSelection(mw, mouseFlags(e->modifiers())); + break; + default: + break; + } + + m_startPos = QPoint(); + + /* Inform about selection changes (left/mid or context menu). Also triggers + * in the case of an empty rubber drag that cleared the selection in + * MousePressEvent. */ + switch (e->button()) { + case Qt::LeftButton: + case Qt::MiddleButton: + case Qt::RightButton: + emitSelectionChanged(); + break; + default: + break; + } + + return true; +} + +void FormWindow::checkPreviewGeometry(QRect &r) +{ + if (!rect().contains(r)) { + if (r.left() < rect().left()) + r.moveTopLeft(QPoint(0, r.top())); + if (r.right() > rect().right()) + r.moveBottomRight(QPoint(rect().right(), r.bottom())); + if (r.top() < rect().top()) + r.moveTopLeft(QPoint(r.left(), rect().top())); + if (r.bottom() > rect().bottom()) + r.moveBottomRight(QPoint(r.right(), rect().bottom())); + } +} + +void FormWindow::startRectDraw(QPoint pos, QWidget *, RectType t) +{ + m_rectAnchor = (t == Insert) ? designerGrid().snapPoint(pos) : pos; + + m_currRect = QRect(m_rectAnchor, QSize(0, 0)); + if (!m_rubberBand) + m_rubberBand = new QRubberBand(QRubberBand::Rectangle, this); + m_rubberBand->setGeometry(m_currRect); + m_rubberBand->show(); +} + +void FormWindow::continueRectDraw(QPoint pos, QWidget *, RectType t) +{ + const QPoint p2 = (t == Insert) ? designerGrid().snapPoint(pos) : pos; + + QRect r(m_rectAnchor, p2); + r = r.normalized(); + + if (m_currRect == r) + return; + + if (r.width() > 1 || r.height() > 1) { + m_currRect = r; + if (m_rubberBand) + m_rubberBand->setGeometry(m_currRect); + } +} + +void FormWindow::endRectDraw() +{ + if (m_rubberBand) { + delete m_rubberBand; + m_rubberBand = nullptr; + } +} + +QWidget *FormWindow::currentWidget() const +{ + return m_currentWidget; +} + +bool FormWindow::setCurrentWidget(QWidget *currentWidget) +{ + if (debugFormWindow) + qDebug() << "setCurrentWidget:" << m_currentWidget << " --> " << currentWidget; + if (currentWidget == m_currentWidget) + return false; + // repaint the old widget unless it is the main window + if (m_currentWidget && m_currentWidget != mainContainer()) { + m_selection->repaintSelection(m_currentWidget); + } + // set new and repaint + m_currentWidget = currentWidget; + if (m_currentWidget && m_currentWidget != mainContainer()) { + m_selection->repaintSelection(m_currentWidget); + } + return true; +} + +void FormWindow::selectWidget(QWidget* w, bool select) +{ + if (trySelectWidget(w, select)) + emitSelectionChanged(); +} + +// Selects a widget and determines the new current one. Returns true if a change occurs. +bool FormWindow::trySelectWidget(QWidget *w, bool select) +{ + if (debugFormWindow) + qDebug() << "trySelectWidget:" << w << select; + if (!isManaged(w) && !isCentralWidget(w)) + return false; + + if (!select && !isWidgetSelected(w)) + return false; + + if (!mainContainer()) + return false; + + if (isMainContainer(w) || isCentralWidget(w)) { + setCurrentWidget(mainContainer()); + return true; + } + + if (select) { + setCurrentWidget(w); + m_selection->addWidget(this, w); + } else { + QWidget *newCurrent = m_selection->removeWidget(w); + if (!newCurrent) + newCurrent = mainContainer(); + setCurrentWidget(newCurrent); + } + return true; +} + +void FormWindow::clearSelection(bool changePropertyDisplay) +{ + if (debugFormWindow) + qDebug() << "clearSelection(" << changePropertyDisplay << ')'; + // At all events, we need a current widget. + m_selection->clear(); + setCurrentWidget(mainContainer()); + + if (changePropertyDisplay) + emitSelectionChanged(); +} + +void FormWindow::emitSelectionChanged() +{ + if (m_blockSelectionChanged) // nothing to do + return; + + m_selectionChangedTimer->start(0); +} + +void FormWindow::selectionChangedTimerDone() +{ + emit selectionChanged(); +} + +bool FormWindow::isWidgetSelected(QWidget *w) const +{ + return m_selection->isWidgetSelected(w); +} + +bool FormWindow::isMainContainer(const QWidget *w) const +{ + return w && (w == this || w == mainContainer()); +} + +void FormWindow::updateChildSelections(QWidget *w) +{ + const QWidgetList l = w->findChildren(); + for (auto *w : l) { + if (isManaged(w)) + updateSelection(w); + } +} + +void FormWindow::repaintSelection() +{ + m_selection->repaintSelection(); +} + +void FormWindow::raiseSelection(QWidget *w) +{ + m_selection->raiseWidget(w); +} + +void FormWindow::updateSelection(QWidget *w) +{ + if (!w->isVisibleTo(this)) { + selectWidget(w, false); + } else { + m_selection->updateGeometry(w); + } +} + +QWidget *FormWindow::designerWidget(QWidget *w) const +{ + while ((w && !isMainContainer(w) && !isManaged(w)) || isCentralWidget(w)) + w = w->parentWidget(); + + return w; +} + +bool FormWindow::isCentralWidget(QWidget *w) const +{ + if (QMainWindow *mainWindow = qobject_cast(mainContainer())) + return w == mainWindow->centralWidget(); + + return false; +} + +void FormWindow::ensureUniqueObjectName(QObject *object) +{ + QString name = object->objectName(); + if (name.isEmpty()) { + QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase(); + if (QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfObject(object))) + name = qdesigner_internal::qtify(item->name()); + } + unify(object, name, true); + object->setObjectName(name); +} + +template +static inline void insertNames(const QDesignerMetaDataBaseInterface *metaDataBase, + Iterator it, const Iterator &end, + QObject *excludedObject, QSet &nameSet) +{ + for ( ; it != end; ++it) + if (excludedObject != *it && metaDataBase->item(*it)) + nameSet.insert((*it)->objectName()); +} + +static QSet languageKeywords() +{ + static const QSet keywords = { + // C++ keywords + u"asm"_s, + u"assert"_s, + u"auto"_s, + u"bool"_s, + u"break"_s, + u"case"_s, + u"catch"_s, + u"char"_s, + u"class"_s, + u"const"_s, + u"const_cast"_s, + u"continue"_s, + u"default"_s, + u"delete"_s, + u"do"_s, + u"double"_s, + u"dynamic_cast"_s, + u"else"_s, + u"enum"_s, + u"explicit"_s, + u"export"_s, + u"extern"_s, + u"false"_s, + u"final"_s, + u"float"_s, + u"for"_s, + u"friend"_s, + u"goto"_s, + u"if"_s, + u"inline"_s, + u"int"_s, + u"long"_s, + u"mutable"_s, + u"namespace"_s, + u"new"_s, + u"noexcept"_s, + u"NULL"_s, + u"nullptr"_s, + u"operator"_s, + u"override"_s, + u"private"_s, + u"protected"_s, + u"public"_s, + u"register"_s, + u"reinterpret_cast"_s, + u"return"_s, + u"short"_s, + u"signed"_s, + u"sizeof"_s, + u"static"_s, + u"static_cast"_s, + u"struct"_s, + u"switch"_s, + u"template"_s, + u"this"_s, + u"throw"_s, + u"true"_s, + u"try"_s, + u"typedef"_s, + u"typeid"_s, + u"typename"_s, + u"union"_s, + u"unsigned"_s, + u"using"_s, + u"virtual"_s, + u"void"_s, + u"volatile"_s, + u"wchar_t"_s, + u"while"_s, + + // java keywords + u"abstract"_s, + u"boolean"_s, + u"byte"_s, + u"extends"_s, + u"finality"_s, + u"implements"_s, + u"import"_s, + u"instanceof"_s, + u"interface"_s, + u"native"_s, + u"null"_s, + u"package"_s, + u"strictfp"_s, + u"super"_s, + u"synchronized"_s, + u"throws"_s, + u"transient"_s, + }; + + return keywords; +} + +bool FormWindow::unify(QObject *w, QString &s, bool changeIt) +{ + using StringSet = QSet; + + QWidget *main = mainContainer(); + if (!main) + return true; + + StringSet existingNames = languageKeywords(); + // build a set of existing names of other widget excluding self + if (!(w->isWidgetType() && isMainContainer(qobject_cast(w)))) + existingNames.insert(main->objectName()); + + const QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase(); + const QWidgetList widgetChildren = main->findChildren(); + if (!widgetChildren.isEmpty()) + insertNames(metaDataBase, widgetChildren.constBegin(), widgetChildren.constEnd(), w, existingNames); + + const auto layoutChildren = main->findChildren(); + if (!layoutChildren.isEmpty()) + insertNames(metaDataBase, layoutChildren.constBegin(), layoutChildren.constEnd(), w, existingNames); + + const auto actionChildren = main->findChildren(); + if (!actionChildren.isEmpty()) + insertNames(metaDataBase, actionChildren.constBegin(), actionChildren.constEnd(), w, existingNames); + + const auto buttonGroupChildren = main->findChildren(); + if (!buttonGroupChildren.isEmpty()) + insertNames(metaDataBase, buttonGroupChildren.constBegin(), buttonGroupChildren.constEnd(), w, existingNames); + + if (!existingNames.contains(s)) + return true; + if (!changeIt) + return false; + + // split 'name_number' + qlonglong num = 0; + qlonglong factor = 1; + qsizetype idx = s.size() - 1; + const char16_t zeroUnicode = u'0'; + for ( ; idx > 0 && s.at(idx).isDigit(); --idx) { + num += (s.at(idx).unicode() - zeroUnicode) * factor; + factor *= 10; + } + // Position index past '_'. + const QChar underscore = u'_'; + if (idx >= 0 && s.at(idx) == underscore) { + idx++; + } else { + num = 1; + s += underscore; + idx = s.size(); + } + // try 'name_n', 'name_n+1' + for (num++ ; ;num++) { + s.truncate(idx); + s += QString::number(num); + if (!existingNames.contains(s)) + break; + } + return false; +} +/* already_in_form is true when we are moving a widget from one parent to another inside the same + * form. All this means is that InsertWidgetCommand::undo() must not unmanage it. */ + +void FormWindow::insertWidget(QWidget *w, QRect rect, QWidget *container, bool already_in_form) +{ + clearSelection(false); + + beginCommand(tr("Insert widget '%1'").arg(WidgetFactory::classNameOf(m_core, w))); // ### use the WidgetDatabaseItem + + /* Reparenting into a QSplitter automatically adjusts child's geometry. We create the geometry + * command before we push the reparent command, so that the geometry command has the original + * geometry of the widget. */ + QRect r = rect; + Q_ASSERT(r.isValid()); + SetPropertyCommand *geom_cmd = new SetPropertyCommand(this); + geom_cmd->init(w, u"geometry"_s, r); // ### use rc.size() + + if (w->parentWidget() != container) { + ReparentWidgetCommand *cmd = new ReparentWidgetCommand(this); + cmd->init(w, container); + m_undoStack.push(cmd); + } + + m_undoStack.push(geom_cmd); + + QUndoCommand *cmd = nullptr; + if (auto dockWidget = qobject_cast(w)) { + if (auto mainWindow = qobject_cast(container)) { + auto addDockCmd = new AddDockWidgetCommand(this); + addDockCmd->init(mainWindow, dockWidget); + cmd = addDockCmd; + } + } + if (cmd == nullptr) { + auto insertCmd = new InsertWidgetCommand(this); + insertCmd->init(w, already_in_form); + cmd = insertCmd; + } + m_undoStack.push(cmd); + + endCommand(); + + w->show(); +} + +QWidget *FormWindow::createWidget(DomUI *ui, QRect rc, QWidget *target) +{ + QWidget *container = findContainer(target, false); + if (!container) + return nullptr; + if (isMainContainer(container)) { + if (QMainWindow *mw = qobject_cast(container)) { + Q_ASSERT(mw->centralWidget() != nullptr); + container = mw->centralWidget(); + } + } + QDesignerResource resource(this); + const FormBuilderClipboard clipboard = resource.paste(ui, container); + if (clipboard.m_widgets.size() != 1) // multiple-paste from DomUI not supported yet + return nullptr; + QWidget *widget = clipboard.m_widgets.first(); + insertWidget(widget, rc, container); + return widget; +} + +static bool isDescendant(const QWidget *parent, const QWidget *child) +{ + for (; child != nullptr; child = child->parentWidget()) { + if (child == parent) + return true; + } + return false; +} + +void FormWindow::resizeWidget(QWidget *widget, QRect geometry) +{ + Q_ASSERT(isDescendant(this, widget)); + + QRect r = geometry; + SetPropertyCommand *cmd = new SetPropertyCommand(this); + cmd->init(widget, u"geometry"_s, r); + cmd->setText(tr("Resize")); + m_undoStack.push(cmd); +} + +void FormWindow::raiseChildSelections(QWidget *w) +{ + const QWidgetList l = w->findChildren(); + if (l.isEmpty()) + return; + m_selection->raiseList(l); +} + +QWidget *FormWindow::containerAt(QPoint pos, QWidget *notParentOf) +{ + QWidget *container = nullptr; + int depth = -1; + const QWidgetList selected = selectedWidgets(); + if (rect().contains(mapFromGlobal(pos))) { + container = mainContainer(); + depth = widgetDepth(container); + } + + for (QWidget *wit : std::as_const(m_widgets)) { + if (qobject_cast(wit) || qobject_cast(wit)) + continue; + if (!wit->isVisibleTo(this)) + continue; + if (selected.indexOf(wit) != -1) + continue; + if (!core()->widgetDataBase()->isContainer(wit) && + wit != mainContainer()) + continue; + + // the rectangles of all ancestors of the container must contain the insert position + QWidget *w = wit; + while (w && !w->isWindow()) { + if (!w->rect().contains((w->mapFromGlobal(pos)))) + break; + w = w->parentWidget(); + } + if (!(w == nullptr || w->isWindow())) + continue; // we did not get through the full while loop + + int wd = widgetDepth(wit); + if (wd == depth && container) { + if (wit->parentWidget()->children().indexOf(wit) > + container->parentWidget()->children().indexOf(container)) + wd++; + } + if (wd > depth && !isChildOf(wit, notParentOf)) { + depth = wd; + container = wit; + } + } + return container; +} + +QWidgetList FormWindow::selectedWidgets() const +{ + return m_selection->selectedWidgets(); +} + +void FormWindow::selectWidgets() +{ + bool selectionChanged = false; + const QWidgetList l = mainContainer()->findChildren(); + const QRect selRect(mapToGlobal(m_currRect.topLeft()), m_currRect.size()); + for (QWidget *w : l) { + if (w->isVisibleTo(this) && isManaged(w)) { + const QPoint p = w->mapToGlobal(QPoint(0,0)); + const QRect r(p, w->size()); + if (r.intersects(selRect) && !r.contains(selRect) && trySelectWidget(w, true)) + selectionChanged = true; + } + } + + if (selectionChanged) + emitSelectionChanged(); +} + +bool FormWindow::handleKeyPressEvent(QWidget *widget, QWidget *, QKeyEvent *e) +{ + if (qobject_cast(widget) || qobject_cast(widget)) + return false; + + e->accept(); // we always accept! + + switch (e->key()) { + default: break; // we don't care about the other keys + + case Qt::Key_Delete: + case Qt::Key_Backspace: + if (e->modifiers() == Qt::NoModifier) + deleteWidgets(); + break; + + case Qt::Key_Tab: + if (e->modifiers() == Qt::NoModifier) + cursor()->movePosition(QDesignerFormWindowCursorInterface::Next); + break; + + case Qt::Key_Backtab: + if (e->modifiers() == Qt::NoModifier) + cursor()->movePosition(QDesignerFormWindowCursorInterface::Prev); + break; + + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + handleArrowKeyEvent(e->key(), e->modifiers()); + break; + } + + return true; +} + +int FormWindow::getValue(QRect rect, int key, bool size) const +{ + if (size) { + if (key == Qt::Key_Left || key == Qt::Key_Right) + return rect.width(); + return rect.height(); + } + if (key == Qt::Key_Left || key == Qt::Key_Right) + return rect.x(); + return rect.y(); +} + +int FormWindow::calcValue(int val, bool forward, bool snap, int snapOffset) const +{ + if (snap) { + const int rest = val % snapOffset; + if (rest) { + const int offset = forward ? snapOffset : 0; + const int newOffset = rest < 0 ? offset - snapOffset : offset; + return val + newOffset - rest; + } + return (forward ? val + snapOffset : val - snapOffset); + } + return (forward ? val + 1 : val - 1); +} + +// ArrowKeyOperation: Stores a keyboard move or resize (Shift pressed) +// operation. +struct ArrowKeyOperation +{ + QRect apply(QRect rect) const; + + bool resize = false; // Resize: Shift-Key->drag bottom/right corner, else just move + int distance = 0; + int arrowKey = Qt::Key_Left; +}; + +} // namespace + +QT_END_NAMESPACE +Q_DECLARE_METATYPE(qdesigner_internal::ArrowKeyOperation) +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +QRect ArrowKeyOperation::apply(QRect rect) const +{ + QRect r = rect; + if (resize) { + if (arrowKey == Qt::Key_Left || arrowKey == Qt::Key_Right) + r.setWidth(r.width() + distance); + else + r.setHeight(r.height() + distance); + } else { + if (arrowKey == Qt::Key_Left || arrowKey == Qt::Key_Right) + r.moveLeft(r.x() + distance); + else + r.moveTop(r.y() + distance); + } + return r; +} + +QDebug operator<<(QDebug in, ArrowKeyOperation op) +{ + in.nospace() << "Resize=" << op.resize << " dist=" << op.distance << " Key=" << op.arrowKey << ' '; + return in; +} + +// ArrowKeyPropertyHelper: Applies a struct ArrowKeyOperation +// (stored as new value) to a list of widgets using to calculate the +// changed geometry of the widget in setValue(). Thus, the 'newValue' +// of the property command is the relative move distance, which is the same +// for all widgets (although resulting in different geometries for the widgets). +// The command merging can then work as it would when applying the same text +// to all QLabels. + +class ArrowKeyPropertyHelper : public PropertyHelper { +public: + ArrowKeyPropertyHelper(QObject* o, SpecialProperty sp, + QDesignerPropertySheetExtension *s, int i) : + PropertyHelper(o, sp, s, i) {} + + Value setValue(QDesignerFormWindowInterface *fw, const QVariant &value, bool changed, + quint64 subPropertyMask) override; +}; + +PropertyHelper::Value ArrowKeyPropertyHelper::setValue(QDesignerFormWindowInterface *fw, const QVariant &value, + bool changed, quint64 subPropertyMask) +{ + // Apply operation to obtain the new geometry value. + QWidget *w = qobject_cast(object()); + const ArrowKeyOperation operation = qvariant_cast(value); + const QRect newGeom = operation.apply(w->geometry()); + return PropertyHelper::setValue(fw, QVariant(newGeom), changed, subPropertyMask); +} + +// ArrowKeyPropertyCommand: Helper factory overwritten to create +// ArrowKeyPropertyHelper and a merge operation that merges values of +// the same direction. +class ArrowKeyPropertyCommand: public SetPropertyCommand { +public: + explicit ArrowKeyPropertyCommand(QDesignerFormWindowInterface *fw, + QUndoCommand *p = nullptr); + + void init(QWidgetList &l, ArrowKeyOperation op); + +protected: + std::unique_ptr + createPropertyHelper(QObject *o, SpecialProperty sp, + QDesignerPropertySheetExtension *s, int i) const override + { return std::make_unique(o, sp, s, i); } + QVariant mergeValue(const QVariant &newValue) override; +}; + +ArrowKeyPropertyCommand::ArrowKeyPropertyCommand(QDesignerFormWindowInterface *fw, + QUndoCommand *p) : + SetPropertyCommand(fw, p) +{ + static const int mid = qRegisterMetaType(); + Q_UNUSED(mid); +} + +void ArrowKeyPropertyCommand::init(QWidgetList &l, ArrowKeyOperation op) +{ + QObjectList ol; + for (QWidget *w : std::as_const(l)) + ol.push_back(w); + SetPropertyCommand::init(ol, u"geometry"_s, QVariant::fromValue(op)); + + setText(op.resize ? FormWindow::tr("Key Resize") : FormWindow::tr("Key Move")); +} + +QVariant ArrowKeyPropertyCommand::mergeValue(const QVariant &newMergeValue) +{ + // Merge move operations of the same arrow key + if (!newMergeValue.canConvert()) + return QVariant(); + ArrowKeyOperation mergedOperation = qvariant_cast(newValue()); + const ArrowKeyOperation newMergeOperation = qvariant_cast(newMergeValue); + if (mergedOperation.resize != newMergeOperation.resize || mergedOperation.arrowKey != newMergeOperation.arrowKey) + return QVariant(); + mergedOperation.distance += newMergeOperation.distance; + return QVariant::fromValue(mergedOperation); +} + +void FormWindow::handleArrowKeyEvent(int key, Qt::KeyboardModifiers modifiers) +{ + const QDesignerFormWindowCursorInterface *c = cursor(); + if (!c->hasSelection()) + return; + + QWidgetList selection; + + // check if a laid out widget is selected + const int count = c->selectedWidgetCount(); + for (int index = 0; index < count; ++index) { + QWidget *w = c->selectedWidget(index); + if (!LayoutInfo::isWidgetLaidout(m_core, w)) + selection.append(w); + } + + simplifySelection(&selection); + + if (selection.isEmpty()) + return; + + QWidget *current = c->current(); + if (!current || LayoutInfo::isWidgetLaidout(m_core, current)) { + current = selection.first(); + } + + const bool size = modifiers & Qt::ShiftModifier; + + const bool snap = !(modifiers & Qt::ControlModifier); + const bool forward = (key == Qt::Key_Right || key == Qt::Key_Down); + const int snapPoint = (key == Qt::Key_Left || key == Qt::Key_Right) ? grid().x() : grid().y(); + + const int oldValue = getValue(current->geometry(), key, size); + + const int newValue = calcValue(oldValue, forward, snap, snapPoint); + + ArrowKeyOperation operation; + operation.resize = modifiers & Qt::ShiftModifier; + operation.distance = newValue - oldValue; + operation.arrowKey = key; + + ArrowKeyPropertyCommand *cmd = new ArrowKeyPropertyCommand(this); + cmd->init(selection, operation); + m_undoStack.push(cmd); +} + +bool FormWindow::handleKeyReleaseEvent(QWidget *, QWidget *, QKeyEvent *e) +{ + e->accept(); + return true; +} + +void FormWindow::selectAll() +{ + bool selectionChanged = false; + for (QWidget *widget : std::as_const(m_widgets)) { + if (widget->isVisibleTo(this) && trySelectWidget(widget, true)) + selectionChanged = true; + } + if (selectionChanged) + emitSelectionChanged(); +} + +void FormWindow::createLayout(int type, QWidget *container) +{ + if (container) { + layoutContainer(container, type); + } else { + LayoutCommand *cmd = new LayoutCommand(this); + cmd->init(mainContainer(), selectedWidgets(), static_cast(type)); + commandHistory()->push(cmd); + } +} + +void FormWindow::morphLayout(QWidget *container, int newType) +{ + MorphLayoutCommand *cmd = new MorphLayoutCommand(this); + if (cmd->init(container, newType)) { + commandHistory()->push(cmd); + } else { + qDebug() << "** WARNING Unable to morph layout."; + delete cmd; + } +} + +void FormWindow::deleteWidgets() +{ + QWidgetList selection = selectedWidgets(); + simplifySelection(&selection); + + deleteWidgetList(selection); +} + +QString FormWindow::fileName() const +{ + return m_fileName; +} + +void FormWindow::setFileName(const QString &fileName) +{ + if (m_fileName == fileName) + return; + + m_fileName = fileName; + emit fileNameChanged(fileName); +} + +QString FormWindow::contents() const +{ + QBuffer b; + if (!mainContainer() || !b.open(QIODevice::WriteOnly)) + return QString(); + + QDesignerResource resource(const_cast(this)); + resource.save(&b, mainContainer()); + + return QString::fromUtf8(b.buffer()); +} + +#if QT_CONFIG(clipboard) +void FormWindow::copy() +{ + QBuffer b; + if (!b.open(QIODevice::WriteOnly)) + return; + + FormBuilderClipboard clipboard; + QDesignerResource resource(this); + resource.setSaveRelative(false); + clipboard.m_widgets = selectedWidgets(); + simplifySelection(&clipboard.m_widgets); + resource.copy(&b, clipboard); + + qApp->clipboard()->setText(QString::fromUtf8(b.buffer()), QClipboard::Clipboard); +} + +void FormWindow::cut() +{ + copy(); + deleteWidgets(); +} + +void FormWindow::paste() +{ + paste(PasteAll); +} +#endif + +// for cases like QMainWindow (central widget is an inner container) or QStackedWidget (page is an inner container) +QWidget *FormWindow::innerContainer(QWidget *outerContainer) const +{ + if (m_core->widgetDataBase()->isContainer(outerContainer)) + if (const QDesignerContainerExtension *container = qt_extension(m_core->extensionManager(), outerContainer)) { + const int currentIndex = container->currentIndex(); + return currentIndex >= 0 ? container->widget(currentIndex) : nullptr; + } + return outerContainer; +} + +QWidget *FormWindow::containerForPaste() const +{ + QWidget *w = mainContainer(); + if (!w) + return nullptr; + do { + // Try to find a close parent, for example a non-laid-out + // QFrame/QGroupBox when a widget within it is selected. + QWidgetList selection = selectedWidgets(); + if (selection.isEmpty()) + break; + simplifySelection(&selection); + + QWidget *containerOfW = findContainer(selection.first(), /* exclude layouts */ true); + if (!containerOfW || containerOfW == mainContainer()) + break; + // No layouts, must be container. No empty page-based containers. + containerOfW = innerContainer(containerOfW); + if (!containerOfW) + break; + if (LayoutInfo::layoutType(m_core, containerOfW) != LayoutInfo::NoLayout || !m_core->widgetDataBase()->isContainer(containerOfW)) + break; + w = containerOfW; + } while (false); + // First check for layout (note that it does not cover QMainWindow + // and the like as the central widget has the layout). + + w = innerContainer(w); + if (!w) + return nullptr; + if (LayoutInfo::layoutType(m_core, w) != LayoutInfo::NoLayout) + return nullptr; + // Go up via container extension (also includes step from QMainWindow to its central widget) + w = m_core->widgetFactory()->containerOfWidget(w); + if (w == nullptr || LayoutInfo::layoutType(m_core, w) != LayoutInfo::NoLayout) + return nullptr; + + if (debugFormWindow) + qDebug() <<"containerForPaste() " << w; + return w; +} + +#if QT_CONFIG(clipboard) +// Construct DomUI from clipboard (paste) and determine number of widgets/actions. +static inline DomUI *domUIFromClipboard(int *widgetCount, int *actionCount) +{ + *widgetCount = *actionCount = 0; + const QString clipboardText = qApp->clipboard()->text(); + if (clipboardText.isEmpty() || clipboardText.indexOf(u'<') == -1) + return nullptr; + + QXmlStreamReader reader(clipboardText); + DomUI *ui = nullptr; + while (!reader.atEnd()) { + if (reader.readNext() == QXmlStreamReader::StartElement) { + if (reader.name().compare("ui"_L1, Qt::CaseInsensitive) == 0 && !ui) { + ui = new DomUI(); + ui->read(reader); + break; + } + reader.raiseError(QCoreApplication::translate("FormWindow", "Unexpected element <%1>").arg(reader.name().toString())); + } + } + if (reader.hasError()) { + delete ui; + ui = nullptr; + designerWarning(QCoreApplication::translate("FormWindow", "Error while pasting clipboard contents at line %1, column %2: %3"). + arg(reader.lineNumber()).arg(reader.columnNumber()).arg(reader.errorString())); + return nullptr; + } + + if (const DomWidget *topLevel = ui->elementWidget()) { + *widgetCount = topLevel->elementWidget().size(); + *actionCount = topLevel->elementAction().size(); + } + if (*widgetCount == 0 && *actionCount == 0) { + delete ui; + return nullptr; + } + return ui; +} +#endif + +static inline QString pasteCommandDescription(int widgetCount, int actionCount) +{ + if (widgetCount == 0) + return FormWindow::tr("Paste %n action(s)", nullptr, actionCount); + if (actionCount == 0) + return FormWindow::tr("Paste %n widget(s)", nullptr, widgetCount); + return FormWindow::tr("Paste (%1 widgets, %2 actions)").arg(widgetCount).arg(actionCount); +} + +#if QT_CONFIG(clipboard) +static void positionPastedWidgetsAtMousePosition(FormWindow *fw, QPoint contextMenuPosition, QWidget *parent, const QWidgetList &l) +{ + // Try to position pasted widgets at mouse position (current mouse position for Ctrl-V or position of context menu) + // if it fits. If it is completely outside, force it to 0,0 + // If it fails, the old coordinates relative to the previous parent will be used. + QPoint currentPos = contextMenuPosition.x() >=0 ? parent->mapFrom(fw, contextMenuPosition) : parent->mapFromGlobal(QCursor::pos()); + const Grid &grid = fw->designerGrid(); + QPoint cursorPos = grid.snapPoint(currentPos); + const QRect parentGeometry = QRect(QPoint(0, 0), parent->size()); + const bool outside = !parentGeometry.contains(cursorPos); + if (outside) + cursorPos = grid.snapPoint(QPoint(0, 0)); + // Determine area of pasted widgets + QRect pasteArea; + for (auto *w : l) + pasteArea = pasteArea.isNull() ? w->geometry() : pasteArea.united(w->geometry()); + + // Mouse on some child? (try to position bottomRight on a free spot to + // get the stacked-offset effect of Designer 4.3, that is, offset by grid if Ctrl-V is pressed continuously + do { + const QPoint bottomRight = cursorPos + QPoint(pasteArea.width(), pasteArea.height()) - QPoint(1, 1); + if (bottomRight.y() > parentGeometry.bottom() || parent->childAt(bottomRight) == nullptr) + break; + cursorPos += QPoint(grid.deltaX(), grid.deltaY()); + } while (true); + // Move. + const QPoint offset = cursorPos - pasteArea.topLeft(); + for (auto *w : l) + w->move(w->pos() + offset); +} + +void FormWindow::paste(PasteMode pasteMode) +{ + // Avoid QDesignerResource constructing widgets that are not used as + // QDesignerResource manages the widgets it creates (creating havoc if one remains unused) + DomUI *ui = nullptr; + do { + int widgetCount; + int actionCount; + ui = domUIFromClipboard(&widgetCount, &actionCount); + if (!ui) + break; + + // Check for actions + if (pasteMode == PasteActionsOnly) + if (widgetCount != 0 || actionCount == 0) + break; + + // Check for widgets: need a container + QWidget *pasteContainer = widgetCount ? containerForPaste() : nullptr; + if (widgetCount && pasteContainer == nullptr) { + + const QString message = tr("Cannot paste widgets. Designer could not find a container " + "without a layout to paste into."); + const QString infoMessage = tr("Break the layout of the " + "container you want to paste into, select this container " + "and then paste again."); + core()->dialogGui()->message(this, QDesignerDialogGuiInterface::FormEditorMessage, QMessageBox::Information, + tr("Paste error"), message, infoMessage, QMessageBox::Ok); + break; + } + + QDesignerResource resource(this); + // Note that the widget factory must be able to locate the + // form window (us) via parent, otherwise, it will not able to construct QLayoutWidgets + // (It will then default to widgets) among other issues. + const FormBuilderClipboard clipboard = resource.paste(ui, pasteContainer, this); + + clearSelection(false); + // Create command sequence + beginCommand(pasteCommandDescription(widgetCount, actionCount)); + + if (widgetCount) { + positionPastedWidgetsAtMousePosition(this, m_contextMenuPosition, pasteContainer, clipboard.m_widgets); + for (QWidget *w : clipboard.m_widgets) { + InsertWidgetCommand *cmd = new InsertWidgetCommand(this); + cmd->init(w); + m_undoStack.push(cmd); + selectWidget(w); + } + } + + if (actionCount) + for (QAction *a : clipboard.m_actions) { + ensureUniqueObjectName(a); + AddActionCommand *cmd = new AddActionCommand(this); + cmd->init(a); + m_undoStack.push(cmd); + } + endCommand(); + } while (false); + delete ui; +} +#endif + +// Draw a dotted frame around containers +bool FormWindow::frameNeeded(QWidget *w) const +{ + if (!core()->widgetDataBase()->isContainer(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + return true; +} + +bool FormWindow::eventFilter(QObject *watched, QEvent *event) +{ + const bool ret = FormWindowBase::eventFilter(watched, event); + if (event->type() != QEvent::Paint) + return ret; + + Q_ASSERT(watched->isWidgetType()); + QWidget *w = static_cast(watched); + QPaintEvent *pe = static_cast(event); + const QRect widgetRect = w->rect(); + const QRect paintRect = pe->rect(); + // Does the paint rectangle touch the borders of the widget rectangle + if (paintRect.x() > widgetRect.x() && paintRect.y() > widgetRect.y() && + paintRect.right() < widgetRect.right() && paintRect.bottom() < widgetRect.bottom()) + return ret; + QPainter p(w); + const QPen pen(QColor(0, 0, 0, 32), 0, Qt::DotLine); + p.setPen(pen); + p.setBrush(QBrush(Qt::NoBrush)); + p.drawRect(widgetRect.adjusted(0, 0, -1, -1)); + return ret; +} + +void FormWindow::manageWidget(QWidget *w) +{ + if (isManaged(w)) + return; + + Q_ASSERT(qobject_cast(w) == 0); + + if (w->hasFocus()) + setFocus(); + + core()->metaDataBase()->add(w); + + m_insertedWidgets.insert(w); + m_widgets.append(w); + +#if QT_CONFIG(cursor) + setCursorToAll(Qt::ArrowCursor, w); +#endif + + emit changed(); + emit widgetManaged(w); + + if (frameNeeded(w)) + w->installEventFilter(this); +} + +void FormWindow::unmanageWidget(QWidget *w) +{ + if (!isManaged(w)) + return; + + m_selection->removeWidget(w); + + emit aboutToUnmanageWidget(w); + + if (w == m_currentWidget) + setCurrentWidget(mainContainer()); + + core()->metaDataBase()->remove(w); + + m_insertedWidgets.remove(w); + m_widgets.removeAt(m_widgets.indexOf(w)); + + emit changed(); + emit widgetUnmanaged(w); + + if (frameNeeded(w)) + w->removeEventFilter(this); +} + +bool FormWindow::isManaged(QWidget *w) const +{ + return m_insertedWidgets.contains(w); +} + +void FormWindow::breakLayout(QWidget *w) +{ + if (w == this) + w = mainContainer(); + // Find the first-order managed child widgets + QWidgetList widgets; + + const QDesignerMetaDataBaseInterface *mdb = core()->metaDataBase(); + for (auto *o : w->children()) { + if (o->isWidgetType()) { + auto *w = static_cast(o); + if (mdb->item(w)) + widgets.push_back(w); + } + } + + BreakLayoutCommand *cmd = new BreakLayoutCommand(this); + cmd->init(widgets, w); + commandHistory()->push(cmd); + clearSelection(false); +} + +void FormWindow::beginCommand(const QString &description) +{ + m_undoStack.beginMacro(description); +} + +void FormWindow::endCommand() +{ + m_undoStack.endMacro(); +} + +void FormWindow::raiseWidgets() +{ + QWidgetList widgets = selectedWidgets(); + simplifySelection(&widgets); + + if (widgets.isEmpty()) + return; + + beginCommand(tr("Raise widgets")); + for (QWidget *widget : std::as_const(widgets)) { + RaiseWidgetCommand *cmd = new RaiseWidgetCommand(this); + cmd->init(widget); + m_undoStack.push(cmd); + } + endCommand(); +} + +void FormWindow::lowerWidgets() +{ + QWidgetList widgets = selectedWidgets(); + simplifySelection(&widgets); + + if (widgets.isEmpty()) + return; + + beginCommand(tr("Lower widgets")); + for (QWidget *widget : std::as_const(widgets)) { + LowerWidgetCommand *cmd = new LowerWidgetCommand(this); + cmd->init(widget); + m_undoStack.push(cmd); + } + endCommand(); +} + +bool FormWindow::handleMouseButtonDblClickEvent(QWidget *w, QWidget *managedWidget, QMouseEvent *e) +{ + if (debugFormWindow) + qDebug() << "handleMouseButtonDblClickEvent:" << w << ',' << managedWidget << "state=" << m_mouseState; + + e->accept(); + + // Might be out of sync due cycling of the parent selection + // In that case, do nothing + if (isWidgetSelected(managedWidget)) + emit activated(managedWidget); + + m_mouseState = MouseDoubleClicked; + return true; +} + + +QMenu *FormWindow::initializePopupMenu(QWidget *managedWidget) +{ + if (!isManaged(managedWidget) || currentTool()) + return nullptr; + + // Make sure the managedWidget is selected and current since + // the SetPropertyCommands must use the right reference + // object obtained from the property editor for the property group + // of a multiselection to be correct. + const bool selected = isWidgetSelected(managedWidget); + bool update = false; + if (selected) { + update = setCurrentWidget(managedWidget); + } else { + clearObjectInspectorSelection(m_core); // We might have a toolbar or non-widget selected in the object inspector. + clearSelection(false); + update = trySelectWidget(managedWidget, true); + raiseChildSelections(managedWidget); // raise selections and select widget + } + + if (update) { + emitSelectionChanged(); + QMetaObject::invokeMethod(core()->formWindowManager(), "slotUpdateActions"); + } + + QWidget *contextMenuWidget = nullptr; + + if (isMainContainer(managedWidget)) { // press on a child widget + contextMenuWidget = mainContainer(); + } else { // press on a child widget + // if widget is laid out, find the first non-laid out super-widget + QWidget *realWidget = managedWidget; // but store the original one + QMainWindow *mw = qobject_cast(mainContainer()); + + if (mw && mw->centralWidget() == realWidget) { + contextMenuWidget = managedWidget; + } else { + contextMenuWidget = realWidget; + } + } + + if (!contextMenuWidget) + return nullptr; + + QMenu *contextMenu = createPopupMenu(contextMenuWidget); + if (!contextMenu) + return nullptr; + + emit contextMenuRequested(contextMenu, contextMenuWidget); + return contextMenu; +} + +bool FormWindow::handleContextMenu(QWidget *, QWidget *managedWidget, QContextMenuEvent *e) +{ + QMenu *contextMenu = initializePopupMenu(managedWidget); + if (!contextMenu) + return false; + const QPoint globalPos = e->globalPos(); + m_contextMenuPosition = mapFromGlobal (globalPos); + contextMenu->exec(globalPos); + delete contextMenu; + e->accept(); + m_contextMenuPosition = QPoint(-1, -1); + return true; +} + +bool FormWindow::setContents(QIODevice *dev, QString *errorMessageIn /* = 0 */) +{ + QDesignerResource r(this); + QScopedPointer ui(r.readUi(dev)); + if (ui.isNull()) { + if (errorMessageIn) + *errorMessageIn = r.errorString(); + return false; + } + + UpdateBlocker ub(this); + clearSelection(); + m_selection->clearSelectionPool(); + m_insertedWidgets.clear(); + m_widgets.clear(); + // The main container is cleared as otherwise + // the names of the newly loaded objects will be unified. + clearMainContainer(); + m_undoStack.clear(); + emit changed(); + + QWidget *w = r.loadUi(ui.data(), formContainer()); + if (w) { + setMainContainer(w); + emit changed(); + } + if (errorMessageIn) + *errorMessageIn = r.errorString(); + return w != nullptr; +} + +bool FormWindow::setContents(const QString &contents) +{ + QString errorMessage; + QByteArray data = contents.toUtf8(); + QBuffer b(&data); + const bool success = b.open(QIODevice::ReadOnly) && setContents(&b, &errorMessage); + if (!success && !errorMessage.isEmpty()) + designerWarning(errorMessage); + return success; +} + +void FormWindow::layoutContainer(QWidget *w, int type) +{ + if (w == this) + w = mainContainer(); + + w = core()->widgetFactory()->containerOfWidget(w); + + // find managed widget children + QWidgetList widgets; + for (auto *o : w->children()) { + if (o->isWidgetType() ) { + auto *widget = static_cast(o); + if (widget->isVisibleTo(this) && isManaged(widget)) + widgets.append(widget); + } + } + + if (widgets.isEmpty()) // QTBUG-50563, observed when using hand-edited forms. + return; + + LayoutCommand *cmd = new LayoutCommand(this); + cmd->init(mainContainer(), widgets, static_cast(type), w); + clearSelection(false); + commandHistory()->push(cmd); +} + +bool FormWindow::hasInsertedChildren(QWidget *widget) const // ### move +{ + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) { + const int index = container->currentIndex(); + if (index < 0) + return false; + widget = container->widget(index); + } + + const QWidgetList l = widgets(widget); + + for (QWidget *child : l) { + if (isManaged(child) && !LayoutInfo::isWidgetLaidout(core(), child) && child->isVisibleTo(const_cast(this))) + return true; + } + + return false; +} + +// "Select Ancestor" sub menu code +void FormWindow::slotSelectWidget(QAction *a) +{ + if (QWidget *w = qvariant_cast(a->data())) + selectSingleWidget(w); +} + +void FormWindow::slotCleanChanged(bool clean) +{ + if (!clean) + emit changed(); +} + +static inline QString objectNameOf(const QWidget *w) +{ + if (const QLayoutWidget *lw = qobject_cast(w)) { + const QLayout *layout = lw->layout(); + const QString rc = layout->objectName(); + if (!rc.isEmpty()) + return rc; + // Fall thru for 4.3 forms which have a name on the widget: Display the class name + return QString::fromUtf8(layout->metaObject()->className()); + } + return w->objectName(); +} + +QAction *FormWindow::createSelectAncestorSubMenu(QWidget *w) +{ + // Find the managed, unselected parents + QWidgetList parents; + QWidget *mc = mainContainer(); + for (QWidget *p = w->parentWidget(); p && p != mc; p = p->parentWidget()) + if (isManaged(p) && !isWidgetSelected(p)) + parents.push_back(p); + if (parents.isEmpty()) + return nullptr; + // Create a submenu listing the managed, unselected parents + QMenu *menu = new QMenu; + QActionGroup *ag = new QActionGroup(menu); + QObject::connect(ag, &QActionGroup::triggered, this, &FormWindow::slotSelectWidget); + for (auto *w : std::as_const(parents)) { + QAction *a = ag->addAction(objectNameOf(w)); + a->setData(QVariant::fromValue(w)); + menu->addAction(a); + } + QAction *ma = new QAction(tr("Select Ancestor"), nullptr); + ma->setMenu(menu); + return ma; +} + +QMenu *FormWindow::createPopupMenu(QWidget *w) +{ + QMenu *popup = createExtensionTaskMenu(this, w, true); + if (!popup) + popup = new QMenu; + // if w doesn't have a QDesignerTaskMenu as a child create one and make it a child. + // insert actions from QDesignerTaskMenu + + QDesignerFormWindowManagerInterface *manager = core()->formWindowManager(); + const bool isFormWindow = qobject_cast(w); + + // Check for special containers and obtain the page menu from them to add layout actions. + if (!isFormWindow) { + if (QStackedWidget *stackedWidget = qobject_cast(w)) { + QStackedWidgetEventFilter::addStackedWidgetContextMenuActions(stackedWidget, popup); + } else if (QTabWidget *tabWidget = qobject_cast(w)) { + QTabWidgetEventFilter::addTabWidgetContextMenuActions(tabWidget, popup); + } else if (QToolBox *toolBox = qobject_cast(w)) { + QToolBoxHelper::addToolBoxContextMenuActions(toolBox, popup); + } + + if (manager->action(QDesignerFormWindowManagerInterface::LowerAction)->isEnabled()) { + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::LowerAction)); + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::RaiseAction)); + popup->addSeparator(); + } +#if QT_CONFIG(clipboard) + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::CutAction)); + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::CopyAction)); +#endif + } + +#if QT_CONFIG(clipboard) + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::PasteAction)); +#endif + + if (QAction *selectAncestorAction = createSelectAncestorSubMenu(w)) + popup->addAction(selectAncestorAction); + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::SelectAllAction)); + + if (!isFormWindow) { + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::DeleteAction)); + } + + popup->addSeparator(); + QMenu *layoutMenu = popup->addMenu(tr("Lay out")); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::AdjustSizeAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::HorizontalLayoutAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::VerticalLayoutAction)); + if (!isFormWindow) { + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SplitHorizontalAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SplitVerticalAction)); + } + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::GridLayoutAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::FormLayoutAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::BreakLayoutAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SimplifyLayoutAction)); + + return popup; +} + +void FormWindow::resizeEvent(QResizeEvent *e) +{ + m_geometryChangedTimer->start(10); + + QWidget::resizeEvent(e); +} + +/*! + Maps \a pos in \a w's coordinates to the form's coordinate system. + + This is the equivalent to mapFromGlobal(w->mapToGlobal(pos)) but + avoids the two roundtrips to the X-Server on Unix/X11. + */ +QPoint FormWindow::mapToForm(const QWidget *w, QPoint pos) const +{ + QPoint p = pos; + const QWidget* i = w; + while (i && !i->isWindow() && !isMainContainer(i)) { + p = i->mapToParent(p); + i = i->parentWidget(); + } + + return mapFromGlobal(w->mapToGlobal(pos)); +} + +bool FormWindow::canBeBuddy(QWidget *w) const // ### rename me. +{ + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), w)) { + const int index = sheet->indexOf(u"focusPolicy"_s); + if (index != -1) { + bool ok = false; + const Qt::FocusPolicy q = static_cast(Utils::valueOf(sheet->property(index), &ok)); + return ok && q != Qt::NoFocus; + } + } + + return false; +} + +QWidget *FormWindow::findContainer(QWidget *w, bool excludeLayout) const +{ + if (!isChildOf(w, this) + || const_cast(w) == this) + return nullptr; + + QDesignerWidgetFactoryInterface *widgetFactory = core()->widgetFactory(); + QDesignerWidgetDataBaseInterface *widgetDataBase = core()->widgetDataBase(); + QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase(); + + QWidget *container = widgetFactory->containerOfWidget(mainContainer()); // default parent for new widget is the formwindow + if (!isMainContainer(w)) { // press was not on formwindow, check if we can find another parent + while (w) { + if (qobject_cast(w) || !metaDataBase->item(w)) { + w = w->parentWidget(); + continue; + } + + const bool isContainer = widgetDataBase->isContainer(w, true) || w == mainContainer(); + + if (!isContainer || (excludeLayout && qobject_cast(w))) { // ### skip QSplitter + w = w->parentWidget(); + } else { + container = w; + break; + } + } + } + + return container; +} + +void FormWindow::simplifySelection(QWidgetList *sel) const +{ + if (sel->size() < 2) + return; + // Figure out which widgets should be removed from selection. + // We want to remove those whose parent widget is also in the + // selection (because the child widgets are contained by + // their parent, they shouldn't be in the selection -- + // they are "implicitly" selected). + QWidget *mainC = mainContainer(); // Quick check for main container first + if (sel->contains(mainC)) { + sel->clear(); + sel->push_back(mainC); + return; + } + QWidgetList toBeRemoved; + toBeRemoved.reserve(sel->size()); + for (auto *child : std::as_const(*sel)) { + for (QWidget *w = child; true ; ) { // Is any of the parents also selected? + QWidget *parent = w->parentWidget(); + if (!parent || parent == mainC) + break; + if (sel->contains(parent)) { + toBeRemoved.append(child); + break; + } + w = parent; + } + } + // Now we can actually remove the widgets that were marked + // for removal in the previous pass. + for (auto *r : std::as_const(toBeRemoved)) + sel->removeAll(r); +} + +FormWindow *FormWindow::findFormWindow(QWidget *w) +{ + return qobject_cast(QDesignerFormWindowInterface::findFormWindow(w)); +} + +bool FormWindow::isDirty() const +{ + return !m_undoStack.isClean(); +} + +void FormWindow::setDirty(bool dirty) +{ + if (dirty) + m_undoStack.resetClean(); + else + m_undoStack.setClean(); +} + +QWidget *FormWindow::containerAt(const QPoint &pos) +{ + QWidget *widget = widgetAt(pos); + return findContainer(widget, true); +} + +static QWidget *childAt_SkipDropLine(QWidget *w, QPoint pos) +{ + const QObjectList &child_list = w->children(); + for (auto i = child_list.size() - 1; i >= 0; --i) { + QObject *child_obj = child_list.at(i); + if (qobject_cast(child_obj) != nullptr) + continue; + QWidget *child = qobject_cast(child_obj); + if (!child || child->isWindow() || !child->isVisible() || + !child->geometry().contains(pos) || child->testAttribute(Qt::WA_TransparentForMouseEvents)) + continue; + const QPoint childPos = child->mapFromParent(pos); + if (QWidget *res = childAt_SkipDropLine(child, childPos)) + return res; + if (child->testAttribute(Qt::WA_MouseNoMask) || child->mask().contains(pos) + || child->mask().isEmpty()) + return child; + } + + return nullptr; +} + +QWidget *FormWindow::widgetAt(const QPoint &pos) +{ + QWidget *w = childAt(pos); + if (qobject_cast(w) != 0) + w = childAt_SkipDropLine(this, pos); + return (w == nullptr || w == formContainer()) ? this : w; +} + +void FormWindow::highlightWidget(QWidget *widget, const QPoint &pos, HighlightMode mode) +{ + Q_ASSERT(widget); + + if (QMainWindow *mainWindow = qobject_cast (widget)) { + widget = mainWindow->centralWidget(); + } + + QWidget *container = findContainer(widget, false); + + if (container == nullptr || core()->metaDataBase()->item(container) == nullptr) + return; + + if (QDesignerActionProviderExtension *g = qt_extension(core()->extensionManager(), container)) { + if (mode == Restore) { + g->adjustIndicator(QPoint()); + } else { + const QPoint pt = widget->mapTo(container, pos); + g->adjustIndicator(pt); + } + } else if (QDesignerLayoutDecorationExtension *g = qt_extension(core()->extensionManager(), container)) { + if (mode == Restore) { + g->adjustIndicator(QPoint(), -1); + } else { + const QPoint pt = widget->mapTo(container, pos); + const int index = g->findItemAt(pt); + g->adjustIndicator(pt, index); + } + } + + QMainWindow *mw = qobject_cast (container); + if (container == mainContainer() || (mw && mw->centralWidget() && mw->centralWidget() == container)) + return; + + if (mode == Restore) { + const auto pit = m_palettesBeforeHighlight.find(container); + if (pit != m_palettesBeforeHighlight.end()) { + container->setPalette(pit.value().first); + container->setAutoFillBackground(pit.value().second); + m_palettesBeforeHighlight.erase(pit); + } + } else { + QPalette p = container->palette(); + if (!m_palettesBeforeHighlight.contains(container)) { + PaletteAndFill paletteAndFill; + if (container->testAttribute(Qt::WA_SetPalette)) + paletteAndFill.first = p; + paletteAndFill.second = container->autoFillBackground(); + m_palettesBeforeHighlight.insert(container, paletteAndFill); + } + + p.setColor(backgroundRole(), p.midlight().color()); + container->setPalette(p); + container->setAutoFillBackground(true); + } +} + +QWidgetList FormWindow::widgets(QWidget *widget) const +{ + if (widget->children().isEmpty()) + return QWidgetList(); + QWidgetList rc; + for (QObject *o : widget->children()) { + if (o->isWidgetType()) { + QWidget *w = qobject_cast(o); + if (isManaged(w)) + rc.push_back(w); + } + } + return rc; +} + +int FormWindow::toolCount() const +{ + return m_widgetStack->count(); +} + +QDesignerFormWindowToolInterface *FormWindow::tool(int index) const +{ + return m_widgetStack->tool(index); +} + +void FormWindow::registerTool(QDesignerFormWindowToolInterface *tool) +{ + Q_ASSERT(tool != nullptr); + + m_widgetStack->addTool(tool); + + if (m_mainContainer) + m_mainContainer->update(); +} + +void FormWindow::setCurrentTool(int index) +{ + m_widgetStack->setCurrentTool(index); +} + +int FormWindow::currentTool() const +{ + return m_widgetStack->currentIndex(); +} + +bool FormWindow::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + if (m_widgetStack == nullptr) + return false; + + QDesignerFormWindowToolInterface *tool = m_widgetStack->currentTool(); + if (tool == nullptr) + return false; + + return tool->handleEvent(widget, managedWidget, event); +} + +void FormWindow::initializeCoreTools() +{ + m_widgetEditor = new WidgetEditorTool(this); + registerTool(m_widgetEditor); +} + +void FormWindow::checkSelection() +{ + m_checkSelectionTimer->start(0); +} + +void FormWindow::checkSelectionNow() +{ + m_checkSelectionTimer->stop(); + + const QWidgetList &sel = selectedWidgets(); + for (QWidget *widget : sel) { + updateSelection(widget); + + if (LayoutInfo::layoutType(core(), widget) != LayoutInfo::NoLayout) + updateChildSelections(widget); + } +} + +QString FormWindow::author() const +{ + return m_author; +} + +QString FormWindow::comment() const +{ + return m_comment; +} + +void FormWindow::setAuthor(const QString &author) +{ + m_author = author; +} + +void FormWindow::setComment(const QString &comment) +{ + m_comment = comment; +} + +void FormWindow::editWidgets() +{ + m_widgetEditor->action()->trigger(); +} + +QStringList FormWindow::resourceFiles() const +{ + return m_resourceFiles; +} + +void FormWindow::addResourceFile(const QString &path) +{ + if (!m_resourceFiles.contains(path)) { + m_resourceFiles.append(path); + setDirty(true); + emit resourceFilesChanged(); + } +} + +void FormWindow::removeResourceFile(const QString &path) +{ + if (m_resourceFiles.removeAll(path) > 0) { + setDirty(true); + emit resourceFilesChanged(); + } +} + +bool FormWindow::blockSelectionChanged(bool b) +{ + const bool blocked = m_blockSelectionChanged; + m_blockSelectionChanged = b; + return blocked; +} + +void FormWindow::editContents() +{ + const QWidgetList sel = selectedWidgets(); + if (sel.size() == 1) { + QWidget *widget = sel.first(); + + if (QAction *a = preferredEditAction(core(), widget)) + a->trigger(); + } +} + +void FormWindow::dragWidgetWithinForm(QWidget *widget, QRect targetGeometry, QWidget *targetContainer) +{ + const bool fromLayout = canDragWidgetInLayout(core(), widget); + const QDesignerLayoutDecorationExtension *targetDeco = qt_extension(core()->extensionManager(), targetContainer); + const bool toLayout = targetDeco != nullptr; + + if (fromLayout) { + // Drag from Layout: We need to delete the widget properly to store the layout state + // Do not simplify the layout when dragging onto a layout + // as this might invalidate the insertion position if it is the same layout + DeleteWidgetCommand *cmd = new DeleteWidgetCommand(this); + unsigned deleteFlags = DeleteWidgetCommand::DoNotUnmanage; + if (toLayout) + deleteFlags |= DeleteWidgetCommand::DoNotSimplifyLayout; + cmd->init(widget, deleteFlags); + commandHistory()->push(cmd); + } + + if (toLayout) { + // Drag from form to layout: just insert. Do not manage + insertWidget(widget, targetGeometry, targetContainer, true); + } else { + // into container without layout + if (targetContainer != widget->parent()) { // different parent + ReparentWidgetCommand *cmd = new ReparentWidgetCommand(this); + cmd->init(widget, targetContainer ); + commandHistory()->push(cmd); + } + resizeWidget(widget, targetGeometry); + selectWidget(widget, true); + widget->show(); + } +} + +static Qt::DockWidgetArea detectDropArea(QMainWindow *mainWindow, QRect area, QPoint drop) +{ + QPoint offset = area.topLeft(); + QRect rect = area; + rect.moveTopLeft(QPoint(0, 0)); + QPoint point = drop - offset; + const int x = point.x(); + const int y = point.y(); + const int w = rect.width(); + const int h = rect.height(); + + if (rect.contains(point)) { + bool topRight = false; + bool topLeft = false; + if (w * y < h * x) // top and right, oterwise bottom and left + topRight = true; + if (w * y < h * (w - x)) // top and left, otherwise bottom and right + topLeft = true; + + if (topRight && topLeft) + return Qt::TopDockWidgetArea; + if (topRight && !topLeft) + return Qt::RightDockWidgetArea; + if (!topRight && topLeft) + return Qt::LeftDockWidgetArea; + return Qt::BottomDockWidgetArea; + } + + if (x < 0) { + if (y < 0) + return mainWindow->corner(Qt::TopLeftCorner); + return y > h ? mainWindow->corner(Qt::BottomLeftCorner) : Qt::LeftDockWidgetArea; + } + if (x > w) { + if (y < 0) + return mainWindow->corner(Qt::TopRightCorner); + return y > h ? mainWindow->corner(Qt::BottomRightCorner) : Qt::RightDockWidgetArea; + } + return y < 0 ? Qt::TopDockWidgetArea :Qt::LeftDockWidgetArea; +} + +bool FormWindow::dropDockWidget(QDesignerDnDItemInterface *item, QPoint global_mouse_pos) +{ + DomUI *dom_ui = item->domUi(); + + QMainWindow *mw = qobject_cast(mainContainer()); + if (!mw) + return false; + + QDesignerResource resource(this); + const FormBuilderClipboard clipboard = resource.paste(dom_ui, mw); + if (clipboard.m_widgets.size() != 1) // multiple-paste from DomUI not supported yet + return false; + + QWidget *centralWidget = mw->centralWidget(); + QPoint localPos = centralWidget->mapFromGlobal(global_mouse_pos); + const QRect centralWidgetAreaRect = centralWidget->rect(); + Qt::DockWidgetArea area = detectDropArea(mw, centralWidgetAreaRect, localPos); + + beginCommand(tr("Drop widget")); + + clearSelection(false); + highlightWidget(mw, QPoint(0, 0), FormWindow::Restore); + + QWidget *widget = clipboard.m_widgets.first(); + + insertWidget(widget, QRect(0, 0, 1, 1), mw); + + selectWidget(widget, true); + mw->setFocus(Qt::MouseFocusReason); // in case focus was in e.g. object inspector + + core()->formWindowManager()->setActiveFormWindow(this); + mainContainer()->activateWindow(); + + QDesignerPropertySheetExtension *propertySheet = qobject_cast(m_core->extensionManager()->extension(widget, Q_TYPEID(QDesignerPropertySheetExtension))); + if (propertySheet) { + const QString dockWidgetAreaName = u"dockWidgetArea"_s; + PropertySheetEnumValue e = qvariant_cast(propertySheet->property(propertySheet->indexOf(dockWidgetAreaName))); + e.value = area; + QVariant v; + v.setValue(e); + SetPropertyCommand *cmd = new SetPropertyCommand(this); + cmd->init(widget, dockWidgetAreaName, v); + m_undoStack.push(cmd); + } + + endCommand(); + return true; +} + +bool FormWindow::dropWidgets(const QList &item_list, QWidget *target, + const QPoint &global_mouse_pos) +{ + + QWidget *parent = target; + if (parent == nullptr) + parent = mainContainer(); + // You can only drop stuff onto the central widget of a QMainWindow + // ### generalize to use container extension + if (QMainWindow *main_win = qobject_cast(target)) { + if (!main_win->centralWidget()) { + designerWarning(tr("A QMainWindow-based form does not contain a central widget.")); + return false; + } + const QPoint main_win_pos = main_win->mapFromGlobal(global_mouse_pos); + const QRect central_wgt_geo = main_win->centralWidget()->geometry(); + if (!central_wgt_geo.contains(main_win_pos)) + return false; + } + + QWidget *container = findContainer(parent, false); + if (container == nullptr) + return false; + + beginCommand(tr("Drop widget")); + + clearSelection(false); + highlightWidget(target, target->mapFromGlobal(global_mouse_pos), FormWindow::Restore); + + QPoint offset; + QDesignerDnDItemInterface *current = nullptr; + QDesignerFormWindowCursorInterface *c = cursor(); + for (QDesignerDnDItemInterface *item : std::as_const(item_list)) { + QWidget *w = item->widget(); + if (!current) + current = item; + if (c->current() == w) { + current = item; + break; + } + } + if (current) { + QRect geom = current->decoration()->geometry(); + QPoint topLeft = container->mapFromGlobal(geom.topLeft()); + offset = designerGrid().snapPoint(topLeft) - topLeft; + } + + for (QDesignerDnDItemInterface *item : std::as_const(item_list)) { + DomUI *dom_ui = item->domUi(); + QRect geometry = item->decoration()->geometry(); + Q_ASSERT(dom_ui != nullptr); + + geometry.moveTopLeft(container->mapFromGlobal(geometry.topLeft()) + offset); + if (item->type() == QDesignerDnDItemInterface::CopyDrop) { // from widget box or CTRL + mouse move + QWidget *widget = createWidget(dom_ui, geometry, parent); + if (!widget) { + endCommand(); + return false; + } + selectWidget(widget, true); + mainContainer()->setFocus(Qt::MouseFocusReason); // in case focus was in e.g. object inspector + } else { // same form move + QWidget *widget = item->widget(); + Q_ASSERT(widget != nullptr); + QDesignerFormWindowInterface *dest = findFormWindow(widget); + if (dest == this) { + dragWidgetWithinForm(widget, geometry, container); + } else { // from other form + FormWindow *source = qobject_cast(item->source()); + Q_ASSERT(source != nullptr); + + source->deleteWidgetList(QWidgetList() << widget); + QWidget *new_widget = createWidget(dom_ui, geometry, parent); + + selectWidget(new_widget, true); + } + } + } + + core()->formWindowManager()->setActiveFormWindow(this); + mainContainer()->activateWindow(); + endCommand(); + return true; +} + +QDir FormWindow::absoluteDir() const +{ + if (fileName().isEmpty()) + return QDir::current(); + + return QFileInfo(fileName()).absoluteDir(); +} + +void FormWindow::layoutDefault(int *margin, int *spacing) +{ + *margin = m_defaultMargin; + *spacing = m_defaultSpacing; +} + +void FormWindow::setLayoutDefault(int margin, int spacing) +{ + m_defaultMargin = margin; + m_defaultSpacing = spacing; +} + +void FormWindow::layoutFunction(QString *margin, QString *spacing) +{ + *margin = m_marginFunction; + *spacing = m_spacingFunction; +} + +void FormWindow::setLayoutFunction(const QString &margin, const QString &spacing) +{ + m_marginFunction = margin; + m_spacingFunction = spacing; +} + +QString FormWindow::pixmapFunction() const +{ + return m_pixmapFunction; +} + +void FormWindow::setPixmapFunction(const QString &pixmapFunction) +{ + m_pixmapFunction = pixmapFunction; +} + +QStringList FormWindow::includeHints() const +{ + return m_includeHints; +} + +void FormWindow::setIncludeHints(const QStringList &includeHints) +{ + m_includeHints = includeHints; +} + +QString FormWindow::exportMacro() const +{ + return m_exportMacro; +} + +void FormWindow::setExportMacro(const QString &exportMacro) +{ + m_exportMacro = exportMacro; +} + +QEditorFormBuilder *FormWindow::createFormBuilder() +{ + return new QDesignerResource(this); +} + +QWidget *FormWindow::formContainer() const +{ + return m_widgetStack->formContainer(); +} + +QUndoStack *FormWindow::commandHistory() const +{ + return &const_cast(m_undoStack); +} + +} // namespace + +QT_END_NAMESPACE + diff --git a/src/designer/src/components/formeditor/formwindow.h b/src/designer/src/components/formeditor/formwindow.h new file mode 100644 index 0000000..149f6a1 --- /dev/null +++ b/src/designer/src/components/formeditor/formwindow.h @@ -0,0 +1,352 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOW_H +#define FORMWINDOW_H + +#include "formeditor_global.h" +#include + +// Qt +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerDnDItemInterface; +class QDesignerTaskMenuExtension; +class DomConnections; +class DomUI; + +class QWidget; +class QAction; +class QLabel; +class QTimer; +class QAction; +class QMenu; +class QRubberBand; + +namespace qdesigner_internal { + +class FormEditor; +class FormWindowCursor; +class WidgetEditorTool; +class FormWindowWidgetStack; +class FormWindowManager; +class FormWindowDnDItem; +class SetPropertyCommand; + +class QT_FORMEDITOR_EXPORT FormWindow: public FormWindowBase +{ + Q_OBJECT + +public: + enum HandleOperation + { + NoHandleOperation, + ResizeHandleOperation, + ChangeLayoutSpanHandleOperation + }; + + explicit FormWindow(FormEditor *core, QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + ~FormWindow() override; + + QDesignerFormEditorInterface *core() const override; + + QDesignerFormWindowCursorInterface *cursor() const override; + + // Overwritten: FormWindowBase + QWidget *formContainer() const override; + + int toolCount() const override; + int currentTool() const override; + void setCurrentTool(int index) override; + QDesignerFormWindowToolInterface *tool(int index) const override; + void registerTool(QDesignerFormWindowToolInterface *tool) override; + + QString author() const override; + void setAuthor(const QString &author) override; + + QString comment() const override; + void setComment(const QString &comment) override; + + void layoutDefault(int *margin, int *spacing) override; + void setLayoutDefault(int margin, int spacing) override; + + void layoutFunction(QString *margin, QString *spacing) override; + void setLayoutFunction(const QString &margin, const QString &spacing) override; + + QString pixmapFunction() const override; + void setPixmapFunction(const QString &pixmapFunction) override; + + QString exportMacro() const override; + void setExportMacro(const QString &exportMacro) override; + + QStringList includeHints() const override; + void setIncludeHints(const QStringList &includeHints) override; + + QString fileName() const override; + void setFileName(const QString &fileName) override; + + QString contents() const override; + bool setContents(QIODevice *dev, QString *errorMessage = nullptr) override; + bool setContents(const QString &) override; + + QDir absoluteDir() const override; + + void simplifySelection(QWidgetList *sel) const override; + + void ensureUniqueObjectName(QObject *object) override; + + QWidget *mainContainer() const override; + void setMainContainer(QWidget *mainContainer) override; + bool isMainContainer(const QWidget *w) const; + + QWidget *currentWidget() const; + + bool hasInsertedChildren(QWidget *w) const; + + QWidgetList selectedWidgets() const; + void clearSelection(bool changePropertyDisplay = true) override; + bool isWidgetSelected(QWidget *w) const; + void selectWidget(QWidget *w, bool select = true) override; + + void selectWidgets(); + void repaintSelection(); + void updateSelection(QWidget *w); + void updateChildSelections(QWidget *w); + void raiseChildSelections(QWidget *w); + void raiseSelection(QWidget *w); + + inline const QWidgetList& widgets() const { return m_widgets; } + inline int widgetCount() const { return m_widgets.size(); } + inline QWidget *widgetAt(int index) const { return m_widgets.at(index); } + + QWidgetList widgets(QWidget *widget) const; + + QWidget *createWidget(DomUI *ui, QRect rect, QWidget *target); + + bool isManaged(QWidget *w) const override; + + void manageWidget(QWidget *w) override; + void unmanageWidget(QWidget *w) override; + + QUndoStack *commandHistory() const override; + void beginCommand(const QString &description) override; + void endCommand() override; + + bool blockSelectionChanged(bool blocked) override; + void emitSelectionChanged() override; + + bool unify(QObject *w, QString &s, bool changeIt); + + bool isDirty() const override; + void setDirty(bool dirty) override; + + static FormWindow *findFormWindow(QWidget *w); + + virtual QWidget *containerAt(const QPoint &pos); + QWidget *widgetAt(const QPoint &pos) override; + void highlightWidget(QWidget *w, const QPoint &pos, HighlightMode mode = Highlight) override; + + void updateOrderIndicators(); + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event); + + QStringList resourceFiles() const override; + void addResourceFile(const QString &path) override; + void removeResourceFile(const QString &path) override; + + void resizeWidget(QWidget *widget, QRect geometry); + + bool dropDockWidget(QDesignerDnDItemInterface *item, QPoint global_mouse_pos); + bool dropWidgets(const QList &item_list, QWidget *target, + const QPoint &global_mouse_pos) override; + + QWidget *findContainer(QWidget *w, bool excludeLayout) const override; + // for WidgetSelection only. + QWidget *designerWidget(QWidget *w) const; + + // Initialize and return a popup menu for a managed widget + QMenu *initializePopupMenu(QWidget *managedWidget) override; + +#if QT_CONFIG(clipboard) + void paste(PasteMode pasteMode) override; +#endif + QEditorFormBuilder *createFormBuilder() override; + + bool eventFilter(QObject *watched, QEvent *event) override; + + HandleOperation handleOperation() const { return m_handleOperation; } + void setHandleOperation(HandleOperation o) { m_handleOperation = o; } + +signals: + void contextMenuRequested(QMenu *menu, QWidget *widget); + +public slots: + void deleteWidgets(); + void raiseWidgets(); + void lowerWidgets(); +#if QT_CONFIG(clipboard) + void copy(); + void cut(); + void paste(); +#endif + void selectAll(); + + void createLayout(int type, QWidget *container = nullptr); + void morphLayout(QWidget *container, int newType); + void breakLayout(QWidget *w); + + void editContents(); + +protected: + virtual QMenu *createPopupMenu(QWidget *w); + void resizeEvent(QResizeEvent *e) override; + + void insertWidget(QWidget *w, QRect rect, QWidget *target, bool already_in_form = false); + +private slots: + void selectionChangedTimerDone(); + void checkSelection(); + void checkSelectionNow(); + void slotSelectWidget(QAction *); + void slotCleanChanged(bool); + +private: + enum MouseState { + NoMouseState, + // Double click received + MouseDoubleClicked, + // Drawing selection rubber band rectangle + MouseDrawRubber, + // Started a move operation + MouseMoveDrag, + // Click on a widget whose parent is selected. Defer selection to release + MouseDeferredSelection + }; + MouseState m_mouseState; + QPointer m_lastClickedWidget; + + void init(); + void initializeCoreTools(); + + int getValue(QRect rect, int key, bool size) const; + int calcValue(int val, bool forward, bool snap, int snapOffset) const; + void handleClickSelection(QWidget *managedWidget, unsigned mouseFlags); + + bool frameNeeded(QWidget *w) const; + + enum RectType { Insert, Rubber }; + + void startRectDraw(QPoint global, QWidget *, RectType t); + void continueRectDraw(QPoint global, QWidget *, RectType t); + void endRectDraw(); + + QWidget *containerAt(QPoint pos, QWidget *notParentOf); + + void checkPreviewGeometry(QRect &r); + + bool handleContextMenu(QWidget *widget, QWidget *managedWidget, QContextMenuEvent *e); + bool handleMouseButtonDblClickEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMousePressEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMouseMoveEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMouseReleaseEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleKeyPressEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e); + bool handleKeyReleaseEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e); + + bool isCentralWidget(QWidget *w) const; + + bool setCurrentWidget(QWidget *currentWidget); + bool trySelectWidget(QWidget *w, bool select); + + void dragWidgetWithinForm(QWidget *widget, QRect targetGeometry, QWidget *targetContainer); + + void setCursorToAll(const QCursor &c, QWidget *start); + + QPoint mapToForm(const QWidget *w, QPoint pos) const; + bool canBeBuddy(QWidget *w) const; + + QWidget *findTargetContainer(QWidget *widget) const; + + void clearMainContainer(); + + static int widgetDepth(const QWidget *w); + static bool isChildOf(const QWidget *c, const QWidget *p); + + void editWidgets() override; + + void updateWidgets(); + + void handleArrowKeyEvent(int key, Qt::KeyboardModifiers modifiers); + + void layoutSelection(int type); + void layoutContainer(QWidget *w, int type); + +private: + QWidget *innerContainer(QWidget *outerContainer) const; + QWidget *containerForPaste() const; + QAction *createSelectAncestorSubMenu(QWidget *w); + void selectSingleWidget(QWidget *w); + + FormEditor *m_core; + FormWindowCursor *m_cursor; + QWidget *m_mainContainer = nullptr; + QWidget *m_currentWidget = nullptr; + + bool m_blockSelectionChanged = false; + + QPoint m_rectAnchor; + QRect m_currRect; + + QWidgetList m_widgets; + QSet m_insertedWidgets; + + class Selection; + Selection *m_selection; + + QPoint m_startPos; + + QUndoStack m_undoStack; + + QString m_fileName; + + using PaletteAndFill = std::pair; + QHash m_palettesBeforeHighlight; + + QRubberBand *m_rubberBand = nullptr; + + QTimer *m_selectionChangedTimer = nullptr; + QTimer *m_checkSelectionTimer = nullptr; + QTimer *m_geometryChangedTimer = nullptr; + + FormWindowWidgetStack *m_widgetStack; + WidgetEditorTool *m_widgetEditor = nullptr; + + QStringList m_resourceFiles; + + QString m_comment; + QString m_author; + QString m_pixmapFunction; + int m_defaultMargin, m_defaultSpacing; + QString m_marginFunction, m_spacingFunction; + QString m_exportMacro; + QStringList m_includeHints; + + QPoint m_contextMenuPosition; + HandleOperation m_handleOperation = NoHandleOperation; + +private: + friend class WidgetEditorTool; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOW_H diff --git a/src/designer/src/components/formeditor/formwindow_dnditem.cpp b/src/designer/src/components/formeditor/formwindow_dnditem.cpp new file mode 100644 index 0000000..2b7a7a0 --- /dev/null +++ b/src/designer/src/components/formeditor/formwindow_dnditem.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindow_dnditem.h" +#include "formwindow.h" + +#include +#include +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +static QWidget *decorationFromWidget(QWidget *w) +{ + QLabel *label = new QLabel(nullptr, Qt::ToolTip); + QPixmap pm = w->grab(QRect(0, 0, -1, -1)); + label->setPixmap(pm); + label->resize((QSizeF(pm.size()) / pm.devicePixelRatio()).toSize()); + + return label; +} + +static DomUI *widgetToDom(QWidget *widget, FormWindow *form) +{ + QDesignerResource builder(form); + builder.setSaveRelative(false); + return builder.copy(FormBuilderClipboard(widget)); +} + +FormWindowDnDItem::FormWindowDnDItem(QDesignerDnDItemInterface::DropType type, FormWindow *form, + QWidget *widget, QPoint global_mouse_pos) + : QDesignerDnDItem(type, form) +{ + QWidget *decoration = decorationFromWidget(widget); + QPoint pos = widget->mapToGlobal(QPoint(0, 0)); + decoration->move(pos); + + init(nullptr, widget, decoration, global_mouse_pos); +} + +DomUI *FormWindowDnDItem::domUi() const +{ + DomUI *result = QDesignerDnDItem::domUi(); + if (result != nullptr) + return result; + FormWindow *form = qobject_cast(source()); + if (widget() == nullptr || form == nullptr) + return nullptr; + + QtResourceModel *resourceModel = form->core()->resourceModel(); + QtResourceSet *currentResourceSet = resourceModel->currentResourceSet(); + /* Short: + * We need to activate the original resourceSet associated with a form + * to properly generate the dom resource includes. + * Long: + * widgetToDom() calls copy() on QDesignerResource. It generates the + * Dom structure. In order to create DomResources properly we need to + * have the associated ResourceSet active (QDesignerResource::saveResources() + * queries the resource model for a qrc path for the given resource file: + * qrcFile = m_core->resourceModel()->qrcPath(ri->text()); + * This works only when the resource file comes from the active + * resourceSet */ + resourceModel->setCurrentResourceSet(form->resourceSet()); + + result = widgetToDom(widget(), form); + const_cast(this)->setDomUi(result); + resourceModel->setCurrentResourceSet(currentResourceSet); + return result; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/formwindow_dnditem.h b/src/designer/src/components/formeditor/formwindow_dnditem.h new file mode 100644 index 0000000..dab5d5f --- /dev/null +++ b/src/designer/src/components/formeditor/formwindow_dnditem.h @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOW_DNDITEM_H +#define FORMWINDOW_DNDITEM_H + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class FormWindow; + +class FormWindowDnDItem : public QDesignerDnDItem +{ +public: + FormWindowDnDItem(QDesignerDnDItemInterface::DropType type, FormWindow *form, + QWidget *widget, QPoint global_mouse_pos); + DomUI *domUi() const override; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOW_DNDITEM_H diff --git a/src/designer/src/components/formeditor/formwindow_widgetstack.cpp b/src/designer/src/components/formeditor/formwindow_widgetstack.cpp new file mode 100644 index 0000000..e42345b --- /dev/null +++ b/src/designer/src/components/formeditor/formwindow_widgetstack.cpp @@ -0,0 +1,184 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindow_widgetstack.h" +#include + +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +FormWindowWidgetStack::FormWindowWidgetStack(QObject *parent) : + QObject(parent), + m_formContainer(new QWidget), + m_formContainerLayout(new QStackedLayout), + m_layout(new QStackedLayout) +{ + m_layout->setContentsMargins(QMargins()); + m_layout->setSpacing(0); + m_layout->setStackingMode(QStackedLayout::StackAll); + + // We choose a QStackedLayout as immediate layout for + // the form windows as it ignores the sizePolicy of + // its child (for example, Fixed would cause undesired side effects). + m_formContainerLayout->setContentsMargins(QMargins()); + m_formContainer->setObjectName(u"formContainer"_s); + m_formContainer->setLayout(m_formContainerLayout); + m_formContainerLayout->setStackingMode(QStackedLayout::StackAll); + // System settings might have different background colors, autofill them + // (affects for example mainwindow status bars) + m_formContainer->setAutoFillBackground(true); +} + +FormWindowWidgetStack::~FormWindowWidgetStack() = default; + +int FormWindowWidgetStack::count() const +{ + return m_tools.size(); +} + +QDesignerFormWindowToolInterface *FormWindowWidgetStack::currentTool() const +{ + return tool(currentIndex()); +} + +void FormWindowWidgetStack::setCurrentTool(int index) +{ + const int cnt = count(); + if (index < 0 || index >= cnt) { + qDebug("FormWindowWidgetStack::setCurrentTool(): invalid index: %d", index); + return; + } + + const int cur = currentIndex(); + if (index == cur) + return; + + if (cur != -1) + m_tools.at(cur)->deactivated(); + + + m_layout->setCurrentIndex(index); + // Show the widget editor and the current tool + for (int i = 0; i < cnt; i++) + m_tools.at(i)->editor()->setVisible(i == 0 || i == index); + + QDesignerFormWindowToolInterface *tool = m_tools.at(index); + tool->activated(); + + emit currentToolChanged(index); +} + +void FormWindowWidgetStack::setSenderAsCurrentTool() +{ + QDesignerFormWindowToolInterface *tool = nullptr; + QAction *action = qobject_cast(sender()); + if (action == nullptr) { + qDebug("FormWindowWidgetStack::setSenderAsCurrentTool(): sender is not a QAction"); + return; + } + + for (QDesignerFormWindowToolInterface *t : std::as_const(m_tools)) { + if (action == t->action()) { + tool = t; + break; + } + } + + if (tool == nullptr) { + qDebug("FormWindowWidgetStack::setSenderAsCurrentTool(): unknown tool"); + return; + } + + setCurrentTool(tool); +} + +int FormWindowWidgetStack::indexOf(QDesignerFormWindowToolInterface *tool) const +{ + return m_tools.indexOf(tool); +} + +void FormWindowWidgetStack::setCurrentTool(QDesignerFormWindowToolInterface *tool) +{ + int index = indexOf(tool); + if (index == -1) { + qDebug("FormWindowWidgetStack::setCurrentTool(): unknown tool"); + return; + } + + setCurrentTool(index); +} + +void FormWindowWidgetStack::setMainContainer(QWidget *w) +{ + // This code is triggered once by the formwindow and + // by integrations doing "revert to saved". Anything changing? + const int previousCount = m_formContainerLayout->count(); + QWidget *previousMainContainer = previousCount + ? m_formContainerLayout->itemAt(0)->widget() : nullptr; + if (previousMainContainer == w) + return; + // Swap + if (previousCount) + delete m_formContainerLayout->takeAt(0); + if (w) + m_formContainerLayout->addWidget(w); +} + +void FormWindowWidgetStack::addTool(QDesignerFormWindowToolInterface *tool) +{ + if (QWidget *w = tool->editor()) { + w->setVisible(m_layout->count() == 0); // Initially only form editor is visible + m_layout->addWidget(w); + } else { + // The form editor might not have a tool initially, use dummy. Assert on anything else + Q_ASSERT(m_tools.isEmpty()); + m_layout->addWidget(m_formContainer); + } + + m_tools.append(tool); + + connect(tool->action(), &QAction::triggered, + this, &FormWindowWidgetStack::setSenderAsCurrentTool); +} + +QDesignerFormWindowToolInterface *FormWindowWidgetStack::tool(int index) const +{ + if (index < 0 || index >= count()) + return nullptr; + + return m_tools.at(index); +} + +int FormWindowWidgetStack::currentIndex() const +{ + return m_layout->currentIndex(); +} + +QWidget *FormWindowWidgetStack::defaultEditor() const +{ + if (m_tools.isEmpty()) + return nullptr; + + return m_tools.at(0)->editor(); +} + +QLayout *FormWindowWidgetStack::layout() const +{ + return m_layout; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/formwindow_widgetstack.h b/src/designer/src/components/formeditor/formwindow_widgetstack.h new file mode 100644 index 0000000..9961754 --- /dev/null +++ b/src/designer/src/components/formeditor/formwindow_widgetstack.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOW_WIDGETSTACK_H +#define FORMWINDOW_WIDGETSTACK_H + +#include "formeditor_global.h" + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowToolInterface; + +class QStackedLayout; +class QWidget; + +namespace qdesigner_internal { + +class QT_FORMEDITOR_EXPORT FormWindowWidgetStack: public QObject +{ + Q_OBJECT +public: + FormWindowWidgetStack(QObject *parent = nullptr); + ~FormWindowWidgetStack() override; + + QLayout *layout() const; + + int count() const; + QDesignerFormWindowToolInterface *tool(int index) const; + QDesignerFormWindowToolInterface *currentTool() const; + int currentIndex() const; + int indexOf(QDesignerFormWindowToolInterface *tool) const; + + void setMainContainer(QWidget *w = nullptr); + + // Return the widget containing the form which can be used to apply embedded design settings to. + // These settings should not affect the other editing tools. + QWidget *formContainer() const { return m_formContainer; } + +signals: + void currentToolChanged(int index); + +public slots: + void addTool(QDesignerFormWindowToolInterface *tool); + void setCurrentTool(QDesignerFormWindowToolInterface *tool); + void setCurrentTool(int index); + void setSenderAsCurrentTool(); + +protected: + QWidget *defaultEditor() const; + +private: + QList m_tools; + QWidget *m_formContainer; + QStackedLayout *m_formContainerLayout; + QStackedLayout *m_layout; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOW_WIDGETSTACK_H diff --git a/src/designer/src/components/formeditor/formwindowcursor.cpp b/src/designer/src/components/formeditor/formwindowcursor.cpp new file mode 100644 index 0000000..4bf230e --- /dev/null +++ b/src/designer/src/components/formeditor/formwindowcursor.cpp @@ -0,0 +1,171 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindowcursor.h" +#include "formwindow.h" + +// sdk +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +FormWindowCursor::FormWindowCursor(FormWindow *fw, QObject *parent) + : QObject(parent), + m_formWindow(fw) +{ + update(); + connect(fw, &QDesignerFormWindowInterface::changed, this, &FormWindowCursor::update); +} + +FormWindowCursor::~FormWindowCursor() = default; + +QDesignerFormWindowInterface *FormWindowCursor::formWindow() const +{ + return m_formWindow; +} + +bool FormWindowCursor::movePosition(MoveOperation op, MoveMode mode) +{ + if (widgetCount() == 0) + return false; + + int iterator = position(); + + if (mode == MoveAnchor) + m_formWindow->clearSelection(false); + + switch (op) { + case Next: + ++iterator; + if (iterator >= widgetCount()) + iterator = 0; + + m_formWindow->selectWidget(m_formWindow->widgetAt(iterator), true); + return true; + + case Prev: + --iterator; + if (iterator < 0) + iterator = widgetCount() - 1; + + if (iterator < 0) + return false; + + m_formWindow->selectWidget(m_formWindow->widgetAt(iterator), true); + return true; + + default: + return false; + } +} + +int FormWindowCursor::position() const +{ + const int index = m_formWindow->widgets().indexOf(current()); + return index == -1 ? 0 : index; +} + +void FormWindowCursor::setPosition(int pos, MoveMode mode) +{ + if (!widgetCount()) + return; + + if (mode == MoveAnchor) + m_formWindow->clearSelection(false); + + if (pos >= widgetCount()) + pos = 0; + + m_formWindow->selectWidget(m_formWindow->widgetAt(pos), true); +} + +QWidget *FormWindowCursor::current() const +{ + return m_formWindow->currentWidget(); +} + +bool FormWindowCursor::hasSelection() const +{ + return !m_formWindow->selectedWidgets().isEmpty(); +} + +int FormWindowCursor::selectedWidgetCount() const +{ + int N = m_formWindow->selectedWidgets().size(); + return N ? N : 1; +} + +QWidget *FormWindowCursor::selectedWidget(int index) const +{ + return hasSelection() + ? m_formWindow->selectedWidgets().at(index) + : m_formWindow->mainContainer(); +} + +void FormWindowCursor::update() +{ + // ### todo +} + +int FormWindowCursor::widgetCount() const +{ + return m_formWindow->widgetCount(); +} + +QWidget *FormWindowCursor::widget(int index) const +{ + return m_formWindow->widgetAt(index); +} + +void FormWindowCursor::setProperty(const QString &name, const QVariant &value) +{ + + // build selection + const int N = selectedWidgetCount(); + Q_ASSERT(N); + + QObjectList selection; + for (int i=0; iinit(selection, name, value, current())) { + m_formWindow->commandHistory()->push(setPropertyCommand); + } else { + delete setPropertyCommand; + qDebug() << "Unable to set property " << name << '.'; + } +} + +void FormWindowCursor::setWidgetProperty(QWidget *widget, const QString &name, const QVariant &value) +{ + SetPropertyCommand *cmd = new SetPropertyCommand(m_formWindow); + if (cmd->init(widget, name, value)) { + m_formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "Unable to set property " << name << '.'; + } +} + +void FormWindowCursor::resetWidgetProperty(QWidget *widget, const QString &name) +{ + ResetPropertyCommand *cmd = new ResetPropertyCommand(m_formWindow); + if (cmd->init(widget, name)) { + m_formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "Unable to reset property " << name << '.'; + } +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/formwindowcursor.h b/src/designer/src/components/formeditor/formwindowcursor.h new file mode 100644 index 0000000..c3559a5 --- /dev/null +++ b/src/designer/src/components/formeditor/formwindowcursor.h @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOWCURSOR_H +#define FORMWINDOWCURSOR_H + +#include "formeditor_global.h" +#include "formwindow.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QT_FORMEDITOR_EXPORT FormWindowCursor: public QObject, public QDesignerFormWindowCursorInterface +{ + Q_OBJECT +public: + explicit FormWindowCursor(FormWindow *fw, QObject *parent = nullptr); + ~FormWindowCursor() override; + + QDesignerFormWindowInterface *formWindow() const override; + + bool movePosition(MoveOperation op, MoveMode mode) override; + + int position() const override; + void setPosition(int pos, MoveMode mode) override; + + QWidget *current() const override; + + int widgetCount() const override; + QWidget *widget(int index) const override; + + bool hasSelection() const override; + int selectedWidgetCount() const override; + QWidget *selectedWidget(int index) const override; + + void setProperty(const QString &name, const QVariant &value) override; + void setWidgetProperty(QWidget *widget, const QString &name, const QVariant &value) override; + void resetWidgetProperty(QWidget *widget, const QString &name) override; + +public slots: + void update(); + +private: + FormWindow *m_formWindow; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOWCURSOR_H diff --git a/src/designer/src/components/formeditor/formwindowmanager.cpp b/src/designer/src/components/formeditor/formwindowmanager.cpp new file mode 100644 index 0000000..5e89c86 --- /dev/null +++ b/src/designer/src/components/formeditor/formwindowmanager.cpp @@ -0,0 +1,1049 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// components/formeditor +#include "formwindowmanager.h" +#include "formwindow_dnditem.h" +#include "formwindow.h" +#include "formeditor.h" +#include "widgetselection.h" +#include "previewactiongroup.h" +#include "formwindowsettings.h" + +// shared +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// SDK +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +# include +#endif +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { debugFWM = 0 }; +} + +static inline QString whatsThisFrom(const QString &str) { /// ### implement me! + return str; +} + +// find the first child of w in a sequence +template +static inline Iterator findFirstChildOf(Iterator it,Iterator end, const QWidget *w) +{ + for (;it != end; ++it) { + if (w->isAncestorOf(*it)) + return it; + } + return it; +} + +namespace qdesigner_internal { + +FormWindowManager::FormWindowManager(QDesignerFormEditorInterface *core, QObject *parent) : + QDesignerFormWindowManager(parent), + m_core(core), + m_activeFormWindow(nullptr), + m_previewManager(new PreviewManager(PreviewManager::SingleFormNonModalPreview, this)), + m_createLayoutContext(LayoutContainer), + m_morphLayoutContainer(nullptr), + m_actionGroupPreviewInStyle(nullptr), + m_actionShowFormWindowSettingsDialog(nullptr) +{ + setupActions(); + qApp->installEventFilter(this); +} + +FormWindowManager::~FormWindowManager() +{ + qDeleteAll(m_formWindows); +} + +QDesignerFormEditorInterface *FormWindowManager::core() const +{ + return m_core; +} + +QDesignerFormWindowInterface *FormWindowManager::activeFormWindow() const +{ + return m_activeFormWindow; +} + +int FormWindowManager::formWindowCount() const +{ + return m_formWindows.size(); +} + +QDesignerFormWindowInterface *FormWindowManager::formWindow(int index) const +{ + return m_formWindows.at(index); +} + +bool FormWindowManager::eventFilter(QObject *o, QEvent *e) +{ + if (!o->isWidgetType()) + return false; + + // If we don't have an active form, we only listen for WindowActivate to speed up integrations + const QEvent::Type eventType = e->type(); + if (m_activeFormWindow == nullptr && eventType != QEvent::WindowActivate) + return false; + + switch (eventType) { // Uninteresting events + case QEvent::Create: + case QEvent::Destroy: + case QEvent::ActionAdded: + case QEvent::ActionChanged: + case QEvent::ActionRemoved: + case QEvent::ChildAdded: + case QEvent::ChildPolished: + case QEvent::ChildRemoved: +#if QT_CONFIG(clipboard) + case QEvent::Clipboard: +#endif + case QEvent::ContentsRectChange: + case QEvent::DeferredDelete: + case QEvent::FileOpen: + case QEvent::LanguageChange: + case QEvent::MetaCall: + case QEvent::ModifiedChange: + case QEvent::Paint: + case QEvent::PaletteChange: + case QEvent::ParentAboutToChange: + case QEvent::ParentChange: + case QEvent::Polish: + case QEvent::PolishRequest: + case QEvent::QueryWhatsThis: + case QEvent::StatusTip: + case QEvent::StyleChange: + case QEvent::Timer: + case QEvent::ToolBarChange: + case QEvent::ToolTip: + case QEvent::WhatsThis: + case QEvent::WhatsThisClicked: + case QEvent::WinIdChange: + case QEvent::DynamicPropertyChange: + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + case QEvent::AcceptDropsChange: + return false; + default: + break; + } + + QWidget *widget = static_cast(o); + + if (qobject_cast(widget)) { // ### remove me + return false; + } + + FormWindow *fw = FormWindow::findFormWindow(widget); + if (fw == nullptr) { + return false; + } + + if (QWidget *managedWidget = findManagedWidget(fw, widget)) { + // Prevent MDI subwindows from being closed by clicking at the title bar + if (managedWidget != widget && eventType == QEvent::Close) { + e->ignore(); + return true; + } + switch (eventType) { + case QEvent::LayoutRequest: + // QTBUG-61439: Suppress layout request while changing the QGridLayout + // span of a QTabWidget, which sends LayoutRequest in resizeEvent(). + if (fw->handleOperation() == FormWindow::ChangeLayoutSpanHandleOperation) { + e->ignore(); + return true; + } + break; + + case QEvent::WindowActivate: { + if (fw->parentWidget()->isWindow() && fw->isMainContainer(managedWidget) && activeFormWindow() != fw) { + setActiveFormWindow(fw); + } + } break; + + case QEvent::WindowDeactivate: { + if (o == fw && o == activeFormWindow()) + fw->repaintSelection(); + } break; + + case QEvent::KeyPress: { + QKeyEvent *ke = static_cast(e); + if (ke->key() == Qt::Key_Escape) { + ke->accept(); + return true; + } + } + Q_FALLTHROUGH(); // don't break... + + // Embedded Design: Drop on different form: Make sure the right form + // window/device is active before having the widget created by the factory + case QEvent::Drop: + if (activeFormWindow() != fw) + setActiveFormWindow(fw); + Q_FALLTHROUGH(); // don't break... + default: { + if (fw->handleEvent(widget, managedWidget, e)) { + return true; + } + } break; + + } // end switch + } + + return false; +} + +void FormWindowManager::addFormWindow(QDesignerFormWindowInterface *w) +{ + FormWindow *formWindow = qobject_cast(w); + if (!formWindow || m_formWindows.contains(formWindow)) + return; + + connect(formWindow, &QDesignerFormWindowInterface::selectionChanged, + this, &FormWindowManager::slotUpdateActions); + connect(formWindow->commandHistory(), &QUndoStack::indexChanged, + this, &FormWindowManager::slotUpdateActions); + connect(formWindow, &QDesignerFormWindowInterface::toolChanged, + this, &FormWindowManager::slotUpdateActions); + + if (ActionEditor *ae = qobject_cast(m_core->actionEditor())) { + connect(w, &QDesignerFormWindowInterface::mainContainerChanged, + ae, &ActionEditor::mainContainerChanged); + } + if (QDesignerObjectInspector *oi = qobject_cast(m_core->objectInspector())) + connect(w, &QDesignerFormWindowInterface::mainContainerChanged, + oi, &QDesignerObjectInspector::mainContainerChanged); + + m_formWindows.append(formWindow); + emit formWindowAdded(formWindow); +} + +void FormWindowManager::removeFormWindow(QDesignerFormWindowInterface *w) +{ + FormWindow *formWindow = qobject_cast(w); + + int idx = m_formWindows.indexOf(formWindow); + if (!formWindow || idx == -1) + return; + + formWindow->disconnect(this); + m_formWindows.removeAt(idx); + emit formWindowRemoved(formWindow); + + if (formWindow == m_activeFormWindow) + setActiveFormWindow(nullptr); + + // Make sure that widget box is enabled by default + if (m_formWindows.isEmpty() && m_core->widgetBox()) + m_core->widgetBox()->setEnabled(true); + +} + +void FormWindowManager::setActiveFormWindow(QDesignerFormWindowInterface *w) +{ + FormWindow *formWindow = qobject_cast(w); + + if (formWindow == m_activeFormWindow) + return; + + FormWindow *old = m_activeFormWindow; + + m_activeFormWindow = formWindow; + + QtResourceSet *resourceSet = nullptr; + if (formWindow) + resourceSet = formWindow->resourceSet(); + m_core->resourceModel()->setCurrentResourceSet(resourceSet); + + slotUpdateActions(); + + if (m_activeFormWindow) { + m_activeFormWindow->repaintSelection(); + if (old) + old->repaintSelection(); + } + + emit activeFormWindowChanged(m_activeFormWindow); + + if (m_activeFormWindow) { + m_activeFormWindow->emitSelectionChanged(); + m_activeFormWindow->commandHistory()->setActive(); + // Trigger setActiveSubWindow on mdi area unless we are in toplevel mode + QMdiSubWindow *mdiSubWindow = nullptr; + if (QWidget *formwindow = m_activeFormWindow->parentWidget()) { + mdiSubWindow = qobject_cast(formwindow->parentWidget()); + } + if (mdiSubWindow) { + for (QWidget *parent = mdiSubWindow->parentWidget(); parent; parent = parent->parentWidget()) { + if (QMdiArea *mdiArea = qobject_cast(parent)) { + mdiArea->setActiveSubWindow(mdiSubWindow); + break; + } + } + } + } +} + +void FormWindowManager::closeAllPreviews() +{ + m_previewManager->closeAllPreviews(); +} + +QWidget *FormWindowManager::findManagedWidget(FormWindow *fw, QWidget *w) +{ + while (w && w != fw) { + if (fw->isManaged(w)) + break; + w = w->parentWidget(); + } + return w; +} + +void FormWindowManager::setupActions() +{ +#if QT_CONFIG(clipboard) + const QIcon cutIcon = createIconSet(QIcon::ThemeIcon::EditCut, + "editcut.png"_L1); + m_actionCut = new QAction(cutIcon, tr("Cu&t"), this); + m_actionCut->setObjectName(u"__qt_cut_action"_s); + m_actionCut->setShortcut(QKeySequence::Cut); + m_actionCut->setStatusTip(tr("Cuts the selected widgets and puts them on the clipboard")); + m_actionCut->setWhatsThis(whatsThisFrom(u"Edit|Cut"_s)); + connect(m_actionCut, &QAction::triggered, this, &FormWindowManager::slotActionCutActivated); + m_actionCut->setEnabled(false); + + const QIcon copyIcon = createIconSet(QIcon::ThemeIcon::EditCopy, "editcopy.png"_L1); + m_actionCopy = new QAction(copyIcon, tr("&Copy"), this); + m_actionCopy->setObjectName(u"__qt_copy_action"_s); + m_actionCopy->setShortcut(QKeySequence::Copy); + m_actionCopy->setStatusTip(tr("Copies the selected widgets to the clipboard")); + m_actionCopy->setWhatsThis(whatsThisFrom(u"Edit|Copy"_s)); + connect(m_actionCopy, &QAction::triggered, this, &FormWindowManager::slotActionCopyActivated); + m_actionCopy->setEnabled(false); + + const QIcon pasteIcon = createIconSet(QIcon::ThemeIcon::EditPaste, "editpaste.png"_L1); + m_actionPaste = new QAction(pasteIcon, tr("&Paste"), this); + m_actionPaste->setObjectName(u"__qt_paste_action"_s); + m_actionPaste->setShortcut(QKeySequence::Paste); + m_actionPaste->setStatusTip(tr("Pastes the clipboard's contents")); + m_actionPaste->setWhatsThis(whatsThisFrom(u"Edit|Paste"_s)); + connect(m_actionPaste, &QAction::triggered, this, &FormWindowManager::slotActionPasteActivated); + m_actionPaste->setEnabled(false); +#endif + + m_actionDelete = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::EditDelete), + tr("&Delete"), this); + m_actionDelete->setObjectName(u"__qt_delete_action"_s); + m_actionDelete->setStatusTip(tr("Deletes the selected widgets")); + m_actionDelete->setWhatsThis(whatsThisFrom(u"Edit|Delete"_s)); + connect(m_actionDelete, &QAction::triggered, this, &FormWindowManager::slotActionDeleteActivated); + m_actionDelete->setEnabled(false); + + m_actionSelectAll = new QAction(tr("Select &All"), this); + m_actionSelectAll->setObjectName(u"__qt_select_all_action"_s); + m_actionSelectAll->setShortcut(QKeySequence::SelectAll); + m_actionSelectAll->setStatusTip(tr("Selects all widgets")); + m_actionSelectAll->setWhatsThis(whatsThisFrom(u"Edit|Select All"_s)); + connect(m_actionSelectAll, &QAction::triggered, this, &FormWindowManager::slotActionSelectAllActivated); + m_actionSelectAll->setEnabled(false); + + m_actionRaise = new QAction(createIconSet("editraise.png"_L1), + tr("Bring to &Front"), this); + m_actionRaise->setObjectName(u"__qt_raise_action"_s); + m_actionRaise->setShortcut(Qt::CTRL | Qt::Key_L); + m_actionRaise->setStatusTip(tr("Raises the selected widgets")); + m_actionRaise->setWhatsThis(tr("Raises the selected widgets")); + connect(m_actionRaise, &QAction::triggered, this, &FormWindowManager::slotActionRaiseActivated); + m_actionRaise->setEnabled(false); + + m_actionLower = new QAction(createIconSet("editlower.png"_L1), + tr("Send to &Back"), this); + m_actionLower->setObjectName(u"__qt_lower_action"_s); + m_actionLower->setShortcut(Qt::CTRL | Qt::Key_K); + m_actionLower->setStatusTip(tr("Lowers the selected widgets")); + m_actionLower->setWhatsThis(tr("Lowers the selected widgets")); + connect(m_actionLower, &QAction::triggered, this, &FormWindowManager::slotActionLowerActivated); + m_actionLower->setEnabled(false); + + m_actionAdjustSize = new QAction(createIconSet("adjustsize.png"_L1), + tr("Adjust &Size"), this); + m_actionAdjustSize->setObjectName(u"__qt_adjust_size_action"_s); + m_actionAdjustSize->setShortcut(Qt::CTRL | Qt::Key_J); + m_actionAdjustSize->setStatusTip(tr("Adjusts the size of the selected widget")); + m_actionAdjustSize->setWhatsThis(whatsThisFrom(u"Layout|Adjust Size"_s)); + connect(m_actionAdjustSize, &QAction::triggered, this, &FormWindowManager::slotActionAdjustSizeActivated); + m_actionAdjustSize->setEnabled(false); + + + m_actionHorizontalLayout = new QAction(createIconSet("edithlayout.png"_L1), + tr("Lay Out &Horizontally"), this); + m_actionHorizontalLayout->setObjectName(u"__qt_horizontal_layout_action"_s); + m_actionHorizontalLayout->setShortcut(Qt::CTRL | Qt::Key_1); + m_actionHorizontalLayout->setStatusTip(tr("Lays out the selected widgets horizontally")); + m_actionHorizontalLayout->setWhatsThis(whatsThisFrom(u"Layout|Lay Out Horizontally"_s)); + m_actionHorizontalLayout->setData(LayoutInfo::HBox); + m_actionHorizontalLayout->setEnabled(false); + connect(m_actionHorizontalLayout, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionVerticalLayout = new QAction(createIconSet("editvlayout.png"_L1), + tr("Lay Out &Vertically"), this); + m_actionVerticalLayout->setObjectName(u"__qt_vertical_layout_action"_s); + m_actionVerticalLayout->setShortcut(Qt::CTRL | Qt::Key_2); + m_actionVerticalLayout->setStatusTip(tr("Lays out the selected widgets vertically")); + m_actionVerticalLayout->setWhatsThis(whatsThisFrom(u"Layout|Lay Out Vertically"_s)); + m_actionVerticalLayout->setData(LayoutInfo::VBox); + m_actionVerticalLayout->setEnabled(false); + connect(m_actionVerticalLayout, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionFormLayout = new QAction(createIconSet("editform.png"_L1), + tr("Lay Out in a &Form Layout"), this); + m_actionFormLayout->setObjectName(u"__qt_form_layout_action"_s); + m_actionFormLayout->setShortcut(Qt::CTRL | Qt::Key_6); + m_actionFormLayout->setStatusTip(tr("Lays out the selected widgets in a form layout")); + m_actionFormLayout->setWhatsThis(whatsThisFrom(u"Layout|Lay Out in a Form"_s)); + m_actionFormLayout->setData(LayoutInfo::Form); + m_actionFormLayout->setEnabled(false); + connect(m_actionFormLayout, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionGridLayout = new QAction(createIconSet("editgrid.png"_L1), + tr("Lay Out in a &Grid"), this); + m_actionGridLayout->setObjectName(u"__qt_grid_layout_action"_s); + m_actionGridLayout->setShortcut(Qt::CTRL | Qt::Key_5); + m_actionGridLayout->setStatusTip(tr("Lays out the selected widgets in a grid")); + m_actionGridLayout->setWhatsThis(whatsThisFrom(u"Layout|Lay Out in a Grid"_s)); + m_actionGridLayout->setData(LayoutInfo::Grid); + m_actionGridLayout->setEnabled(false); + connect(m_actionGridLayout, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionSplitHorizontal = new QAction(createIconSet("edithlayoutsplit.png"_L1), + tr("Lay Out Horizontally in S&plitter"), this); + m_actionSplitHorizontal->setObjectName(u"__qt_split_horizontal_action"_s); + m_actionSplitHorizontal->setShortcut(Qt::CTRL | Qt::Key_3); + m_actionSplitHorizontal->setStatusTip(tr("Lays out the selected widgets horizontally in a splitter")); + m_actionSplitHorizontal->setWhatsThis(whatsThisFrom(u"Layout|Lay Out Horizontally in Splitter"_s)); + m_actionSplitHorizontal->setData(LayoutInfo::HSplitter); + m_actionSplitHorizontal->setEnabled(false); + connect(m_actionSplitHorizontal, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionSplitVertical = new QAction(createIconSet("editvlayoutsplit.png"_L1), + tr("Lay Out Vertically in Sp&litter"), this); + m_actionSplitVertical->setObjectName(u"__qt_split_vertical_action"_s); + m_actionSplitVertical->setShortcut(Qt::CTRL | Qt::Key_4); + m_actionSplitVertical->setStatusTip(tr("Lays out the selected widgets vertically in a splitter")); + m_actionSplitVertical->setWhatsThis(whatsThisFrom(u"Layout|Lay Out Vertically in Splitter"_s)); + connect(m_actionSplitVertical, &QAction::triggered, this, &FormWindowManager::createLayout); + m_actionSplitVertical->setData(LayoutInfo::VSplitter); + + m_actionSplitVertical->setEnabled(false); + + m_actionBreakLayout = new QAction(createIconSet("editbreaklayout.png"_L1), + tr("&Break Layout"), this); + m_actionBreakLayout->setObjectName(u"__qt_break_layout_action"_s); + m_actionBreakLayout->setShortcut(Qt::CTRL | Qt::Key_0); + m_actionBreakLayout->setStatusTip(tr("Breaks the selected layout")); + m_actionBreakLayout->setWhatsThis(whatsThisFrom(u"Layout|Break Layout"_s)); + connect(m_actionBreakLayout, &QAction::triggered, this, &FormWindowManager::slotActionBreakLayoutActivated); + m_actionBreakLayout->setEnabled(false); + + m_actionSimplifyLayout = new QAction(tr("Si&mplify Grid Layout"), this); + m_actionSimplifyLayout->setObjectName(u"__qt_simplify_layout_action"_s); + m_actionSimplifyLayout->setStatusTip(tr("Removes empty columns and rows")); + m_actionSimplifyLayout->setWhatsThis(whatsThisFrom(u"Layout|Simplify Layout"_s)); + connect(m_actionSimplifyLayout, &QAction::triggered, this, &FormWindowManager::slotActionSimplifyLayoutActivated); + m_actionSimplifyLayout->setEnabled(false); + + m_actionDefaultPreview = new QAction(tr("&Preview..."), this); + m_actionDefaultPreview->setObjectName(u"__qt_default_preview_action"_s); + m_actionDefaultPreview->setStatusTip(tr("Preview current form")); + m_actionDefaultPreview->setWhatsThis(whatsThisFrom(u"Form|Preview"_s)); + connect(m_actionDefaultPreview, &QAction::triggered, + this, &FormWindowManager::showPreview); + + m_undoGroup = new QUndoGroup(this); + + m_actionUndo = m_undoGroup->createUndoAction(this); + m_actionUndo->setEnabled(false); + + m_actionUndo->setIcon(createIconSet(QIcon::ThemeIcon::EditUndo, "undo.png"_L1)); + m_actionRedo = m_undoGroup->createRedoAction(this); + m_actionRedo->setEnabled(false); + m_actionRedo->setIcon(createIconSet(QIcon::ThemeIcon::EditRedo, "redo.png"_L1)); + + m_actionShowFormWindowSettingsDialog = new QAction(tr("Form &Settings..."), this); + m_actionShowFormWindowSettingsDialog->setObjectName(u"__qt_form_settings_action"_s); + connect(m_actionShowFormWindowSettingsDialog, &QAction::triggered, + this, &FormWindowManager::slotActionShowFormWindowSettingsDialog); + m_actionShowFormWindowSettingsDialog->setEnabled(false); +} + +#if QT_CONFIG(clipboard) +void FormWindowManager::slotActionCutActivated() +{ + m_activeFormWindow->cut(); +} + +void FormWindowManager::slotActionCopyActivated() +{ + m_activeFormWindow->copy(); + slotUpdateActions(); +} + +void FormWindowManager::slotActionPasteActivated() +{ + m_activeFormWindow->paste(); +} +#endif + +void FormWindowManager::slotActionDeleteActivated() +{ + m_activeFormWindow->deleteWidgets(); +} + +void FormWindowManager::slotActionLowerActivated() +{ + m_activeFormWindow->lowerWidgets(); +} + +void FormWindowManager::slotActionRaiseActivated() +{ + m_activeFormWindow->raiseWidgets(); +} + +static inline QWidget *findLayoutContainer(const FormWindow *fw) +{ + QWidgetList l(fw->selectedWidgets()); + fw->simplifySelection(&l); + return l.isEmpty() ? fw->mainContainer() : l.constFirst(); +} + +void FormWindowManager::createLayout() +{ + QAction *a = qobject_cast(sender()); + if (!a) + return; + const int type = a->data().toInt(); + switch (m_createLayoutContext) { + case LayoutContainer: + // Cannot create a splitter on a container + if (type != LayoutInfo::HSplitter && type != LayoutInfo::VSplitter) + m_activeFormWindow->createLayout(type, findLayoutContainer(m_activeFormWindow)); + break; + case LayoutSelection: + m_activeFormWindow->createLayout(type); + break; + case MorphLayout: + m_activeFormWindow->morphLayout(m_morphLayoutContainer, type); + break; + } +} + +void FormWindowManager::slotActionBreakLayoutActivated() +{ + const QWidgetList layouts = layoutsToBeBroken(); + if (layouts.isEmpty()) + return; + + if (debugFWM) { + qDebug() << "slotActionBreakLayoutActivated: " << layouts.size(); + for (const QWidget *w : layouts) + qDebug() << w; + } + + m_activeFormWindow->beginCommand(tr("Break Layout")); + for (QWidget *layout : layouts) + m_activeFormWindow->breakLayout(layout); + m_activeFormWindow->endCommand(); +} + +void FormWindowManager::slotActionSimplifyLayoutActivated() +{ + Q_ASSERT(m_activeFormWindow != nullptr); + QWidgetList selectedWidgets = m_activeFormWindow->selectedWidgets(); + m_activeFormWindow->simplifySelection(&selectedWidgets); + if (selectedWidgets.size() != 1) + return; + SimplifyLayoutCommand *cmd = new SimplifyLayoutCommand(m_activeFormWindow); + if (cmd->init(selectedWidgets.constFirst())) { + m_activeFormWindow->commandHistory()->push(cmd); + } else { + delete cmd; + } +} + +void FormWindowManager::slotActionAdjustSizeActivated() +{ + Q_ASSERT(m_activeFormWindow != nullptr); + + m_activeFormWindow->beginCommand(tr("Adjust Size")); + + QWidgetList selectedWidgets = m_activeFormWindow->selectedWidgets(); + m_activeFormWindow->simplifySelection(&selectedWidgets); + + if (selectedWidgets.isEmpty()) { + Q_ASSERT(m_activeFormWindow->mainContainer() != nullptr); + selectedWidgets.append(m_activeFormWindow->mainContainer()); + } + + // Always count the main container as unlaid-out + for (QWidget *widget : std::as_const(selectedWidgets)) { + bool unlaidout = LayoutInfo::layoutType(core(), widget->parentWidget()) == LayoutInfo::NoLayout; + bool isMainContainer = m_activeFormWindow->isMainContainer(widget); + + if (unlaidout || isMainContainer) { + AdjustWidgetSizeCommand *cmd = new AdjustWidgetSizeCommand(m_activeFormWindow); + cmd->init(widget); + m_activeFormWindow->commandHistory()->push(cmd); + } + } + + m_activeFormWindow->endCommand(); +} + +void FormWindowManager::slotActionSelectAllActivated() +{ + m_activeFormWindow->selectAll(); +} + +void FormWindowManager::showPreview() +{ + slotActionGroupPreviewInStyle(QString(), -1); +} + +void FormWindowManager::slotActionGroupPreviewInStyle(const QString &style, int deviceProfileIndex) +{ + QDesignerFormWindowInterface *fw = activeFormWindow(); + if (!fw) + return; + + QString errorMessage; + if (!m_previewManager->showPreview(fw, style, deviceProfileIndex, &errorMessage)) { + const QString title = tr("Could not create form preview", "Title of warning message box"); + core()->dialogGui()->message(fw, QDesignerDialogGuiInterface::FormEditorMessage, QMessageBox::Warning, + title, errorMessage); + } +} + +// The user might click on a layout child or the actual layout container. +QWidgetList FormWindowManager::layoutsToBeBroken(QWidget *w) const +{ + if (!w) + return QWidgetList(); + + if (debugFWM) + qDebug() << "layoutsToBeBroken: " << w; + + QWidget *parent = w->parentWidget(); + if (m_activeFormWindow->isMainContainer(w)) + parent = nullptr; + + QWidget *widget = core()->widgetFactory()->containerOfWidget(w); + + // maybe we want to remove following block + const QDesignerWidgetDataBaseInterface *db = m_core->widgetDataBase(); + const QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfObject(widget)); + if (!item) { + if (debugFWM) + qDebug() << "layoutsToBeBroken: Don't have an item, recursing for parent"; + return layoutsToBeBroken(parent); + } + + const bool layoutContainer = (item->isContainer() || m_activeFormWindow->isMainContainer(widget)); + + if (!layoutContainer) { + if (debugFWM) + qDebug() << "layoutsToBeBroken: Not a container, recursing for parent"; + return layoutsToBeBroken(parent); + } + + QLayout *widgetLayout = widget->layout(); + QLayout *managedLayout = LayoutInfo::managedLayout(m_core, widgetLayout); + if (!managedLayout) { + if (qobject_cast(widget)) { + if (debugFWM) + qDebug() << "layoutsToBeBroken: Splitter special"; + QWidgetList list = layoutsToBeBroken(parent); + list.append(widget); + return list; + } + if (debugFWM) + qDebug() << "layoutsToBeBroken: Is a container but doesn't have a managed layout (has an internal layout), returning 0"; + return QWidgetList(); + } + + if (managedLayout) { + QWidgetList list; + if (debugFWM) + qDebug() << "layoutsToBeBroken: Is a container and has a layout"; + if (qobject_cast(widget)) { + if (debugFWM) + qDebug() << "layoutsToBeBroken: red layout special case"; + list = layoutsToBeBroken(parent); + } + list.append(widget); + return list; + } + if (debugFWM) + qDebug() << "layoutsToBeBroken: Is a container but doesn't have a layout at all, returning 0"; + return QWidgetList(); + +} + +QSet FormWindowManager::getUnsortedLayoutsToBeBroken(bool firstOnly) const +{ + // Return a set of layouts to be broken. + QSet layouts; + + QWidgetList selection = m_activeFormWindow->selectedWidgets(); + if (selection.isEmpty() && m_activeFormWindow->mainContainer()) + selection.append(m_activeFormWindow->mainContainer()); + + for (QWidget *selectedWidget : std::as_const(selection)) { + // find all layouts + const QWidgetList &list = layoutsToBeBroken(selectedWidget); + if (!list.isEmpty()) { + for (QWidget *widget : list) + layouts.insert(widget); + if (firstOnly) + return layouts; + } + } + return layouts; +} + +bool FormWindowManager::hasLayoutsToBeBroken() const +{ + // Quick check for layouts to be broken + return !getUnsortedLayoutsToBeBroken(true).isEmpty(); +} + +QWidgetList FormWindowManager::layoutsToBeBroken() const +{ + // Get all layouts. This is a list of all 'red' layouts (QLayoutWidgets) + // up to the first 'real' widget with a layout in hierarchy order. + const QSet unsortedLayouts = getUnsortedLayoutsToBeBroken(false); + // Sort in order of hierarchy + QWidgetList orderedLayoutList; + for (QWidget *wToBeInserted : unsortedLayouts) { + if (!orderedLayoutList.contains(wToBeInserted)) { + // try to find first child, use as insertion position, else append + const auto firstChildPos = findFirstChildOf(orderedLayoutList.begin(), orderedLayoutList.end(), wToBeInserted); + if (firstChildPos == orderedLayoutList.end()) { + orderedLayoutList.push_back(wToBeInserted); + } else { + orderedLayoutList.insert(firstChildPos, wToBeInserted); + } + } + } + return orderedLayoutList; +} + +static inline bool hasManagedLayoutItems(const QDesignerFormEditorInterface *core, QWidget *w) +{ + if (const QLayout *ml = LayoutInfo::managedLayout(core, w)) { + // Try to find managed items, ignore dummy grid spacers + const int count = ml->count(); + for (int i = 0; i < count; i++) + if (!LayoutInfo::isEmptyItem(ml->itemAt(i))) + return true; + } + return false; +} + +void FormWindowManager::slotUpdateActions() +{ + m_createLayoutContext = LayoutSelection; + m_morphLayoutContainer = nullptr; + bool canMorphIntoVBoxLayout = false; + bool canMorphIntoHBoxLayout = false; + bool canMorphIntoGridLayout = false; + bool canMorphIntoFormLayout = false; + bool hasSelectedWidgets = false; + int unlaidoutWidgetCount = 0; +#if QT_CONFIG(clipboard) + bool pasteAvailable = false; +#endif + bool layoutAvailable = false; + bool breakAvailable = false; + bool simplifyAvailable = false; + bool layoutContainer = false; + bool canChangeZOrder = true; + + do { + if (m_activeFormWindow == nullptr || m_activeFormWindow->currentTool() != 0) + break; + + breakAvailable = hasLayoutsToBeBroken(); + + QWidgetList simplifiedSelection = m_activeFormWindow->selectedWidgets(); + + hasSelectedWidgets = !simplifiedSelection.isEmpty(); +#if QT_CONFIG(clipboard) + pasteAvailable = qApp->clipboard()->mimeData() && qApp->clipboard()->mimeData()->hasText(); +#endif + + m_activeFormWindow->simplifySelection(&simplifiedSelection); + QWidget *mainContainer = m_activeFormWindow->mainContainer(); + if (simplifiedSelection.isEmpty() && mainContainer) + simplifiedSelection.append(mainContainer); + + // Always count the main container as unlaid-out + for (auto *w : std::as_const(simplifiedSelection)) { + if (w == mainContainer || !LayoutInfo::isWidgetLaidout(m_core, w)) + ++unlaidoutWidgetCount; + + if (qobject_cast(w) || qobject_cast(w)) + canChangeZOrder = false; + } + + // Figure out layouts: Looking at a group of dangling widgets + if (simplifiedSelection.size() != 1) { + layoutAvailable = unlaidoutWidgetCount > 1; + //breakAvailable = false; + break; + } + // Manipulate layout of a single widget + m_createLayoutContext = LayoutSelection; + QWidget *widget = core()->widgetFactory()->containerOfWidget(simplifiedSelection.first()); + if (widget == nullptr) // We are looking at a page-based container with 0 pages + break; + + const QDesignerWidgetDataBaseInterface *db = m_core->widgetDataBase(); + const QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfObject(widget)); + if (!item) + break; + + QLayout *widgetLayout = LayoutInfo::internalLayout(widget); + QLayout *managedLayout = LayoutInfo::managedLayout(m_core, widgetLayout); + // We don't touch a layout created by a custom widget + if (widgetLayout && !managedLayout) + break; + + layoutContainer = (item->isContainer() || m_activeFormWindow->isMainContainer(widget)); + + layoutAvailable = layoutContainer && m_activeFormWindow->hasInsertedChildren(widget) && managedLayout == nullptr; + simplifyAvailable = SimplifyLayoutCommand::canSimplify(m_core, widget); + if (layoutAvailable) { + m_createLayoutContext = LayoutContainer; + } else { + /* Cannot create a layout, have some layouts to be broken and + * exactly one, non-empty layout with selected: check the morph layout options + * (Note that there might be > 1 layouts to broken if the selection + * is a red layout, however, we want the inner-most layout here). */ + if (breakAvailable && simplifiedSelection.size() == 1 + && hasManagedLayoutItems(m_core, widget)) { + int type; + m_morphLayoutContainer = widget; // Was: page of first selected + m_createLayoutContext = MorphLayout; + if (MorphLayoutCommand::canMorph(m_activeFormWindow, m_morphLayoutContainer, &type)) { + canMorphIntoVBoxLayout = type != LayoutInfo::VBox; + canMorphIntoHBoxLayout = type != LayoutInfo::HBox; + canMorphIntoGridLayout = type != LayoutInfo::Grid; + canMorphIntoFormLayout = type != LayoutInfo::Form; + } + } + } + } while(false); + +#if QT_CONFIG(clipboard) + m_actionCut->setEnabled(hasSelectedWidgets); + m_actionCopy->setEnabled(hasSelectedWidgets); + m_actionPaste->setEnabled(pasteAvailable); +#endif + m_actionDelete->setEnabled(hasSelectedWidgets); + m_actionLower->setEnabled(canChangeZOrder && hasSelectedWidgets); + m_actionRaise->setEnabled(canChangeZOrder && hasSelectedWidgets); + + + m_actionSelectAll->setEnabled(m_activeFormWindow != nullptr); + + m_actionAdjustSize->setEnabled(unlaidoutWidgetCount > 0); + + m_actionHorizontalLayout->setEnabled(layoutAvailable || canMorphIntoHBoxLayout); + m_actionVerticalLayout->setEnabled(layoutAvailable || canMorphIntoVBoxLayout); + m_actionSplitHorizontal->setEnabled(layoutAvailable && !layoutContainer); + m_actionSplitVertical->setEnabled(layoutAvailable && !layoutContainer); + m_actionFormLayout->setEnabled(layoutAvailable || canMorphIntoFormLayout); + m_actionGridLayout->setEnabled(layoutAvailable || canMorphIntoGridLayout); + + m_actionBreakLayout->setEnabled(breakAvailable); + m_actionSimplifyLayout->setEnabled(simplifyAvailable); + m_actionShowFormWindowSettingsDialog->setEnabled(m_activeFormWindow != nullptr); +} + +QDesignerFormWindowInterface *FormWindowManager::createFormWindow(QWidget *parentWidget, Qt::WindowFlags flags) +{ + FormWindow *formWindow = new FormWindow(qobject_cast(core()), parentWidget, flags); + formWindow->setProperty(WidgetFactory::disableStyleCustomPaintingPropertyC, QVariant(true)); + addFormWindow(formWindow); + return formWindow; +} + +QPixmap FormWindowManager::createPreviewPixmap() const +{ + const QDesignerFormWindowInterface *fw = activeFormWindow(); + if (!fw) + return QPixmap(); + QString errorMessage; + const QPixmap pix = m_previewManager->createPreviewPixmap(fw, QString(), &errorMessage); + if (pix.isNull() && !errorMessage.isEmpty()) + qWarning("Preview pixmap creation failed: %s", qPrintable(errorMessage)); + return pix; +} + +void FormWindowManager::deviceProfilesChanged() +{ + if (m_actionGroupPreviewInStyle) + m_actionGroupPreviewInStyle->updateDeviceProfiles(); +} + +// DnD stuff + +void FormWindowManager::dragItems(const QList &item_list) +{ + QDesignerMimeData::execDrag(item_list, m_core->topLevel()); +} + +QUndoGroup *FormWindowManager::undoGroup() const +{ + return m_undoGroup; +} + +void FormWindowManager::slotActionShowFormWindowSettingsDialog() +{ + QDesignerFormWindowInterface *fw = activeFormWindow(); + if (!fw) + return; + + QDialog *settingsDialog = nullptr; + const bool wasDirty = fw->isDirty(); + + // Ask the language extension for a dialog. If not, create our own + if (QDesignerLanguageExtension *lang = qt_extension(m_core->extensionManager(), m_core)) + settingsDialog = lang->createFormWindowSettingsDialog(fw, /*parent=*/ nullptr); + + if (!settingsDialog) + settingsDialog = new FormWindowSettings(fw); + + QString title = QFileInfo(fw->fileName()).fileName(); + if (title.isEmpty()) // Grab the title from the outer window if no filename + if (const QWidget *window = m_core->integration()->containerWindow(fw)) + title = window->windowTitle(); + + settingsDialog->setWindowTitle(tr("Form Settings - %1").arg(title)); + if (settingsDialog->exec()) + if (fw->isDirty() != wasDirty) + emit formWindowSettingsChanged(fw); + + delete settingsDialog; +} + +QAction *FormWindowManager::action(Action action) const +{ + switch (action) { +#if QT_CONFIG(clipboard) + case QDesignerFormWindowManagerInterface::CutAction: + return m_actionCut; + case QDesignerFormWindowManagerInterface::CopyAction: + return m_actionCopy; + case QDesignerFormWindowManagerInterface::PasteAction: + return m_actionPaste; +#endif + case QDesignerFormWindowManagerInterface::DeleteAction: + return m_actionDelete; + case QDesignerFormWindowManagerInterface::SelectAllAction: + return m_actionSelectAll; + case QDesignerFormWindowManagerInterface::LowerAction: + return m_actionLower; + case QDesignerFormWindowManagerInterface::RaiseAction: + return m_actionRaise; + case QDesignerFormWindowManagerInterface::UndoAction: + return m_actionUndo; + case QDesignerFormWindowManagerInterface::RedoAction: + return m_actionRedo; + case QDesignerFormWindowManagerInterface::HorizontalLayoutAction: + return m_actionHorizontalLayout; + case QDesignerFormWindowManagerInterface::VerticalLayoutAction: + return m_actionVerticalLayout; + case QDesignerFormWindowManagerInterface::SplitHorizontalAction: + return m_actionSplitHorizontal; + case QDesignerFormWindowManagerInterface::SplitVerticalAction: + return m_actionSplitVertical; + case QDesignerFormWindowManagerInterface::GridLayoutAction: + return m_actionGridLayout; + case QDesignerFormWindowManagerInterface::FormLayoutAction: + return m_actionFormLayout; + case QDesignerFormWindowManagerInterface::BreakLayoutAction: + return m_actionBreakLayout; + case QDesignerFormWindowManagerInterface::AdjustSizeAction: + return m_actionAdjustSize; + case QDesignerFormWindowManagerInterface::SimplifyLayoutAction: + return m_actionSimplifyLayout; + case QDesignerFormWindowManagerInterface::DefaultPreviewAction: + return m_actionDefaultPreview; + case QDesignerFormWindowManagerInterface::FormWindowSettingsDialogAction: + return m_actionShowFormWindowSettingsDialog; + } + qWarning("FormWindowManager::action: Unhanded enumeration value %d", action); + return nullptr; +} + +QActionGroup *FormWindowManager::actionGroup(ActionGroup actionGroup) const +{ + switch (actionGroup) { + case QDesignerFormWindowManagerInterface::StyledPreviewActionGroup: + if (m_actionGroupPreviewInStyle == nullptr) { + // Wish we could make the 'this' pointer mutable ;-) + QObject *parent = const_cast(this); + m_actionGroupPreviewInStyle = new PreviewActionGroup(m_core, parent); + connect(m_actionGroupPreviewInStyle, &PreviewActionGroup::preview, + this, &FormWindowManager::slotActionGroupPreviewInStyle); + } + return m_actionGroupPreviewInStyle; + } + qWarning("FormWindowManager::actionGroup: Unhanded enumeration value %d", actionGroup); + return nullptr; +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/formwindowmanager.h b/src/designer/src/components/formeditor/formwindowmanager.h new file mode 100644 index 0000000..001390b --- /dev/null +++ b/src/designer/src/components/formeditor/formwindowmanager.h @@ -0,0 +1,150 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOWMANAGER_H +#define FORMWINDOWMANAGER_H + +#include "formeditor_global.h" + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; +class QActionGroup; +class QUndoGroup; +class QDesignerFormEditorInterface; +class QDesignerWidgetBoxInterface; + +namespace qdesigner_internal { + +class FormWindow; +class PreviewManager; +class PreviewActionGroup; + +class QT_FORMEDITOR_EXPORT FormWindowManager + : public QDesignerFormWindowManager +{ + Q_OBJECT +public: + explicit FormWindowManager(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~FormWindowManager() override; + + QDesignerFormEditorInterface *core() const override; + + QAction *action(Action action) const override; + QActionGroup *actionGroup(ActionGroup actionGroup) const override; + + QDesignerFormWindowInterface *activeFormWindow() const override; + + int formWindowCount() const override; + QDesignerFormWindowInterface *formWindow(int index) const override; + + QDesignerFormWindowInterface *createFormWindow(QWidget *parentWidget = nullptr, Qt::WindowFlags flags = {}) override; + + QPixmap createPreviewPixmap() const override; + + bool eventFilter(QObject *o, QEvent *e) override; + + void dragItems(const QList &item_list) override; + + QUndoGroup *undoGroup() const; + + PreviewManager *previewManager() const override { return m_previewManager; } + +public slots: + void addFormWindow(QDesignerFormWindowInterface *formWindow) override; + void removeFormWindow(QDesignerFormWindowInterface *formWindow) override; + void setActiveFormWindow(QDesignerFormWindowInterface *formWindow) override; + void closeAllPreviews() override; + void deviceProfilesChanged(); + +private slots: +#if QT_CONFIG(clipboard) + void slotActionCutActivated(); + void slotActionCopyActivated(); + void slotActionPasteActivated(); +#endif + void slotActionDeleteActivated(); + void slotActionSelectAllActivated(); + void slotActionLowerActivated(); + void slotActionRaiseActivated(); + void createLayout(); + void slotActionBreakLayoutActivated(); + void slotActionAdjustSizeActivated(); + void slotActionSimplifyLayoutActivated(); + void showPreview() override; + void slotActionGroupPreviewInStyle(const QString &style, int deviceProfileIndex); + void slotActionShowFormWindowSettingsDialog(); + + void slotUpdateActions(); + +private: + void setupActions(); + FormWindow *findFormWindow(QWidget *w); + QWidget *findManagedWidget(FormWindow *fw, QWidget *w); + + void setCurrentUndoStack(QUndoStack *stack); + +private: + enum CreateLayoutContext { LayoutContainer, LayoutSelection, MorphLayout }; + + QDesignerFormEditorInterface *m_core; + FormWindow *m_activeFormWindow; + QList m_formWindows; + + PreviewManager *m_previewManager; + + /* Context of the layout actions and base for morphing layouts. Determined + * in slotUpdateActions() and used later on in the action slots. */ + CreateLayoutContext m_createLayoutContext; + QWidget *m_morphLayoutContainer; + + // edit actions +#if QT_CONFIG(clipboard) + QAction *m_actionCut = nullptr; + QAction *m_actionCopy = nullptr; + QAction *m_actionPaste = nullptr; +#endif + QAction *m_actionSelectAll = nullptr; + QAction *m_actionDelete = nullptr; + QAction *m_actionLower = nullptr; + QAction *m_actionRaise = nullptr; + // layout actions + QAction *m_actionHorizontalLayout = nullptr; + QAction *m_actionVerticalLayout = nullptr; + QAction *m_actionFormLayout = nullptr; + QAction *m_actionSplitHorizontal = nullptr; + QAction *m_actionSplitVertical = nullptr; + QAction *m_actionGridLayout = nullptr; + QAction *m_actionBreakLayout = nullptr; + QAction *m_actionSimplifyLayout = nullptr; + QAction *m_actionAdjustSize = nullptr; + // preview actions + QAction *m_actionDefaultPreview = nullptr; + mutable PreviewActionGroup *m_actionGroupPreviewInStyle = nullptr; + QAction *m_actionShowFormWindowSettingsDialog = nullptr; + + QAction *m_actionUndo = nullptr; + QAction *m_actionRedo = nullptr; + + QSet getUnsortedLayoutsToBeBroken(bool firstOnly) const; + bool hasLayoutsToBeBroken() const; + QWidgetList layoutsToBeBroken(QWidget *w) const; + QWidgetList layoutsToBeBroken() const; + + QUndoGroup *m_undoGroup = nullptr; + +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOWMANAGER_H diff --git a/src/designer/src/components/formeditor/formwindowsettings.cpp b/src/designer/src/components/formeditor/formwindowsettings.cpp new file mode 100644 index 0000000..ad0e32f --- /dev/null +++ b/src/designer/src/components/formeditor/formwindowsettings.cpp @@ -0,0 +1,247 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindowsettings.h" +#include "ui_formwindowsettings.h" + +#include +#include + +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// Data structure containing form dialog data providing comparison +struct FormWindowData { + void fromFormWindow(FormWindowBase* fw); + void applyToFormWindow(FormWindowBase* fw) const; + + bool layoutDefaultEnabled{false}; + int defaultMargin{0}; + int defaultSpacing{0}; + + bool layoutFunctionsEnabled{false}; + QString marginFunction; + QString spacingFunction; + + QString pixFunction; + + QString author; + + QStringList includeHints; + + bool hasFormGrid{false}; + Grid grid; + bool idBasedTranslations{false}; + bool connectSlotsByName{true}; + + friend bool comparesEqual(const FormWindowData &lhs, + const FormWindowData &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(FormWindowData) +}; + +QDebug operator<<(QDebug str, const FormWindowData &d) +{ + str.nospace() << "LayoutDefault=" << d.layoutDefaultEnabled << ',' << d.defaultMargin + << ',' << d.defaultSpacing << " LayoutFunctions=" << d.layoutFunctionsEnabled << ',' + << d.marginFunction << ',' << d.spacingFunction << " PixFunction=" + << d.pixFunction << " Author=" << d.author << " Hints=" << d.includeHints + << " Grid=" << d.hasFormGrid << d.grid.deltaX() << d.grid.deltaY() + << " ID-based translations" << d.idBasedTranslations + << " Connect slots by name" << d.connectSlotsByName + << '\n'; + return str; +} + +bool comparesEqual(const FormWindowData &lhs, const FormWindowData &rhs) noexcept +{ + return lhs.layoutDefaultEnabled == rhs.layoutDefaultEnabled && + lhs.defaultMargin == rhs.defaultMargin && + lhs.defaultSpacing == rhs.defaultSpacing && + lhs.layoutFunctionsEnabled == rhs.layoutFunctionsEnabled && + lhs.marginFunction == rhs.marginFunction && + lhs.spacingFunction == rhs.spacingFunction && + lhs.pixFunction == rhs.pixFunction && + lhs.author == rhs.author && + lhs.includeHints == rhs.includeHints && + lhs.hasFormGrid == rhs.hasFormGrid && + lhs.grid == rhs.grid && + lhs.idBasedTranslations == rhs.idBasedTranslations && + lhs.connectSlotsByName == rhs.connectSlotsByName; +} + +void FormWindowData::fromFormWindow(FormWindowBase* fw) +{ + defaultMargin = defaultSpacing = INT_MIN; + fw->layoutDefault(&defaultMargin, &defaultSpacing); + + auto container = fw->formContainer(); + QStyle *style = container->style(); + layoutDefaultEnabled = defaultMargin != INT_MIN || defaultSpacing != INT_MIN; + if (defaultMargin == INT_MIN) + defaultMargin = style->pixelMetric(QStyle::PM_LayoutLeftMargin, nullptr, container); + if (defaultSpacing == INT_MIN) + defaultSpacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, nullptr); + + + marginFunction.clear(); + spacingFunction.clear(); + fw->layoutFunction(&marginFunction, &spacingFunction); + layoutFunctionsEnabled = !marginFunction.isEmpty() || !spacingFunction.isEmpty(); + + pixFunction = fw->pixmapFunction(); + + author = fw->author(); + + includeHints = fw->includeHints(); + includeHints.removeAll(QString()); + + hasFormGrid = fw->hasFormGrid(); + grid = hasFormGrid ? fw->designerGrid() : FormWindowBase::defaultDesignerGrid(); + idBasedTranslations = fw->useIdBasedTranslations(); + connectSlotsByName = fw->connectSlotsByName(); +} + +void FormWindowData::applyToFormWindow(FormWindowBase* fw) const +{ + fw->setAuthor(author); + fw->setPixmapFunction(pixFunction); + + if (layoutDefaultEnabled) { + fw->setLayoutDefault(defaultMargin, defaultSpacing); + } else { + fw->setLayoutDefault(INT_MIN, INT_MIN); + } + + if (layoutFunctionsEnabled) { + fw->setLayoutFunction(marginFunction, spacingFunction); + } else { + fw->setLayoutFunction(QString(), QString()); + } + + fw->setIncludeHints(includeHints); + + const bool hadFormGrid = fw->hasFormGrid(); + fw->setHasFormGrid(hasFormGrid); + if (hasFormGrid || hadFormGrid != hasFormGrid) + fw->setDesignerGrid(hasFormGrid ? grid : FormWindowBase::defaultDesignerGrid()); + fw->setUseIdBasedTranslations(idBasedTranslations); + fw->setConnectSlotsByName(connectSlotsByName); +} + +// -------------------------- FormWindowSettings + +FormWindowSettings::FormWindowSettings(QDesignerFormWindowInterface *parent) : + QDialog(parent), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::FormWindowSettings), + m_formWindow(qobject_cast(parent)), + m_oldData(new FormWindowData) +{ + Q_ASSERT(m_formWindow); + + m_ui->setupUi(this); + m_ui->gridPanel->setCheckable(true); + m_ui->gridPanel->setResetButtonVisible(false); + + QString deviceProfileName = m_formWindow->deviceProfileName(); + if (deviceProfileName.isEmpty()) + deviceProfileName = tr("None"); + m_ui->deviceProfileLabel->setText(tr("Device Profile: %1").arg(deviceProfileName)); + + m_oldData->fromFormWindow(m_formWindow); + setData(*m_oldData); +} + +FormWindowSettings::~FormWindowSettings() +{ + delete m_oldData; + delete m_ui; +} + +FormWindowData FormWindowSettings::data() const +{ + FormWindowData rc; + rc.author = m_ui->authorLineEdit->text(); + + if (m_ui->pixmapFunctionGroupBox->isChecked()) { + rc.pixFunction = m_ui->pixmapFunctionLineEdit->text(); + } else { + rc.pixFunction.clear(); + } + + rc.layoutDefaultEnabled = m_ui->layoutDefaultGroupBox->isChecked(); + rc.defaultMargin = m_ui->defaultMarginSpinBox->value(); + rc.defaultSpacing = m_ui->defaultSpacingSpinBox->value(); + + rc.layoutFunctionsEnabled = m_ui->layoutFunctionGroupBox->isChecked(); + rc.marginFunction = m_ui->marginFunctionLineEdit->text(); + rc.spacingFunction = m_ui->spacingFunctionLineEdit->text(); + + const QString hints = m_ui->includeHintsTextEdit->toPlainText(); + if (!hints.isEmpty()) { + rc.includeHints = hints.split(u'\n'); + // Purge out any lines consisting of blanks only + const QRegularExpression blankLine(u"^\\s*$"_s); + Q_ASSERT(blankLine.isValid()); + rc.includeHints.erase(std::remove_if(rc.includeHints.begin(), rc.includeHints.end(), + [blankLine](const QString &hint){ return blankLine.match(hint).hasMatch(); }), + rc.includeHints.end()); + } + + rc.hasFormGrid = m_ui->gridPanel->isChecked(); + rc.grid = m_ui->gridPanel->grid(); + rc.idBasedTranslations = m_ui->idBasedTranslationsCheckBox->isChecked(); + rc.connectSlotsByName = m_ui->connectSlotsByNameCheckBox->isChecked(); + return rc; +} + +void FormWindowSettings::setData(const FormWindowData &data) +{ + m_ui->layoutDefaultGroupBox->setChecked(data.layoutDefaultEnabled); + m_ui->defaultMarginSpinBox->setValue(data.defaultMargin); + m_ui->defaultSpacingSpinBox->setValue(data.defaultSpacing); + + m_ui->layoutFunctionGroupBox->setChecked(data.layoutFunctionsEnabled); + m_ui->marginFunctionLineEdit->setText(data.marginFunction); + m_ui->spacingFunctionLineEdit->setText(data.spacingFunction); + + m_ui->pixmapFunctionLineEdit->setText(data.pixFunction); + m_ui->pixmapFunctionGroupBox->setChecked(!data.pixFunction.isEmpty()); + + m_ui->authorLineEdit->setText(data.author); + + if (data.includeHints.isEmpty()) { + m_ui->includeHintsTextEdit->clear(); + } else { + m_ui->includeHintsTextEdit->setText(data.includeHints.join(u'\n')); + } + + m_ui->gridPanel->setChecked(data.hasFormGrid); + m_ui->gridPanel->setGrid(data.grid); + m_ui->idBasedTranslationsCheckBox->setChecked(data.idBasedTranslations); + m_ui->connectSlotsByNameCheckBox->setChecked(data.connectSlotsByName); +} + +void FormWindowSettings::accept() +{ + // Anything changed? -> Apply and set dirty + const FormWindowData newData = data(); + if (newData != *m_oldData) { + newData.applyToFormWindow(m_formWindow); + m_formWindow->setDirty(true); + } + + QDialog::accept(); +} +} +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/formwindowsettings.h b/src/designer/src/components/formeditor/formwindowsettings.h new file mode 100644 index 0000000..37e5101 --- /dev/null +++ b/src/designer/src/components/formeditor/formwindowsettings.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOWSETTINGS_H +#define FORMWINDOWSETTINGS_H + +#include + +QT_BEGIN_NAMESPACE + +namespace Ui { + class FormWindowSettings; +} + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +struct FormWindowData; +class FormWindowBase; + +/* Dialog to edit the settings of a QDesignerFormWindowInterface. + * It sets the dirty flag on the form window if something was changed. */ + +class FormWindowSettings: public QDialog +{ + Q_DISABLE_COPY_MOVE(FormWindowSettings) + Q_OBJECT +public: + explicit FormWindowSettings(QDesignerFormWindowInterface *formWindow); + ~FormWindowSettings() override; + + void accept() override; + +private: + FormWindowData data() const; + void setData(const FormWindowData&); + + QT_PREPEND_NAMESPACE(Ui)::FormWindowSettings *m_ui; + FormWindowBase *m_formWindow; + FormWindowData *m_oldData; +}; +} + +QT_END_NAMESPACE + +#endif // FORMWINDOWSETTINGS_H diff --git a/src/designer/src/components/formeditor/formwindowsettings.ui b/src/designer/src/components/formeditor/formwindowsettings.ui new file mode 100644 index 0000000..1fd1e35 --- /dev/null +++ b/src/designer/src/components/formeditor/formwindowsettings.ui @@ -0,0 +1,386 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + FormWindowSettings + + + + 0 + 0 + 463 + 654 + + + + Form Settings + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Grid + + + + + + + &Include Hints + + + + 6 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + + + + + + Translations + + + + + + ID-based + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Pixmap Function + + + true + + + + 6 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + + + + + + + + Qt::Horizontal + + + + + + + &Author + + + + 6 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + + + + + + Qt::Vertical + + + + 111 + 115 + + + + + + + + Embedded Design + + + + + + TextLabel + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Layout &Default + + + true + + + + 8 + + + 8 + + + 8 + + + 8 + + + 6 + + + + + &Spacing: + + + defaultSpacingSpinBox + + + + + + + &Margin: + + + defaultMarginSpinBox + + + + + + + + + + + + + + + + &Layout Function + + + true + + + + 8 + + + 8 + + + 8 + + + 8 + + + 6 + + + + + + + + + + + Ma&rgin: + + + marginFunctionLineEdit + + + + + + + Spa&cing: + + + spacingFunctionLineEdit + + + + + + + + + + + + Connections + + + + + + Connect slots by name + + + + + + + + + + + qdesigner_internal::GridPanel + QGroupBox +
gridpanel_p.h
+ 1 +
+
+ + authorLineEdit + defaultMarginSpinBox + defaultSpacingSpinBox + marginFunctionLineEdit + spacingFunctionLineEdit + pixmapFunctionLineEdit + + + + + buttonBox + accepted() + FormWindowSettings + accept() + + + 294 + 442 + + + 150 + 459 + + + + + buttonBox + rejected() + FormWindowSettings + reject() + + + 373 + 444 + + + 357 + 461 + + + + +
diff --git a/src/designer/src/components/formeditor/images/color.png b/src/designer/src/components/formeditor/images/color.png new file mode 100644 index 0000000000000000000000000000000000000000..54b7ebcdee015a8e44ec3a12d7f030b0bf222fe1 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@K3?z5nYFPoKcmjMvT!Hle|NH;%-@pI-e}NBQ zrvdpYo-U3d9MQ=M4Gb3p6bcmDnwZvxI$UtzVP*aHzwrTsp$H2DgN&H<(XFc<*#Olu Nc)I$ztaD0e0sw4SBhUZ< literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/configure.png b/src/designer/src/components/formeditor/images/configure.png new file mode 100644 index 0000000000000000000000000000000000000000..d9f2fd8c072248bd1ac7983dfcc75bce112e5dba GIT binary patch literal 1016 zcmVD)XO`;E7-c$%> zEyAGhrp|0diMaeo(UM8ooU@psW=nH(UpA&WXQtC~rb*6esAXk^Qbr8-^lo5Oka=bo z59b{I=lq`kdH>J*vH-B$RcUGIVx!S`WoT$V!$z-F#tHO=*HptA(+|c{7*D*dmj*p){qQ1WVoxi_-9*e~~N?JW{ zgPfe4B%5sj!z05O86AOAseDG@O{8U{1@ktbQfRunyV2kO3jNks=;?X!H99(a)y#VK zjqCuo4Wgo=ywqy-WLsMs1_rF??d?IA^($f};#gUbriHeE}h{a-OdF&#Q=-sWto3BrvI$7c2;jx?GD;TGU zQX}Z>>~vhZdT@^#gm#qwg@$yHTVSIr$}2redL-@d&l zFE4kbq@*Ybww|=mRqJ1}&&Z&rrY7HHYDUrpKK4X&P*PGtb|o^9k6wYX>NvC@Lzl5O||=*K_&%zb+8CvQAZpM1DLXwnrd6JsoroHg9k5&41RJ zkwIf);{ub(RQKS49Px?g5FQZ*KA#V{Tt4RK=eN^Yb0*3A?Li`uc$v*+1I>AyOE`n@ zZDHVYxlkw+pMrvd4ins)Y2W<3p-eBLjJHZ8MFc;Ku<%fDI2>rT+OLt3k>?4%)Sa%M znJr8D`ue&=1tJ$bG;AwELPDU^>AuIr#9SlzN_VF&tFW+e1-1SQ+C8{O4r9yKU<3vR zI(Kf5i;EKze2u$sm%-rRpn;;-nw6E6(bCc~5gZ&$6S3C8Dhm-S)bK_V)>rZqo;Bl((Cm@xw*N{E)Dn}aF+qi_WjiQ mZ%GqrX=yD4W;1D|f9VGpO9jynHk=Xw0000zQLHV>pgRTEmjmPmE=~L&Tz>HVT3*E| z_sA``d`xT>Vi6bF)_L?&`mu&Zok(f|5s#P1v@>xR?aS3$H1O|%J3Jb??A z#q;V%47E1}nu5Nun!%J&Gnn!`nj-MC<}iW28F}(?QEa8ySD^X)yl)HB8mW!jN+p8> z^PWbd)ef!5ySFd+Zo;2Jv03#1!a;ak2@id+zW0kSUsp}@*zUDj-i%$D*gW|8BZ#8G zrOCVv%ZHH8!oCHEW`QGRVk_(V#QKNXo}+y3Q$Z=c)QojS0dO3L<%K0S(mQKFhZxbd zBFO3Dq*JuBGk^Z`t5IO|!j0Wi0+7q*n49@V_IGX^=n*4oUK^K_pLeNtHg{Q=PnnM6 z^okKRya4|N6A^dgB(ZDjUUca@2F8JxH-Q5D1){UnjEjhw82|tP07*qoM6N<$f|--~ Ab^rhX literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/dropdownbutton.png b/src/designer/src/components/formeditor/images/dropdownbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..5dd964946ce821f00df9f939d9835e3dc15229e9 GIT binary patch literal 527 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6w*!1aT!A!6puN2vqFzf&3&`l~ z>;#hk|NjS)M~)o%{Q2{X7cbJ&(+?gzc=P7X3l}aF7Z*oFL`;}4;p)|^XU?4Y@ZrPX zzkh%I`gQ#H@%{VvZ`rct>C>lc*RI{TapU91kAM95F>TtkojZ5#*|X={w{PFSfB*C6 z&#G0cPMtcnY}vAF*RH*N`*zWyMVBvMe);m{{Q2|e%$c)m*RDHv?#!Gy^WnpXy}iAW zk&*ZA-Fx%q&HelL)06&z{47xt6O{Q9TSd_y1c8 zRCC(X#W6%8JoKWg*dYf2*NaU%9$lEgQeIwu|9{tuB@bG5P5$?sceUfmBd6c(_tMfl z;?NsEYsz#6#-2}$T-VN6*8H;XS;)Lju@mpsI8>gQrtnHkDx}@D;rX%dSxNV-`VQCq zn3rJ3vorr|x_I&Xx7=A9*koG|YncD)3g72_zOshXKV3O_ zzG&$?!RM>$g^fe^_Wi#rpIPUqJToE0>h$}yTnjqOR8qR7{>!SWy_m+Lc=FQE_-7(l h4W4FrnDK`GWN5u?#;rQ9I{@e{22WQ%mvv4FO#sQL_xu0= literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/edit.png b/src/designer/src/components/formeditor/images/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..a5e49adf99d73cdb5f9c03affdb8ec8dfee34cbe GIT binary patch literal 929 zcmV;S177@zP)Bza>=E25^Bq0)|NzVTH$bnDc49RjwPL37L&9Vr(Cnv+RUBv^m|{2VdxykyFLHB z?|z@}v)`WQr2znKqpBoen_7*}_FBFd@w3T?@FL%B82;2lL*tFV&Yw@Wok93420lla z&z86o=QjjT2|c=%7oS!RRu?QP0s@z7_G2|$vP`&&p!)- z4~=l+TA0YfNIekYitCypF<#x1rVC>=rx0!p!}&~TPNu^Xw)M1?v1ZUbE_Fb-qw;2i zzY?Y!z2moGy=vOys6HIICD5R5}(?uj|HxUuNgq%&3Dv`$x$48dxpC6 zMfkLdKF0bVzG{Gz1ul>9B65kws%&5ibv~HGpz!=CeA<+OU6ur2F+hwLkoahs8N#h$ zFq;7agg=kQr_BtoP=wPZoxbJQf+3vy9WBw{QY#7{$kM0q0)(3zVqwzYO9D{Ga;=FP zoNt*X&|l?>7U|#uUKm5+k<#Kz0y@flJ-c6Doay7UprgbM+Bvq+kZM5UGy9lW)OzrR z0QAW9@fzW|yU_O;1q5<+aKGI4W!-;Q9IoG1K07QbIwW00000NkvXXu0mjf DXpf{- literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/editdelete-16.png b/src/designer/src/components/formeditor/images/editdelete-16.png new file mode 100644 index 0000000000000000000000000000000000000000..ef5c799c15f3764ddd48671d8e907b884ce5af5e GIT binary patch literal 553 zcmV+^0@nSBP)RANu;3PQULfnZ9(JSNtE+ju(s1wC>x7D3V$V^7Tf=6{%cfER5-CQXJ#@H%=6#_%o z;$E8|JkkBmKny(et8{ROKdjwuT@fcR76buo+lFOXT9tubODR#UR^jfr zS+DHf50dl9buuCL*EhZ>Xg9;+QkP=n+t@UhvBQUnSw1|ILq=jdy ruuXn6#8`;&a32spLwtq4PC4uc7VgOrHrKF}00000NkvXXu0mjf+dAul literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/emptyicon.png b/src/designer/src/components/formeditor/images/emptyicon.png new file mode 100644 index 0000000000000000000000000000000000000000..897220e2130cd0fb0bdb3aca460b4229b883f908 GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@O3?%^u89IVj3+}8-|2h{L$Cqi9*>|X z3fw*KUg@mInx2k9p66Iq71ni)P19i8wy3o>Gs`lwPo5ITL=L0kZz3n5loBWb_`VOD z$IW`1Y+tCh(fXs)u!OQKAx%^G+VA0ZwOZ5aj`-#nINe5w;~11u#wdy+UY+no&KXee zi2?6D&x0@wo&6+%=GSUXxceNa|Ga?_lTdf3jL|W;1|$|%47P@(-wR1_u|h94Z-vD6 zYdvr=0YBw|8(=S{X~fX=4}xGmfl~UAgvA^jxq?jq)?_XLP7+uneg@b_|HHuG>hc2q w0jI(T{lf#zQ_C~Ub9-M9E6Z;?KS?8g035XLO8$RU&;S4c07*qoM6N<$g6#gep8x;= literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/fileopen-16.png b/src/designer/src/components/formeditor/images/fileopen-16.png new file mode 100644 index 0000000000000000000000000000000000000000..d832c621cc566451dfa8683d373e1aec0d08c283 GIT binary patch literal 549 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47+C{+LR|m<{|^*lNI3yQ5Rh`5 zIqf8f>oE~3LIedeC*{PlV{d!6zY3iE$e{X0!LsKvxfg!_{{8FMuaBQTN#vaW^5x6+ zOP>|WE>Styp|T(YIMUZSD8(-+%o0@&5h$KY#vw z{_^Gdi_&pS^hb^7WfHyDxpX^yu4%4ZE>&`FEfA&q;(E;9)#?yj06peqy^xP`u z;7}I2$l-V{`_D&*v>!X_S47=X+kAL#@ZqiO>F-w;OSl)DT{H8xKkvR3n`R$P6x5ti zHKp?R#%89yP8VJjR$JfCG=H%4S?BDIT~3#*JGMl#bgR@X7F|E%-UJ^m{il+Nolj0K z5c%FZ$Kw;@IgwSdf3B_++p{bF(W9?3+f4Q52u+ho|MQ*Mlg&#f&3fe-P*i%l`njxg HN@xNALPZC! literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/leveldown.png b/src/designer/src/components/formeditor/images/leveldown.png new file mode 100644 index 0000000000000000000000000000000000000000..742b7fb84b03cbe2d316b6187a90232fc2bdc79c GIT binary patch literal 557 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 P00000NkvXXu0mjf%~7mV literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/back.png b/src/designer/src/components/formeditor/images/mac/back.png new file mode 100644 index 0000000000000000000000000000000000000000..e58177f43cbd7e3f7d3886fe04278acb404c3f60 GIT binary patch literal 678 zcmV;X0$KfuP)J~2iFzHobR$W9+ed&iz#gEmW3Y&v zZ*1~MdipzQPYUyy_~Z-G`o@qna0>{{%>1pCHZ|9c3=DO#|4^B1;VbnCc=)W9ww8m4 z5Dx_AeopgXzy^VPz%mf{RiNioWwhsV2m3k#e&uEe!zFx$pqOEPVH#0TK(sQx&BWn* zqx`Q2i^!>#*2L)b+g-FLjck4v377E{R4Ncv5S+PyuQ8$#$gRtDp0Op+s|WHmRvNvJ7zd!z7-kcBSVc z_~L*!C{+>EtN7Fen^`C#i@?y-J4A)lg+B1=;Kz4Z%7e*Tj#t0Sg%}Y4<*J=$W`T(0 zfIE{D;0dX|{tEQ*qfOrK&&#Me!Yy0cg-^T%RZ`vE@$xZX5m<3Tf(V+A=3BoNF8s|n z{9Yjiya48^fXk86pr+z#@Tn<20mDERSTVEWKfT8e{7KYRtIBHHAITZSk@xgqT>t<8 M07*qoM6N<$f+VOsGXMYp literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/buddytool.png b/src/designer/src/components/formeditor/images/mac/buddytool.png new file mode 100644 index 0000000000000000000000000000000000000000..2a4287089b07bddcd695085b95ba5c23c366177e GIT binary patch literal 2046 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/editcopy.png b/src/designer/src/components/formeditor/images/mac/editcopy.png new file mode 100644 index 0000000000000000000000000000000000000000..f551364464a7f4500a2660fd3aa450e79010b0cb GIT binary patch literal 1468 zcmV;t1w;CYP)j91nuo^rMU``FpK}lmcH2BoZ zAH4JCyEWmjwMzgT#=uOCdmd7JuO$v)s8lK_84mKr3f?+?`0VR1?tg<)*HQ}J{^;_d zx+S0Y_4P-WQtN}){+tE#xOPNB3Z5T~fVOJdJdnQKmA@NUp8wZ z?$zp{)J%XNrjyRSus42A?Cl?jb<@afrs%r1WnkxaM7vZ3@+)At< zl`TV!sF2PwGTtu5AKvyjs4rA-qt%${9h(@D41&{(T zn8XDH>P|Q8whCx0La*w<^E@zO22eD33b<_!Q`>H{03sKo>$;F2@wvzODg1sK81Se7 zXg2VlTtfnRF4nRvL9>8>%_G3Q_jpaTF#^JSA{Ha<@i)NsIHARWyv9?(=O$}&1Ssr8 zEDDn5bPZisMnMBy1?bt#Jx4g$#%n0TPNX!_qa+0JbW~kLfJ9nCPGg4pF{+ML3+gaHLO^`>>3d5AaWOXXLDedCV3tU)P&1)oPTCjX*K5}0@QqH zO(G~76n^TCB#t^`Ik$*k#>bF(lmH~zwB+suqqLR=5F{VyIR3p>h-svrPjk~WiIfCT zL!y!I&5m8e$d!l4mh?p>q)stJ7Fjj8$jsFE%#H5~03raG4_Dc!$cT(gi@c~ zLseB0#6L51ows7ID$$1^8eBO$E%%%;5Jcm#~)CE0Jxx)4}dz+2FRW35VI&O1b5c ztKVJ_qymyagMj8jXkHb6&l~<30XPL<7Qj^iH-Gr@Oj@d$Ao7XPguGq^8idrf;(q`V WtAfH(WC6_p0000P)<*VVWcuS0Ev&yc^UgmMxPuCq=o+h)UUDGbvCiz9KZ#z>#2T zH*7#DEhZZSSjH(6t!!&*4Y=|TLKTXy*{F8wRwLgUrhFc}3~-1;E4$B<4F?4UCH_=X(?2pYg3Kt<)7W_X9hIt7q`_@k0wj;P zCfM+)m>3-G@5iiwfL76-3y8I0zP_2K%FFu)2M1B&N>2}dn~=~air9DByWrkbv=$cr zW-u6#IhUKu`P>}h9olgM)(;$H&K!xq0;} zmZYZMsIIQY*49?+?Cdmh{`6@qiHp0);s~!9ZNqy7us5e`YOW0r52ISG#^mH=tgWrZ z;^Jb|YPER0Aph2D9v%mU*9&I6j%Do_vBJS&_ko0j>t$tS#t{4a`=gDG4Gs(p;PHkA zOo@)}p3bJ6%BYPfuy?umb~`)&d?Yt_vZA5_v$L}?FE0-(D=V?Sz8)`Kx@6?CPG<;E zsnQ=5_9+F>$CJI__W+)D`vJy`ZD(eN1y)w5NlU}S`T3K(Vq<%gZN12L2=UTp0B0!K&mQ7SXV z{VXv z>S`#owl-dS9bgBYJtX4^;?#e#IfgL1l?{0uT_METfDHM8dfVYJ(58)+?c}9jxoO5BWAeIx)5pQns^E+0Ukuh;h ztv1mAZ^%=ymh-Ri^1_23+|>mnbq&=)SAjx-yzfh#osAUHgyWqb0uWC>@)i$fL!<{nxj2az zWULo$M?Zl;A;wokgwx|B(xbTVAc`Ty+W|iR44{biW-+;(vhGgE-Txy2O3^8bsV1R< zm`6NJ*b_@c-e-~mXEE9>Oc7uvhRb}>at@-pm;H+(Tq2CyifPM^nN7Kjgot@UgtZ8t zy~v}fZOFnG2`AXNx!ikGwxJ|~!)7H?Vr{}8S}4N(h%js^o}-X^&-@MJSdp!hcmgc| O0000 zME5R?{U5q7s2fuutc-4qF{a%$rcLUjDgoj0xc$z+&~Ze#piL)^=ggU5&Uc=3Eyr=_ ze-2As7=|$hGX9AB*qTEab=467j4}Lpw^Qw)>-ihDuf0NJWzqJd%y|0lwGJkY_aBwh`N~I2bKHodr zwhtZ!Kw8`kh&pa>Z)s#?gxO#^olXXW!MRK(lYDXyJOt+Y`kK1Bx@c%h_FuxFA1whi zvRap^O6Bx=y)-;LOzZ3Gq9l3EAOe8^g+ig=#>Pe(=caMj?~Xknfi#+kHBC%R(CFx> zC{G5#Trq{2as@!Q`XU~u?BOAeEiDn5CjQL7RRBe&YikskoTR>) znR^w&p5#3jT1R*I~?g8&J!EeUt96-WL0D-fYYpY~$Z(`3R17PgqV=^NVSN+6w5{@h` zk_CAV%6-_fx8ihTR{dFl@-6@~Dh>rB^AwIQlA|ST+l@$dp8y2wp2uQzSyS#cOsc@; z&?^8zJ$27Vs4u!Cl&kS?8^D7<28d^iG0MPl2g-c_;lXwpTRz0RJGv~+7@F&weRDJh zpgfIwE=W1Er~&drVIHop$g?fp3U?upFPm-&YWoTaSK3S<=2gpY%XpS?un3tA8j4m+ z)a}F8G6eO3?Eba7N`-AG$2~>I5N>D7Z744?i1=r+frhI?2l0?6Tmv|T@?0V=%8;)P zK)L^8Oza8e*khUnEg%rwC)|vtIY0^!?r=9~MkZvz*$9OeV)at%1#efX%1c=Lr>4m5 z$8K{Eu!0)TosCES=!gauqC&ZhDD(ZO{XC8aUcaLLxrpnxrF95&5OZ@?57j=y>XpHL zg5L-incrCwwmBL-=Z^-!QiJ!nBxRq_`bE1&xCY?p?`RE3ol@)1w^t;@Lolxwr3 zOkKwO1!fQQ6u669HJ6J65FUe`5Q%PUYDyYWk9;W7%P5+FBC>dz&j{Y1Xa* zduBDF2E5^`{AsbJM+5e7&4fnG!2Y_!4fIyQZB+BOgJ^ZpVO!(Co;rvOXv9B+@tY*M zxFs5}XI3NrH3XHykX7&+wNBB|d@BdaLoE!w9I)^PY8FJ?V5Q0lC_i-qTN^9HIc3*? zJ+m6&AUclZPam6mG+<91Lb7ge{F&@xHuP z!Ws>o-$!x}2|vu~0y>0F;Anep+3$4@_`R;7fX_7&@VQ5UabWUn*W&|86IQ$jCV(*j zbgp68GkCPK;88tXGd`dZ0~B}#3cxe)s3OnZ^K1M%?tfvkibhU=sth~<$ z*}g0@RW?9BH9+w{b4gg>4B*fd-z>x7jR+<fdqFuU@6;bFe<4>13931E4Ej8_0VpF@7fw=!*&ARG$g6<`1(y#r-X zj8}k2cSn%|MgVQU3mLb~^dZ1_I{NCJHtB=8$PT=JTV;1^Gt+i|YI-P)Pe5(wAcFCG z*?>BT0BQqf+8cP)PWbN`(Tf$ofjpT%H<eq= zj}fi!MNr8a$ogK^WCq0iH=-mkR6Av6ZgJzNrTQZ&Ve&p|sm{zTZUDw*Lg(#c!h&(! zIlF@J>$i~6v8;idMIoR91fWvFjP_3_&ssV6Q)dq!(wWUm{fZEF{+c}eYFg2a7?gnauVn$1uQX^X3jbQYysH$lf z8+_PqnCSR>9jG)LhimmMHR$ckdg_}$X*LemfU#!dXtlnDh}=;SE5^iv*pHuD7I@Rs zh(yG+qn$Be9GC#E1J~LSPLSTZbH}Plu|S;s{!f7x@C!IjzE4%v79N2!m^(<|v>8W4 zUVwE<+V>PVOX{{X2Aq||z)3PmoD4K_0ReF`@d*$)0<@<9RaFs5SCIb2&l1+%{$59r h3P?qy#cJ-%{{p-I^mMwt(g^?n002ovPDHLkV1h4QQJeq( literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/edithlayout.png b/src/designer/src/components/formeditor/images/mac/edithlayout.png new file mode 100644 index 0000000000000000000000000000000000000000..ec880bb5cba9b40cefead20133a99780b0de49d3 GIT binary patch literal 1395 zcmV-(1&sQMP)*PS)Y z?CK4Cb)~@uVH?^ej68l0lvJn!0b=p`|HH~cC)_+5(g}ofI+}` zJVFv1z+>Y{hXfKc8VxX?mxwx^BBu*R!yzJ3A_tXl+cjZbS1OzLHGs|d69zBynn5ks z0%@9H$?w&ttR%PuUgFlxoc@GVPZNqc0}Rs`(+S}02u%Z+&ZHKV$IecKJc}?KX)hpZ z)?K+awVmk{i{*lT0;U3kh0nuHZKu6BA)FN)=a?ISxIU_7f;e?O0|HJoEG} zk~ktLbO;#j3xgn7{dOHs)&k3UPGTMnlNb+E676q7JzxbGgtY|m2qA1Z&x%r2swXC;*ufRe;+VP$h(I?g15z zg>L|7Hikf{KXq7c!GI@r63p**luuVPezlo$k(iw(;} zMPu|6V8$w)H8iU~Ai4s<0C2Jfc-=?P6=hj!z!nqB%5N>Fu|;^px4#*g4tP%O&`L@hD*s3)wrs$Twknv$noOk?j;Hi$*gFL1c_oW=0b1M4ekD zNMh1lL^gq|l9dQk!N|VRD?27Gpdkk6y$RK{V^y+BGe_-Z)lt}$Ai?Wcp{||GxXyPj zSnm%oqCA|I5ITx68J6Dn6FB|p$CzFGp&cco>vKns9%Q!zu4hkuq8Lq6#RAfaF;tGOE$pxl8*tyMJpBK)Tq!@%7$JYJJ3DC852G zcOZKJhKv21=g-Y#AdUpUcw=p2xYD{QV_RYg(&T%!f|R{{6T$xL3zh`cpfwms~zfq{X&_^u1-19fsgM)IVWx1p$5BO-*BL1bWU z{pyObv9S}w!^0=(10kI-M*Ad3@{AjHSOgC!w<)p^^0LJ?G&QWKjrYX2XEIdDP_0*% zwyt&@2YPyXwv&7%>XAOzyiha=38{ia!b{5mW)jcg?4P~MHL-1K(bd&ORnV^ZsZ`2a zko<|dT3Ehj3rr(R%{xm(NJ3RE^zpCn@4>-+on9I-{1Z$-V`F0*GTOY~xWL~RJRSNi z)F}lV*xL!GPM?RWx=ncyMXP{m3G?p7UHECoV_ptKjFt`(z!!{TdWLK`5}NL=G*FZTj0$3%aBYa^OQ&=t|ILu zYHn`+pw`6K*=)DDipR-%o z1KeM?UffXW<(m)5*4EY|-QC@Xk@HMbNown7v`=y*4@OV%yvifAVvGBUcp1kP&++N# z==ghdbTomNQI15Nb(Y>lLqkIoa{mR%huJDx1!k;7uC7@u78CNsOb#A;Ps=s5w|o7_ zi_x#GamHl}7ITAD+A4A%u=xDBE`(=ek(yU(P)Y!59tcJFaFxuat!Nh#Q%SgFY6;{9 z8rNuAn}`a4O8`P`6$B7)X~@lUFQk=Ho@+p6wL5{3tp)+OR+z2mHzJKkC&J}28E+Fn zr3nB%W-aJOG;-w}JbZB9C$RD7CPKjF=q)qCFsqGZUkE~N6#y`D=PBANTkqv786mp) zXv&(M{xTlY?x%v7-=_Y(HU5AJ#_DN4x{UKIPD}UplW*jqYre3U|C~~d%hQ$1#3NcP zf14qwM|wLi1UYhKSyJiF$b_Y}gWq%mLtwZvibd#!32<%tvV$`j@_j9ySD0I8IbvSt|)xUXgoXaE!lMU)>t0RIDXYp~}EGa(KD0000>6LNbIlz6B`2qQdv>k|G?0tYZ=1OjinM2VkkmY zp~4^(;2&g3B-)|vF~muTeRh-Bbp~$n`RqGC{@(9i|q_RnueE3+;tf5wkbA6KQV zjf`8qc<&F=5?I)petkb$dHMYDTD@MMSDAKlU@#caYPDVf2f+x008G&Bc4;&kg)5}r z@8=|;19O3p3d{^Ep+=Kyq#_`jB-*q;R0Lod5>x_E%K(tTIfrwuFhFvRR2BT210#2{ zssJpGLeh6lAgL4yB;^3uuo9Lg!RTW&_Sx8TD+5SXfhLDa0N(8Fa`O2&H7Pnd=ErtBGWLLc0RTxTx!KdCqw%?>FKfD=>x6l&)9DaJQDF(1&F1tw%<^+8=^n5~ hd2PHW)PMhX{1-0^X)=$OtQi0R002ovPDHLkV1g?%0$Bh6 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/editpaste.png b/src/designer/src/components/formeditor/images/mac/editpaste.png new file mode 100644 index 0000000000000000000000000000000000000000..64c0b2d6ab88a7edf7b4087622fdb3e58c57ce61 GIT binary patch literal 1906 zcmV-&2aWiNP)2`C_)0KC{3#V>P6y5 z{***fN|TDJMNOkdidvOO7HMchA*}*}7Gf?oHpW-*jkUcO@9yl*<#ae&W!3d!3;Ig0 z&S=gYeb4#6Z_dsNDJA73gxC(OXK{5&DgVxWN&%DvUaM(Z)C2pT<2W5GBJbHh0G$Pp zUhYv^>8=kvz3Hc~@7wwFCpNBm^5Vsd3=R!3H0XJ#;K4yPdiU|>+5P0^XFhm!=WA=5 z!q0i|5$;h&3BZQ-Xm(dsWsa*MNl#A?;c%F{DV0iLObygI-2-(?v~(&$`FtMRwyCYHr6N8>qC&%Vxi1+d^%O{OfySWV z!oU88nVCbwMFIkd=q|b@@Cku&Te2)-AaKE`2t4(|&%XZBFS{NBHULqe8E8R?&w+Oi z|6qMzS%4!)jtE`XHK{oYVAZM>_%b?@5K>CdAOzHhg9HqnfKOvQE4V%}$-gJZ2yWWB zXY00Ydn&>qmevLc1$3$_b?U-?qE&u+Mw0EvKAQ>v`%3`kccl<`ivz5QR+6jKxa;R+-IR|+(t?knF)Iehi$%|z?!Rd1A$>X&3PNiY~x&HVlC&Di#hB191Y z_U_#)J32aCLkN5t_;n4#(9sasx{E7O0EVU!Ff{dyna!#oKA(?rvMh^4B0)_}jRFV+ z0_Z#-&H6t3$^q3ak1s>THFTx-nbY~tx0*}RSANJ4&{*(}tW zCJV`N95R`VDg(tvXJbo%O9R>GfukBlAwB6D7&`cU0$WN3VkxdB4UAP=c(DChzP#gQ z>bE>g&BI%{)<44O)2E5WVyfn+PMzZL;lpYyp0IHI_;C&$I>f<)2kGqWr2obsww>36 zr}+T70$^x1%ehI1)*T&e`{7Y4x4y#o13S3vJjP}D1exV~XnA};mZ;;{hn?zF93GwI zn@{cK%9Se$K-YCbp%9gomFhYYiC`E8u4+>EE^mPlOlBqVrLBDDz%NMY^(0a`gl>T* zNatK)ag$UkPj}wsa#ethty{R#+s9b8jhV9(7%G7LB4|rXi#oJ~5L8uFF_8psPL>4_ z8Vsi-yI%Y;;U&u$>3o-|{>vDF3R+t>uzb~{^iG7iF`ObcV=_9GVa&538kveEXn!n7 zLlHpj-rF^Gb#(v=D&?fT(Ux~KKLZ-HLC z+c&t_6KBlMkV@uBr!CJm2tXFk1n&(|0EMGof%%L)LYH^Xz_N%SZx7(Fj8M0-mV#K( zP(?gr(SPb~lJ)=Lp@&}}nf75>4z}Ye8$Jzbstiyf1*J@Ju3QF`8zMe@p#Y_-t@QCg z*w5K3XIcNqZbCtWtY_Pno?Q%G15k}7C<+M(i7aM^yJkRHsBxao56mz)Yq6u_EqZ37 z=z$c$pss@3mVwXEP`h+pBqfp*<`x0aG=WfNp8BU!y!u`j{kPJHYLn*r3bLj{&a^ST zantG=&YUP;0$}kH2=TvJC{=4ynAdl&XDDH#H>_sOy056tE{HQD(AL(rFo0aF2No#7 zD_J_{-WPyG0%G9|WX8Gyia|}uXhL!=<}#L& z=3u@q@$IS=jrR)RIF2$H3%`bz12b7k|BUBpmx(!PPNil4Y*zLU$7RpDE$!>8n_8bK z0xTYQdU~4C(NQMSL10eq#J$~ z14yUS>VHI6R~I)1N71TR5^3HH8t;??82xnExzRl($H!B$%YzvLCV_Dv4fp^Z2wHj5 za2=-{fcgNLn3zyM%iZ1G#L_x}hP70!-iGE6{oc}yx4p09bL9ZK{@CUG1c(6xfC+?w zN+7^INeTce#WOQAT)%#uOP4M&IzCO_*F;6bX8d)Lv#xaB8@_h#Pk;U0k8XRwZrQiM zo9GonTn98DPzam{bU*@5VNUn?gSJF4Gds(~__zu>Ff>Zau0pSFK{P!&W~CD!4xIbw zgG;C1?*rxllM)%smn;S&2ME9^fEZr@1VXWqTT@*9>@Q4b1NfuO7>(NzX7ndJ0| s3m^aX zKl$a)fiL*(_~ws)_{Fb3SODdguK*q-fPeh+9mSo}GyuZ++st-5a{6daiiVgbHk%F3IZlo{&MyXLvknm$ zhArL)=DjeD7yI>qGqAiISuK^-rD6RuBZ^SQ8I!??-xvzE5CSPN+^`%l3* z4m>(-u_CmgdP>_ka*8#ca;D_Mu({&$Vq{7Y?=5|2F+k%X#=M_^gLy!-Fsw8E%#dOx zI4}ZeX1!X|2FJWNcxQ0VpqiPr!Z;>SI6sdy&psB^&R#5LI4i7{YrOY}fDr|qI9SYS zTPOvN4the^y)fbI?%uOnMS`={vK)s)5&4|E`Bqi`c3S|m)*?YTKi_b2+#>>;b)@YA zX3+zRAQ*vu1~F1A5w{avX9?Z{V2beRlgFp8U%&p6TdYJ9hzJ*NuV~wr!-G18j6rE~3uqi<11S~OtB4PQ z7$cuNK47~o^t~s>iMI7nK-e|xhNfu{JSYeX3h-e9ZrsIGa0Y@iJo%)>NMY7B10u24@_b&4dv{ znIgtmoHb~HuJHsXl%gCSSjLqB5v;Y`qVC;4h6#!&MiiZtm=ec_ftX;}MEX7e&^8Um zK#WSxnOWy43MocJ8$babEnw|f;p$2$(^TibZFjb0`re_%;vHPR+u*H$ z>`?(@gtX0s#-R#b>xkP-*9H`2dATKofQkXGD-#@3hTw&q3iG~WyWZ?)ffexHf`WI~ zhfDBS3prCK!dmJ`Ho-C_XgrK#uGN@QK~xY?WS=H{@%!KL*T4K3BZ4X*fKpKuv{nfK zF;uHih4;RK!jC@t3@{HC@Xepj1_(do7WIqe@%K(nt+9LvfB!c;{lSxeRRJG*5?=p+ z+^xGh_w!e;UVV9Vbi|(AZ@~jTSl7oAaN`)=pPFZX0ad+HQK80&BFOt*)VfJUK&c>F zFZ&A+k-FaJzHvbWZUzC?RgeEad`Xmz(bD~&)akAe2aCcs1i#NP)D)2BfM%teFgTaf(}Mjr2nyC*=5n^>hf zrTl#nJTs65p`wUlY##v!dp%CSoCMi|7cXDU6wr?^5?Fj5L_mCz0QB!9s%{kjdmwt> z4GW_6_6!i6{}^DSFd#Ci4~Wby3(A^Ss7?ip`fxx3o{^Sn8K~OScnH}4)FTcAq9q>? z9iJsI7Y0PfXa5(7w4@+JD}!ifyR#BaDLu`dw&GEu`S})Ddou$>KYtBq&W8cfDDeQ1 zNH|C2Wk@8TB@-V@W&&ow%MtZI^!OSGM9V%P`h1!|^O>lsx$QPW)qsjB^0)kS!nq`w z9TgQpL97^)3Sy7GU0GmvDu_fR^QBLK1=r`@xapon?pmxwo9koX1{eaDz;AayR#iK3 z3tS)tQsCMxlDc4x2M9=kk&BTyLAGev?+H*<6_M-PS^y8r+H literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/editvlayoutsplit.png b/src/designer/src/components/formeditor/images/mac/editvlayoutsplit.png new file mode 100644 index 0000000000000000000000000000000000000000..5a02c944e03885a1d144b5a4119420e2d0de2bd1 GIT binary patch literal 872 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 UB0_&5sQ>@~07*qoM6N<$f^ZKn4*&oF literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/forward.png b/src/designer/src/components/formeditor/images/mac/forward.png new file mode 100644 index 0000000000000000000000000000000000000000..34b91f09fa3aa8e0b329edc97054c11defba8016 GIT binary patch literal 655 zcmV;A0&x9_P)cJAZUz`NyfQlF14XgvfeqKhRM8=g$nIpi07v2Pm$T0PG z+s5t5ev&IuzPy9iGfJ_*Szxjq-U_S#@di#sMFxj0Y^u zs2IrSA-xLzn@Xv~L10-`OD_1{xQ}!;h8VCMGZhEg7)ve(nH0=Nlth##z-1t-sznz( zI^knI8`}k}=1Q^8xg4a|;6+&Z7-tr^27CjyRJH7a$AX8**swS%Z2U-ZpmSmP5GMj$ z1y+C!prEQ%7kpz7mY14W%oynFgWlfyOj9X{G^0Np=uW$J^8**~h`aaUdlDkgAkhJB z2loJbfPHrEakyVc$6#mx5^=)7buY9XEP!Q$7GNCj4jnzl`B(FVY;?m5b-|rNMD_xg pfC->zKW))&VdLne22{OJfIn@@R^)Beh^qhq002ovPDHLkV1l$X9{d0R literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/insertimage.png b/src/designer/src/components/formeditor/images/mac/insertimage.png new file mode 100644 index 0000000000000000000000000000000000000000..b8673e13bc617c4796e8a6a272cfc8938d17c152 GIT binary patch literal 1280 zcmV+b1^@bqP)Fy+L)HFeo_&^jt2#QIBAPyXeAUIJ3{}lfRL2x1{3OaX`fe?u# z<_q7aM58g7bRHc${iv$c``&YoRcz=A70Sva?SjRr+PAoC?X`DZu6XbHHxUAe$U$I* z-NiNU{q+#VL2K=I>;gmt0AtKaV8?*4OVE)|G{fl&``$lGYI$VX`tp_O9e5C}+HoEQ zDvdKtVJd;$8Ns{+6h$b%vxv#%9;^Alii8J=B%lZWByWfdvKu-%2zm*M#XP>kVj8~%3#TK%ypJv$>h zKxP<-llcj7WFMS*37$Iu)jVVt`ss)n{O2IN@C5XRuuy{KDiZ@EHo9=`+KeQCAW@yU zOW4n}tIiObUAVppzpcaVZTR9GSU0OnH9gCKk7p(@L3;paufk6^0f98rj=QM}ml9yc zyaRzGg1|r$!qDpSTC2O1Jxl^|sN3G?sQ?}Xox2gOO_op@8Z}mkVP9qZSx2p+#zIWW5^Q z<}v!6*U0PvL?%0-X2GEtZbA?M9$h{Mcuiz(0;S5wgymbbhiz){5hUAY_4*~kq`=bR zj}#X^CG|{3dDfue$7u{WBUTVI{~F~4oUyu z{0~==IKmDac(cf$J)}}=631sLRX@Q4b1W^CX=V*_@;O28Ii&Lvv3&<4Wdvv@ z^AjMm4(|h&mrGncbBV3hCIDiD*4jNP`%A1}-k`L%NUoYAE=I@@7WUNXHQPi!qMKSe z?!Od^7brwuQLsmm&Iv+Oo}a)EosURI&bjFEA#jKXQ3ATk9jpSv4yvK>WIp7Vmc@zV zgQpVed;`4t&k5Y@hP0X?GI`b_A^<@XS z+9zDUJU@ZbVjlB&8Bk%PA}TE4oP!Zf6cHFWoCwwlao~v%Hc~J#oT|g|+JJrL49oUg z0^8(gdw2d0YF5Ruh|?PS zI}CRIE7_aJUS~HEP6F`kGW`7pZq@7cH}pHf)TuRPIHQ&UR!KJIjGCRCtjQSncRnrv qX2OZcj59#tR7g23Hhum6xc>%pC}OM}#;hd(00008XoPuMz?Y|Rw{0vjgv+B!ch zy;dm_ut20@$;9K+a_T>ye{*<+*6l4ly3tG?44ev#Dh)O)`svI2yTab}@8^|GHkfho zNeOTfAYE#xV zTYdQ2V(!{EY<(cpv1OlC|Mk$nFJwxr5_&M{ssYb!Y!6iMT-Lnu z^%LX6O%qa2gs^lmow~IC?DKoCclAn#7p^FN%^<|Ei6_mNUm#sTTOc~pY5nu>t=(d~ zq~A~M3_fyFjkTusWO04OyzP?5OwSte2Tr^2{`Z+?r;S>P_p|Ifa>5t{+wz6q>78?A zjm)yTU*U1P=G*Jdx;OcCUKB)#cx$;a{ezuM^;+MJ)Bq-|n aKk(|Y38wz&&UFFC3WKMspUXO@geCxP=D{TZ literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/plus.png b/src/designer/src/components/formeditor/images/mac/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..1ee45423e3958221bd545ee6f122d989302cab3f GIT binary patch literal 810 zcmV+_1J(SAP)B<|3F)+>u<}zfuZe7)NT=Ybf%p@9RIu@KocNNrs{{OFYs@fvL zX^u9hJWdQiy;9GCE&}Y4r^W#*azk#i!X z0tQeD-}3nENIJp}&;hlob6(uM5O*#)UjX|Y5+K#c2tsS zZMN$5yBRrqOa_ap_a;nIVsn9|z+v%N9CfB%If8gRp1^qXR>?32 z*3w6Ivpw-?m;j)qg+kXMEtJw|G)lG5(gLLcMIhjghO{k_bu#)CdcjL^)(HV4rIl#l9 z`bJLq*V>D~Rp82RirDxVP5}7Rn~Yjj!U$|-YlH0pxKM8{H$~096rTrE8Kjh7nlCq5 ziUE@h66K&=O}1m34oT2-J2r6&15LJLTiV(KGC9`_1Eo1-3Ch!yU=dz^csbc-8xNq17Gynhq07*qoM6N<$f^&^p>Hq)$ literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/redo.png b/src/designer/src/components/formeditor/images/mac/redo.png new file mode 100644 index 0000000000000000000000000000000000000000..8875bf246c5ec3fb50ba3e8b809152aa421227fc GIT binary patch literal 1752 zcmV;}1}FK6P)w#k*dH0@s?zwlm z0O;}}(dB;v|0x0=2BvrrNkpMUCb5LD`&S7tfe%rtTCFuCWa?WIGmWOiETbtk=jOfm z%(J%^t4pu*eVrs{`&A!+g#61X@%!_DkzT;K5LT=LJXbRjt5xO#LA-izU|SLZ#}&kKD)KqsB>}00 zz}kFWv)iH!9Z7^gGXWu`YCSN8y|-oWr!la92C%9W$U6e;JOyk$38Z}stSBZK5}LgR z_-GZqLNasIKm^IM~&O1z>aO*;|pRrPtGU z)-_h1GMe^ZWuoK2$~`n^9*OW3w~F_r_&zDDmJ(IC9=Qltui0T zKgP-r>09L3*%5)laPw7~`l_==Q{@#Py%q?kBL3MVKt(=gdqj*C^oFM7POD$IroJ;R z5d@Mp?$o!aGz^vwM12L6pQj307CD{TEh-m5M7^o%M)ehwdHeT3!a-o+c6wwZFq6LV zCW+4&M^k2QTM}=X6m|QgVwYHVisi;HLQ}8V&x$CadM{9^Bat-PlBtuv6 z-OYPd4IIll?uv3CU<=?zKX|bnVZ3IQ05NG=!=t{iCNC&{sIWDlI&+t2V23d`%K*rvVi`6|##YIIhmg4;R={PD#~qN#Jom3yD0fq2U&Ag0hOgYI~2U z0esa>qI(DE=I9?c-YFm{CA0kOt)hB*g6)VP2_<)iFMZ*~eCOBXyy`}Ud&B}hA4m+6ctgT^KX8ow zoJkJ8tI~^V8}3%z1PZvmH*u`j(-U8CLgKi%a=B?wUHdZuk-Uejun+ON3^>GY=8~n; z=c*56m!4`?#uVOoYohZkJ|9ci65}Ln<+FDtyG3l+RehnQ{w`4W3vh^DI`9*FPE(h2 zLdv3(s$9p@$NpvSh+tRl9>)uQM9gyd36dtwIRCKLJ*^9i9q5D`LzwnK<~7bw5=lV{yz zEPEMyh|JYf=6>-6o@)!_H}3L`tBf&4#vCbg^pQDjI%PsaK};nao@7G#&Ov^wkY^{z zSi@!PzA{&Lnfu9ONDL!J5Tl6EGFXRWD8Csc&kU0>2FaLxWR4!z>YuoX_E)M!VDz;D$H~RQtAOdA+G=b|DVXfKoI@;pQ;7a z!c!9D7tA1OVDFM#)bRFI`d!AXvO1u+s;7%%2#0KP!Un;Fgaq5gCk{*~e&)l-9KQS# jBO6=3qr$;M$qWpMPuN;S?ziRvH86O(`njxgN@xNAXMir1 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/signalslottool.png b/src/designer/src/components/formeditor/images/mac/signalslottool.png new file mode 100644 index 0000000000000000000000000000000000000000..71c9b07a8d8e7b56c3c8370e5c48375d33c2df70 GIT binary patch literal 1989 zcmV;$2RitPP) zvh%&?ec$!%t*o{DKQF%=eEbXF+XLLg|L!{Y>T{3o8wfy(|7!2OJBEjc$+8TsHJ;~T zj6o@dFjd~mT6r&f!!VS&axB@c=Xp*X$26NwzV_&I03QIL`v)`A)0{YQg43r@(`Yo9 zpP%QgrH|O?EO--@5xXA3x4DY}HJ`)oYvd68>&dxGDJuM;ggC#ih zrdkb*ZQin{Ua4i&hiX^@QRuU`k6QQ$I%Hq&pHXxEE9v4#T=*-P^;aN zwbHb*Ci8;<+_d|Z1KW4L{>1e3s(Ecj7#kX6_l?`Q|BIjCp|9UYQjJmC7eH@aHd)nPvPy5rmp#DB#Jb595ar#}R?=v$VA2z$8h+(9jS`EfF~e1Grq= zmV@$)3w>I9C_-xSD35*X9tUD-_5zRXKTJD4Pn_gDao|PXJvztKd;_JySSwP91R{-- zp~wr;G#w0}f1p6zTpG>gb}OYC)_M5BPqTi_Fq1Qj{NSmV`SCAb;mFZhC(p4{^Jry7 z0;hLLeU_&9zE7o68GH)p2cWg1t|*+QlEqvOOf?+l`+GjeclZ5{$?0>T6j%Uw@Az5n z`lJ!?^1NfP>;OTheR0K_a(Oehopf?nto7Y_~9C~T9F;e_MIPSR>Fl%VU}H90$PZ?4{W^|}X&!f^It#%F@ng;Ls48-!IX${F^}cP82L;Su~WqSG-l?|vvnyOlB1 z$Vn4eSm;o6TPvGTOjMp{pm<}R>kvd+I}C6 zMw?TorZ|3bhJ}SDQ5cgO=(IZ&#&E?*Lc3kyhYFKr4lGvT=>aAzohtB8o?f=o@|4J)p(Fvo4q>@h{?we1lz>XK+QGfGcFas9b|0a_x42T)s>$cPB zxbhK2LZ;?%@sXMX-{5Gb3i z2tcTn5D=2(;qpXC2m{7ALpmo{VnwwQvU&3+Hf*?(daXiX71rW_=UGNljp%k$@&bG9 z_=j4nuBe+pbyjBJ*=L{S$dMyLd;gcOgxr^=bRV7%Y`WPss>0@sj#SY_N~dMW@{GXq zQCg8@Ig9NkwWP-Sb*s(BjlsoA;2Tq{+y@T6e!^2W+@`=PYps@yMOn!J)@7QuGh)4X zl~Hn1DvYj9@O_O|+IBk9zyds9k*3%vg5{;0E5|AqhKK7}B@WEux#bT6crq5@U;yBA8VoL z`$p9Kmsh3YV~jXn6GxiB7gKupT9bC$U_7%APyjiQ0$$%- zzXaLgL(iD^j=bG}gvFl#7OeG@HdZOCdQpBG09?QI#uWkBzG7WK_3l%^|2F_f_doII zZ%WX8VR|_SG9c~U7kz8I@Q3FCjsXa?{(sT$fuI+DS-3CM3cyV-ie6-WQCMpYD_s8q X&4>18D&iK100000NkvXXu0mjf*I?IE literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/simplifyrichtext.png b/src/designer/src/components/formeditor/images/mac/simplifyrichtext.png new file mode 100644 index 0000000000000000000000000000000000000000..cdfc086bbf871f36cd7193a4ba4b8dbb96acf360 GIT binary patch literal 1917 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 xS8%`J0SE54h~8%x|UWDJrFd7@*pzS|20U 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 KMNUMnLSTZ+RFne% literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/textitalic.png b/src/designer/src/components/formeditor/images/mac/textitalic.png new file mode 100644 index 0000000000000000000000000000000000000000..0170ee26a6fc568e9b6490e4a45e04f4ff55a19e GIT binary patch literal 1164 zcmV;71atd|P)8>eW)l!AzGVM8#k=|EC4uK5YENM=>0xku0=q3%Tx@Cy=CPKKK1sA6|N30mSsXZy!Ca>jN5} z@ZcGS$#QE2LlE2rz5BYAo}Mll$j*K|%ZG;~fQZL?LqSQAOdFul0MPYJH;4ctijdG_ z5a~b(`Hfo!07M>23W&KczB~`e8_x_6C8wrdNy$o7u}}z)Kro07?K(Su>4*Gx7p`nl zYUZGg?Wzc=9^N~4?4`4#hsF{$6F`yxG!9=sR6)3Adm7^=0c_h{z*wD?ikr7E|LeX9p!`Qe zVeP4KSFcvH+yh7q@bIs-s^8}b3=O7Jp<2z&ze5H1Pq9G#@4tX-A{Eza)PCFo0f43J zf9+WWM&o)q!f!u|`adWGtSsjY%>SN5V5F-z$qNt|N&E^sblsUNiKxfZ@+X4Irqzh>Aw?C>se*5X~I8~7jLZNj*@*{BD zUO@ZJ6-0e@^8J@X;qYXrI~6f%?iI`!&wr`Ln7#Z+EHeNAQF?%ekr1Mh7@7R-EPzys zNMQ8U4<=rC^VDZ!Z=E?eG<P`TXFfSHJ*D@>`qd^VQnd=ezrG9QHJSSY3ZUEJ=g0fT zkBs-_mRAPPef`bB&Gp5TrIm#Z0I)p%8PY(gCfXOF5Rm204owR<;0UEc(d27P!)zAv z>vbwKP*hdwkH?iQ{H1)`U1(GcsNO9#DD)!I^av5}xeI=opS|HYaAtXF@pWvmAUI8_ z(XdUkdS?wBM?e9h6p%GVjie}d4j*n8Z(gq4Eg27b4mb$=_2TSh9Iqm(H;;%!qwt?K zO%DykBJ5W9ckTj*U`QP?kP1>pw(f&6t*at6Z~ogxz(J$Kqk#Y|vgv>H<;eCC@J4|V ewuk0+hta>;sLepRg`ge)0000=P)qhXCOuvAB1>1$~LJ*45g^Hl43wge}u?lK;3c7G(7uL^E5Eoro zL90--YNtA5+o_%DOxu}v?@f*&Sp>rMPN^99b8>IaBe^-hcTOUz%0ca^6AJ+Zpx8kv zWP9O@xpHtrR5ed>eDMZwjNv9D?{#!B!#aQA-27btM`_2-d@w(oGBLD$fDmHeVGMDMWL2|_QD!rE>$TGWX#s}PlC!gbz@_VO zXlCQ&mP>1bh_Gw}-~6m;@(`47*RpTpO!R@^eOm0-S2}th@F66hw(D4#!o^>eiIwpX z48&QA%JXpi(W8Y}d@=f@49>lJH+@2+_S0fis_ z1ZNGiY3#2HjdKw^!%u&Kk&poYKKfXr)DY&_D>a0*Ol5~443(_~pu-^$&H(QLHmN{2 z&0>}6J-a08bXeKbauYFzYj>x zgiT{m1<(kOu)WjatWEDBXTixtl8*+#H5t<2g=O0@om%=nXGsy*-Cgp;<5OOGc^E>m z0;tlq9anC^-d>9Um#+u*9s~}rH2iVbpgyPQtD8dTm5Gz+uXM|RApEka2}s{7Yug5A z!5aAOjsq$PpFIAy0HC}H90dfR+H&&#UeEVhv&0fe{Exzi#1R2+Ix(LipLo^VOaE0f5Hl^4d6q06r+j2;M6q zBq(?9ba+0tSy1QkLE7!iztm8o#@)Ix_Kr0oLJ#$bT!TX1M>z|$lMx8p1l}-&zADX(K}ZeVNJ(b&>r0=5Abk4sPv)y3z;fwv z&Jt7;JRt!Hh@=eZoJ0#66l3$aH3o!KF-0W78-c`#A!r6y12hdhck1*ogaUYkU7H{K>!d$NI)j&kTeFMkoic*J>|snr}p=#` zT8V&ZBHsvX?;8X~Ltt|!gQ^O*VqZ1uP6X8$a{oaFPf>2&Rb&Lh^Us`IP={bdb<+`1 zY{`Tp7nAC8QlNC@xw@E?Ojn?*E+zHFqz(ajus&4KXBEP;*efdOx2pZ4lLPz%BE0P^ TcPZ9h00000NkvXXu0mjf)&4_P literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/textleft.png b/src/designer/src/components/formeditor/images/mac/textleft.png new file mode 100644 index 0000000000000000000000000000000000000000..83a66d553556318edf017cbb0575eb767401d708 GIT binary patch literal 1235 zcmV;^1T6cBP)Z1U5Qg9C?isHQ7moNJguein{0qo|6CVP}oIv6dtu{dl;Q%rxkl?_DT!7q=5Qj)8 zND+`rferwbYuKRTN5k-R+*5?%J(qt7b?= zHA24$;&|oDv0$-51Bi!w55Y5iego?=6iko4x^ij!J^(l*d+y?gZ|9kQ;*<7;ggDJQQ!nwG)iE>FOg335L^+^nHP!m)KRdH;Rz^V++ zJb)J<__>32iZWf_M$f0_G{VK1>vuf}q}$$5AEU6{*d&zBh5HEu+$0ENp~)f4WzyISA@`)jo){G z`PUj-Z~g`VW0$c`!}^Z0*+CTa)eRfNTTv zm?`)!^U0f+f6i}NsRKn{79%3SMCF}YXJP*OOg+ISE z&y71Op^llp2H`?yCH(x8@w3w-)`h}qd(}>-yR*c~Cm;frO2Xm88q1>s0MyqS(k{p% zD4d1001JVcVTiCYBE0(M1OP5{L5Qm6K(W!qjR(NWl7R^0?mcK#BS@GFh=KrYO&uEV zwT*A?8x3H2WU+Y=%L52^_XMM2eocb^a2>ey#@cy>@X71Ia0)sD$RM=c5UR=n2&xWRyEUt#p7JvG0K(QkZq~O#SpN?0Vf!WN%Ijb-kq6i=WBCU|l%}40E zwbYJt2ZFY8B2su8&lr^kFTXzF*piV4Ve%^BeM|e+z8Af(w(ds{a-1#tYcpaVm0h&$0}{XPtcB=pgOOD zi`#MDR30C~G*?UG>6a#*PsOJ$eKuaL8^9XCD_Df8VtwhT8$sbni9zLc$cH}e3C_IM^J1dzBKiRO;1TJa)=~gW57z_sPmFFU%-B0rP!+h{X?w5) z-4CD|gLeK%KTr$vfq=|FC4BLf$#$NC0p%ur>UuHB-i)%h;QZwzLFuAw{$i3UovO|% xU-mGX%w%-tFmc*65gv7%l*(`PYlaU`_8+QxueyjM5HSD%002ovPDHLkV1fu7LRJ6( literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/textright.png b/src/designer/src/components/formeditor/images/mac/textright.png new file mode 100644 index 0000000000000000000000000000000000000000..e7c04645cf4db5caf5558a356d6969eeefb5c17c GIT binary patch literal 1406 zcmV-^1%djBP)#K_=t~%38c?zswBGxN0hto8X>(%-@OYLs&MeQvTjL6967!|V%)O|VyAq5IN{!vN*p=9|4X>W{$sWl*arhfcuVi#>`kcSw2lrN;Q!XnpAn0gX3yZEPlr_kqYMs=44K4TKPgBS#zy zApit?5Yp6Biz5J9Ny^|r%;lzMV3y;;WXjNBOe;0|YeFl5*#q!e2La_2NSn>=3`hsd zt?k?@Q&N?JHrTX?s1z|!E5@k!e2RepWIzfe#h8^M6k%hdb=$P1Q6C$vH>Nvq_0Mww z#tz_-Y4@t#sCw#9;mFmo62j?E?=ZgHx?}hHl|w=>ICRVy8TL4*{CY|m8O~>adn9B1 zazRA-^^|bOqKsM;@x$M+W@$iC4jnf}mUx^hLxal8+o~A2D!>&2zIf~H`uYo#o?7g< z-11;XUoFCbB=tm2$+CcRN*qJ873hl{W`+;ooZ!9ToX~7#%o&Vu%1i-m_PZhluXIR( zc7ZTdq`+pRqdf%`0$d5Yz9RaIu~x(ss8fI?MYMosF}6xj`XUV2JvLh3GgE-E6L_=@ zHthhRRJ?-L!-*aa7~j6Qr@rBqfjqB295Y6i3aR(}c2ZckLJ1yz8HY8?q1p5tKBa89 zO=z|PKm84BZxy0Q`0*I59uCAc<(F|-H9t_RDJp^O+xJ#7pcvom+gIN(dC{<7s8du8 zpFxsks49_DLNMZ5UM4SHN|-xT<8sr$z(oakk$^#(<|48r05Eq*dG>`8g!gaWGiBw} zyZ_jg>*;B@-V}tk>vA193p_YBTDR#AOs{@Zu=Te=*jE7RN`=T5ptaf{Y?k0GSH)db z5Ox#LkMS(O_P@XQO14iNSb@cz5?)oBK)2NgpE($t8e+>k5_ z_z)PJ9g(I6NIoYEI8_YjtHIOH-7ujPY~A z@K7L%9OLKV*7;xtNB<2hnZXEphKz-whGscp6d9Ke~R2I%QMB(Iwv21~o zW`VPpl!b#}!8mqf%-=8 z4Kv9fPjZS?bNxO9KuUmtYCcyTgpgOi5DXPavs{1(`3S^uX8}7pq@YuD+L&}+3tq>@ zq^dH;r6T1jHYUE*a?`fTcZG}gU9+@r&pU{46(J=7R-(KdUH=^5KeRqG?paFC4*&oF M07*qoM6N<$g4^GUS^xk5 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/textsubscript.png b/src/designer/src/components/formeditor/images/mac/textsubscript.png new file mode 100644 index 0000000000000000000000000000000000000000..ff431f396da9bfe074f7091f88d02edcfb68b928 GIT binary patch literal 1054 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/mac/undo.png b/src/designer/src/components/formeditor/images/mac/undo.png new file mode 100644 index 0000000000000000000000000000000000000000..a3bd5e0bf214adf2b0c780fd2f7de652395d555a GIT binary patch literal 1746 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/qtlogo128x128.png b/src/designer/src/components/formeditor/images/qtlogo128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..8dfeb1a02f4ec1ff2f1e5bfc2bb2ade8fbdcba03 GIT binary patch literal 2143 zcmbtWdo)zrAK!z#EzgpWxm^+C3_`994W($xV-PNRSC{HXp*!VuVjk388tz3>Lf#1_ zB(LHAP~=f4Vjc*2WaUQ~VSZ;k*1f;Ae*fLQ_St8Dzw7hbd!0W{yuIxSDe*nx001c) z>tjUZ4f7vSL1bOvBys@|h_*lFWQCM~#{)drnOB5wfDPWHVftY8!6Y;rHW^JY7>BW+ zZzCPe{&+n$3=PvTqws-GRE+^xe~^kDf^}FF4EvTAhoU(Wj~$HHVv+UOGz6wI7>gOA zX)$AUnN)~P)#1cy55{1JC=fG-PhrJjITUD!ro$v-m^2KFtj(rE94cmrieZwmEGonz z>#z}pSS*vWVS%{NAcVrC>L48^6`^1d7@NEyPUd%*WIl^U(P5J@Yy^qSjgp0cDGf1yLNNP7uXw0v~>h10YRZ1!Xlz#;u4ZlGO}{=3Ojcx z?pE5PqK4l0qxyc016mjz2&;S0z|iQBv56_p%p7ld)arz_%}K(k)3$c@4n#-iGcK+_ zyL+7V^!D-fJ9qxVF9CrUF9!vOgkAfUbp1v|WK=YKGnPV&yY*Yb9eQHYAIT}HY4_3} zWIW8w%6^oSn^*X__(@4=+0$p`6_r&lYHI818yJmEfBw6EK~LMJING?IRvb&ttJ z$A!x7w`au2^qNM~6~i(@W=#xO(>ia`xpjWy#$EGkLnimHpC8{%P1zpKkzH9CDO0Oa zKz|~U2=;R!psz?L!uONE-sm~CNOb>;xeK5FK@cj69I6%y9;sA;>*z;f`+4RI6R%OOLSpOci(V3q zYiU;jwP~=Ea45&`XlTA9+%PP%OdnZRQOSSFyOdiG8SA4I7|TqBR-52U$tb$SbkvW{ zZ~K(pulfVVVO&h0(jDm0IiNl~9OMX@^dGbr#v7iiF8@XK4Y7sI)IV<<~r}a$`=>(ml%YBCenl|MiiRoe3#Qp0`gshI8Tbv=QmrEs=hy#z~r{abx*u z9HTT!+BH+e$PR}$7izHh77AauO@dWrRyF!U)Ntv|f~x|J7Yj3ZjL}`vWfCzi_GVR$ zC{bIs;D?)XjIZvp>P*=4y2-&e<;cq!qJ)qhtReDpL?8s*vs)ekno&3 zbwvC|d!8OA7C(Pwy*pB$g?u z^53g0qDykah@6vHS0G(`%Tryouu2jI%Ms>7hn7uwvK>ONzD}u5wCMdFaWeUfM^+kY z@vi%A|8SpvTr)aI<;>{&{i}eLnBv%E^xo`hdU)o*x0a*RnQ9#EXYtBF_4#aR6WgPq zp}4Zy(P3dsavF7mRLH(7-#yp1RPj{aS6 zyz7}7`rJ=u$^&<%&2)FXI+ELC{z*AMU}r1E>gdPvceIEF#`R{Ii85#m;8hqh0t zNvz1!T*gWROJ|crLs)ObMUg#_Xut&#^PCK8t`ae>xvh^9wb(sS3z$Yz1GPKd{D|`M za*nOKo<1^%DC^;Qk<}#&Ib;W=5Eov=T(ml1cm<^?DpkZ0egj5zu=g`LFb{W@s!UtJ zox?z{z{Ga2*Y26T>T8?8;znskmUux? z-zQz8Ad2&ZvD1VL%HFr56lCT!WGOIGya?N)dN0a%#C`7|LAf;m<=~!c zdDFKRJ~^=x;$+Q}XcY54HA2Y=>+kdd5spj@gXKE|j0H=2YM9OfDpOA@mh}QakQZIr zk#YWBZ!Nl%XAQ=8J9HnmYd>z#yVIa^uT}qXi{9-{o!iY?x0`eywCUVw(Z1WFcdt$NZnMta zR-L0e0onpl0Fwe)fg3q{weAFZ*}o*nFPMRm ziG`h$i(gPkOk7$R@x9{A$|KQo%4`06i`1$M4-+$kHrc?usKIG}*7@~20?}Y0@O%6P+ z%^iE2_L?8LSN%Te?EnA0F6jaiatai(< zu9`nr`|gHIJ2P2Q&wRLl*g<(iVZiNuygvW;96xCPiAm8cg|lUMbRZ~kc)I$ztaD0e F0szRA7a#xt literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/qtlogo32x32.png b/src/designer/src/components/formeditor/images/qtlogo32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..d34a6a17f593dd0132efbf5d552b1bdcc4be3397 GIT binary patch literal 775 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0V6+JE32_B-8UFtd{l7~4YJs>*$ui6+tqG1Xy5PBxznP3w?p$@i^2VNprH1hcFnuZy7$|3?>6b&Yt_Ej zq4%Iw=WdJM{TAH^ZQ4M_y*9nOtvdJGwSnp%wCdh%)4tcD10q4Px_5zEfU-bttM2_) zU7!?@dml))0J%`{do9{P)9$wEfXo4D)B#EX*-*VeL7VEVR8%!|_4JKQEG%to zo!vYZr^|S=*8~M1`tAFVU%&tU+ZLbK42(8$PZ!4!i_=#xMVdDSiX8j+ z-&<8Bc&4(E+9{hoCwz?fC50zG|6R{4{yt0h{f45``>MWK?(g3G&F+J`){OAprUM(Z zn&RKNuA9~MTf6%Imgx%H5U?bH(M+LvFJ^7>qkojP}3zQ@JX z)<~s!3eG3ECV#1J@~u(z4C*oTFW}uh=}XZ0NmCS_&Pr+N(|(yb<-p=8DK}XAYA)Of zYTu}r$?KF`-IqF-_ZP#W_(^f>2W}KFtgMN@(WsmKy>(NJ!=$#1s%^4QWFF}43HWn! pK8vh_X!LY`r?vkpE}rPk?G_H^}gS?83{1OQ|VZ<_!B literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/qtlogo64x64.png b/src/designer/src/components/formeditor/images/qtlogo64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..e40b5c6fc8000413b531cc942c16c56ec518a0ec GIT binary patch literal 1219 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/righttoleft.png b/src/designer/src/components/formeditor/images/righttoleft.png new file mode 100644 index 0000000000000000000000000000000000000000..759066479456e1f10ad5a04e091c21a1dbcf624e GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~)3?z59=Y9ZEoB=)|t_%$6K<5Ad|IeP;yA#M_ zED7=pW^j0RBMr#W@N{tu;fPL7P!P{>c&RGTmC4oejH~g&oP`bojSCVV8I&5-78GkR ZGhDY|d#&+xpE^(vgQu&X%Q~loCIFm3CZhlV literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/sort.png b/src/designer/src/components/formeditor/images/sort.png new file mode 100644 index 0000000000000000000000000000000000000000..883bfa9de53f583815335c11b73cf6d99f97f612 GIT binary patch literal 563 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6PXc^GT>t<74`dE+(BL`wFVKhH zB|(0{46}GS6kqXgjb}eyS*3NVG=Ygx;BLmfu5CHq@7DQl6)(UgozU#Y#a|t$U}fnEO5J>IK=CCZBhxm$P~J*#!fQ-R$Y&7*Y|}dhY6_W(S^z zi#g)gnM%vczyDkJ{=eN@Chnyn`*zPcnZne6Y4$H(Q=ykL92NC~&SfZ=YAohRjcd@5 zf27*YxcQ9f_J5uy6?S*?bTj*$U)G}FT{(O8tm6;o8UIwCo$SPuAl9Xt^z_ust5T^9 zG3tBn&FIK3w|V#NR}1@LK`H-be3R3(COqR_vgW|;ZBN#oy^~kJp@!|_3m*OTTtCj7 p%;(!*UlaG|@7KSN{r1-vY#08bDfFaHyOA3d@1CxHF6*2UngI0mkfi_s literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/submenu.png b/src/designer/src/components/formeditor/images/submenu.png new file mode 100644 index 0000000000000000000000000000000000000000..3deb28e3a889175411a1d6f06bc08e42adeb6014 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=%3?!FCJ6-`&(g8jpu0R?D7X1f;l$4aIQ>OxX zR#sLFY}}vDXnzE9m`Z~Df*G=Ie{rn?@?1S#978ywlM_6=lNlOSOc@nsPFuqENKI37 zCF4=YkPVW~QIn&hIwhHtCq-#YV80UZh-ryLaC2{?i1Vgy?iogGpD#5qFcfd(-~T#M RJQ`>qgQu&X%Q~loCICt{H2VMm literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/calendarwidget.png b/src/designer/src/components/formeditor/images/widgets/calendarwidget.png new file mode 100644 index 0000000000000000000000000000000000000000..26737b88389095fef9bcbc9b5ad053adef9b2192 GIT binary patch literal 968 zcmV;(12_DMP)LFKLqG7pmVbA94UGzu8Fbx1Vmyof&+(GL z<~db7VH^d+u;wZ~PWUTSeEK$45?^nC(a4x2LDO^*T+U8(EW_ftHPhzSw$$O^_Qhr6 zd3pg>I2U=XfJ$$9aOb7_u!$9R_r(X?>-kU~Y)qV#Qsv6#bX8WDH`d_ic;v5ZX%uIh zw83Xg6JVbzX@P)GU0CNrOZ}q10{m`{y5jsRhu+)U>tKqa7^><*JRbjNr8H-{-(ShP zySwWN42MH03oeHI2}){d3MQc7cI*K=^PUFd{ z)#y8P7_mo>EhvqFKv2$Rvk{@V7%ZSeL+Cqv1i4rYnc)#j?Z+!u@$vE%gxcGY`SJxX zckkir!vabk4Ep6`S=T`&sGR5@n$BlsY>FBn!7F_ymJR0uUH8!F$HcseahN;yr)OZso^legnLLx zK3~Tb%P_$-niko;i)WDF30WHK|@M7Sgt z>|j&9BUojX!aShl;2>TfI0R=Xgyw5kQQ&I?<+T+I{@cr}H=OViD`_hSSDa5IENFgg z41Ig|LE?wLT#H8nyM-@fokxUASgl&!c|NwJFALiF+6sds$QKsI_Z6aQs zH46>5q5{fWjh2*2Ff4Gfh1%P|+%9PR0om<_!V`s$c3SNv4$|qgpuE0BngE+t4qUx) z1A^;Ytx{BP+Y0}d&7!8}*2xJ-p3bBv?s9?6-&`x+_8ufy~p_z0#J^Twc6ia)m5QCfm0000)XgCX(I1ezh|N7TW0U5?rfuprA12#H z?Rge-({)gE83bLF;*I#HeER&3=agHfQwSQoVY1)vwX?(bcT@pT{fkupEzH*gmSI75 zi(UH#^Hs;xOv4(Ow#JOCxH~X_=grLmdd1o$ONQ0KU=U8H6Eib2H8njwjn2+aq)a9p zjgI0wukk6P6_qYdhBX3xv$uy#<~6d}EONOV@~lwEqgX7WR4SreE=g;m(J02p$0hW5 zVgf%1zGJf|@OySsfg8kC2y|s-1us`u5s5?yM6kZLhK-GN#A7ie5^+4Ao5S$%a0U97 zJvl)?vd64Nfwcn8W;17?8?~Zen1gk&#tK;ce1R@4F2ZKB!DuwX>+v8Fk74e7l($&* zg56`y?D`+DKuz2{-`k5Voeo1oL-2Uq=b@q`8)dg8R7}}vIG@kKHaRJw$5ty2+uIT6 z#v3c@SD^j< z{g|4XQs77IF1t~2@)TH%mD}xx!SE2<{79e&O-(pxX~DND)NZ%K<(gGs8|x9ynQ)14 zj8outxuE0y{lqKZudl}kF3VM+Vg&tuzXDrX_g~?1sl*A0OXFp=SP*Dz#8)mUD`;Pz zbm|0}PN%W35L95xx$s`OjLppiwzf7A4u>%^VuDjXQF`8IyInz3oS-a1q2=0Oaa;wu zv$Kt4GRadIhLzJ{Fc{!)IHX^aP7&piFBS{@^3cWb*w`52JP2N|7e1d)IuPQdiT6I= zGpRtp5Am=HEiWM)3SmAFxDako{T}wKWM*B$sfNXKSjY78BFiN*%lpKBO*O2RAKW_m vo4YSjm%LBxS69PV{STs**K6ecbqf3w-V8QQ2TjG^00000NkvXXu0mjfV}F1m literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/columnview.png b/src/designer/src/components/formeditor/images/widgets/columnview.png new file mode 100644 index 0000000000000000000000000000000000000000..4132ee6b1da27ddb1dcac96264323d64d8c711e0 GIT binary patch literal 518 zcmV+h0{Q)kP)r zmA`7jP#DEOZJJQDg@RJW#dpY}xVsg62%X)1fIfmtU&6&dxQUBz;2?sFi&PLH{z*)3 z@3A)_>eV(0(gR1lNAk<@GknGvV{{M2gdeycx#Pl7pc;&mnuN)oN@o7%=`}BzL=ARw|V+TOEz#Xj&)~sM&1Nd_MPD~=fp4X@>X#!c{cyOjd2!5x#%i_t5DO%f&=kP#A zV+INOS^QyynHt<*?!SjBfC=$F77~ojX1a|@!2ON_wuSGbZ++~5^|eSWe*gdg07*qo IM6N<$g1jj0P5=M^ literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/combobox.png b/src/designer/src/components/formeditor/images/widgets/combobox.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3ed79f7ec26c340158750fa2adba33bb090514 GIT binary patch literal 853 zcmV-b1FHOqP)In z<3^(G2WCQ7B!xq+vqrZ^Y4(_}_uZEX#r zD4KC76vFK6EH*YaTGk1K5C{T~VDSHYsHz6Getmr%`FtKs!qwGPAE*gDFwn2(a?^aH z(Kx#>^p#}=OG}SXE|sykxQNNgNsNt+VQy}&ZH^#G((jp>dkMzGUugovFw9Y{R)78fV|n>0l1a(U;rRgh zC-7-dUDu&$8pg*bkYBj({^ktbwl812D*J#B4r(S7rU-3z*P4ksv%2X^i6o6Rx{>bM zNF?HrIP?K;ec8g3Hx-1t#FLVl=++6GlMvg&-n=`|z|HIZNJK?=>(nB}As0jPa;Brc zJ}!R7QBy%w~gx!aOAf5I@&F6k7 z*tZ6+a)f_cGZUB^tA+B9V)}EyEcr^bYkpY zCtTw;boG|u_}K(xTLTfRkNh({JiMB}tMUjYXy(=@@KH+uE)9qnoU&tZ(vE=%JBQoR zf5(oS<94{l?C2Y{qwT7QqQhD|`BWG}LPF{}oGk;>_GzVc%^I}yq~pe@gzLi+z8{is z?Y4xAHzZuRF5${e364Gq-}Xwl?38e_Egc1t8V^T?-q-1L8{EgTxKZ7-{g2Nxoc&=l zF1SRT?-kMM6w%ZrV)EVyZjbiB)@ViR6%j2C5zUuH*xEN?cfEj@wrLR@96Z9oK?`6G zeSsQ9&?6lTwk|!Iy3J_(&Wtk-Gfp&SV)oY=&=?+b;)8}PeA;ftskRIpu*IRUPJrd0 zfY^jJ&}y~ivMiNjsG#l>w7#98u2qF&=OXb*hXD=e47^bW96WBuFSFA!ynnD8d-kQ^ z{gaPiR~?g~1(JYgp4Sq%nHni8pL9x+nuju!pJ1@^$(C9*thN5I9?;>P{eiGluES41 zPss2d=S95udH|m@4;5R+?e|%%-{LJpcf2d$wL%7sMl;KqkI7(i6p8;(6GLvP0M~#6|7FbmJ%=>2w+zjz z6!5Br!PnQ9z$O_?HYG>Zo?)o|jN#2PEq3mW!rPTm5X+;mqcjS6C6D5*;&m7qbIH)V zlf&4W6Nj9Vd1!8#fb=W|wOZ|Ruh@&cPd45EB}2^@4DZ<(s!lOfG%%DNV<f3Io*>U24(t%-2ccmR z0s)$M#V*N;MQ|0R6a@--e>Es=Us)+ZpIb5A7Qq>%6a@--pKMBwBJq<>NmA5(rBUO` z_G1@AJ>DS7y%t1)Lf$8vlA}ocq*IcV(tT=_Zq*xPrPcFN^IEHj-lRO&D!(&N@BRb# WWB+-F*HLBw0000u2v literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/dateedit.png b/src/designer/src/components/formeditor/images/widgets/dateedit.png new file mode 100644 index 0000000000000000000000000000000000000000..6827fa742ef25f13abc3b802e3c6396351d309c4 GIT binary patch literal 672 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 BpGyD$ literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/dialogbuttonbox.png b/src/designer/src/components/formeditor/images/widgets/dialogbuttonbox.png new file mode 100644 index 0000000000000000000000000000000000000000..b1f89fbb36f17920935f3118d2bf4cb7988eb16e GIT binary patch literal 1003 zcmVLKVx5A{$)_0k^@8dOldL@yQu z-3zRQNXxETm=#v5Rz|gMxohNJTz7V6PUp<*Of#EW1HV0U&Ybg^-}!!jXNMFQd%jjw zRJZ{Um@Z0@a#7PbO?gf&|+dA9E{;l3YlQmfvc9n(R~Xc|5dxKBgFB zT2R#0)kOpj(I;A=*6>Gr@LlvG9g|SxTxfa82tfs7Gh=`sd_Rl9^ezmMqj`$zJ)tD4|6js zM}?Y$=O}1ks~kLJSwd^@0($6u*aLZ3RdB!p)8@z)@Slv6Qlvupgce^tx}zYnB-nD9Ego)bGk}sLL6&6mk+z;B zy-rhO=*=5MZroI%(u@+dO+0yuSW~kKRN;EYW+_s)Z_bw2YoN9ci3l@_mDR-4r=ZN7 zp=LQ$e-4Szpay4sOI3^p*5gO*QHUQusd~B&9)Qf|Iri;U`wA<$_{r0wXPgSt$D$n_ zh&_7-;&#JXvjJqEH09PgNGzIgxu002ovPDHLkV1fhR#=HOk literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/dockwidget.png b/src/designer/src/components/formeditor/images/widgets/dockwidget.png new file mode 100644 index 0000000000000000000000000000000000000000..9eee04f701917da08dc036ee9da189dde473eaeb GIT binary patch literal 638 zcmV-^0)hRBP)u`|P@|xZm&B6u^j42M4{ih|qQ zUkD-SblM0ZNRkAtbCe2EO3`lL1EAOI)jckXVui=HvLKG5xdk~|=eVvz7=|of2ThJs z6a|Z`^f=2hrqd}>wvbZJ8>KayvMd8&=irPc$IJ7))+wAn$Kwe?h|0$_9bv5{3zq#ZmY%h)|U=kd!=Rlaj@R?ExUCPzpqH*+65j?e%jHDoeii-v)^4{e zr>Cb)U@`|FB9(Hv%<%AV^bQRTkB;&(BXjK0bJVf9LJ(jZUXS zr_*6#VggmI0ObBN?#cYK)G!PYkwo-;pY82!eBUPsg6N+}<2Z1Zem`Rn1Z-_>;kquK z=h197ql6LT?&P<%}E|-^=_`c88)fJ6KgD?!!8UGekM5x#6?Ck9HI3*6;qp_;; z`ufW8@o@t5(r7feySt-auOlLijg289tgWr#c^-RvdjoA5NfFn0b90kgtrjhMd3ixqX|-CJ zOyicz)VQB_A-QF`+=$|)oK;b^LTi8V0?U>m6et3mi{fM zh+vFiV`GEq>1o#2*D=PV+pT-H#?RQeL#(wuV^w8jWQ5h#)xIt5w%ct=r4o+gFgiL) zu~?*7EcTqwg+hURK94b`$M-=H@csSG{rx>6;sO|JEsKkbQ(+i3Rkbo`*9TltE>OF@ fy}dSnb(#JH+v>SwVvk$N00000NkvXXu0mjfbB$*s literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/fontcombobox.png b/src/designer/src/components/formeditor/images/widgets/fontcombobox.png new file mode 100644 index 0000000000000000000000000000000000000000..6848f15c2e6a0be5772062b7d072b61a45277a8e GIT binary patch literal 966 zcmV;%13CPOP)V?J8r#58qLiEXVyoQdf|M}iV89RwFr8ib;>RS~NecimY*=%Rw?G7BS8 z3$BXkNN{)U?LLshy;g=u9#*@6G$V*XN}(lQA(!`-eN{at40)@IU7~ zt+%(gGB7X@;UOfY1fWnTOifHoyaZsT)9H>(CWB>JJgg=sb1$F-CZM1X4E8*vVW89f{UH@MPKwn;_dg>JO*({e*DV9&1;CJBG z+8XO+Zri@i>))S>xSsnTjX}xxUFOCwp?y8VV!h6dp&^bRJ4QaACvoNsug2rd%*?dF z5!0-sl6^a*BD6~gL6}#uS}wD2{yd&-Gv3p~n+FcCva+K7i-*vE%iLmw)M>!yzxa|^ zsmw2?N#@;mFiivB_d;llrZmJ>;0CY>wF(7(8y&?4W?xM6+2Pj-0)Yqugb36<2xBgn zTiO6F7K@b1B?W36=hkg1SFTW-yvFuwm5QNrZN!r&L4O0w0pg}hBGyV1>ERM zk=A^c|GcLFNK|w^kEm%9sD97_O9?_q5}lpwbn5CZrD__L@4m-y9HwJYj-LEbJ>F=l z@>c|Cnuh0lg#H3-tax#8k&eV8h~WO1)oLtVyG|J>WDaupsl6%>GK@6urBVsk^YJ|o zxzP#%NQ5u3EeC+wH|L007FWBvc>DbiaO*ysNeGGW`?#(P>#YDavvh@k{GGgdL{u&8 z>}Bq18oS;)Le(lGYptc>pQtLQIm3?Q0J6y)kmlT&-Hew(Yjf(*_3z$CAn9E1IT>_1_7UMe5!<%4bv* ojhyly)uTOo_I^G*Jp2{F-%53J+_JrO%K!iX07*qoM6N<$f|R7xr2qf` literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/frame.png b/src/designer/src/components/formeditor/images/widgets/frame.png new file mode 100644 index 0000000000000000000000000000000000000000..68f5da0a363b599fdce0121ad67afbbe05a494d8 GIT binary patch literal 721 zcmV;?0xtcDP)d>E{C`wY){5j1G3pY1$(slz~a3wF!oVjO?=U!hcV+{8Z*1f0# z>glV)J1uKtFvhUDNdZbJETwME%K-d1c>YgZF5=niuZb|g%lBWQmLf)i;Lf!oSX{I zWFBk^XEGTom9h~1exG)`EqG_ANVQrKJRVPIHk*PS$03`|Z3<_zSy>zoheT0JaG{XL zbzQ;p`J5mK&{_jv+cv)MvD$qiyjYCmm@sTgQZ{Sjc^;OPY&FIZ1OagzU*{=2uaPpA z5?(q9naySiX<7Kbk8RtsCyH8JUJeDj?ry4>Hws5li}83YxY2lw<1AaK-Tpf)|ET!f`xfFc=6?t5w?w|Ju0j3N@CoMZ-00000NkvXXu0mjf DPhU54 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/graphicsview.png b/src/designer/src/components/formeditor/images/widgets/graphicsview.png new file mode 100644 index 0000000000000000000000000000000000000000..93fe7603ae8b4b9c8af8947afc8608369c579aed GIT binary patch literal 1182 zcmV;P1Y!G$P)b z)oV;sXB@|IP4>b!%d#wSS+bezs+(@vCY#e`b|cQj5dvG>4FN$ZlyWKb0!kJ^I$JJd zWy;0UmV)hR!CEd`p&melGQ~xlV1QCq91x)t8Hh4zOYLt@yRAVH#Vl*`P5PqebDli; z=igNTR{bBX=tr@!vFj8HWno4}M$>5h1(v227?bySP`6*l(bPMqm;S1{WtgSci@HB zo!A(39?x%^M8M8z$%YVSc259P%_F_KEKEjd9ru(Jtd>uZWMqJuswS8R`>M;a=lC?i zfT(gHMh%pWvuLt;z zfNQ}GZB-RSxdjAO{fm41?!K_HvAsV+3a}cBk)D+Uo}|u)CDsGU4L}MFaGQZ4+YBg; z7Ak`Y+nFVc;X9K0cfmVSbsqEJ)zr?; z&Tj<*!886h)ODI2@-tl!x7u+e{~KJfj-p6w#LSJG5S6HLr1d(6uG!Js+Y6n6#_a4Y zbah4qM9cAI=1=J9A3~-u=bY7Q9p>}-jefYP#b~zE$?$fuDiM`O55S}s!amv$N?i#X zoq~z6tI%q-kd~`Jovg?F{5%-phjFr{7eysf1jJNE5NkDq!Fa+CYgC$)k){Ja_*X>; zjCq0hz1xVnxm!>wm5?cv=rdbDQ53c^75FO40PiP?a~RjBXOSf?^j-;L$r9plI8XMp zm0_Ib;upFUPNxH@TrSL(-%u)*;XQ&~>Kg`4)7ZRUiTHwMa$*Mp!_r~uvY?~mXMDyI zuJ>Bq-Q64Rn!URETD!DN>Xt|(7rdv@)zhB2KW~$J?L%0a442I#n3|e`Addolz7N@A zGSAU6BubibYiTpCz4p w09r~~^T_Z&_+z9*QVnT>G)O8Z1ugf?Uqik&$H?zCga7~l07*qoM6N<$g1;^*CjbBd literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/groupbox.png b/src/designer/src/components/formeditor/images/widgets/groupbox.png new file mode 100644 index 0000000000000000000000000000000000000000..4025b4dc512b2314a43e1b6c0f2a853034fa1ba5 GIT binary patch literal 439 zcmV;o0Z9IdP)hzOj1mBp!eB9ImJ{$eykrQQFPn?Sg!K&Q9`Qkf`@%|Ns7<&m z;0TU+=3ONS9&eStK9lxa>ye+*zT2b5oKbJw>D@r7bN?i!= zaqEyN1ERvY3oPJq2x*gs#msO7u6SWWx`HdzFa#Va4(b*K4k)uvm9T;}JCp&32e1|e z9-nWlV~xfWvfgP6;BbJ#U={HFsKb!@TAO? hWZ%kQP+PFz@t@=lnvuN#3#tGB002ovPDHLkV1jyox{?3@ literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/hscrollbar.png b/src/designer/src/components/formeditor/images/widgets/hscrollbar.png new file mode 100644 index 0000000000000000000000000000000000000000..466c58de5bf9ef818002f3b6453fead298fb5256 GIT binary patch literal 408 zcmV;J0cZY+P)@lMQt}3;PA<3 zQ*n@nSkm=FL+8w}cWpSh$xy6(PL#?$Zdyp@cclVNq~@br!|A=@{2@cJ@_F$$&qB|M zS9T2Bmxkjz!^NZF>N!KP^1n>H*mKAIO#H)+zb1Yu_$s<#$GW@sg6{Z(oP?{W`rS3i z9BKKE`laRljc~^kF0YX(QYX^`Q1|{?`0|GGulNKIPwQ3En2RF-00005P)9G1&8sskV7c%Jv;`@ZMlzhr zW$}x$g5UUxGh+fjP=@ECAwEWQaa~LKRbA{JF~t4E9%3iUYrxUm zB3+vFnjEM_?D@PdKJ`H0JBfRT4RJl(BDM<|dPE`-WV2aKJUOS}7l}{a*Tsj=>x1I) zIMV4fR8>VjpWi^CP(Z0v(mYh#&Em~O%H?u_GOUT8eo*j}#3wQW-$;BYtc!gUlQP#V@qd@yJ%b5x$de)%G+@euph zXWS+?ocE=h#p7=j+$%Y8eL~Oh&}Bo+(q8!jyK2Bu+^xB4rDAMGL8j=$=s0Egw!mPl ziBwb{5VzZn%#_|AE~Es`UZuT_w0=OW4u@lI=#nnCH9AJf-=|wmr00000 LNkvXXu0mjfVa`}| literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/label.png b/src/designer/src/components/formeditor/images/widgets/label.png new file mode 100644 index 0000000000000000000000000000000000000000..5d7d7b4cc9c575724f7604354e5ec160730140bf GIT binary patch literal 953 zcmV;q14jIbP)3(&@4Y^os<2hzeSy5kygGk)bJ} zCTgLH3PLZCQE2lmC@Ly@Tk71F(`wtl^KAFpHt(!Hbl}GxE_c73opYWe0wDSy{62_8 zb8>QWpw()j)9Jim&s8cF$b!BpXqjmxqih;r4l1mnfO|v!Kd<#FkVf8p|KPOg8>$c1rCP; zlarG$zv{+leLmWcC9~{%!^6X8G}KGX>Rp@>h2hJ|E_$2JPD}YpR``b9Zim@yhVkW7e7seNJBQ;z z&3qpi7?{qNg-?4e_h30uQJB6Ay$vP!F)$rFbmUn${(%%;fmN{c;>;w8r6LFv%8TO(88?@a zL?Tf}*dATh!ZtdL+2Zw+yEwc(3JEbGOm>`5c#a2?2NH`2IhT2>RxL-@^`rRq<&B{9 zSw{^D)R9;nC1#QfL?*Gucc^7tNfvW*6{|Th(pItqrnmiG(%z10(V08Sze6q6K0pRr@b0^Dj8NSo0jHr*|I@c>MM@-IP>yoVmnbw zSQ}68gQ2qqxvB^uX#&@yg+j|Cg5S?Ra+Y}|@uP^wapi1MevXC20-3Y2WUz*E8}SmM z6_R=_dVg|{%=w0{<4VJB>^82ZUQ)kefgkdeN4v(!J?lzWqkN=)E%uSKWfkVSUGAGI bxa@xc$n3O$6DLQj00000NkvXXu0mjfZN0ZO literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/lcdnumber.png b/src/designer/src/components/formeditor/images/widgets/lcdnumber.png new file mode 100644 index 0000000000000000000000000000000000000000..c3cac182659b239a4f4bba0d073317140e22cb73 GIT binary patch literal 555 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6_X2!ET!A#HpuN3aOG^u)tFyBc z$oT*NKZ6TM;NZc7A3l6|@#4jsH*X$4eti7+@%#7hr>CbMIdbH}g$of85jSt%JagvE z=g*%fOqftyTzvKF)jfOmJbn6f=gytercL|x>({q$-}dj{zh%poA3uJqUAuPU#*Kge z{8_bX)u~gbmMvR$?b@}sZ{IFjwCM8X%P(KPoIij5oH=uL?b>zc&YhVvXFh!Ru(!82 zGBWbsy?cNE{{8;_`wRJ-%|K^Klmz(&tNHQ@s`6NQb8t(uD!DVUryn_S?nYD(!~C~7 zOMq&gc)B=-RK$f|NX=(9UdPF5Bu)U zwUg=)IrQ-NdQItOhWz5%q+L_}80xA&rq4QM!L-{pHnYRSUoDpV*ITXQHp{Pm_xa}V zbkC7ZORj!z`0U*1>bUy7;PdBPtGmMP7oThQZSLTWf7A{z^Q>*u#;mObbC0xJdpT)gzUu`>;Kd>cd&U$0T z*upgD#@o_6Hy9Z`wVF;e^@*}7weD5C#5QQ<|d}62BjvZR2H60wE-&H=;`7ZqH%ufdYl8!hO`$i{0`1rT_nDmGs<@2+>HlwRu-td2pjF-w72Cky%O&Zi*gz=FCf0 z_7+(=ot2gCv~I)E00q8NpI)xhez0MRhkp7+(<>n<#XA=*OXs+tpX%AUbSd`*z2p=9 z(suHcf?kEsVbp25@A&t;ucLK6UXEpB}P literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/lineedit.png b/src/designer/src/components/formeditor/images/widgets/lineedit.png new file mode 100644 index 0000000000000000000000000000000000000000..75fc890f407f698ee43245fd97dc3de1237bf2fc GIT binary patch literal 405 zcmV;G0c!qR4C8o zl08eqP#8uJDg6jdh76rL2!&)2|A1W_3)b#}tB|38qCY@E6bGT3larH+lMbR&LCBCH zPIhSrX=p(gd0cKw3Q2XB!*e+o9xl9>7x8!a2W3iBuyJq;aj>aSg3{^vVAfYy$gy@) zhBUCDGMFv&y^)ESvqJ;yzP|LC3nouO?6Cz-sJT)zL%Oo1r%Lr8#6CW_p+1DBnrbRc z*8N_H0|IbEO)~#8&e+(TcS7_``CTYcqC}Xi`)#)TCKM@BBowV)Ufv_!WXrKexnOd= zDm#aI)$;}(btyy_AL`pL7aHoSkTV^Lm<}zd5iT>E+U;Lz@5( zEUI`kY2g#(;?tsuM-_oZnHu%NC3UD%qm1l zXSRF2Z7~wrbThNp-DjVjd1hkyd|oUrF5>@(fDp>o>kVvgZ}Zp0{QNxD*T2d?MQ^M0 zgCO8ZzVG|!bUJ9>G!4k3-$$YER2+Q%^d(#;VzC%ImsgiKJakYly+ox_L8H-N76k75 zbcrAOzTc4-`D}@fJ!!*!ZX1^-v@?_#LMoN!!^vb4>GWs_NQ@PrVy-PsAp*9p>zcqi z1Q!?Q*xK5}^wbn;wJLrd9E2f>hDNdWLq8smqgWh)>-7QK_9P!1AJ1Xs{R(ehSa^X# zVW!Kp$Bw4wc|1eE<1yO-#<^Thf(e|So?>(BJKnx|hyDFMRH{`Yd75dz51FFbnOU^m z_F#WHV#z(}7+X z$abIZDKH9$9_e((1g57Q*N?HW@eLnVSMg(a2ae+mNG%Kjt-C6rCzOiC@D&Cyv3W9* zLa9{b&H1N!3`@z8R-=hywv;%Xv~Cikg*Cv~_1aQ$7nsORpxJCn|I#Rgs4CcYFX~A4 z(egHciRJaxHL6kxh3NuLPEJs*)gsmA_h#T5YqJ#?1)_kn*({b{FEgXLxjERj&CEzf zoUFDR`efT}HCwILovn!(XEIs2xsKhvT`av?;^ft8zd{X1rz&w(#zN{p`6wA`SoDXY zUv9(6$8tT2`;N2oGX*XT#$rHjngvtU<=a;GuHU)~LKaDY3P+kKDm@$5I;vx6|l1QV%yYX#@Q&P~UEEZ!ja< bryhR*e#vOLcrHAt00000NkvXXu0mjf<4kxu literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/listview.png b/src/designer/src/components/formeditor/images/widgets/listview.png new file mode 100644 index 0000000000000000000000000000000000000000..d1308d5758804bf47f5b9a70431d9590cd2a29c8 GIT binary patch literal 756 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/pushbutton.png b/src/designer/src/components/formeditor/images/widgets/pushbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..61f779ce2bf0c468f3c2e72c8b0d6cd15576be62 GIT binary patch literal 408 zcmV;J0cZY+P)=6A zNtD}P!MXXTj0QTumrDxf^Y0b8H3quGH)kw&M%3&Z!Vt# ztp0P)=8v%5p)27NN&e7YR|;DU2J;QYTZXGJT=WqD0000S literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/radiobutton.png b/src/designer/src/components/formeditor/images/widgets/radiobutton.png new file mode 100644 index 0000000000000000000000000000000000000000..10c1d8c3efdfc8c9285e79f5befa6e130e3e4871 GIT binary patch literal 586 zcmV-Q0=4~#P)e-}uYe3$jjbfsHmPMlsq}ERvv6ED#AYLMWrdDD_}rb;xWO2^JA#hczNf zg1QXqlD11khrxC!X%tE?WD!LjcNo4b{@)J97XOqw#&iAf@Vxwl{=M{HV%cQKE~i{_ z$tk-G*;c_aPN$9(c2?G!KC`{jT}lneBZrI?u$p>v+vi7_;x%O+GE8itds05R)nvgk zj^|F+UhthSeC88#ykm-GPW+*dFiQ9x1JJa&XAzK3* zhYBNn9GkUTOP*uJCkfH`J3S6`x!JhvEQb5(J9Wp7|-i&^3g zFNxIgMsnOB!x~cw`Q>b2SBv?KR}=}?aT|9S=PDjkNd@Hk4R5X7WSS@R*YQEJj4{ZD zsuepsO8pdg!~{JA337nTTw{oR#AdBy*!K^F7P8#uE)!(A&Nx>%%TfGG;f!@bL!Ld8 z9kmfIG0H_QFw7|q6Z;Vnspuc?UJjDn@+qXa5=ttmgyIUV7^Gj_9rDO4zXA%#FYl@m{d@R- Y0&Fzl}$?mQ51#`{e%8Qn?Uz1 z+eAb$k|a1_5C#$M3JL;Ug|v#2DIE72E@mOLE8N&7aN);BXCt@}+-L?Oh@S4bd1aa} zYG#x0aL*hs5AR%N&VZsQcgbXO7>mVFmSv1aqkJ_({oeopQN{|SQmJ91(ZKn9&Y8_- z7Q}yMn$6}93WWl)fbUGa4wQ5{onj`F!B8kM}S<%$rKRA3N?dE(J%#Kn}9 zq`&zYkHW}8e89zn!GMeD7AehN#PT`d;U(a<3|4W!-}fv&y9c~@1l&J0iK*!IdY;AC zPk=WX@Z^@n-EP;j`1Tp_;T7=c%0}Gjbole?MXbC4=H390Q#MDT>$>N}j^aop!e!L> zS7OIA_A7RLD*TEa-#ou!hgsoY>@eNF#qD;RZ%n7te5FlhsBgw)9)}?wkAD=4MXXdR zSglsEUa#{BCoo?dvHOBBsm|wdB9Tzp%kdcq1ds>@gD7foEqbDMgGo>L7la)bTVa*4 m#@JwNF?JZc4(z!>KH>{b2U6CCu7TMA0000EXE@}y1()^sS5MaViDF{o zvP`Z9O4((%=mw;iMCCb##6UE{NiQ!i6B833A0J&^T}MYpAqCYFx1aC|2-w@(FJ8QO z>$zKXb#={4wiirV5*ZnJ?8c+!=4NAK7woxk^N(xgcVJxexk-hBAV-J+r*Um)mM zSi5*fR(;paXYZoh=Q*ZU)YR1E=H~JW2`^o`)Ya8Bth&FVqQWk_dB%(x2^HU(^-R6aV0{Sa2fdOYzhDMl4sI4-Nf$;X<8XTot2=q$O8@=)`7_3{ z+;eta9kKo+jYUT7>LH9vqY}(|3uiugZYr^Kk<^S8jLDPFf0ImjJjfoKKg8 zeSLfng9z|UJ@*-tI&B>vfC c{5^jEzUwLD%#E!&poGNW>FVdQ&MBb@0C+?e?f?J) literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/spinbox.png b/src/designer/src/components/formeditor/images/widgets/spinbox.png new file mode 100644 index 0000000000000000000000000000000000000000..cdd9fe141324c16758bd305b945339be013f1bc7 GIT binary patch literal 680 zcmV;Z0$2TsP)a zoyP8k=kpoub{j&7w^_LRPg2;xZjf9qhg2#BA%rhbyQp_k*il|-Y6E`)6+(Dx{A#j0 zi+7TE0(${GGWKHcHo(i`*^?)e2~<_Zdc8)eRPwF!TaAeb)oK-$O2xM$9+^LB?3dMO zG{Ss7cXn*CScIY|uYvs*iin^n3U<34w%aW-nG9;Rn)jCW3(7eMV+_q^6Zw1|jYb2E zF>hDctlbBa?F!p^?r+g(6!m)j?UvS8s}&N71d_=l;_*0Qu^3{p*xmUY3WX312EiD+ zbCGG9xZQ5pY&PJW>j1!n5Ga?+UzTNQL?nNz`iCn@2k^aKuYcHIU8cWiH_ACDVfTgr O0000M272 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/table.png b/src/designer/src/components/formeditor/images/widgets/table.png new file mode 100644 index 0000000000000000000000000000000000000000..4bbd9c2d081f15879cd05dfb1818683af9c156ad GIT binary patch literal 483 zcmV<90UZ8`P)q?8P7XJO4mU&%5=EcEsjtyzh;WMH9E_A2T_g~qKL@8d zHB^Imi+J8~z3=aRd!EpG`Mlm8_a*=U0Im&B&-M&VL%ZI6+Pe?7bsYeJfoC|`e6Zly zRcMcU_ax|AmjECo7TK`uHnb<+YCy+|cL3m2jbE^&6AGS4aEKiM(AdFO2<=di5kyz&oxz%~Fl^cX(?0hJ1zTT=mm zR3QNY2TE{h)q4Q&U4`F(fQM$_Gx`7^^)Uhjlu7_407xY&K){hXfR6xRDDf8%&^H0! zumu3p7JdQ(QUXW-P)j5rG}kH#AOS!v%}t=zgl2epvbiV$BLMKn$jlcmR5Ik*-irkr zz&-$g*Kpy;jd@4|$CfP#EdT&etK1lyhBS5MR!3U_000_kZfY7$U6dwf+6n~#0BAH) Z%K!KgkpVaS25JBR002ovPDHLkV1oTdvk}@P)|`T3V$EmNFr7||JkRkPnWibT*^J3#g0&VAK}34@q9|Hf4}?H&0F&oAkB_gY>IDd>!q0ik z$qPuHyrvEhk?q^aUEt<}s=u3SU1rtG<2t zL>ZY!JBhL8J2`=G74Z2SCz+eQ3KGDap(4l-2=i z2eqJWjK+Werb~VY80vKyw^>~02Z0y!;v)TTqXUbq4{eijk^x08<4vF*xB#kU^V%A& z0sXBM6;Nvrye3q&2ks9c*#xSqQ^T}}d!EO9eCsvQWzp78Ti=U`xJ(6-i`D721El|O zVm<=SjVtzDr@O#t184>$KqjsnBjA>^W&?2P;OU1{H literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/textedit.png b/src/designer/src/components/formeditor/images/widgets/textedit.png new file mode 100644 index 0000000000000000000000000000000000000000..32e897d972759d27d3371b07b477c7ac701c474d GIT binary patch literal 823 zcmV-71IYY|P)k|>DHMF^e*Bh?U$NhR*iOb=zZyV*p0 zabTD?GjIO$-v7OMqba3$h!y}?US1xQQYM8EF)1ZdN`w$t@_loMoKs3IWHOn(0DzP- zIWjU5i$o%xoA2_daW9+A#x^!KlE5n;fDj@Ui9~2?YqNVW7{u@QwrfE{I z*SWd5p<1nSeSJ-(QlVTfQ!bYo7#KiG8MCR7Qo3$#ZLa9f2Q`{^oD#cZqa1jcJ-FrEI-gt>#L%roa{r>D@HL zFNf)S)@K7mpGOhj1*fG`dY<;!V6|G!^~{~#yMmY6N%7&!2lU6f4SHF*WcTMT+`#(R zbr;XoYPIRZ@o+pdJj3*xX_r#xlTMb#msm_JdH|eMG+OTBxcgl<_qC(vV`KF8_Oi0FV)O3-E`BOv7{=ve_){?d^1QbPx`Q(RCeN*Io5F5D4J+`_VMbmG_2WaC>{p;o%`dh#Y{X zlwxXXYS1*zq?9st$G7u8UsMiQI5;@i(;n(G{R2@4iLGj8ABX?|002ovPDHLkV1mzY Bd945d literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/timeedit.png b/src/designer/src/components/formeditor/images/widgets/timeedit.png new file mode 100644 index 0000000000000000000000000000000000000000..c66d91b2f78c740ff59a0490da286a05a916e877 GIT binary patch literal 1353 zcmV-P1-AN$P)ljZo6!Y-2&BGgce$YRIs)hAN&Ct z3=ckN)L4m8qyK;rqA}5+(HD(Bh>7~3G5$z|_#-BU2cwB#B1%l~M@w3468V_kufG`im9Fd@>y~>gYqS8&R5eG7 z!Y^Sf*I%}pa%9cqZ1niM4-P!zz%2^C{n>AJ^mOE&o1BdZqGN3B5(fMG$+xvp{JqHZ ziD~*b^;4RkXZXknAB|K=n!4!BH!(3?-uc19S3Um^@Xb#jTEC(-GhB|X-q0|{mQAbZ z@4uXeh717v_wVQMw};ueV*tLnU4>S@$R8BY$nMxD6KW`ZF%y5u7g2u z3j)=5v~Y?WSI6}A^&ujNSgcsAv!pg9F=^emViB=)uIyy{RV^GXoFWKRALwm$urV^| zoMUZnj!W0|paWg2Oa$iyCypdBRI3S2Tun|85x~*iwTkum5a%4mFz_eXx`9$GbmR;= zqY1-G9V+4wC!|S2T#ak7g}8G<6orUb)+}$J6br`ML0EVoiFE_B<&@>^31KB-DxRXF z<05<=;Cl+?`&7ymy1Q0mti_37MX=T|Jw1(cj_wr!v!f{@?$3%-Yl%}qDJWMeRLT|b zJlsN$(=_3ypT{}$%^`jm9>!RMbAsnVWnq1wHE}AWwl?nyK%DozIG9bAu(Gq0-gUhw zrBKR8DTVL*?09GgLwDZE3%g%pd}5-W?_JkRPj3%}84qzaRvm1u8A(#Z=_o@KMm5+u zsPgfZj}8L1Zr#c~cW>vFmtW5g9e&IDC2uTYj6Q{la#% zukn=P_xU1cqY}QaFxIf?%8f)4(w5Jol#dgkICF-t3JxO{Pr1>0#p7+IJxa_(Vb1c| zc)--;6rNHjrD)1z$z~d9X=)+R0lx2Z-8I+J+}wgviZF_J=iofQ&Ki`MiSf45o;uj= zx+6)_^nTCA$;T5u2SF?Ybj5@8!Yq*KYYTP2#tcYMEyaYFVgi}Dm{n)2J!NnYGn z;L&|&NvdViB!2t&Yqxy4__ygjq4Ib2zUd*`rJ>p1(9yyTJ({&E8d=kx!S^-46cc_b zCVVzlWn^*=m6WVQy*s;S{66QNkVV1gY{}4DH>ot;>pb1+{D2^nCDWJ%Ag+d_afoMA zJZI)42!<+q@BQ@e49_`-=Xn9J^gOlDuymO}@W2q+Wg7?@J3!?Ca8f1-PZAb?wBNn) z5lLwVm|xJ@1Q4ofonR_D%ZW6js`X{6t7j0TnOYv(_GzBXNidZ+rf1s=S8;XmHZuB>ZZW2;V z+LU%7bP;f4!A)BuscD*%+BUsZc7F0LFnoK7cWXm6a8o zbEpWSiU=GX9g)doz&REc7Fb+d#P_`5zaRo4?X$)h(&@zz{L=@p*5aJQ)a$6~S;pfD zlF4bZ*(~{do@8>0rgI>Is-U8XSVVB0Lnf`yFdrji&(7G zXtZjyT<3PGARefLZ(r?j#cieC4T8d8MTH=h0M_+CJUuT5s6*&*AePb8GWr)XJP;V5tQmIg@ z@3XgKI}q*lebl$G@$Z8{uq~BJynMv}idwknTfq(;4_036AE9NS_7`D3{1eH&-4Xx* N002ovPDHLkV1g}mVk7_n literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/toolbutton.png b/src/designer/src/components/formeditor/images/widgets/toolbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..0bff069a5df3ac4dccbd9cfcd2dc8e4b822ae7b5 GIT binary patch literal 1167 zcmV;A1aSL_P)0#0?im+f}R$5h6g;*?xWHNaoDS_5g^qw?{Rj8^028$xpQg~P~ z6Wh4_cs!1yqoWf^_xJZPF)@L@zCLtyeU6Tf4wRObLf3T&VWaf}n8=|pm{_JvBoZ=? z!}9VnrlzK_zP^sTcZV@MJA;9N0bIJ&f(sX#k(rqxg_+P;tVPT%VB%xdawXB|9$H%3 z;P+RexcC&FJ^KUq?~ll}#>Pe*9!kYfRnYB_sa&@jz}2{@g(xO(*p9y|zPetwRX(ZFgX#oXLH zwznf7aL(uXD7jYkLg<$G)16rvQyOKi1a!mH#f&CgzggQisJxM z5JDrB0jYLoW*SePJmO^1cBfBQke{#f0I9z<@Oa$RiX$5EOB5CsA}`PFCgNO`nfN|s z627(g_EHo`WtEiy+_=$+iV7dQgv#HhM86;u8s+^|&6<4bx`u1lu2WJU``Vyn4#C8H zNrytAkJzKU(YhYF3cdrp-U8W*f&wohk!?JG{yQD*T}pU{?u@5QDl0g9HgHHDPjSTx z0D~e(!DFw*`+<@b;PT}wWWldfRLz@6Wa|;NVup!Xbryxkj2!doQwhATnCG9YPg*pM ze%dqXxO=j*bMo`^J&cUv;v&s3jO|1s`4?I4DU&8B_9(*qG%K(%q4jSPi^UZ_|9n*G z$a4ZaEoIXjoQ7d{kwOO%s*0jS9S+AoA-P$EE+qf}002ovPDHLkV1hb~FP{Ja literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/vline.png b/src/designer/src/components/formeditor/images/widgets/vline.png new file mode 100644 index 0000000000000000000000000000000000000000..35a7300a580a121f815819409ed7a3a69b9d8c24 GIT binary patch literal 314 zcmV-A0mc4_P)eJoN>#n0|v8NirueRF<`-oIErrz$ui@-fa`u@6iY`{Ror|bOkLMf zaP?%ImvFr%9){sb>@v~!7yrdyEq-{?VqkvK3vtKULhOCg8pU}DiGg_ujpJz3H08{2 z*0!w_dzX;P^V}8PZ^V7ybMw45r)jcH({Pq?=Hy~$c-Q9J7X6Q(8|hxroEq~LdjJ3c M07*qoM6N<$f|gi?2mk;8 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/widgets/vscrollbar.png b/src/designer/src/components/formeditor/images/widgets/vscrollbar.png new file mode 100644 index 0000000000000000000000000000000000000000..28b7c40c6e2c596a7cfecc8be3ad14941e8899c8 GIT binary patch literal 415 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#Dl5R22v2|&P<#HS#}^oU2n zj`PU>jSrL^WLSg#|Nmdl<|M%qv9N6w^T)qWPf!29zvwBK#Q8av!spmJnV;r2ToKMT zP!~AOu+j8H>WQ|dA1B(JW6yc6Ie5om4bKrdgXYAxLk4FGH0IBaXs&FC5bdZlVKFbe@tfS=8dEv2wlh;c;|#&6a*EiTX@lTA)5=#pxR8ZO^HQFm{b^p z5|cv67@3$2GbO1&NQz1bD;S~%dS!KC{R>YQzJq0G3%%^(;l23I`Mu|RIOjb`Vlx4G zfF0On&b+PSf6M&~v=G<>BmeO0U=95r7; zha>1nD>~YSYGTOOf&3Rxtatt5>!a(|xm>O<8!OKCH~}n}w)z)+1i~9V&hCo+K&`Q* z!31{)gF$t6c4iuy5AK<;E5lPzBTg;g*&b_4V~-z*WFL6H{SwiVee- z%jA_ANJD#jyOPOd23(fYLF+g4Gk6*D&u4ZCkVPfO*ub#}o#kKwT1(T?}f@!NDX0AST0mw)zt+C5) z0WubCI|w9o15zNelb5UoGPHf79P-8X9`y_7mf{k{L5*L_|cCmX=~v6%t;&cyV<5 zykj>Wb#-+ezH-;OxX0JmchaOuR#`3Q?>%!&t*EI1y1&`CW1*0Os+pOYp=X$}v9Z0q zy{oHh^OEfuAw7*i*Xxx8`2{oZa&xfwO1dyA8HZbG*uS0s@4>F>Z;wjq{+9my@r}>+ zpWWP_=0Ed(#8}>OyZI-6RnByv&PSdujv*CsYbUr?H#>;9Cbl`mync7jdCxl_`1b$% zS#hq!Z?>m5@A$rVH7l=XiMV3#g0Q0(e2cbu*Bva_Vsd!R8oTN1-cMIgfA;M2rHe`_ zfjgq_-??|w*2Z@CiOyNyl=ld9x3j1iFHtjC8ZNo&@lzw-&36`9Prb_g*w;q1{*=QN z%L|HZ2Bxw8D$9N@V3b;zJ+Wia^jV6xoL)=Sq@h{ugELlH1d|Fwsq}u;uePjpWDc8S*u%#qVvtd0zkazkmPtGV6u?$bTR2 XI?rfBQh$sY(6WGajZqH6;Dcu>pHYr zEoB>z$0SLr@MC^9n{j)4TS*TgwVE1#e}9kf`xHe1 zl*JqlJ!-XDRoF1f*DT8jf{}8n*G)XnQ@oTC-}lL~3@H2A^9I!Gb=HrT<1&(fJYRLd za5&UDAPmREaiXx}>}$n*QaB99ESF1#2ZI9|jfTS0={M%{xx(FU7t69Vz9KxCOjs-y zim>fIySuvzFBS`~udfyEblTWR>Si=wzkw>A`C;4r0f7GB?kwtR?ILANz+vE>jR=xj*s6w&I(6IR>k(W{KiFJ yNLfCGL?S6uC;@>GAP^Wvx$K`>_~O62^L_ya)C>E9Py`780000T zl}k%iQ5462YoC*^UT7&oUm$7{1ql{7?F%#!B8cJ=O`=(YC`ScBj+&JpAjc7vp=lyY z54`3zjouJ+)#}`?uX`T5!P)yf(9}T-7Mr`yxxe*aYp;8kXsrtgKvKTwLs|R;!y5Jr3y39M7#Lk7p&z=lLh-2De zz&*|18CAoa`}zZ#JK|vI+X8l{icP7<&Ijuh-#(*~ckD1O%?{Jx0P};Ole@EE*M)pP zU*C5VrS=?n5upT>1L7EQ8vKM8=1-7Vv7}`ly^v81py19~l}?DH;Boe-7{`1z$pRgF9vG!y&+~|=<$egsWo9Np z1i}FPH3N<^inYJK6;o4F&zr|*y6T$%o%V6vDKT{BC7jq7Llb>gag=Gr^z`)I>1(tC zhuDU7w!o=&@N=yQAo3wxN>aI6>wXdW1nemk3eURvfL9#YGC`;zw34f{5UfIF5?~Gp zT=VD)u*>s2g2q~^WE1lvShAJBy=udAz$4&~1^))tipAo@2&LnVDto^h+iC!l6#(7? zH-Rd!40u3U%mxvl4mb;3B>p4Wj?Dp&frr36P%-djv75!Lr(Hms@!iC8{;yS~fxswT zFurVJVHPaF4xq_+9pG#M4uP5hCTmJ-ttGn)4cy^BEY`mcmO(at>WXLp0000XGHQn6-j#1Pd4}$EaxI)l!mnYZMn0G9WpJH<^NHXE6e5UuUVGVcu~ll{$qV?DoQ?%k}{-{?V-1XJsZz(qCe)-6L-<(INLYu;jX4CLH2U*aTY^Ha~#yXGh#9Bbj^P%E@rh9}0Eu4$}o zPLVi~5(8*B1;E<-qZ&|h7B0o4UCE(13v2wF@tK)M*M<7ja%scNv1C4w(@ ztHx-{?zQb4`Q%*=zV|xYpIb+7NfQ=YxID80*QGf{q$z+jj{r%_<9VLcdA1Un`-`KX zD)$80dCT$+)-Ubg%_o*JG_`?q!(~Qi5~9D-W&rtT>g(%k)UfO#wCuu2G!Xz$ki?0w z!nGFUN+q}+l-xQc90va!1xo(_IJN%J`65x5<)K&?l*cXtD2Z}0;*~%r57#CHfdA9} Y26AiL{H)wuH2?qr07*qoM6N<$fJ~2iFzHobR$W9+ed&iz#gEmW3Y&v zZ*1~MdipzQPYUyy_~Z-G`o@qna0>{{%>1pCHZ|9c3=DO#|4^B1;VbnCc=)W9ww8m4 z5Dx_AeopgXzy^VPz%mf{RiNioWwhsV2m3k#e&uEe!zFx$pqOEPVH#0TK(sQx&BWn* zqx`Q2i^!>#*2L)b+g-FLjck4v377E{R4Ncv5S+PyuQ8$#$gRtDp0Op+s|WHmRvNvJ7zd!z7-kcBSVc z_~L*!C{+>EtN7Fen^`C#i@?y-J4A)lg+B1=;Kz4Z%7e*Tj#t0Sg%}Y4<*J=$W`T(0 zfIE{D;0dX|{tEQ*qfOrK&&#Me!Yy0cg-^T%RZ`vE@$xZX5m<3Tf(V+A=3BoNF8s|n z{9Yjiya48^fXk86pr+z#@Tn<20mDERSTVEWKfT8e{7KYRtIBHHAITZSk@xgqT>t<8 M07*qoM6N<$f+VOsGXMYp literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/buddytool.png b/src/designer/src/components/formeditor/images/win/buddytool.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd968bbf5fb473cecf134e7c1dc0793e869445d GIT binary patch literal 997 zcmVM&= zaCqIkO%%LMHg(=I2^41ZLv&gA?K~%)&gM4jz?bvB&pGG&dCq&D!z&VrM8Qnb|5Pvl zku6b^8jVKu_V!|Ma1c734t;%n{C!|xfNNxA1pWQ}d~A6R^`xq*Dsqw91q=-hp{1n- zjg5^^sZ?ldYePdr16ONnD>^zl_;XWJ6Q4&hnoCMZNN@}o9v+6OUImRt!_CxcHSfuY zVx;cwZXPEPy*D>Evt;U#n3(7opx5iUnYK%d_V#wZn07%i%9x(Jy1IB=t=8~3#Ylo6 zI0vAt$gHfa3<`w;6%`fe>FMF7v>O`l?Cj({#mStKNlr>~44^}#tt*vEgocJfB9UOH z$8PusMME5!jv&&hOs0$_Wg8$5CEh4!K;8)YMd78QZpdA@__JsLlf# zl)(GPD|#j4keoZukdl((9H6eQj+f$ci5@wf3B1i`eHQQ{19)-{i%T*WpM~juBqUK+ zAr6Uf449ahfL5!u)ICWO#n!ZxvTPaPT_K>1!Q!(R{46;SNPS_Bae*n#6Yib|vDMwL z*)CvaW(HKMq~!|~5-kTB6+l_6wdB6Q1s6~=$1+H|7q)v#zKEmq3he+sfGSC^H}LEY z1_Of6$=D8_0_AZly6y+WZ^G(kj@txNmNyvSlPw4b%+Ah2H>3ld1(}oh4m2ge%Y;9= z;SUJb<`xKpnq)pUAr=6ckByCCa&i)*qoW8tp983F;#*}r7W4K1Nv^Qh%!v$=?fVym z1Lo%DU^I>`$;e@Miz^Wpn+wy6IDEZi-`oO8c7-X|&kDi;^YimO^O=tyK?goPJ&mcU zDcn=MVvrmxSpI4X$-;etN!TmDD3bN<8#4Y{7QEK$zbC} z0jSOmLqXhr4iP!?!=XB-QZyCrgE?!9ZSyNEUVH$xx5FWX?ONHvdKYA^XfiD_Jq&*2)<;3$TJbVi36-_g=il`qIOwd2kX!xaZ$hv=M+6gnf=5 z%-sAu5Vb{-5Keg6z6ToxtcEP8Gx3I*Up#5&CjSdyHKgEj;JW7j1y~JP&z$rdsg5DL TV66v=00000NkvXXu0mjfW+uQs literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/down.png b/src/designer/src/components/formeditor/images/win/down.png new file mode 100644 index 0000000000000000000000000000000000000000..29d1d4439a139c662aecca94b6f43a465cfb9cc6 GIT binary patch literal 594 zcmV-Y0j z)Xz`TU>wKswOeUBH_Vo3LZ*V4p&U4v;LVFDq!ObUNJtQHC_UYOy}c$4_Z z287Mpy&>Gkk3$;%;XTGD)-SARcb^V+y#l_lys$a@k{nD+qgKLE+C6xLudGK{sd70w zcE71nDjtqr6rQslcH!s21HbzIZLG4Ku(F%O+U^xp_O4>4nBl-LJ{^?W2788E7ww3c$dW3qz>Ki(HSZqJlD~5#;x#SD}gQ7 zgv0(;bxhbL9Yezjn5K`uZiTiRwq2=|ckJ6DkxX7Tsy45p8>IMse%D zf;Vqf6vh<#P(J!fv{R}3IKcTOvuzkL=(>--JPth;j^KP+u2DCF7oBg1O2Gjh4Lf=+d??SxhZHK2zLU9 zo&plu0F!>OQ!*70I2NWRs6=_tn$#>iEtK=#J_SyI${uHVQ^;9gd~N^D(@twOc~gsvu5zz^5`+wjwp|MH#zu$mrR*UxJ@zaX?gqfvRH|JG-zfEG!)1LbCD|LB>s z*d#B#-PP3BPnVM5egqe@LHivOHs`4zB}}%IZM*kFzEg#*%KgI|I3f4W zr-;y(EkkYG`^XH7KtfcsnVc2T_-s=uekj_5i>cyA5^pc9LZL_^2CQk5OcR=`sv6?G zvZTL0=B)?%4>uw&H0lA56JZpj<=xqJ;Mj0samC$NIZ=kMM9Jen?cI&rJ9j`Ev&tAT zYgS2hb#>&l$5f-y#7m{pkJCLpE_I6%QNQB#kpO4sDz#dj)zZ=;%g@g*WjQ)l)JE{` z^-4t0t&9J?Rghrl5ep&n^3(~1!o+FcfF8wFe96cmZm_9)eFD;agYMIns}5;zr*Gdk z9LR|@9O3cCy9Eh2$_pE3F9f5cxm(ORFg*rF{<36r4ERW0jK=V= zC++XXV4o}t*N292zDti?1%+r>u^gRi-ZrCn=;r3O&1_&G z!SugbGVlkmdQBWY4dy<%v@#0nEtzQT&?8q_h!bHvw7;<&b)==wx%q`CF021J(%Gr* zVssRmxPku~7)haBF;msKl*A`MPJ-1e4aPUTrRJ7CH(k)Z98OZiGaNh1XU{U~yxnoiefF5Zk*O7n#j*6d zc;*9b1^bDM%b%A2dZxX$a;B5<%a>fyI@=YreY#J_s3tZxRzT)>5}403U`*VK^y2cP z<-g3%t|ZmoPFL*cFj(^f^C5-y>3?)=V&ydWjHwTfjV3PiZlUE*E0h)yJ%$u|T4+M) fo=+@>IXU+q-@P0QWAg;!00000NkvXXu0mjfObmNZ literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/editcopy.png b/src/designer/src/components/formeditor/images/win/editcopy.png new file mode 100644 index 0000000000000000000000000000000000000000..1121b47d8b6b48f6cdf525851e7c0f502454ffe1 GIT binary patch literal 1325 zcmV+|1=9M7P)+{buk{F=nCJX#i^AQGtBfP^U9E=s!&EKn3+*9AyOC5y5_m4HQB2~pH` zRYX%2)m58>DgqW&sAx;EBU4=37{7w!SM2eOJs!`@eVlXtjqY3tTS5?(-{yaHb@l)B zec!pdSBVIB!=;~|e%P7xymRJ$5osgh_&4efy`JyCUMhx9Kk}ogMRI&{UihAR)H(VP z5`ixu00_b{5e5q-zVY>M|NPMBA38(Y-oZLchI61bL7|V$#mUE~-@NcRpbiK;|6O(N zfk%J%Y@t+fHjUDudrucpC_qYs_XMTNy?z?C^q*foM`|MO`|`smOlCq5Y$|AffYRZk z1m#2g^824X`-5laF9IMU{OT;%tWD%U5hqqQHn+rzNOfaXUit9}xpwhevNrpY)K=e+ z>e6d6H~pftTia5-{gJHQcvaqe>5RPb)EA!+5j21}j7vL!fJ7h$Fxhq7s2$=XbQ*apq!syTR7h7DJ;ak=_$L@YoY5qR0x4qnI>JyURTfv?kuG)9hFQ(Bzig%{x2T0Vw))rZsUuJTqc?}RB zayzmI$t>XKE_%9=#B-p^;FY>ji9xH7Anaju4o)p z9%uqB5oz!1paas%Pl^wu;12>?@7Wcg0d(Zx zhlqm$W(R^Xh!-Io1+@po6A*q0JdbvDCs~`i(%Gx6@WB#qEmxCq5xK#C!6ygR1K}{F z4Jh9Og<%N$=~Oqu`pG7hW&{7(U;2z<{$co^Ef>^0+f z`TE<9*>|q)G;S}O%acqlZ1T6YZ9d*=aWjvj1$6F8Kzppmtr|VSaptG4)aU>DhsN&h zS#$oPdv2-51h5X&^WrRbO)e0I{N~r^&-Nf3pIBgF>R(LU+~oac!W>Y~iu;rt_-#>2 jRe%!E1{&Go|C3Ju!Z7S4^>URZ00000NkvXXu0mjfFDrHZ literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/editcut.png b/src/designer/src/components/formeditor/images/win/editcut.png new file mode 100644 index 0000000000000000000000000000000000000000..4b6c82c7a77a319c7b7e2cf0b307068fa2569d89 GIT binary patch literal 1384 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/editform.png b/src/designer/src/components/formeditor/images/win/editform.png new file mode 100644 index 0000000000000000000000000000000000000000..452fcd8878b7c7999b5317aae0cb90c28df36a36 GIT binary patch literal 349 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAH3s;Exc>kD-yxy_$O8fhL*Fs( z!rTArAO26TYTokr|C4Y3r{DNL<@)~%ENYmvXLW(Yb#V6h5A=elF{r5}E)%Jgjs8 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/editgrid.png b/src/designer/src/components/formeditor/images/win/editgrid.png new file mode 100644 index 0000000000000000000000000000000000000000..789bf7d96088434f71fc68dfe9d9e12308fb6172 GIT binary patch literal 349 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAH3s;Exc>kDAIO9NhlmD!$2cGv zQ{J3j)m&8DJmvcT={NqbfB1jPKuqGPcmK~md7EF;Tv*$D`PtjBxWsFp{@?uc|IXL{k!8(K zzW;yr?f;AK|KI-lZ@M$R7-&6vNswPK1Gln%#ga45C+v0tg<3pa978JN)}GtW#}p{w z9C+`{qq8&T6#x3q?^4wGgsDsP@|;Ivk1tN>W>5%{C|J8nv+0n*$xYHei8r2A@`p=2 zE3|3Wp8q^K_=oL`-KJT>Cn(ZZ9H^7Pb$F@H^@7&KsVdStK78M~e%)zopr0BilN6aWAK literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/edithlayout.png b/src/designer/src/components/formeditor/images/win/edithlayout.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd3f0ce467d85487c16aa133cb536a630ea27f1 GIT binary patch literal 455 zcmV;&0XY7NP)Vo_CgIm}L12lDk~|M2VtIB4<7{q5;_Dg!`OQB{Vc@$7k} z`cPKS-BYQ<(Rc>*QkkkY3INIGkE>yw`!fH~|F6q?y;F--S5=j6i0Z`F}7s zz}g^ACVwoe?ID&6Xn=VNa??1qSs`(jN0_f5whf8QQ&9ND>crw(6ewg0a{^>Q5vf5c za}}5>6H$3PJpk)H#b++_x$a34h%XoJHX`V_`-S}u=toi x0?Q5zv2PV3(xdtl^=s1-cvsa`GTZgF7;iSorLl{}7Zgumy_y5no|DRXWeBs0YOCSDUe)jgt$N$$q z{lEG7|LxEJ?*P%)|98LqzyJ0Bqp$y;eEa|O+yA!7vkGgQ3+h^*ef$6X`~Mf;|A$)C ztpWy_UP+K&FaskCD=RlQpRlsBinhM7yRUy}d{IS3Mb*5;OP1|Bc<|tvv*#{fz53wI z`wu{{FgsTd7|k}GE{-7@!MziGy$=TnxSror=_xhQOWI4owLr0B5l7x%-G5QrcTIS; zZbx88NapIRv-+<;U*tb&X}M|p>A5=%KFH5Jmy@`KS7*6H$hIXD;_G?+yVsk3c{NSP z;Qum}RR=FkVs$al5V$yrwP%6Ho0yhW5{@@T3^!Q|T6b(b!hAJG?O;IV-V6T{ot%np zcqdu%wY@oAJgM3>u>QleuD{W#m+npbo|(4kivRl>sg0Q$(NAChbeZJdD^?;&`SaP-dr)yFjzEaWB#;0)2lVNpYQ53yz-g`6q5s-rT1N;NqTFO7L!^U4=tR%4n3kxd?qR|oxwWPzs#KHzmj3FjM zOpMWpBnEWN8g}R7&GVi+^M+jnws1DXFz@4>d(S=hjk$7dcC3C0QOT3NIVHchqii0FHQ`=*6>#=cXnErQ-;-;)p8nG%YxC zLwTBUHZhYGlw}#s%ygJ2;xVE@%E1^tRXZVwt*Tx)bV`352BigDgsV z_58`jPdjhtG$T1zvV4)$)|aKM!8n5dyz`n4`lETJR&Jewziq0*d5HwhsU(Z&%g1+f z_wU^3_4|E_<5&ptJQpKaYh_)l)neJMi)>|CE?`Yr2e7-lON)z(OF{s9Dvpn*c>$ah zgO)cBX)qWFDDF4C}e(fb91r=mR;m!VdV^DgiUZyyWM6< zv}^_mGQk{3XJ==H6zh^CsrKZVtRJg^XICiRNK)8>H!5kQNH&R8T_J!N!zDe#(m-DP z4;dj($Sg?J*fdXbYPBsP!^Bpuk?a5nH$g^K$vFhitIMSTojWT-*+W z!xdV2Qu3?7QOF8hHJ(q?v>6Z4B2&yGD+$Vv*;GlPsXl8kS4vX^2><|1Q48oFq6SvbUDGu1xUMxqgwg}G*4PMek%0riJ7lW%Rp4RIaglj*66-1}Pf}3W zHbmz5ghfY3k!_>`3Fb1+$nr5@v=P}LpQxhLF~MQ~7p*;dFro9#`!^CvC>Wv_Log_0 z3Uj*le*nce=En)PZr)z~r^mk^jOLMzG!Va|2LO(zF;;AhEFzZDnyRzQIy2O&k*NL> zZJZ>)*}|I_LUU1w2!-dL)|FdA=VBa1hMSz!XuJmJTmTO?MCx)}Gg)Y)AVtV97$TVD zTmtBc^9UG@3m>c}eNjsnTvZ=<0*+uNihR~3MPvXF1?S)sirT0^q*S9fGC+%FMLu~{ zoOSK*@6-DFdP5eP>f~1}5QYB0{NCQ)RD$hZueUU<{9 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/editpaste.png b/src/designer/src/components/formeditor/images/win/editpaste.png new file mode 100644 index 0000000000000000000000000000000000000000..ffab15aaf8d57d13b63c788babec8eece7c2e979 GIT binary patch literal 1482 zcmV;*1vUDKP)2?}XaDXA(* zNUKVLR8hq%h0qqaDY#J*kX$P?&5KY{}Z4qXU$MGX5Ku`#v@^UG>sg(HFZe}N%=2g3-o2x-Y zKIQJ!JknBk+t8*5Cnw~8Am)iY{p~{e+aC3=BTp0E`T%7j!j))~f$>;bar?Zy;*;%k zb#-7n0X#2>pwP-9h2p1MHd7+~oarEUbdZE!vt{QB)urKDfn3{W|C)VrgbAR@Knzo{ zhrsKlv!k7+a5Ld>6U}LU7!LE^;VsO~4^uwtRzl_9^3A?uW2pME-4EUSWW%Oq&!4Sb zyFXELbJf}dJC>>M{?{k^`6D#R>2Jz&8v^p;i!aFP)!&iYhnLAc)ittk(Y^9ijgrLJ zkX(+qB$8v7vWA~6itc!njhVU=4va#jjA2e?CC84{^HIw&A{{3g=xrh3Z^P#bf(Il) z2Eg=#=_NikK>vkKyp#vP;*E_nT#MyODF>M7fiegv=2he|n$V1l*x0s(LQ!5G;==I` z6iHwVQWrr4lp+xuW}vSdbb!44sW^_0Q1Nv|aBdn9xg;POEL$<9$j9A_ONm`fknmDy zt;or-InnH7?52PiXbZ$cDMQkW)7 zo+Ry_Lt`&QyTRv&yFL#${PRzEzFEvEFQfHh0*qk9AZY@bm<U z*@aRuh6VC`@YTXNj}@QcrgxU}@oyhz;O!@fMH-p^kiV;aENVBt>Cp^J;T!K`6QDb1`WzZ z*+DFK3gBRAG@!DY9QRQ6lpsgmwzu_#^ zgXK&@+j&@Sh~#M8bho`cdg?r%0A7{@0Sm8j|F*8WttuyGHKb=SzE+V5MrcVYVC&&LU9bezOhC|=Khh6h#@nlhuCBSSTUqpC$Ex$~RbV=X&SpS2nP4?`rCwy`RJL=ddl{Pz_uMOuEw0SDB*! kH>AflK=n!07*qoM6N<$f|Xs>*8l(j literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/editraise.png b/src/designer/src/components/formeditor/images/win/editraise.png new file mode 100644 index 0000000000000000000000000000000000000000..bb8362c1f1e155338ae3ef1839199e19905fd03a GIT binary patch literal 1045 zcmV+w1nT>VP)1LNej4D|>9t8mb_`sv5mD!sDe05`YSn4B?h65k(c;VO=v3K~6%Xc_#M&nR~Bad;98x2M=zmD(()z!-o&K zb?erT3BW{9g23@H$fYpck+Me#*-qk}{sB8b5L z{yr0skhff=9btRxOazz&ih#hnrrFgU%wV(W)ROQKcsM2#83MIdB7&3>s!Ci`rvN6P z1W7?~!aGW!KsE?>N-~t7W}%o;l3+6Xpp?QG12bDb3xaGy_`tTkZ>B`(R)({~GfMAH z&dPAM!4e|poor^PDmmvx7_(3j)MD&#_0gw;LrO{nj6UXR1MtBrC)^zsK}1L?v0AM- zJ3U)?SP~rn{gi|6zClEYh{a*|95L0k+Y}L~OrDPZ{2h7Y#@vIDOQyAUDZ#x@KfC)( zkN3^Vm3OXRyXxUfP?7VyV6iuE@cw((4@)V8JMPYUy*@9|_9lAfvJjD*hlhu^pFVlQ zdT)=`+OoD&OGMCn$IM76ae8`6trZ_*UQ8E<-HU9!w}-n^Yo)bDN(m8J8fjOG-aDm~ zDbWa2jSKVh`Id9u&K(vB?t;!?I2B`Nmx9+?FO&Qt6A_XbInQb8?)V~tbK-aPE~OwM zv`t$OzBIw($B#KUIJl&$i|V>~kX_k!>qkdNbM61m7dCwWy!)E3GmriQuF|TSFjm!f P00000NkvXXu0mjfUcTOP literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/editvlayout.png b/src/designer/src/components/formeditor/images/win/editvlayout.png new file mode 100644 index 0000000000000000000000000000000000000000..7ad28fdeabd8d5b44ee6c50bb5ec339e31cbafd0 GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAwFLNtxc>kDAIO9NhlmD!$2cJA z8I>3ilbBxBoKe-BU(;Mz+gw!JJmvcT={NqbyY+Vc!~dJ^yxsEn|DJnq!{QPn%bKIg znvXnud*bc?lkfhYdiOu2vN^4?Ii|e%?31@=-~7Mu_W$K)Z?Am(f9=!%o1gyQ`TGCK zxBpMR|9|%F|BLVc-~RevueU}O=mPeVAirP+Ze{(7C1;#Z*zE)gRd~8MhE&9@J!dX- z*nr0+P;knEIZx*l@B05YCpq9PPiVmxDUs+0>~>CWGN%rF7tOeFV#B%vb3EI8dBt*4 zUwl(YdtD(nF-3Ay!i``KDq$8|g3 j3u~Bsk7=42zV5R4*ZC3(f@d9ffda?= literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/editvlayoutsplit.png b/src/designer/src/components/formeditor/images/win/editvlayoutsplit.png new file mode 100644 index 0000000000000000000000000000000000000000..720e18bb32f1945fb20a32b3facc14fcff525a7d GIT binary patch literal 740 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbQ|Pfzda>C&cyt|NlS{Ab>F7Tx3BA zTT)UIuB@l02cps;qM^CDdGh4R5H&zj-!Tp%wR-hxAQz~>HKxfkp$*8+s+|o{mY$xT zmX_8(ZNaRCtDk)PFCifzD=Vv{q-1MryJ`19Ha0eOb@lkF*3yR7stL1trp}snz{zE6<{y+8Z|G78+FTVYM?Zf|@AO4$}nmzsY|K*SWW@cvU z8tR3$%|*4%Iy$-)b&a|}N=J9f_5X&3hSPzj?Y>|N0ze%=Xu9s!TP`jxK0dxpciviB zTAq0OUszc9r=j~rHXwz_PwO+-J3Zx5C3s@DB(EZShI6ONOq{UOn06|Om1^b;rbcP zUsQ?;R=gGZ8T4~k%&EygbUqbWR38nR*|hF;s}I+6Ym11d9$s^VIXNF*=V5=F*Z2Kb z^4q`P9N)h%c=r91jr%D^>02+kH}V{P`jK+0!@eb@$aTTJ=EI90PY%BL z^JuE?R27cmB`+Wkx8a_@65j^u^siVQp>_ zPwn8;)Y0kK7)l|lBE%wFmC-v$eL~)Yfy# z^Ob>gz1owlwa4nuL@ZOXxYpY|_=&3meg}r2l-hGYxpQ6xt+akF&AwE-Rj{%5WonE%=bMnbMjE6fFxi8F4En`*+~J=7Qars`&=Io zhuwOigX}AaoB2WbpNI;Z{V^LaLk(JDtl#%qy z0f>Sm3j{{aCY8&CEXN`MM|ZE8%R+uhohP>-UnM8m)ey5NDv83-9qxc}B#Vq3KoHKA zr9$dh3?K|tQxUGnzF?FXfupNPBJcqpK@|`Kj_Uz9$Li`v=>Q=x&=v?xE!YNlY%Pp2 z`(c97k@x;0y2r zSkx79I6z~aP_V?vz+;97?mOd?a$#bTcUmw3OasdW&9Ju0FEPu=Z0000NkluMLp6h%0z3=<;A2g_#%st8T@8|R6`^WPY zN+~|cy!}H_Tu~a@KD%tm_m%7DBeBj`kG8vQU){vgw8h}2SJgOx_Wy$a@FNi_DPZfe z`nBJwsC#Z*QE@GJoQ`tuDa(UlIM}E5i50 zBA$rOU%pfGhYOm1CieM!Vph$nt*?~TY^loi`LXV`5}%pm#t@`))-rF!=crz=2waH* z;&%?&t(Tk+00|(JQV1c$vDa8QD>{33#j+hQ&7QY9D6ARciDBIrh-i^WiBC;mWoDv} z!6`vsx{B(RFHl;wgmA7OUDr{{iz~czzq@99`0t%p{lXW}qVfl#^_OC$#}{5(QTOzo zk_Fo%L2DS5x`{{AAP_~_I{M52cSceSW=aW^ZDx7>212EvwFb$?inmyRDnA3U(o1R+5=&d7U!}&E2ZTB0{=@8pPcNL+3uBftDi44tm{OIN(koEl8E)va^lqVyXUj5-2)s2 zuHBC#0hkYqo2M57N?UsXwh`2YX_07*qo IM6N<$f-Pt`I{*Lx literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/filesave.png b/src/designer/src/components/formeditor/images/win/filesave.png new file mode 100644 index 0000000000000000000000000000000000000000..e65a29d5f1735d95d5e96fce818e251fbbc88ee1 GIT binary patch literal 2699 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 FV1kc9>)`+Z literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/forward.png b/src/designer/src/components/formeditor/images/win/forward.png new file mode 100644 index 0000000000000000000000000000000000000000..34b91f09fa3aa8e0b329edc97054c11defba8016 GIT binary patch literal 655 zcmV;A0&x9_P)cJAZUz`NyfQlF14XgvfeqKhRM8=g$nIpi07v2Pm$T0PG z+s5t5ev&IuzPy9iGfJ_*Szxjq-U_S#@di#sMFxj0Y^u zs2IrSA-xLzn@Xv~L10-`OD_1{xQ}!;h8VCMGZhEg7)ve(nH0=Nlth##z-1t-sznz( zI^knI8`}k}=1Q^8xg4a|;6+&Z7-tr^27CjyRJH7a$AX8**swS%Z2U-ZpmSmP5GMj$ z1y+C!prEQ%7kpz7mY14W%oynFgWlfyOj9X{G^0Np=uW$J^8**~h`aaUdlDkgAkhJB z2loJbfPHrEakyVc$6#mx5^=)7buY9XEP!Q$7GNCj4jnzl`B(FVY;?m5b-|rNMD_xg pfC->zKW))&VdLne22{OJfIn@@R^)Beh^qhq002ovPDHLkV1l$X9{d0R literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/insertimage.png b/src/designer/src/components/formeditor/images/win/insertimage.png new file mode 100644 index 0000000000000000000000000000000000000000..cfab6375f7554b991d50a0c30bc4c431ed7c77ab GIT binary patch literal 885 zcmV-*1B(2KP)z2#D*<4OA{-$~GePHrD`W)^&HA}hI6wn3nP8)Jnunn=y2Qjh(SU1A z;A^lJl(3(uwES;`gXqJ9dkuJT5%L0B5iAX0Fa)DvCj}7@_SF>!HA)U%_&|bZI`ID6 z&Oiz5X7J5#@Y)He2k_%fICBAn2OF8~3WMWqc;XlcaAymaR^ZzkaB&GjAI7`zSX^zu zX2puuHsS2oZXIdi)|8bn6}llo-Ki2V9{3&v1^|)_@)D9!MS|D|t=*7NgBYm!Ah?GH zH@IDc{xm#!3?c(b?k@N1ZMc57!k|%uC75L?QT0x;LaGz(hU}Y;WljSg7zvBv>V^i z>3>GOxk>1a$!vitx)jNCBwMeNrZ16~fCZ5OLWh;10dYNmnyD}t?R-kPYB<^G@WbV+ z=&684cZDLo!d&kQlx^T?NQ#0e_9;|?PM$)x-b1HvqEx_ktzt#FohF{1V|jU%^qscie}rOO`Y7{LV~jd{yAzz7t9paK7aoN=dXXvX#C%y2%rSn z1`$C3QGil-BGiqd8w&HGVrjGFoxb4|7b||#UL9|-g@~X4s{{o1nm}MY=Cq&`gbK6> zp$G5IT0ZfwaYB8?pI5&GKD_^m;^_!~HpXI!#nMtjO~81Hzz9zVps{p9%L3P!kw2J| zWoFXLNWP1dDQn5_!3pMT8q*5!u{4#XAHckyaNN7gqWXni`Z*@M33-Z?|5Sz)PAOea zCF8>e*gX&t-fC_zGdNF6hJ@uU;`}GP{5IH(QJHhk=E$DPo>FGuzW*(3hT78!taj>B zeDw0`90i9Hl&g#mnLigAkX`tEGvJ3UkxkWXmhir@bR4|iz&)-}r300000 LNkvXXu0mjfgHV~P literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/minus.png b/src/designer/src/components/formeditor/images/win/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..c0dc274bb4fe04dab9220c9ce39fd3be89d08712 GIT binary patch literal 429 zcmV;e0aE^nP)<@ELK+5mf)&HQGufU<-T zBC6sBQo0Xen_HG(wsrovyXEZt)A|7JAZq=LG0fYAshlbGju?6^H$ z#q(M~k{(Y_p6_Q@bMy9cg{q}Civ;D|6cXpd;j(ejj!kV}hyJi>_p9gx{<*+6 XZ_4%z@%F;;00000NkvXXu0mjf6#2YG literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/plus.png b/src/designer/src/components/formeditor/images/win/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..ecf0589415464d3a432931d311c32550a53dae8d GIT binary patch literal 709 zcmV;$0y_PPP)g)r>qUh;|~v(@^U5(^ERwC$Wgg^wRYFo4c>EYN#})l#)s+lz`Uq(7pmE4yTs= z$A8!jT{g?FZ1_#K{1zSCp<_C9vJUOdc2h*;aEV;V_>!-aX@ZdyqxEuF$*_^dM)lR< zaO$~lW_jno^TX{!&cE(qMrKZWP-sCK4ltzHkYOalh{0hn7##h{V~?pkZ_97&>H!>g zPB`)`9v-Q*qS8Pmfqc}bBDSLd-Hfb=R)cuIN$5N!K8n4F7w^#3-ZgN*? z{A}?2(K8e|fmF(=;jifD5*f0llI(UO r(pvkt);u!M&-s-o&^cYW>~G6o+k}(Vi#=;y00000NkvXXu0mjfTZTZ= literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/redo.png b/src/designer/src/components/formeditor/images/win/redo.png new file mode 100644 index 0000000000000000000000000000000000000000..686ad141c6e9fdff892db4098589ed9046bc1772 GIT binary patch literal 1212 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 Tg6EMl00000NkvXXu0mjf6WBk& literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/textanchor.png b/src/designer/src/components/formeditor/images/win/textanchor.png new file mode 100644 index 0000000000000000000000000000000000000000..1911ab0d5ab49b86b5d381f25c28a29c9368a78a GIT binary patch literal 1581 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 Hu0mjfNaS)2 literal 0 HcmV?d00001 diff --git a/src/designer/src/components/formeditor/images/win/textjustify.png b/src/designer/src/components/formeditor/images/win/textjustify.png new file mode 100644 index 0000000000000000000000000000000000000000..9de0c8808502f5c30ed02ff038e0325464ee0fa1 GIT binary patch literal 695 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 axbX+reAIe(fQ}9T0000z8wB<%qK}Q^REM+ z&I^UY*xK3}!Z3vEx~SD^sMqTVf9Ltvs&)jhAdBpkOz$F5?sA@59=$lEdbV3qlS4v zp-7Q);0MT$-^9ek1kMaS!k6U;u^6Yq02NUc1LIu=PjT?f~B}@ZgzlFAmWt@qaBAKITLZ_X+fPJ_A8~#t7P?#&K&>iu3S8W|JF52MVZ~N&)rhwPKG=tq{Egu`L51UR9A^|4gOY00I?wLDFZex_Lj4k@8kH9eWsN+9+UoVG+w4kSKB?LXT51x z(Qbo{tMq*i&)!bAiGfZyv!Gk|d8cEF*tGaj6 + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +struct Property { + Property() = default; + Property(QDesignerPropertySheetExtension *sheet, int id) + : m_sheet(sheet), m_id(id) {} + + QDesignerPropertySheetExtension *m_sheet{nullptr}; + int m_id{-1}; +}; + +struct ItemViewPropertySheetPrivate { + ItemViewPropertySheetPrivate(QDesignerFormEditorInterface *core, + QHeaderView *horizontalHeader, + QHeaderView *verticalHeader); + + inline QStringList realPropertyNames(); + inline QString fakePropertyName(const QString &prefix, const QString &realName); + + // Maps index of fake property to index of real property in respective sheet + QMap m_propertyIdMap; + + // Maps name of fake property to name of real property + QHash m_propertyNameMap; + + QHash m_propertySheet; + QStringList m_realPropertyNames; +}; + +// Name of the fake group +static constexpr auto headerGroup = "Header"_L1; + +// Name of the real properties +static constexpr auto visibleProperty = "visible"_L1; +static constexpr auto cascadingSectionResizesProperty = "cascadingSectionResizes"_L1; +static constexpr auto defaultSectionSizeProperty = "defaultSectionSize"_L1; +static constexpr auto highlightSectionsProperty = "highlightSections"_L1; +static constexpr auto minimumSectionSizeProperty = "minimumSectionSize"_L1; +static constexpr auto showSortIndicatorProperty = "showSortIndicator"_L1; +static constexpr auto stretchLastSectionProperty = "stretchLastSection"_L1; + +/***************** ItemViewPropertySheetPrivate *********************/ + +ItemViewPropertySheetPrivate::ItemViewPropertySheetPrivate(QDesignerFormEditorInterface *core, + QHeaderView *horizontalHeader, + QHeaderView *verticalHeader) +{ + if (horizontalHeader) + m_propertySheet.insert(horizontalHeader, + qt_extension + (core->extensionManager(), horizontalHeader)); + if (verticalHeader) + m_propertySheet.insert(verticalHeader, + qt_extension + (core->extensionManager(), verticalHeader)); +} + +QStringList ItemViewPropertySheetPrivate::realPropertyNames() +{ + if (m_realPropertyNames.isEmpty()) + m_realPropertyNames = { + visibleProperty, cascadingSectionResizesProperty, + defaultSectionSizeProperty, highlightSectionsProperty, + minimumSectionSizeProperty, showSortIndicatorProperty, + stretchLastSectionProperty + }; + return m_realPropertyNames; +} + +QString ItemViewPropertySheetPrivate::fakePropertyName(const QString &prefix, + const QString &realName) +{ + // prefix = "header", realPropertyName = "isVisible" returns "headerIsVisible" + QString fakeName = prefix + realName.at(0).toUpper() + realName.mid(1); + m_propertyNameMap.insert(fakeName, realName); + return fakeName; +} + +/***************** ItemViewPropertySheet *********************/ + +/*! + \class qdesigner_internal::ItemViewPropertySheet + + \brief + Adds header fake properties to QTreeView and QTableView objects + + QHeaderView objects are currently not shown in the object inspector. + This class adds some fake properties to the property sheet + of QTreeView and QTableView objects that nevertheless allow the manipulation + of the headers attached to the item view object. + + Currently the defaultAlignment property is not shown because the property sheet + would only show integers, instead of the Qt::Alignment enumeration. + + The fake properties here need special handling in QDesignerResource, uiloader and uic. + */ + +ItemViewPropertySheet::ItemViewPropertySheet(QTreeView *treeViewObject, QObject *parent) + : QDesignerPropertySheet(treeViewObject, parent), + d(new ItemViewPropertySheetPrivate(core(), treeViewObject->header(), nullptr)) +{ + initHeaderProperties(treeViewObject->header(), u"header"_s); +} + +ItemViewPropertySheet::ItemViewPropertySheet(QTableView *tableViewObject, QObject *parent) + : QDesignerPropertySheet(tableViewObject, parent), + d(new ItemViewPropertySheetPrivate(core(), + tableViewObject->horizontalHeader(), + tableViewObject->verticalHeader())) +{ + initHeaderProperties(tableViewObject->horizontalHeader(), u"horizontalHeader"_s); + initHeaderProperties(tableViewObject->verticalHeader(), u"verticalHeader"_s); +} + +ItemViewPropertySheet::~ItemViewPropertySheet() +{ + delete d; +} + +void ItemViewPropertySheet::initHeaderProperties(QHeaderView *hv, const QString &prefix) +{ + QDesignerPropertySheetExtension *headerSheet = d->m_propertySheet.value(hv); + Q_ASSERT(headerSheet); + const QString headerGroupS = headerGroup; + const QStringList &realPropertyNames = d->realPropertyNames(); + for (const QString &realPropertyName : realPropertyNames) { + const int headerIndex = headerSheet->indexOf(realPropertyName); + Q_ASSERT(headerIndex != -1); + const QVariant defaultValue = realPropertyName == visibleProperty ? + QVariant(true) : headerSheet->property(headerIndex); + const QString fakePropertyName = d->fakePropertyName(prefix, realPropertyName); + const int fakeIndex = createFakeProperty(fakePropertyName, defaultValue); + d->m_propertyIdMap.insert(fakeIndex, Property(headerSheet, headerIndex)); + setAttribute(fakeIndex, true); + setPropertyGroup(fakeIndex, headerGroupS); + } +} + +/*! + Returns the mapping of fake property names to real property names + */ +QHash ItemViewPropertySheet::propertyNameMap() const +{ + return d->m_propertyNameMap; +} + +QVariant ItemViewPropertySheet::property(int index) const +{ + const auto it = d->m_propertyIdMap.constFind(index); + if (it != d->m_propertyIdMap.constEnd()) + return it.value().m_sheet->property(it.value().m_id); + return QDesignerPropertySheet::property(index); +} + +void ItemViewPropertySheet::setProperty(int index, const QVariant &value) +{ + const auto it = d->m_propertyIdMap.find(index); + if (it != d->m_propertyIdMap.end()) { + it.value().m_sheet->setProperty(it.value().m_id, value); + } else { + QDesignerPropertySheet::setProperty(index, value); + } +} + +void ItemViewPropertySheet::setChanged(int index, bool changed) +{ + const auto it = d->m_propertyIdMap.find(index); + if (it != d->m_propertyIdMap.end()) { + it.value().m_sheet->setChanged(it.value().m_id, changed); + } else { + QDesignerPropertySheet::setChanged(index, changed); + } +} + +bool ItemViewPropertySheet::isChanged(int index) const +{ + const auto it = d->m_propertyIdMap.constFind(index); + if (it != d->m_propertyIdMap.constEnd()) + return it.value().m_sheet->isChanged(it.value().m_id); + return QDesignerPropertySheet::isChanged(index); +} + +bool ItemViewPropertySheet::hasReset(int index) const +{ + const auto it = d->m_propertyIdMap.constFind(index); + if (it != d->m_propertyIdMap.constEnd()) + return it.value().m_sheet->hasReset(it.value().m_id); + return QDesignerPropertySheet::hasReset(index); +} + +bool ItemViewPropertySheet::reset(int index) +{ + const auto it = d->m_propertyIdMap.find(index); + if (it != d->m_propertyIdMap.end()) { + QDesignerPropertySheetExtension *headerSheet = it.value().m_sheet; + const int headerIndex = it.value().m_id; + const bool resetRC = headerSheet->reset(headerIndex); + // Resetting for "visible" might fail and the stored default + // of the Widget database is "false" due to the widget not being + // visible at the time it was determined. Reset to "true" manually. + if (!resetRC && headerSheet->propertyName(headerIndex) == visibleProperty) { + headerSheet->setProperty(headerIndex, QVariant(true)); + headerSheet->setChanged(headerIndex, false); + return true; + } + return resetRC; + } + return QDesignerPropertySheet::reset(index); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/itemview_propertysheet.h b/src/designer/src/components/formeditor/itemview_propertysheet.h new file mode 100644 index 0000000..5ef0619 --- /dev/null +++ b/src/designer/src/components/formeditor/itemview_propertysheet.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ITEMVIEW_PROPERTYSHEET_H +#define ITEMVIEW_PROPERTYSHEET_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +struct ItemViewPropertySheetPrivate; + +class ItemViewPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit ItemViewPropertySheet(QTreeView *treeViewObject, QObject *parent = nullptr); + explicit ItemViewPropertySheet(QTableView *tableViewObject, QObject *parent = nullptr); + ~ItemViewPropertySheet(); + + QHash propertyNameMap() const; + + // QDesignerPropertySheet + QVariant property(int index) const override; + void setProperty(int index, const QVariant &value) override; + + void setChanged(int index, bool changed) override; + bool isChanged(int index) const override; + + bool hasReset(int index) const override; + bool reset(int index) override; + +private: + void initHeaderProperties(QHeaderView *hv, const QString &prefix); + + ItemViewPropertySheetPrivate *d; +}; + +using QTreeViewPropertySheetFactory = QDesignerPropertySheetFactory; +using QTableViewPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ITEMVIEW_PROPERTYSHEET_H diff --git a/src/designer/src/components/formeditor/layout_propertysheet.cpp b/src/designer/src/components/formeditor/layout_propertysheet.cpp new file mode 100644 index 0000000..3b08c61 --- /dev/null +++ b/src/designer/src/components/formeditor/layout_propertysheet.cpp @@ -0,0 +1,509 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "layout_propertysheet.h" + +// sdk +#include +#include +// shared + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include // Remove once there is an editor for lists + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto leftMargin = "leftMargin"_L1; +static constexpr auto topMargin = "topMargin"_L1; +static constexpr auto rightMargin = "rightMargin"_L1; +static constexpr auto bottomMargin = "bottomMargin"_L1; +static constexpr auto horizontalSpacing = "horizontalSpacing"_L1; +static constexpr auto verticalSpacing = "verticalSpacing"_L1; +static constexpr auto spacing = "spacing"_L1; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +static constexpr auto sizeConstraint = "sizeConstraint"_L1; +#else +static constexpr auto horizontalSizeConstraint = "horizontalSizeConstraint"_L1; +static constexpr auto verticalSizeConstraint = "verticalSizeConstraint"_L1; +#endif +static constexpr auto boxStretchPropertyC = "stretch"_L1; +static constexpr auto gridRowStretchPropertyC = "rowStretch"_L1; +static constexpr auto gridColumnStretchPropertyC = "columnStretch"_L1; +static constexpr auto gridRowMinimumHeightPropertyC = "rowMinimumHeight"_L1; +static constexpr auto gridColumnMinimumWidthPropertyC = "columnMinimumWidth"_L1; + +namespace { + enum LayoutPropertyType { + LayoutPropertyNone, + LayoutPropertyLeftMargin, + LayoutPropertyTopMargin, + LayoutPropertyRightMargin, + LayoutPropertyBottomMargin, + LayoutPropertySpacing, + LayoutPropertyHorizontalSpacing, + LayoutPropertyVerticalSpacing, +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + LayoutPropertySizeConstraint, +#else + LayoutPropertyHorizontalSizeConstraint, + LayoutPropertyVerticalSizeConstraint, +#endif + LayoutPropertyBoxStretch, + LayoutPropertyGridRowStretch, + LayoutPropertyGridColumnStretch, + LayoutPropertyGridRowMinimumHeight, + LayoutPropertyGridColumnMinimumWidth + }; +} + +// Check for a comma-separated list of integers. Used for +// per-cell stretch properties and grid per row/column properties. +// As it works now, they are passed as QByteArray strings. The +// property sheet refuses all invalid values. This could be +// replaced by lists once the property editor can handle them. + +static bool isIntegerList(const QString &s) +{ + // Check for empty string or comma-separated list of integers + static const QRegularExpression re(u"^[0-9]+(,[0-9]+)+$"_s); + Q_ASSERT(re.isValid()); + return s.isEmpty() || re.match(s).hasMatch(); +} + +// Quick lookup by name +static LayoutPropertyType layoutPropertyType(const QString &name) +{ + static const QHash namePropertyMap = { + {leftMargin, LayoutPropertyLeftMargin}, + {topMargin, LayoutPropertyTopMargin}, + {rightMargin, LayoutPropertyRightMargin}, + {bottomMargin, LayoutPropertyBottomMargin}, + {horizontalSpacing, LayoutPropertyHorizontalSpacing}, + {verticalSpacing, LayoutPropertyVerticalSpacing}, + {spacing, LayoutPropertySpacing}, +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + {sizeConstraint, LayoutPropertySizeConstraint}, +#else + {horizontalSizeConstraint, LayoutPropertyHorizontalSizeConstraint}, + {verticalSizeConstraint, LayoutPropertyVerticalSizeConstraint}, +#endif + {boxStretchPropertyC, LayoutPropertyBoxStretch}, + {gridRowStretchPropertyC, LayoutPropertyGridRowStretch}, + {gridColumnStretchPropertyC, LayoutPropertyGridColumnStretch}, + {gridRowMinimumHeightPropertyC, LayoutPropertyGridRowMinimumHeight}, + {gridColumnMinimumWidthPropertyC, LayoutPropertyGridColumnMinimumWidth} + }; + return namePropertyMap.value(name, LayoutPropertyNone); +} + +// return the layout margin if it is margin +static int getLayoutMargin(const QLayout *l, LayoutPropertyType type) +{ + int left, top, right, bottom; + l->getContentsMargins(&left, &top, &right, &bottom); + switch (type) { + case LayoutPropertyLeftMargin: + return left; + case LayoutPropertyTopMargin: + return top; + case LayoutPropertyRightMargin: + return right; + case LayoutPropertyBottomMargin: + return bottom; + default: + Q_ASSERT(0); + break; + } + return 0; +} + +// return the layout margin if it is margin +static void setLayoutMargin(QLayout *l, LayoutPropertyType type, int margin) +{ + int left, top, right, bottom; + l->getContentsMargins(&left, &top, &right, &bottom); + switch (type) { + case LayoutPropertyLeftMargin: + left = margin; + break; + case LayoutPropertyTopMargin: + top = margin; + break; + case LayoutPropertyRightMargin: + right = margin; + break; + case LayoutPropertyBottomMargin: + bottom = margin; + break; + default: + Q_ASSERT(0); + break; + } + l->setContentsMargins(left, top, right, bottom); +} + +namespace qdesigner_internal { + +// ---------- LayoutPropertySheet: This sheet is never visible in +// the property editor. Rather, the sheet pulled for QLayoutWidget +// forwards all properties to it. Some properties (grid spacings) must be handled +// manually, as they are QDOC_PROPERTY only and not visible to introspection. Ditto +// for the 4 margins. + +LayoutPropertySheet::LayoutPropertySheet(QLayout *l, QObject *parent) + : QDesignerPropertySheet(l, parent), m_layout(l) +{ + const QString layoutGroup = u"Layout"_s; + int pindex = createFakeProperty(leftMargin, 0); + setPropertyGroup(pindex, layoutGroup); + + pindex = createFakeProperty(topMargin, 0); + setPropertyGroup(pindex, layoutGroup); + + pindex = createFakeProperty(rightMargin, 0); + setPropertyGroup(pindex, layoutGroup); + + pindex = createFakeProperty(bottomMargin, 0); + setPropertyGroup(pindex, layoutGroup); + + const int visibleMask = LayoutProperties::visibleProperties(m_layout); + if (visibleMask & LayoutProperties::HorizSpacingProperty) { + pindex = createFakeProperty(horizontalSpacing, 0); + setPropertyGroup(pindex, layoutGroup); + + pindex = createFakeProperty(verticalSpacing, 0); + setPropertyGroup(pindex, layoutGroup); + + setAttribute(indexOf(spacing), true); + } + + // Stretch + if (visibleMask & LayoutProperties::BoxStretchProperty) { + pindex = createFakeProperty(boxStretchPropertyC, QByteArray()); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + } else { + // Add the grid per-row/column stretch and size limits + if (visibleMask & LayoutProperties::GridColumnStretchProperty) { + const QByteArray empty; + pindex = createFakeProperty(gridRowStretchPropertyC, empty); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + pindex = createFakeProperty(gridColumnStretchPropertyC, empty); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + pindex = createFakeProperty(gridRowMinimumHeightPropertyC, empty); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + pindex = createFakeProperty(gridColumnMinimumWidthPropertyC, empty); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + } + } + // SizeConstraint cannot possibly be handled as a real property + // as it affects the layout parent widget and thus + // conflicts with Designer's special layout widget. + // It will take effect on the preview only. +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + pindex = createFakeProperty(sizeConstraint); + setPropertyGroup(pindex, layoutGroup); +#else + pindex = createFakeProperty(horizontalSizeConstraint); + setPropertyGroup(pindex, layoutGroup); + pindex = createFakeProperty(verticalSizeConstraint); + setPropertyGroup(pindex, layoutGroup); +#endif +} + +LayoutPropertySheet::~LayoutPropertySheet() = default; + +void LayoutPropertySheet::setProperty(int index, const QVariant &value) +{ + const LayoutPropertyType type = layoutPropertyType(propertyName(index)); + if (QLayoutWidget *lw = qobject_cast(m_layout->parent())) { + switch (type) { + case LayoutPropertyLeftMargin: + lw->setLayoutLeftMargin(value.toInt()); + return; + case LayoutPropertyTopMargin: + lw->setLayoutTopMargin(value.toInt()); + return; + case LayoutPropertyRightMargin: + lw->setLayoutRightMargin(value.toInt()); + return; + case LayoutPropertyBottomMargin: + lw->setLayoutBottomMargin(value.toInt()); + return; + default: + break; + } + } + switch (type) { + case LayoutPropertyLeftMargin: + case LayoutPropertyTopMargin: + case LayoutPropertyRightMargin: + case LayoutPropertyBottomMargin: + setLayoutMargin(m_layout, type, value.toInt()); + return; + case LayoutPropertyHorizontalSpacing: + if (QGridLayout *grid = qobject_cast(m_layout)) { + grid->setHorizontalSpacing(value.toInt()); + return; + } + if (QFormLayout *form = qobject_cast(m_layout)) { + form->setHorizontalSpacing(value.toInt()); + return; + } + break; + case LayoutPropertyVerticalSpacing: + if (QGridLayout *grid = qobject_cast(m_layout)) { + grid->setVerticalSpacing(value.toInt()); + return; + } + if (QFormLayout *form = qobject_cast(m_layout)) { + form->setVerticalSpacing(value.toInt()); + return; + } + break; + case LayoutPropertyBoxStretch: + // TODO: Remove the regexp check once a proper editor for integer + // lists is in place? + if (QBoxLayout *box = qobject_cast(m_layout)) { + const QString stretch = value.toString(); + if (isIntegerList(stretch)) + QFormBuilderExtra::setBoxLayoutStretch(value.toString(), box); + } + break; + case LayoutPropertyGridRowStretch: + if (QGridLayout *grid = qobject_cast(m_layout)) { + const QString stretch = value.toString(); + if (isIntegerList(stretch)) + QFormBuilderExtra::setGridLayoutRowStretch(stretch, grid); + } + break; + case LayoutPropertyGridColumnStretch: + if (QGridLayout *grid = qobject_cast(m_layout)) { + const QString stretch = value.toString(); + if (isIntegerList(stretch)) + QFormBuilderExtra::setGridLayoutColumnStretch(value.toString(), grid); + } + break; + case LayoutPropertyGridRowMinimumHeight: + if (QGridLayout *grid = qobject_cast(m_layout)) { + const QString minSize = value.toString(); + if (isIntegerList(minSize)) + QFormBuilderExtra::setGridLayoutRowMinimumHeight(minSize, grid); + } + break; + case LayoutPropertyGridColumnMinimumWidth: + if (QGridLayout *grid = qobject_cast(m_layout)) { + const QString minSize = value.toString(); + if (isIntegerList(minSize)) + QFormBuilderExtra::setGridLayoutColumnMinimumWidth(minSize, grid); + } + break; + default: + break; + } + QDesignerPropertySheet::setProperty(index, value); +} + +QVariant LayoutPropertySheet::property(int index) const +{ + const LayoutPropertyType type = layoutPropertyType(propertyName(index)); + if (const QLayoutWidget *lw = qobject_cast(m_layout->parent())) { + switch (type) { + case LayoutPropertyLeftMargin: + return lw->layoutLeftMargin(); + case LayoutPropertyTopMargin: + return lw->layoutTopMargin(); + case LayoutPropertyRightMargin: + return lw->layoutRightMargin(); + case LayoutPropertyBottomMargin: + return lw->layoutBottomMargin(); + default: + break; + } + } + switch (type) { + case LayoutPropertyLeftMargin: + case LayoutPropertyTopMargin: + case LayoutPropertyRightMargin: + case LayoutPropertyBottomMargin: + return getLayoutMargin(m_layout, type); + case LayoutPropertyHorizontalSpacing: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return grid->horizontalSpacing(); + if (const QFormLayout *form = qobject_cast(m_layout)) + return form->horizontalSpacing(); + break; + case LayoutPropertyVerticalSpacing: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return grid->verticalSpacing(); + if (const QFormLayout *form = qobject_cast(m_layout)) + return form->verticalSpacing(); + break; + case LayoutPropertyBoxStretch: + if (const QBoxLayout *box = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::boxLayoutStretch(box).toUtf8())); + break; + case LayoutPropertyGridRowStretch: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::gridLayoutRowStretch(grid).toUtf8())); + break; + case LayoutPropertyGridColumnStretch: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::gridLayoutColumnStretch(grid).toUtf8())); + break; + case LayoutPropertyGridRowMinimumHeight: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::gridLayoutRowMinimumHeight(grid).toUtf8())); + break; + case LayoutPropertyGridColumnMinimumWidth: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::gridLayoutColumnMinimumWidth(grid).toUtf8())); + break; + default: + break; + } + return QDesignerPropertySheet::property(index); +} + +bool LayoutPropertySheet::reset(int index) +{ + int left, top, right, bottom; + m_layout->getContentsMargins(&left, &top, &right, &bottom); + const LayoutPropertyType type = layoutPropertyType(propertyName(index)); + bool rc = true; + switch (type) { + case LayoutPropertyLeftMargin: + m_layout->setContentsMargins(-1, top, right, bottom); + break; + case LayoutPropertyTopMargin: + m_layout->setContentsMargins(left, -1, right, bottom); + break; + case LayoutPropertyRightMargin: + m_layout->setContentsMargins(left, top, -1, bottom); + break; + case LayoutPropertyBottomMargin: + m_layout->setContentsMargins(left, top, right, -1); + break; + case LayoutPropertyBoxStretch: + if (QBoxLayout *box = qobject_cast(m_layout)) + QFormBuilderExtra::clearBoxLayoutStretch(box); + break; + case LayoutPropertyGridRowStretch: + if (QGridLayout *grid = qobject_cast(m_layout)) + QFormBuilderExtra::clearGridLayoutRowStretch(grid); + break; + case LayoutPropertyGridColumnStretch: + if (QGridLayout *grid = qobject_cast(m_layout)) + QFormBuilderExtra::clearGridLayoutColumnStretch(grid); + break; + case LayoutPropertyGridRowMinimumHeight: + if (QGridLayout *grid = qobject_cast(m_layout)) + QFormBuilderExtra::clearGridLayoutRowMinimumHeight(grid); + break; + case LayoutPropertyGridColumnMinimumWidth: + if (QGridLayout *grid = qobject_cast(m_layout)) + QFormBuilderExtra::clearGridLayoutColumnMinimumWidth(grid); + break; + default: + rc = QDesignerPropertySheet::reset(index); + break; + } + return rc; +} + +void LayoutPropertySheet::setChanged(int index, bool changed) +{ + const LayoutPropertyType type = layoutPropertyType(propertyName(index)); + switch (type) { + case LayoutPropertySpacing: + if (LayoutProperties::visibleProperties(m_layout) & LayoutProperties::HorizSpacingProperty) { + setChanged(indexOf(horizontalSpacing), changed); + setChanged(indexOf(verticalSpacing), changed); + } + break; + default: + break; + } + QDesignerPropertySheet::setChanged(index, changed); +} + +void LayoutPropertySheet::stretchAttributesToDom(QDesignerFormEditorInterface *core, QLayout *lt, DomLayout *domLayout) +{ + // Check if the respective stretch properties of the layout are changed. + // If so, set them to the DOM + const int visibleMask = LayoutProperties::visibleProperties(lt); + if (!(visibleMask & (LayoutProperties::BoxStretchProperty|LayoutProperties::GridColumnStretchProperty|LayoutProperties::GridRowStretchProperty))) + return; + const QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), lt); + Q_ASSERT(sheet); + + // Stretch + if (visibleMask & LayoutProperties::BoxStretchProperty) { + const int index = sheet->indexOf(boxStretchPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeStretch(sheet->property(index).toString()); + } + if (visibleMask & LayoutProperties::GridColumnStretchProperty) { + const int index = sheet->indexOf(gridColumnStretchPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeColumnStretch(sheet->property(index).toString()); + } + if (visibleMask & LayoutProperties::GridRowStretchProperty) { + const int index = sheet->indexOf(gridRowStretchPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeRowStretch(sheet->property(index).toString()); + } + if (visibleMask & LayoutProperties::GridRowMinimumHeightProperty) { + const int index = sheet->indexOf(gridRowMinimumHeightPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeRowMinimumHeight(sheet->property(index).toString()); + } + if (visibleMask & LayoutProperties::GridColumnMinimumWidthProperty) { + const int index = sheet->indexOf(gridColumnMinimumWidthPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeColumnMinimumWidth(sheet->property(index).toString()); + } +} + +void LayoutPropertySheet::markChangedStretchProperties(QDesignerFormEditorInterface *core, QLayout *lt, const DomLayout *domLayout) +{ + // While the actual values are applied by the form builder, we still need + // to mark them as 'changed'. + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), lt); + Q_ASSERT(sheet); + if (!domLayout->attributeStretch().isEmpty()) + sheet->setChanged(sheet->indexOf(boxStretchPropertyC), true); + if (!domLayout->attributeRowStretch().isEmpty()) + sheet->setChanged(sheet->indexOf(gridRowStretchPropertyC), true); + if (!domLayout->attributeColumnStretch().isEmpty()) + sheet->setChanged(sheet->indexOf(gridColumnStretchPropertyC), true); + if (!domLayout->attributeColumnMinimumWidth().isEmpty()) + sheet->setChanged(sheet->indexOf(gridColumnMinimumWidthPropertyC), true); + if (!domLayout->attributeRowMinimumHeight().isEmpty()) + sheet->setChanged(sheet->indexOf(gridRowMinimumHeightPropertyC), true); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/layout_propertysheet.h b/src/designer/src/components/formeditor/layout_propertysheet.h new file mode 100644 index 0000000..5d59071 --- /dev/null +++ b/src/designer/src/components/formeditor/layout_propertysheet.h @@ -0,0 +1,44 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LAYOUT_PROPERTYSHEET_H +#define LAYOUT_PROPERTYSHEET_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class DomLayout; + +namespace qdesigner_internal { + +class LayoutPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit LayoutPropertySheet(QLayout *object, QObject *parent = nullptr); + ~LayoutPropertySheet() override; + + void setProperty(int index, const QVariant &value) override; + QVariant property(int index) const override; + bool reset(int index) override; + void setChanged(int index, bool changed) override; + + static void stretchAttributesToDom(QDesignerFormEditorInterface *core, QLayout *lt, DomLayout *domLayout); + static void markChangedStretchProperties(QDesignerFormEditorInterface *core, QLayout *lt, const DomLayout *domLayout); + +private: + QLayout *m_layout; +}; + +using LayoutPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LAYOUT_PROPERTYSHEET_H diff --git a/src/designer/src/components/formeditor/line_propertysheet.cpp b/src/designer/src/components/formeditor/line_propertysheet.cpp new file mode 100644 index 0000000..b155e78 --- /dev/null +++ b/src/designer/src/components/formeditor/line_propertysheet.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "line_propertysheet.h" +#include "formwindow.h" + +// sdk +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +LinePropertySheet::LinePropertySheet(Line *object, QObject *parent) + : QDesignerPropertySheet(object, parent) +{ + clearFakeProperties(); +} + +LinePropertySheet::~LinePropertySheet() = default; + +bool LinePropertySheet::isVisible(int index) const +{ + const QString name = propertyName(index); + + if (name == "frameShape"_L1) + return false; + return QDesignerPropertySheet::isVisible(index); +} + +void LinePropertySheet::setProperty(int index, const QVariant &value) +{ + QDesignerPropertySheet::setProperty(index, value); +} + +QString LinePropertySheet::propertyGroup(int index) const +{ + return QDesignerPropertySheet::propertyGroup(index); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/line_propertysheet.h b/src/designer/src/components/formeditor/line_propertysheet.h new file mode 100644 index 0000000..ba1dbe5 --- /dev/null +++ b/src/designer/src/components/formeditor/line_propertysheet.h @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LINE_PROPERTYSHEET_H +#define LINE_PROPERTYSHEET_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class LinePropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit LinePropertySheet(Line *object, QObject *parent = nullptr); + ~LinePropertySheet() override; + + void setProperty(int index, const QVariant &value) override; + bool isVisible(int index) const override; + QString propertyGroup(int index) const override; +}; + +using LinePropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LINE_PROPERTYSHEET_H diff --git a/src/designer/src/components/formeditor/previewactiongroup.cpp b/src/designer/src/components/formeditor/previewactiongroup.cpp new file mode 100644 index 0000000..abcf0b8 --- /dev/null +++ b/src/designer/src/components/formeditor/previewactiongroup.cpp @@ -0,0 +1,100 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "previewactiongroup.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { MaxDeviceActions = 20 }; + +namespace qdesigner_internal { + +PreviewActionGroup::PreviewActionGroup(QDesignerFormEditorInterface *core, QObject *parent) : + QActionGroup(parent), + m_core(core) +{ + /* Create a list of up to MaxDeviceActions invisible actions to be + * populated with device profiles (actiondata: index) followed by the + * standard style actions (actiondata: style name). */ + connect(this, &PreviewActionGroup::triggered, this, &PreviewActionGroup::slotTriggered); + setExclusive(true); + + // Create invisible actions for devices. Set index as action data. + for (int i = 0; i < MaxDeviceActions; i++) { + QAction *a = new QAction(this); + a->setObjectName(QString::asprintf("__qt_designer_device_%d_action", i)); + a->setVisible(false); + a->setData(i); + addAction(a); + } + // Create separator at index MaxDeviceActions + QAction *sep = new QAction(this); + sep->setObjectName(u"__qt_designer_deviceseparator"_s); + sep->setSeparator(true); + sep->setVisible(false); + addAction(sep); + // Populate devices + updateDeviceProfiles(); + + // Add style actions + const QStringList styles = QStyleFactory::keys(); + // Make sure ObjectName is unique in case toolbar solution is used. + // Create styles. Set style name string as action data. + for (const auto &s : styles) { + QAction *a = new QAction(tr("%1 Style").arg(s), this); + a->setObjectName("__qt_designer_style_"_L1 + s + "_action"_L1); + a->setData(s); + addAction(a); + } +} + +void PreviewActionGroup::updateDeviceProfiles() +{ + const QDesignerSharedSettings settings(m_core); + const auto profiles = settings.deviceProfiles(); + const auto al = actions(); + // Separator? + const bool hasProfiles = !profiles.isEmpty(); + al.at(MaxDeviceActions)->setVisible(hasProfiles); + int index = 0; + if (hasProfiles) { + // Make actions visible + const int maxIndex = qMin(static_cast(MaxDeviceActions), profiles.size()); + for (; index < maxIndex; index++) { + const QString name = profiles.at(index).name(); + al.at(index)->setText(name); + al.at(index)->setVisible(true); + } + } + // Hide rest + for ( ; index < MaxDeviceActions; index++) + al.at(index)->setVisible(false); +} + +void PreviewActionGroup::slotTriggered(QAction *a) +{ + // Device or style according to data. + const QVariant data = a->data(); + switch (data.metaType().id()) { + case QMetaType::QString: + emit preview(data.toString(), -1); + break; + case QMetaType::Int: + emit preview(QString(), data.toInt()); + break; + default: + break; + } +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/previewactiongroup.h b/src/designer/src/components/formeditor/previewactiongroup.h new file mode 100644 index 0000000..cb0ee0a --- /dev/null +++ b/src/designer/src/components/formeditor/previewactiongroup.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PREVIEWACTIONGROUP_H +#define PREVIEWACTIONGROUP_H + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +/* PreviewActionGroup: To be used as a submenu for 'Preview in...' + * Offers a menu of styles and device profiles. */ + +class PreviewActionGroup : public QActionGroup +{ + Q_DISABLE_COPY_MOVE(PreviewActionGroup) + Q_OBJECT +public: + explicit PreviewActionGroup(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + +signals: + void preview(const QString &style, int deviceProfileIndex); + +public slots: + void updateDeviceProfiles(); + +private slots: + void slotTriggered(QAction *); + +private: + QDesignerFormEditorInterface *m_core; +}; +} + +QT_END_NAMESPACE + +#endif // PREVIEWACTIONGROUP_H diff --git a/src/designer/src/components/formeditor/qdesigner_resource.cpp b/src/designer/src/components/formeditor/qdesigner_resource.cpp new file mode 100644 index 0000000..adc8d27 --- /dev/null +++ b/src/designer/src/components/formeditor/qdesigner_resource.cpp @@ -0,0 +1,2301 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_resource.h" +#include "formwindow.h" +#include "dynamicpropertysheet.h" +#include "qdesigner_tabwidget_p.h" +#include "iconloader_p.h" +#include "qdesigner_toolbox_p.h" +#include "qdesigner_stackedbox_p.h" +#include "qdesigner_toolbar_p.h" +#include "qdesigner_dockwidget_p.h" +#include "qdesigner_menu_p.h" +#include "qdesigner_menubar_p.h" +#include "qdesigner_membersheet_p.h" +#include "qtresourcemodel_p.h" +#include "qmdiarea_container.h" +#include "qwizard_container.h" +#include "layout_propertysheet.h" + +#include +#include +#include +#include +#include +#include +#include + +// shared +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// sdk +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +Q_DECLARE_METATYPE(QWidgetList) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using QFBE = QFormBuilderExtra; + +namespace { + using DomPropertyList = QList; +} + +static constexpr auto currentUiVersion = "4.0"_L1; +static constexpr auto clipboardObjectName = "__qt_fake_top_level"_L1; + +#define OLD_RESOURCE_FORMAT // Support pre 4.4 format. + +namespace qdesigner_internal { + +static bool supportsQualifiedEnums(const QVersionNumber &qtVersion) +{ + if (qtVersion >= QVersionNumber{6, 6, 2}) + return true; + + switch (qtVersion.majorVersion()) { + case 6: // Qt 6 + switch (qtVersion.minorVersion()) { + case 5: // 6.5 LTS + if (qtVersion.microVersion() >= 4) + return true; + break; + case 2: // 6.2 LTS + if (qtVersion.microVersion() >= 13) + return true; + break; + } + break; + + case 5: // Qt 5 LTS + if (qtVersion >= QVersionNumber{5, 15, 18}) + return true; + break; + } + return false; +} + +// -------------------- QDesignerResourceBuilder: A resource builder that works on the property sheet icon types. +class QDesignerResourceBuilder : public QResourceBuilder +{ +public: + QDesignerResourceBuilder(QDesignerFormEditorInterface *core, DesignerPixmapCache *pixmapCache, DesignerIconCache *iconCache); + + void setPixmapCache(DesignerPixmapCache *pixmapCache) { m_pixmapCache = pixmapCache; } + void setIconCache(DesignerIconCache *iconCache) { m_iconCache = iconCache; } + bool isSaveRelative() const { return m_saveRelative; } + void setSaveRelative(bool relative) { m_saveRelative = relative; } + QStringList usedQrcFiles() const { return m_usedQrcFiles.keys(); } +#ifdef OLD_RESOURCE_FORMAT + QStringList loadedQrcFiles() const { return m_loadedQrcFiles.keys(); } // needed only for loading old resource attribute of tag. +#endif + + QVariant loadResource(const QDir &workingDirectory, const DomProperty *icon) const override; + + QVariant toNativeValue(const QVariant &value) const override; + + DomProperty *saveResource(const QDir &workingDirectory, const QVariant &value) const override; + + bool isResourceType(const QVariant &value) const override; +private: + + QDesignerFormEditorInterface *m_core; + DesignerPixmapCache *m_pixmapCache; + DesignerIconCache *m_iconCache; + const QDesignerLanguageExtension *m_lang; + bool m_saveRelative; + mutable QMap m_usedQrcFiles; + mutable QMap m_loadedQrcFiles; +}; + +QDesignerResourceBuilder::QDesignerResourceBuilder(QDesignerFormEditorInterface *core, DesignerPixmapCache *pixmapCache, DesignerIconCache *iconCache) : + m_core(core), + m_pixmapCache(pixmapCache), + m_iconCache(iconCache), + m_lang(qt_extension(core->extensionManager(), core)), + m_saveRelative(true) +{ +} + +static inline void setIconPixmap(QIcon::Mode m, QIcon::State s, const QDir &workingDirectory, + QString path, PropertySheetIconValue &icon, + const QDesignerLanguageExtension *lang = nullptr) +{ + if (lang == nullptr || !lang->isLanguageResource(path)) + path = QFileInfo(workingDirectory, path).absoluteFilePath(); + icon.setPixmap(m, s, PropertySheetPixmapValue(path)); +} + +QVariant QDesignerResourceBuilder::loadResource(const QDir &workingDirectory, const DomProperty *property) const +{ + switch (property->kind()) { + case DomProperty::Pixmap: { + PropertySheetPixmapValue pixmap; + DomResourcePixmap *dp = property->elementPixmap(); + if (!dp->text().isEmpty()) { + if (m_lang != nullptr && m_lang->isLanguageResource(dp->text())) { + pixmap.setPath(dp->text()); + } else { + pixmap.setPath(QFileInfo(workingDirectory, dp->text()).absoluteFilePath()); + } +#ifdef OLD_RESOURCE_FORMAT + if (dp->hasAttributeResource()) + m_loadedQrcFiles.insert(QFileInfo(workingDirectory, dp->attributeResource()).absoluteFilePath(), false); +#endif + } + return QVariant::fromValue(pixmap); + } + + case DomProperty::IconSet: { + PropertySheetIconValue icon; + DomResourceIcon *di = property->elementIconSet(); + const bool hasTheme = di->hasAttributeTheme(); + if (hasTheme) { + const QString &theme = di->attributeTheme(); + const qsizetype themeEnum = theme.startsWith("QIcon::"_L1) + ? QDesignerResourceBuilder::themeIconIndex(theme) : -1; + if (themeEnum != -1) + icon.setThemeEnum(themeEnum); + else + icon.setTheme(theme); + } + if (const int flags = iconStateFlags(di)) { // new, post 4.4 format + if (flags & NormalOff) + setIconPixmap(QIcon::Normal, QIcon::Off, workingDirectory, di->elementNormalOff()->text(), icon, m_lang); + if (flags & NormalOn) + setIconPixmap(QIcon::Normal, QIcon::On, workingDirectory, di->elementNormalOn()->text(), icon, m_lang); + if (flags & DisabledOff) + setIconPixmap(QIcon::Disabled, QIcon::Off, workingDirectory, di->elementDisabledOff()->text(), icon, m_lang); + if (flags & DisabledOn) + setIconPixmap(QIcon::Disabled, QIcon::On, workingDirectory, di->elementDisabledOn()->text(), icon, m_lang); + if (flags & ActiveOff) + setIconPixmap(QIcon::Active, QIcon::Off, workingDirectory, di->elementActiveOff()->text(), icon, m_lang); + if (flags & ActiveOn) + setIconPixmap(QIcon::Active, QIcon::On, workingDirectory, di->elementActiveOn()->text(), icon, m_lang); + if (flags & SelectedOff) + setIconPixmap(QIcon::Selected, QIcon::Off, workingDirectory, di->elementSelectedOff()->text(), icon, m_lang); + if (flags & SelectedOn) + setIconPixmap(QIcon::Selected, QIcon::On, workingDirectory, di->elementSelectedOn()->text(), icon, m_lang); + } else if (!hasTheme) { +#ifdef OLD_RESOURCE_FORMAT + setIconPixmap(QIcon::Normal, QIcon::Off, workingDirectory, di->text(), icon, m_lang); + if (di->hasAttributeResource()) + m_loadedQrcFiles.insert(QFileInfo(workingDirectory, di->attributeResource()).absoluteFilePath(), false); +#endif + } + return QVariant::fromValue(icon); + } + default: + break; + } + return QVariant(); +} + +QVariant QDesignerResourceBuilder::toNativeValue(const QVariant &value) const +{ + if (value.canConvert()) { + if (m_pixmapCache) + return m_pixmapCache->pixmap(qvariant_cast(value)); + } else if (value.canConvert()) { + if (m_iconCache) + return m_iconCache->icon(qvariant_cast(value)); + } + return value; +} + +DomProperty *QDesignerResourceBuilder::saveResource(const QDir &workingDirectory, const QVariant &value) const +{ + DomProperty *p = new DomProperty; + if (value.canConvert()) { + const PropertySheetPixmapValue pix = qvariant_cast(value); + DomResourcePixmap *rp = new DomResourcePixmap; + const QString pixPath = pix.path(); + switch (pix.pixmapSource(m_core)) { + case PropertySheetPixmapValue::LanguageResourcePixmap: + rp->setText(pixPath); + break; + case PropertySheetPixmapValue::ResourcePixmap: { + rp->setText(pixPath); + const QString qrcFile = m_core->resourceModel()->qrcPath(pixPath); + if (!qrcFile.isEmpty()) { + m_usedQrcFiles.insert(qrcFile, false); +#ifdef OLD_RESOURCE_FORMAT // Legacy: Add qrc path + rp->setAttributeResource(workingDirectory.relativeFilePath(qrcFile)); +#endif + } + } + break; + case PropertySheetPixmapValue::FilePixmap: + rp->setText(m_saveRelative ? workingDirectory.relativeFilePath(pixPath) : pixPath); + break; + } + p->setElementPixmap(rp); + return p; + } + if (value.canConvert()) { + const PropertySheetIconValue icon = qvariant_cast(value); + const auto &pixmaps = icon.paths(); + const int themeEnum = icon.themeEnum(); + const QString theme = themeEnum != -1 + ? QDesignerResourceBuilder::fullyQualifiedThemeIconName(themeEnum) : icon.theme(); + if (!pixmaps.isEmpty() || !theme.isEmpty()) { + DomResourceIcon *ri = new DomResourceIcon; + if (!theme.isEmpty()) + ri->setAttributeTheme(theme); + for (auto itPix = pixmaps.cbegin(), end = pixmaps.cend(); itPix != end; ++itPix) { + const QIcon::Mode mode = itPix.key().first; + const QIcon::State state = itPix.key().second; + DomResourcePixmap *rp = new DomResourcePixmap; + const PropertySheetPixmapValue &pix = itPix.value(); + const PropertySheetPixmapValue::PixmapSource ps = pix.pixmapSource(m_core); + const QString pixPath = pix.path(); + rp->setText(ps == PropertySheetPixmapValue::FilePixmap && m_saveRelative ? workingDirectory.relativeFilePath(pixPath) : pixPath); + if (state == QIcon::Off) { + switch (mode) { + case QIcon::Normal: + ri->setElementNormalOff(rp); +#ifdef OLD_RESOURCE_FORMAT // Legacy: Set Normal off as text/path in old format. + ri->setText(rp->text()); +#endif + if (ps == PropertySheetPixmapValue::ResourcePixmap) { + // Be sure that ri->text() file comes from active resourceSet (i.e. make appropriate + // resourceSet active before calling this method). + const QString qrcFile = m_core->resourceModel()->qrcPath(ri->text()); + if (!qrcFile.isEmpty()) { + m_usedQrcFiles.insert(qrcFile, false); +#ifdef OLD_RESOURCE_FORMAT // Legacy: Set Normal off as text/path in old format. + ri->setAttributeResource(workingDirectory.relativeFilePath(qrcFile)); +#endif + } + } + break; + case QIcon::Disabled: ri->setElementDisabledOff(rp); break; + case QIcon::Active: ri->setElementActiveOff(rp); break; + case QIcon::Selected: ri->setElementSelectedOff(rp); break; + } + } else { + switch (mode) { + case QIcon::Normal: ri->setElementNormalOn(rp); break; + case QIcon::Disabled: ri->setElementDisabledOn(rp); break; + case QIcon::Active: ri->setElementActiveOn(rp); break; + case QIcon::Selected: ri->setElementSelectedOn(rp); break; + } + } + } + p->setElementIconSet(ri); + return p; + } + } + delete p; + return nullptr; +} + +bool QDesignerResourceBuilder::isResourceType(const QVariant &value) const +{ + return value.canConvert() + || value.canConvert(); +} +// ------------------------- QDesignerTextBuilder + +template // for DomString, potentially DomStringList +inline void translationParametersToDom(const PropertySheetTranslatableData &data, DomElement *e) +{ + const QString propertyComment = data.disambiguation(); + if (!propertyComment.isEmpty()) + e->setAttributeComment(propertyComment); + const QString propertyExtracomment = data.comment(); + if (!propertyExtracomment.isEmpty()) + e->setAttributeExtraComment(propertyExtracomment); + const QString &id = data.id(); + if (!id.isEmpty()) + e->setAttributeId(id); + if (!data.translatable()) + e->setAttributeNotr(u"true"_s); +} + +template // for DomString, potentially DomStringList +inline void translationParametersFromDom(const DomElement *e, PropertySheetTranslatableData *data) +{ + if (e->hasAttributeComment()) + data->setDisambiguation(e->attributeComment()); + if (e->hasAttributeExtraComment()) + data->setComment(e->attributeExtraComment()); + if (e->hasAttributeId()) + data->setId(e->attributeId()); + if (e->hasAttributeNotr()) { + const QString notr = e->attributeNotr(); + const bool translatable = !(notr == "true"_L1 || notr == "yes"_L1); + data->setTranslatable(translatable); + } +} + +class QDesignerTextBuilder : public QTextBuilder +{ +public: + QDesignerTextBuilder() = default; + + QVariant loadText(const DomProperty *icon) const override; + + QVariant toNativeValue(const QVariant &value) const override; + + DomProperty *saveText(const QVariant &value) const override; +}; + +QVariant QDesignerTextBuilder::loadText(const DomProperty *text) const +{ + if (const DomString *domString = text->elementString()) { + PropertySheetStringValue stringValue(domString->text()); + translationParametersFromDom(domString, &stringValue); + return QVariant::fromValue(stringValue); + } + return QVariant(QString()); +} + +QVariant QDesignerTextBuilder::toNativeValue(const QVariant &value) const +{ + if (value.canConvert()) + return QVariant::fromValue(qvariant_cast(value).value()); + return value; +} + +static inline DomProperty *stringToDomProperty(const QString &value) +{ + DomString *domString = new DomString(); + domString->setText(value); + DomProperty *property = new DomProperty(); + property->setElementString(domString); + return property; +} + +static inline DomProperty *stringToDomProperty(const QString &value, + const PropertySheetTranslatableData &translatableData) +{ + DomString *domString = new DomString(); + domString->setText(value); + translationParametersToDom(translatableData, domString); + DomProperty *property = new DomProperty(); + property->setElementString(domString); + return property; +} + +DomProperty *QDesignerTextBuilder::saveText(const QVariant &value) const +{ + if (value.canConvert()) { + const PropertySheetStringValue str = qvariant_cast(value); + return stringToDomProperty(str.value(), str); + } + if (value.canConvert()) + return stringToDomProperty(value.toString()); + return nullptr; +} + +QDesignerResource::QDesignerResource(FormWindow *formWindow) : + QEditorFormBuilder(formWindow->core()), + m_formWindow(formWindow), + m_copyWidget(false), + m_selected(nullptr), + m_resourceBuilder(new QDesignerResourceBuilder(m_formWindow->core(), m_formWindow->pixmapCache(), m_formWindow->iconCache())) +{ + // Check language unless extension present (Jambi) + QDesignerFormEditorInterface *core = m_formWindow->core(); + if (const QDesignerLanguageExtension *le = qt_extension(core->extensionManager(), core)) + d->m_language = le->name(); + + setWorkingDirectory(formWindow->absoluteDir()); + setResourceBuilder(m_resourceBuilder); + setTextBuilder(new QDesignerTextBuilder()); + + // ### generalise + const QString designerWidget = u"QDesignerWidget"_s; + const QString layoutWidget = u"QLayoutWidget"_s; + const QString widget = u"QWidget"_s; + m_internal_to_qt.insert(layoutWidget, widget); + m_internal_to_qt.insert(designerWidget, widget); + m_internal_to_qt.insert(u"QDesignerDialog"_s, u"QDialog"_s); + m_internal_to_qt.insert(u"QDesignerMenuBar"_s, u"QMenuBar"_s); + m_internal_to_qt.insert(u"QDesignerMenu"_s, u"QMenu"_s); + m_internal_to_qt.insert(u"QDesignerDockWidget"_s, u"QDockWidget"_s); + + // invert + for (auto it = m_internal_to_qt.cbegin(), cend = m_internal_to_qt.cend(); it != cend; ++it ) { + if (it.value() != designerWidget && it.value() != layoutWidget) + m_qt_to_internal.insert(it.value(), it.key()); + + } +} + +QDesignerResource::~QDesignerResource() = default; + +DomUI *QDesignerResource::readUi(QIODevice *dev) +{ + return d->readUi(dev); +} + +static inline QString messageBoxTitle() +{ + return QApplication::translate("Designer", "Qt Widgets Designer"); +} + +void QDesignerResource::save(QIODevice *dev, QWidget *widget) +{ + // Do not write fully qualified enumerations for spacer/line orientations + // and other enum/flag properties for older Qt versions since that breaks + // older uic. + const auto qtVersion = m_formWindow->core()->integration()->qtVersion(); + d->m_fullyQualifiedEnums = supportsQualifiedEnums(qtVersion); + d->m_separateSizeConstraints = qtVersion >= QVersionNumber(7, 0, 0); + QAbstractFormBuilder::save(dev, widget); +} + +void QDesignerResource::saveDom(DomUI *ui, QWidget *widget) +{ + QAbstractFormBuilder::saveDom(ui, widget); + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), widget); + Q_ASSERT(sheet != nullptr); + + const QVariant classVar = sheet->property(sheet->indexOf(u"objectName"_s)); + QString classStr; + if (classVar.canConvert()) + classStr = classVar.toString(); + else + classStr = qvariant_cast(classVar).value(); + ui->setElementClass(classStr); + + for (int index = 0; index < m_formWindow->toolCount(); ++index) { + QDesignerFormWindowToolInterface *tool = m_formWindow->tool(index); + Q_ASSERT(tool != nullptr); + tool->saveToDom(ui, widget); + } + + const QString author = m_formWindow->author(); + if (!author.isEmpty()) { + ui->setElementAuthor(author); + } + + const QString comment = m_formWindow->comment(); + if (!comment.isEmpty()) { + ui->setElementComment(comment); + } + + const QString exportMacro = m_formWindow->exportMacro(); + if (!exportMacro.isEmpty()) { + ui->setElementExportMacro(exportMacro); + } + + if (m_formWindow->useIdBasedTranslations()) + ui->setAttributeIdbasedtr(true); + if (!m_formWindow->connectSlotsByName()) // Don't write out if true (default) + ui->setAttributeConnectslotsbyname(false); + + const QVariantMap designerFormData = m_formWindow->formData(); + if (!designerFormData.isEmpty()) { + DomPropertyList domPropertyList; + for (auto it = designerFormData.cbegin(), cend = designerFormData.cend(); it != cend; ++it) { + if (DomProperty *prop = variantToDomProperty(this, widget->metaObject(), it.key(), it.value())) + domPropertyList += prop; + } + if (!domPropertyList.isEmpty()) { + DomDesignerData* domDesignerFormData = new DomDesignerData; + domDesignerFormData->setElementProperty(domPropertyList); + ui->setElementDesignerdata(domDesignerFormData); + } + } + + if (!m_formWindow->includeHints().isEmpty()) { + const QString local = u"local"_s; + const QString global = u"global"_s; + QList ui_includes; + const QStringList &includeHints = m_formWindow->includeHints(); + ui_includes.reserve(includeHints.size()); + for (QString includeHint : includeHints) { + if (includeHint.isEmpty()) + continue; + DomInclude *incl = new DomInclude; + const QString location = includeHint.at(0) == u'<' ? global : local; + includeHint.remove(u'"'); + includeHint.remove(u'<'); + includeHint.remove(u'>'); + incl->setAttributeLocation(location); + incl->setText(includeHint); + ui_includes.append(incl); + } + + DomIncludes *includes = new DomIncludes; + includes->setElementInclude(ui_includes); + ui->setElementIncludes(includes); + } + + int defaultMargin = INT_MIN, defaultSpacing = INT_MIN; + m_formWindow->layoutDefault(&defaultMargin, &defaultSpacing); + + if (defaultMargin != INT_MIN || defaultSpacing != INT_MIN) { + DomLayoutDefault *def = new DomLayoutDefault; + if (defaultMargin != INT_MIN) + def->setAttributeMargin(defaultMargin); + if (defaultSpacing != INT_MIN) + def->setAttributeSpacing(defaultSpacing); + ui->setElementLayoutDefault(def); + } + + QString marginFunction, spacingFunction; + m_formWindow->layoutFunction(&marginFunction, &spacingFunction); + if (!marginFunction.isEmpty() || !spacingFunction.isEmpty()) { + DomLayoutFunction *def = new DomLayoutFunction; + + if (!marginFunction.isEmpty()) + def->setAttributeMargin(marginFunction); + if (!spacingFunction.isEmpty()) + def->setAttributeSpacing(spacingFunction); + ui->setElementLayoutFunction(def); + } + + QString pixFunction = m_formWindow->pixmapFunction(); + if (!pixFunction.isEmpty()) { + ui->setElementPixmapFunction(pixFunction); + } + + if (QDesignerExtraInfoExtension *extra = qt_extension(core()->extensionManager(), core())) + extra->saveUiExtraInfo(ui); + + if (MetaDataBase *metaDataBase = qobject_cast(core()->metaDataBase())) { + const MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(m_formWindow->mainContainer()); + const QStringList fakeSlots = item->fakeSlots(); + const QStringList fakeSignals =item->fakeSignals(); + if (!fakeSlots.isEmpty() || !fakeSignals.isEmpty()) { + DomSlots *domSlots = new DomSlots(); + domSlots->setElementSlot(fakeSlots); + domSlots->setElementSignal(fakeSignals); + ui->setElementSlots(domSlots); + } + } +} + +QWidget *QDesignerResource::load(QIODevice *dev, QWidget *parentWidget) +{ + QScopedPointer ui(readUi(dev)); + return ui.isNull() ? nullptr : loadUi(ui.data(), parentWidget); +} + +QWidget *QDesignerResource::loadUi(DomUI *ui, QWidget *parentWidget) +{ + QWidget *widget = create(ui, parentWidget); + // Store the class name as 'reset' value for the main container's object name. + if (widget) + widget->setProperty("_q_classname", widget->objectName()); + else if (d->m_errorString.isEmpty()) + d->m_errorString = QFormBuilderExtra::msgInvalidUiFile(); + return widget; +} + +bool QDesignerResource::saveRelative() const +{ + return m_resourceBuilder->isSaveRelative(); +} + +void QDesignerResource::setSaveRelative(bool relative) +{ + m_resourceBuilder->setSaveRelative(relative); +} + +QWidget *QDesignerResource::create(DomUI *ui, QWidget *parentWidget) +{ + // Load extra info extension. This is used by Jambi for preventing + // C++ UI files from being loaded + if (QDesignerExtraInfoExtension *extra = qt_extension(core()->extensionManager(), core())) { + if (!extra->loadUiExtraInfo(ui)) { + const QString errorMessage = QApplication::translate("Designer", "This file cannot be read because the extra info extension failed to load."); + core()->dialogGui()->message(parentWidget->window(), QDesignerDialogGuiInterface::FormLoadFailureMessage, + QMessageBox::Warning, messageBoxTitle(), errorMessage, QMessageBox::Ok); + return nullptr; + } + } + + qdesigner_internal::WidgetFactory *factory = qobject_cast(core()->widgetFactory()); + Q_ASSERT(factory != nullptr); + + QDesignerFormWindowInterface *previousFormWindow = factory->currentFormWindow(m_formWindow); + + m_isMainWidget = true; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + QWidget *mainWidget = QAbstractFormBuilder::create(ui, parentWidget); + + if (m_formWindow) { + m_formWindow->setUseIdBasedTranslations(ui->attributeIdbasedtr()); + // Default to true unless set. + const bool connectSlotsByName = !ui->hasAttributeConnectslotsbyname() || ui->attributeConnectslotsbyname(); + m_formWindow->setConnectSlotsByName(connectSlotsByName); + } + + if (mainWidget && m_formWindow) { + m_formWindow->setAuthor(ui->elementAuthor()); + m_formWindow->setComment(ui->elementComment()); + m_formWindow->setExportMacro(ui->elementExportMacro()); + + // Designer data + QVariantMap designerFormData; + if (ui->hasElementDesignerdata()) { + const DomPropertyList domPropertyList = ui->elementDesignerdata()->elementProperty(); + for (auto *prop : domPropertyList) { + const QVariant vprop = domPropertyToVariant(this, mainWidget->metaObject(), prop); + if (vprop.metaType().id() != QMetaType::UnknownType) + designerFormData.insert(prop->attributeName(), vprop); + } + } + m_formWindow->setFormData(designerFormData); + + m_formWindow->setPixmapFunction(ui->elementPixmapFunction()); + + if (DomLayoutDefault *def = ui->elementLayoutDefault()) { + m_formWindow->setLayoutDefault(def->attributeMargin(), def->attributeSpacing()); + } + + if (DomLayoutFunction *fun = ui->elementLayoutFunction()) { + m_formWindow->setLayoutFunction(fun->attributeMargin(), fun->attributeSpacing()); + } + + if (DomIncludes *includes = ui->elementIncludes()) { + const auto global = "global"_L1; + QStringList includeHints; + const auto &elementInclude = includes->elementInclude(); + for (DomInclude *incl : elementInclude) { + QString text = incl->text(); + + if (text.isEmpty()) + continue; + + if (incl->hasAttributeLocation() && incl->attributeLocation() == global ) { + text.prepend(u'<'); + text.append(u'>'); + } else { + text.prepend(u'"'); + text.append(u'"'); + } + + includeHints.append(text); + } + + m_formWindow->setIncludeHints(includeHints); + } + + // Register all button groups the form builder adds as children of the main container for them to be found + // in the signal slot editor + auto *mdb = core()->metaDataBase(); + for (auto *child : mainWidget->children()) { + if (QButtonGroup *bg = qobject_cast(child)) + mdb->add(bg); + } + // Load tools + for (int index = 0; index < m_formWindow->toolCount(); ++index) { + QDesignerFormWindowToolInterface *tool = m_formWindow->tool(index); + Q_ASSERT(tool != nullptr); + tool->loadFromDom(ui, mainWidget); + } + } + + factory->currentFormWindow(previousFormWindow); + + if (const DomSlots *domSlots = ui->elementSlots()) { + if (MetaDataBase *metaDataBase = qobject_cast(core()->metaDataBase())) { + QStringList fakeSlots; + QStringList fakeSignals; + if (addFakeMethods(domSlots, fakeSlots, fakeSignals)) { + MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(mainWidget); + item->setFakeSlots(fakeSlots); + item->setFakeSignals(fakeSignals); + } + } + } + if (mainWidget) { + // Initialize the mainwindow geometry. Has it been explicitly specified? + bool hasExplicitGeometry = false; + const auto &properties = ui->elementWidget()->elementProperty(); + if (!properties.isEmpty()) { + for (const DomProperty *p : properties) { + if (p->attributeName() == "geometry"_L1) { + hasExplicitGeometry = true; + break; + } + } + } + if (hasExplicitGeometry) { + // Geometry was specified explicitly: Verify that smartMinSize is respected + // (changed fonts, label wrapping policies, etc). This does not happen automatically in docked mode. + const QSize size = mainWidget->size(); + const QSize minSize = size.expandedTo(qSmartMinSize(mainWidget)); + if (minSize != size) + mainWidget->resize(minSize); + } else { + // No explicit Geometry: perform an adjustSize() to resize the form correctly before embedding it into a container + // (which might otherwise squeeze the form) + mainWidget->adjustSize(); + } + // Some integration wizards create forms with main containers + // based on derived classes of QWidget and load them into Designer + // without the plugin existing. This will trigger the auto-promotion + // mechanism of Designer, which will set container=false for + // QWidgets. For the main container, force container=true and warn. + const QDesignerWidgetDataBaseInterface *wdb = core()->widgetDataBase(); + const int wdbIndex = wdb->indexOfObject(mainWidget); + if (wdbIndex != -1) { + QDesignerWidgetDataBaseItemInterface *item = wdb->item(wdbIndex); + // Promoted main container that is not of container type + if (item->isPromoted() && !item->isContainer()) { + item->setContainer(true); + qWarning("** WARNING The form's main container is an unknown custom widget '%s'." + " Defaulting to a promoted instance of '%s', assuming container.", + item->name().toUtf8().constData(), item->extends().toUtf8().constData()); + } + } + } + return mainWidget; +} + +QWidget *QDesignerResource::create(DomWidget *ui_widget, QWidget *parentWidget) +{ + const QString className = ui_widget->attributeClass(); + if (!m_isMainWidget && className == "QWidget"_L1 + && !ui_widget->elementLayout().isEmpty() + && !ui_widget->hasAttributeNative()) { + // ### check if elementLayout.size() == 1 + + QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), parentWidget); + + if (container == nullptr) { + // generate a QLayoutWidget iff the parent is not an QDesignerContainerExtension. + ui_widget->setAttributeClass(u"QLayoutWidget"_s); + } + } + + // save the actions + const auto &actionRefs = ui_widget->elementAddAction(); + ui_widget->setElementAddAction(QList()); + + QWidget *w = QAbstractFormBuilder::create(ui_widget, parentWidget); + + // restore the actions + ui_widget->setElementAddAction(actionRefs); + + if (w == nullptr) + return nullptr; + + // ### generalize using the extension manager + QDesignerMenu *menu = qobject_cast(w); + QDesignerMenuBar *menuBar = qobject_cast(w); + + if (menu) + menu->hide(); + + for (DomActionRef *ui_action_ref : actionRefs) { + const QString name = ui_action_ref->attributeName(); + if (name == "separator"_L1) { + QAction *sep = new QAction(w); + sep->setSeparator(true); + w->addAction(sep); + addMenuAction(sep); + } else if (QAction *a = d->m_actions.value(name)) { + w->addAction(a); + } else if (QActionGroup *g = d->m_actionGroups.value(name)) { + w->addActions(g->actions()); + } else if (QMenu *menu = w->findChild(name)) { + w->addAction(menu->menuAction()); + addMenuAction(menu->menuAction()); + } + } + + if (menu) + menu->adjustSpecialActions(); + else if (menuBar) + menuBar->adjustSpecialActions(); + + ui_widget->setAttributeClass(className); // fix the class name + applyExtensionDataFromDOM(this, core(), ui_widget, w); + + return w; +} + +QLayout *QDesignerResource::create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) +{ + QLayout *l = QAbstractFormBuilder::create(ui_layout, layout, parentWidget); + + if (QGridLayout *gridLayout = qobject_cast(l)) { + QLayoutSupport::createEmptyCells(gridLayout); + } else { + if (QFormLayout *formLayout = qobject_cast(l)) + QLayoutSupport::createEmptyCells(formLayout); + } + // While the actual values are applied by the form builder, we still need + // to mark them as 'changed'. + LayoutPropertySheet::markChangedStretchProperties(core(), l, ui_layout); + return l; +} + +QLayoutItem *QDesignerResource::create(DomLayoutItem *ui_layoutItem, QLayout *layout, QWidget *parentWidget) +{ + if (ui_layoutItem->kind() == DomLayoutItem::Spacer) { + const DomSpacer *domSpacer = ui_layoutItem->elementSpacer(); + Spacer *spacer = static_cast(core()->widgetFactory()->createWidget(u"Spacer"_s, parentWidget)); + if (domSpacer->hasAttributeName()) + changeObjectName(spacer, domSpacer->attributeName()); + core()->metaDataBase()->add(spacer); + + spacer->setInteractiveMode(false); + applyProperties(spacer, ui_layoutItem->elementSpacer()->elementProperty()); + spacer->setInteractiveMode(true); + + if (m_formWindow) { + m_formWindow->manageWidget(spacer); + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), spacer)) + sheet->setChanged(sheet->indexOf(u"orientation"_s), true); + } + + return new QWidgetItem(spacer); + } + if (ui_layoutItem->kind() == DomLayoutItem::Layout && parentWidget) { + DomLayout *ui_layout = ui_layoutItem->elementLayout(); + QLayoutWidget *layoutWidget = new QLayoutWidget(m_formWindow, parentWidget); + core()->metaDataBase()->add(layoutWidget); + if (m_formWindow) + m_formWindow->manageWidget(layoutWidget); + (void) create(ui_layout, nullptr, layoutWidget); + return new QWidgetItem(layoutWidget); + } + return QAbstractFormBuilder::create(ui_layoutItem, layout, parentWidget); +} + +void QDesignerResource::changeObjectName(QObject *o, QString objName) +{ + m_formWindow->unify(o, objName, true); + o->setObjectName(objName); + +} + +/* If the property is a enum or flag value, retrieve + * the existing enum/flag via property sheet and use it to convert */ + +static bool readDomEnumerationValue(const DomProperty *p, + const QDesignerPropertySheetExtension* sheet, int index, + QVariant &v) +{ + switch (p->kind()) { + case DomProperty::Set: { + const QVariant sheetValue = sheet->property(index); + if (sheetValue.canConvert()) { + const PropertySheetFlagValue f = qvariant_cast(sheetValue); + bool ok = false; + v = f.metaFlags.parseFlags(p->elementSet(), &ok); + if (!ok) + designerWarning(f.metaFlags.messageParseFailed(p->elementSet())); + return true; + } + } + break; + case DomProperty::Enum: { + const QVariant sheetValue = sheet->property(index); + if (sheetValue.canConvert()) { + const PropertySheetEnumValue e = qvariant_cast(sheetValue); + bool ok = false; + v = e.metaEnum.parseEnum(p->elementEnum(), &ok); + if (!ok) + designerWarning(e.metaEnum.messageParseFailed(p->elementEnum())); + return true; + } + } + break; + default: + break; + } + return false; +} + +// ### fixme Qt 7 remove this: Exclude deprecated properties of Qt 5. +static bool isDeprecatedQt5Property(const QObject *o, const DomProperty *p) +{ + const QString &propertyName = p->attributeName(); + switch (p->kind()) { + case DomProperty::Set: + if (propertyName == u"features" && o->inherits("QDockWidget") + && p->elementSet() == u"QDockWidget::AllDockWidgetFeatures") { + return true; + } + break; + case DomProperty::Enum: + if (propertyName == u"sizeAdjustPolicy" && o->inherits("QComboBox") + && p->elementEnum() == u"QComboBox::AdjustToMinimumContentsLength") { + return true; + } + break; + default: + break; + } + return false; +} + +void QDesignerResource::applyProperties(QObject *o, const QList &properties) +{ + if (properties.isEmpty()) + return; + + auto *sheet = qt_extension(core()->extensionManager(), o); + if (!sheet) + return; + + auto *dynamicSheet = qt_extension(core()->extensionManager(), o); + if (dynamicSheet != nullptr && !dynamicSheet->dynamicPropertiesAllowed()) + dynamicSheet = nullptr; + + for (DomProperty *p : properties) { + if (isDeprecatedQt5Property(o, p)) // ### fixme Qt 7 remove this + continue; // ### fixme Qt 7 remove this: Exclude deprecated value of Qt 5. + const QString &propertyName = p->attributeName(); + if (propertyName == "numDigits"_L1 && o->inherits("QLCDNumber")) { // Deprecated in Qt 4, removed in Qt 5. + applyProperty(o, p, u"digitCount"_s, sheet, dynamicSheet); +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) // Qt 6 reading Qt 7 forms + } else if (propertyName == "horizontalSizeConstraint"_L1 && o->inherits("QLayout")) { + applyProperty(o, p, u"sizeConstraint"_s, sheet, dynamicSheet); +#else // Qt 7 reading pre Qt 7 forms + } else if (propertyName == "sizeConstraint"_L1 && o->inherits("QLayout")) { + applyProperty(o, p, u"horizontalSizeConstraint"_s, sheet, dynamicSheet); + applyProperty(o, p, u"verticalSizeConstraint"_s, sheet, dynamicSheet); +#endif + } else { + applyProperty(o, p, propertyName, sheet, dynamicSheet); + } + } +} + +void QDesignerResource::applyProperty(QObject *o, DomProperty* p, const QString &propertyName, + QDesignerPropertySheetExtension *sheet, + QDesignerDynamicPropertySheetExtension *dynamicSheet) +{ + const int index = sheet->indexOf(propertyName); + QVariant v; + if (!readDomEnumerationValue(p, sheet, index, v)) + v = toVariant(o->metaObject(), p); + switch (p->kind()) { + case DomProperty::String: + if (index != -1 && sheet->property(index).userType() == qMetaTypeId()) { + const DomString *key = p->elementString(); + PropertySheetKeySequenceValue keyVal(QKeySequence(key->text())); + translationParametersFromDom(key, &keyVal); + v = QVariant::fromValue(keyVal); + } else { + const DomString *str = p->elementString(); + PropertySheetStringValue strVal(v.toString()); + translationParametersFromDom(str, &strVal); + v = QVariant::fromValue(strVal); + } + break; + case DomProperty::StringList: { + const DomStringList *list = p->elementStringList(); + PropertySheetStringListValue listValue(list->elementString()); + translationParametersFromDom(list, &listValue); + v = QVariant::fromValue(listValue); + } + break; + default: + break; + } + d->applyPropertyInternally(o, propertyName, v); + if (index != -1) { + sheet->setProperty(index, v); + sheet->setChanged(index, true); + } else if (dynamicSheet != nullptr) { + QVariant defaultValue = QVariant(v.metaType()); + bool isDefault = (v == defaultValue); + if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QIcon)); + isDefault = (qvariant_cast(v) == PropertySheetIconValue()); + } else if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QPixmap)); + isDefault = (qvariant_cast(v) == PropertySheetPixmapValue()); + } else if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QString)); + isDefault = (qvariant_cast(v) == PropertySheetStringValue()); + } else if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QStringList)); + isDefault = (qvariant_cast(v) == PropertySheetStringListValue()); + } else if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QKeySequence)); + isDefault = (qvariant_cast(v) == PropertySheetKeySequenceValue()); + } + if (defaultValue.metaType().id() != QMetaType::User) { + const int idx = dynamicSheet->addDynamicProperty(p->attributeName(), defaultValue); + if (idx != -1) { + sheet->setProperty(idx, v); + sheet->setChanged(idx, !isDefault); + } + } + } + + if (propertyName == "objectName"_L1) + changeObjectName(o, o->objectName()); +} + +QWidget *QDesignerResource::createWidget(const QString &widgetName, QWidget *parentWidget, const QString &_name) +{ + QString name = _name; + if (m_isMainWidget) + m_isMainWidget = false; + + QWidget *w = core()->widgetFactory()->createWidget(widgetName, parentWidget); + if (!w) + return nullptr; + + if (name.isEmpty()) { + QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase(); + if (QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfObject(w))) + name = qtify(item->name()); + } + + changeObjectName(w, name); + + QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), parentWidget); + if (!qobject_cast(w) && (!parentWidget || !container)) { + m_formWindow->manageWidget(w); + if (parentWidget) { + QWidgetList list = qvariant_cast(parentWidget->property("_q_widgetOrder")); + list.append(w); + parentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(list)); + QWidgetList zOrder = qvariant_cast(parentWidget->property("_q_zOrder")); + zOrder.append(w); + parentWidget->setProperty("_q_zOrder", QVariant::fromValue(zOrder)); + } + } else { + core()->metaDataBase()->add(w); + } + + w->setWindowFlags(w->windowFlags() & ~Qt::Window); + // Make sure it is non-modal (for example, KDialog calls setModal(true) in the constructor). + w->setWindowModality(Qt::NonModal); + + return w; +} + +QLayout *QDesignerResource::createLayout(const QString &layoutName, QObject *parent, const QString &name) +{ + QWidget *layoutBase = nullptr; + QLayout *layout = qobject_cast(parent); + + if (parent->isWidgetType()) + layoutBase = static_cast(parent); + else { + Q_ASSERT( layout != nullptr ); + layoutBase = layout->parentWidget(); + } + + LayoutInfo::Type layoutType = LayoutInfo::layoutType(layoutName); + if (layoutType == LayoutInfo::NoLayout) { + designerWarning(QCoreApplication::translate("QDesignerResource", "The layout type '%1' is not supported, defaulting to grid.").arg(layoutName)); + layoutType = LayoutInfo::Grid; + } + QLayout *lay = core()->widgetFactory()->createLayout(layoutBase, layout, layoutType); + if (lay != nullptr) + changeObjectName(lay, name); + + return lay; +} + +// save +DomWidget *QDesignerResource::createDom(QWidget *widget, DomWidget *ui_parentWidget, bool recursive) +{ + QDesignerMetaDataBaseItemInterface *item = core()->metaDataBase()->item(widget); + if (!item) + return nullptr; + + if (qobject_cast(widget) && !m_copyWidget) + return nullptr; + + const QDesignerWidgetDataBaseInterface *wdb = core()->widgetDataBase(); + QDesignerWidgetDataBaseItemInterface *widgetInfo = nullptr; + const int widgetInfoIndex = wdb->indexOfObject(widget, false); + if (widgetInfoIndex != -1) { + widgetInfo = wdb->item(widgetInfoIndex); + // Recursively add all dependent custom widgets + QDesignerWidgetDataBaseItemInterface *customInfo = widgetInfo; + while (customInfo && customInfo->isCustom()) { + m_usedCustomWidgets.insert(customInfo, true); + const QString extends = customInfo->extends(); + if (extends == customInfo->name()) + break; // There are faulty files around that have name==extends + const int extendsIndex = wdb->indexOfClassName(customInfo->extends()); + customInfo = extendsIndex != -1 ? wdb->item(extendsIndex) : nullptr; + } + } + + DomWidget *w = nullptr; + + if (QTabWidget *tabWidget = qobject_cast(widget)) + w = saveWidget(tabWidget, ui_parentWidget); + else if (QStackedWidget *stackedWidget = qobject_cast(widget)) + w = saveWidget(stackedWidget, ui_parentWidget); + else if (QToolBox *toolBox = qobject_cast(widget)) + w = saveWidget(toolBox, ui_parentWidget); + else if (QToolBar *toolBar = qobject_cast(widget)) + w = saveWidget(toolBar, ui_parentWidget); + else if (QDesignerDockWidget *dockWidget = qobject_cast(widget)) + w = saveWidget(dockWidget, ui_parentWidget); + else if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) + w = saveWidget(widget, container, ui_parentWidget); + else if (QWizardPage *wizardPage = qobject_cast(widget)) + w = saveWidget(wizardPage, ui_parentWidget); + else + w = QAbstractFormBuilder::createDom(widget, ui_parentWidget, recursive); + + Q_ASSERT( w != nullptr ); + + if (!qobject_cast(widget) && w->attributeClass() == "QWidget"_L1) + w->setAttributeNative(true); + + const QString className = w->attributeClass(); + if (m_internal_to_qt.contains(className)) + w->setAttributeClass(m_internal_to_qt.value(className)); + + if (isPromoted( core(), widget)) { // is promoted? + Q_ASSERT(widgetInfo != nullptr); + + w->setAttributeClass(widgetInfo->name()); + + const auto &prop_list = w->elementProperty(); + for (DomProperty *prop : prop_list) { + if (prop->attributeName() == "geometry"_L1) { + if (DomRect *rect = prop->elementRect()) { + rect->setElementX(widget->x()); + rect->setElementY(widget->y()); + } + break; + } + } + } else if (widgetInfo != nullptr && m_usedCustomWidgets.contains(widgetInfo)) { + if (widgetInfo->name() != w->attributeClass()) + w->setAttributeClass(widgetInfo->name()); + } + addExtensionDataToDOM(this, core(), w, widget); + return w; +} + +DomLayout *QDesignerResource::createDom(QLayout *layout, DomLayout *ui_parentLayout, DomWidget *ui_parentWidget) +{ + QDesignerMetaDataBaseItemInterface *item = core()->metaDataBase()->item(layout); + + if (item == nullptr) { + layout = layout->findChild(); + // refresh the meta database item + item = core()->metaDataBase()->item(layout); + } + + if (item == nullptr) { + // nothing to do. + return nullptr; + } + + if (qobject_cast(layout->parentWidget()) != 0) { + // nothing to do. + return nullptr; + } + + m_chain.push(layout); + + DomLayout *l = QAbstractFormBuilder::createDom(layout, ui_parentLayout, ui_parentWidget); + Q_ASSERT(l != nullptr); + LayoutPropertySheet::stretchAttributesToDom(core(), layout, l); + + m_chain.pop(); + + return l; +} + +DomLayoutItem *QDesignerResource::createDom(QLayoutItem *item, DomLayout *ui_layout, DomWidget *ui_parentWidget) +{ + DomLayoutItem *ui_item = nullptr; + + if (Spacer *s = qobject_cast(item->widget())) { + if (!core()->metaDataBase()->item(s)) + return nullptr; + + DomSpacer *spacer = new DomSpacer(); + const QString objectName = s->objectName(); + if (!objectName.isEmpty()) + spacer->setAttributeName(objectName); + // ### filter the properties + spacer->setElementProperty(computeProperties(item->widget())); + + ui_item = new DomLayoutItem(); + ui_item->setElementSpacer(spacer); + d->m_laidout.insert(item->widget(), true); + } else if (QLayoutWidget *layoutWidget = qobject_cast(item->widget())) { + // Do not save a QLayoutWidget if it is within a layout (else it is saved as "QWidget" + Q_ASSERT(layoutWidget->layout()); + DomLayout *l = createDom(layoutWidget->layout(), ui_layout, ui_parentWidget); + ui_item = new DomLayoutItem(); + ui_item->setElementLayout(l); + d->m_laidout.insert(item->widget(), true); + } else if (!item->spacerItem()) { // we use spacer as fake item in the Designer + ui_item = QAbstractFormBuilder::createDom(item, ui_layout, ui_parentWidget); + } else { + return nullptr; + } + return ui_item; +} + +void QDesignerResource::createCustomWidgets(DomCustomWidgets *dom_custom_widgets) +{ + QSimpleResource::handleDomCustomWidgets(core(), dom_custom_widgets); +} + +DomTabStops *QDesignerResource::saveTabStops() +{ + QDesignerMetaDataBaseItemInterface *item = core()->metaDataBase()->item(m_formWindow); + Q_ASSERT(item); + + QStringList tabStops; + const QWidgetList &tabOrder = item->tabOrder(); + for (QWidget *widget : tabOrder) { + if (m_formWindow->mainContainer()->isAncestorOf(widget)) + tabStops.append(widget->objectName()); + } + + if (!tabStops.isEmpty()) { + DomTabStops *dom = new DomTabStops; + dom->setElementTabStop(tabStops); + return dom; + } + + return nullptr; +} + +void QDesignerResource::applyTabStops(QWidget *widget, DomTabStops *tabStops) +{ + if (tabStops == nullptr || widget == nullptr) + return; + + QWidgetList tabOrder; + const QStringList &elementTabStop = tabStops->elementTabStop(); + for (const QString &widgetName : elementTabStop) { + if (QWidget *w = widget->findChild(widgetName)) { + tabOrder.append(w); + } + } + + QDesignerMetaDataBaseItemInterface *item = core()->metaDataBase()->item(m_formWindow); + Q_ASSERT(item); + item->setTabOrder(tabOrder); +} + +/* Unmanaged container pages occur when someone adds a page in a custom widget + * constructor. They don't have a meta DB entry which causes createDom + * to return 0. */ +inline QString msgUnmanagedPage(QDesignerFormEditorInterface *core, + QWidget *container, int index, QWidget *page) +{ + return QCoreApplication::translate("QDesignerResource", +"The container extension of the widget '%1' (%2) returned a widget not managed by Designer '%3' (%4) when queried for page #%5.\n" +"Container pages should only be added by specifying them in XML returned by the domXml() method of the custom widget."). + arg(container->objectName(), WidgetFactory::classNameOf(core, container), + page->objectName(), WidgetFactory::classNameOf(core, page)). + arg(index); +} + +DomWidget *QDesignerResource::saveWidget(QWidget *widget, QDesignerContainerExtension *container, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(widget, ui_parentWidget, false); + QList ui_widget_list; + + for (int i=0; icount(); ++i) { + QWidget *page = container->widget(i); + Q_ASSERT(page); + + if (DomWidget *ui_page = createDom(page, ui_widget)) { + ui_widget_list.append(ui_page); + } else { + designerWarning(msgUnmanagedPage(core(), widget, i, page)); + } + } + + ui_widget->setElementWidget(ui_widget_list); + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QStackedWidget *widget, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(widget, ui_parentWidget, false); + QList ui_widget_list; + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) { + for (int i=0; icount(); ++i) { + QWidget *page = container->widget(i); + Q_ASSERT(page); + if (DomWidget *ui_page = createDom(page, ui_widget)) { + ui_widget_list.append(ui_page); + } else { + designerWarning(msgUnmanagedPage(core(), widget, i, page)); + } + } + } + + ui_widget->setElementWidget(ui_widget_list); + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QToolBar *toolBar, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(toolBar, ui_parentWidget, false); + if (const QMainWindow *mainWindow = qobject_cast(toolBar->parentWidget())) { + const bool toolBarBreak = mainWindow->toolBarBreak(toolBar); + const Qt::ToolBarArea area = mainWindow->toolBarArea(toolBar); + + auto attributes = ui_widget->elementAttribute(); + + DomProperty *attr = new DomProperty(); + attr->setAttributeName(u"toolBarArea"_s); + attr->setElementEnum(QLatin1StringView(toolBarAreaMetaEnum().valueToKey(area))); + attributes << attr; + + attr = new DomProperty(); + attr->setAttributeName(u"toolBarBreak"_s); + attr->setElementBool(toolBarBreak ? u"true"_s : u"false"_s); + attributes << attr; + ui_widget->setElementAttribute(attributes); + } + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QDesignerDockWidget *dockWidget, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(dockWidget, ui_parentWidget, true); + if (QMainWindow *mainWindow = qobject_cast(dockWidget->parentWidget())) { + const Qt::DockWidgetArea area = mainWindow->dockWidgetArea(dockWidget); + DomProperty *attr = new DomProperty(); + attr->setAttributeName(u"dockWidgetArea"_s); + attr->setElementNumber(int(area)); + ui_widget->setElementAttribute(ui_widget->elementAttribute() << attr); + } + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QTabWidget *widget, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(widget, ui_parentWidget, false); + QList ui_widget_list; + + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) { + const int current = widget->currentIndex(); + for (int i=0; icount(); ++i) { + QWidget *page = container->widget(i); + Q_ASSERT(page); + + DomWidget *ui_page = createDom(page, ui_widget); + if (!ui_page) { + designerWarning(msgUnmanagedPage(core(), widget, i, page)); + continue; + } + QList ui_attribute_list; + + // attribute `icon' + widget->setCurrentIndex(i); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), widget); + PropertySheetIconValue icon = qvariant_cast(sheet->property(sheet->indexOf(u"currentTabIcon"_s))); + DomProperty *p = resourceBuilder()->saveResource(workingDirectory(), QVariant::fromValue(icon)); + if (p) { + p->setAttributeName(QFormBuilderStrings::iconAttribute); + ui_attribute_list.append(p); + } + // attribute `title' + p = textBuilder()->saveText(sheet->property(sheet->indexOf(u"currentTabText"_s))); + if (p) { + p->setAttributeName(QFormBuilderStrings::titleAttribute); + ui_attribute_list.append(p); + } + + // attribute `toolTip' + QVariant v = sheet->property(sheet->indexOf(u"currentTabToolTip"_s)); + if (!qvariant_cast(v).value().isEmpty()) { + p = textBuilder()->saveText(v); + if (p) { + p->setAttributeName(QFormBuilderStrings::toolTipAttribute); + ui_attribute_list.append(p); + } + } + + // attribute `whatsThis' + v = sheet->property(sheet->indexOf(u"currentTabWhatsThis"_s)); + if (!qvariant_cast(v).value().isEmpty()) { + p = textBuilder()->saveText(v); + if (p) { + p->setAttributeName(QFormBuilderStrings::whatsThisAttribute); + ui_attribute_list.append(p); + } + } + + ui_page->setElementAttribute(ui_attribute_list); + + ui_widget_list.append(ui_page); + } + widget->setCurrentIndex(current); + } + + ui_widget->setElementWidget(ui_widget_list); + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QToolBox *widget, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(widget, ui_parentWidget, false); + QList ui_widget_list; + + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) { + const int current = widget->currentIndex(); + for (int i=0; icount(); ++i) { + QWidget *page = container->widget(i); + Q_ASSERT(page); + + DomWidget *ui_page = createDom(page, ui_widget); + if (!ui_page) { + designerWarning(msgUnmanagedPage(core(), widget, i, page)); + continue; + } + + // attribute `label' + QList ui_attribute_list; + + // attribute `icon' + widget->setCurrentIndex(i); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), widget); + PropertySheetIconValue icon = qvariant_cast(sheet->property(sheet->indexOf(u"currentItemIcon"_s))); + DomProperty *p = resourceBuilder()->saveResource(workingDirectory(), QVariant::fromValue(icon)); + if (p) { + p->setAttributeName(QFormBuilderStrings::iconAttribute); + ui_attribute_list.append(p); + } + p = textBuilder()->saveText(sheet->property(sheet->indexOf(u"currentItemText"_s))); + if (p) { + p->setAttributeName(QFormBuilderStrings::labelAttribute); + ui_attribute_list.append(p); + } + + // attribute `toolTip' + QVariant v = sheet->property(sheet->indexOf(u"currentItemToolTip"_s)); + if (!qvariant_cast(v).value().isEmpty()) { + p = textBuilder()->saveText(v); + if (p) { + p->setAttributeName(QFormBuilderStrings::toolTipAttribute); + ui_attribute_list.append(p); + } + } + + ui_page->setElementAttribute(ui_attribute_list); + + ui_widget_list.append(ui_page); + } + widget->setCurrentIndex(current); + } + + ui_widget->setElementWidget(ui_widget_list); + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QWizardPage *wizardPage, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(wizardPage, ui_parentWidget, true); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), wizardPage); + // Save the page id (string) attribute, append to existing attributes + const QString pageIdPropertyName = QLatin1StringView(QWizardPagePropertySheet::pageIdProperty); + const int pageIdIndex = sheet->indexOf(pageIdPropertyName); + if (pageIdIndex != -1 && sheet->isChanged(pageIdIndex)) { + DomProperty *property = variantToDomProperty(this, wizardPage->metaObject(), pageIdPropertyName, sheet->property(pageIdIndex)); + Q_ASSERT(property); + property->elementString()->setAttributeNotr(u"true"_s); + DomPropertyList attributes = ui_widget->elementAttribute(); + attributes.push_back(property); + ui_widget->setElementAttribute(attributes); + } + return ui_widget; +} + +// Do not save the 'currentTabName' properties of containers +static inline bool checkContainerProperty(const QWidget *w, const QString &propertyName) +{ + if (qobject_cast(w)) + return QToolBoxWidgetPropertySheet::checkProperty(propertyName); + if (qobject_cast(w)) + return QTabWidgetPropertySheet::checkProperty(propertyName); + if (qobject_cast(w)) + return QStackedWidgetPropertySheet::checkProperty(propertyName); + if (qobject_cast(w)) + return QMdiAreaPropertySheet::checkProperty(propertyName); + return true; +} + +bool QDesignerResource::checkProperty(QObject *obj, const QString &prop) const +{ + const QDesignerMetaObjectInterface *meta = core()->introspection()->metaObject(obj); + + const int pindex = meta->indexOfProperty(prop); + if (pindex != -1 && !meta->property(pindex)->attributes().testFlag(QDesignerMetaPropertyInterface::StoredAttribute)) + return false; + + if (prop == "objectName"_L1 || prop == "spacerName"_L1) // ### don't store the property objectName + return false; + + if (!d->m_separateSizeConstraints && prop == "verticalSizeConstraint"_L1) // 7.0 + return false; + + QWidget *check_widget = nullptr; + if (obj->isWidgetType()) + check_widget = static_cast(obj); + + if (check_widget && prop == "geometry"_L1) { + if (check_widget == m_formWindow->mainContainer()) + return true; // Save although maincontainer is technically laid-out by embedding container + if (m_selected && m_selected == check_widget) + return true; + + return !LayoutInfo::isWidgetLaidout(core(), check_widget); + } + + if (check_widget && !checkContainerProperty(check_widget, prop)) + return false; + + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), obj)) { + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core()->extensionManager(), obj); + const int pindex = sheet->indexOf(prop); + if (sheet->isAttribute(pindex)) + return false; + + if (!dynamicSheet || !dynamicSheet->isDynamicProperty(pindex)) + return sheet->isChanged(pindex); + if (!sheet->isVisible(pindex)) + return false; + return true; + } + + return false; +} + +bool QDesignerResource::addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) +{ + if (item->widget() == nullptr) { + return false; + } + + QGridLayout *grid = qobject_cast(layout); + QBoxLayout *box = qobject_cast(layout); + + if (grid != nullptr) { + const int rowSpan = ui_item->hasAttributeRowSpan() ? ui_item->attributeRowSpan() : 1; + const int colSpan = ui_item->hasAttributeColSpan() ? ui_item->attributeColSpan() : 1; + grid->addWidget(item->widget(), ui_item->attributeRow(), ui_item->attributeColumn(), rowSpan, colSpan, item->alignment()); + return true; + } + if (box != nullptr) { + box->addItem(item); + return true; + } + + return QAbstractFormBuilder::addItem(ui_item, item, layout); +} + +bool QDesignerResource::addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) +{ + core()->metaDataBase()->add(widget); // ensure the widget is in the meta database + + if (! QAbstractFormBuilder::addItem(ui_widget, widget, parentWidget) || qobject_cast (parentWidget)) { + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), parentWidget)) + container->addWidget(widget); + } + + if (QTabWidget *tabWidget = qobject_cast(parentWidget)) { + const int tabIndex = tabWidget->count() - 1; + const int current = tabWidget->currentIndex(); + + tabWidget->setCurrentIndex(tabIndex); + + const auto &attributes = ui_widget->elementAttribute(); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), parentWidget); + if (auto *picon = QFBE::propertyByName(attributes, QFormBuilderStrings::iconAttribute)) { + QVariant v = resourceBuilder()->loadResource(workingDirectory(), picon); + sheet->setProperty(sheet->indexOf(u"currentTabIcon"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::titleAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentTabText"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::toolTipAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentTabToolTip"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::whatsThisAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentTabWhatsThis"_s), v); + } + tabWidget->setCurrentIndex(current); + } else if (QToolBox *toolBox = qobject_cast(parentWidget)) { + const int itemIndex = toolBox->count() - 1; + const int current = toolBox->currentIndex(); + + toolBox->setCurrentIndex(itemIndex); + + const auto &attributes = ui_widget->elementAttribute(); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), parentWidget); + if (auto *picon = QFBE::propertyByName(attributes, QFormBuilderStrings::iconAttribute)) { + QVariant v = resourceBuilder()->loadResource(workingDirectory(), picon); + sheet->setProperty(sheet->indexOf(u"currentItemIcon"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::labelAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentItemText"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::toolTipAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentItemToolTip"_s), v); + } + toolBox->setCurrentIndex(current); + } + + return true; +} + +bool QDesignerResource::copy(QIODevice *dev, const FormBuilderClipboard &selection) +{ + m_copyWidget = true; + + DomUI *ui = copy(selection); + + d->m_laidout.clear(); + m_copyWidget = false; + + if (!ui) + return false; + + QXmlStreamWriter writer(dev); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(1); + writer.writeStartDocument(); + ui->write(writer); + writer.writeEndDocument(); + delete ui; + return true; +} + +DomUI *QDesignerResource::copy(const FormBuilderClipboard &selection) +{ + if (selection.empty()) + return nullptr; + + m_copyWidget = true; + + DomWidget *ui_widget = new DomWidget(); + ui_widget->setAttributeName(clipboardObjectName); + bool hasItems = false; + // Widgets + if (!selection.m_widgets.isEmpty()) { + QList ui_widget_list; + for (auto *w : selection.m_widgets) { + m_selected = w; + DomWidget *ui_child = createDom(w, ui_widget); + m_selected = nullptr; + if (ui_child) + ui_widget_list.append(ui_child); + } + if (!ui_widget_list.isEmpty()) { + ui_widget->setElementWidget(ui_widget_list); + hasItems = true; + } + } + // actions + if (!selection.m_actions.isEmpty()) { + QList domActions; + for (QAction* action : std::as_const(selection.m_actions)) { + if (DomAction *domAction = createDom(action)) + domActions += domAction; + } + if (!domActions.isEmpty()) { + ui_widget-> setElementAction(domActions); + hasItems = true; + } + } + + d->m_laidout.clear(); + m_copyWidget = false; + + if (!hasItems) { + delete ui_widget; + return nullptr; + } + // UI + DomUI *ui = new DomUI(); + ui->setAttributeVersion(currentUiVersion); + ui->setElementWidget(ui_widget); + ui->setElementResources(saveResources(m_resourceBuilder->usedQrcFiles())); + if (DomCustomWidgets *cws = saveCustomWidgets()) + ui->setElementCustomWidgets(cws); + return ui; +} + +FormBuilderClipboard QDesignerResource::paste(DomUI *ui, QWidget *widgetParent, QObject *actionParent) +{ + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + const int saved = m_isMainWidget; + m_isMainWidget = false; + + FormBuilderClipboard rc; + + // Widgets + const DomWidget *topLevel = ui->elementWidget(); + initialize(ui); + const auto &domWidgets = topLevel->elementWidget(); + if (!domWidgets.isEmpty()) { + const QPoint offset = m_formWindow->grid(); + for (DomWidget* domWidget : domWidgets) { + if (QWidget *w = create(domWidget, widgetParent)) { + w->move(w->pos() + offset); + // ### change the init properties of w + rc.m_widgets.append(w); + } + } + } + const auto domActions = topLevel->elementAction(); + for (DomAction *domAction : domActions) { + if (QAction *a = create(domAction, actionParent)) + rc.m_actions .append(a); + } + + m_isMainWidget = saved; + + if (QDesignerExtraInfoExtension *extra = qt_extension(core()->extensionManager(), core())) + extra->loadUiExtraInfo(ui); + + createResources(ui->elementResources()); + + return rc; +} + +FormBuilderClipboard QDesignerResource::paste(QIODevice *dev, QWidget *widgetParent, QObject *actionParent) +{ + DomUI ui; + QXmlStreamReader reader(dev); + bool uiInitialized = false; + + while (!reader.atEnd()) { + if (reader.readNext() == QXmlStreamReader::StartElement) { + if (reader.name().compare("ui"_L1, Qt::CaseInsensitive)) { + ui.read(reader); + uiInitialized = true; + } else { + //: Parsing clipboard contents + reader.raiseError(QCoreApplication::translate("QDesignerResource", "Unexpected element <%1>").arg(reader.name().toString())); + } + } + } + if (reader.hasError()) { + //: Parsing clipboard contents + designerWarning(QCoreApplication::translate("QDesignerResource", "Error while pasting clipboard contents at line %1, column %2: %3") + .arg(reader.lineNumber()).arg(reader.columnNumber()) + .arg(reader.errorString())); + uiInitialized = false; + } else if (!uiInitialized) { + //: Parsing clipboard contents + designerWarning(QCoreApplication::translate("QDesignerResource", "Error while pasting clipboard contents: The root element is missing.")); + } + + if (!uiInitialized) + return FormBuilderClipboard(); + + FormBuilderClipboard clipBoard = paste(&ui, widgetParent, actionParent); + + return clipBoard; +} + +void QDesignerResource::layoutInfo(DomLayout *layout, QObject *parent, int *margin, int *spacing) +{ + QAbstractFormBuilder::layoutInfo(layout, parent, margin, spacing); +} + +DomCustomWidgets *QDesignerResource::saveCustomWidgets() +{ + if (m_usedCustomWidgets.isEmpty()) + return nullptr; + + // We would like the list to be in order of the widget database indexes + // to ensure that base classes come first (nice optics) + QDesignerFormEditorInterface *core = m_formWindow->core(); + QDesignerWidgetDataBaseInterface *db = core->widgetDataBase(); + const bool isInternalWidgetDataBase = qobject_cast(db); + QMap orderedMap; + + for (auto it = m_usedCustomWidgets.cbegin(), end = m_usedCustomWidgets.cend(); it != end; ++it) { + QDesignerWidgetDataBaseItemInterface *item = it.key(); + const QString name = item->name(); + DomCustomWidget *custom_widget = new DomCustomWidget; + + custom_widget->setElementClass(name); + if (item->isContainer()) + custom_widget->setElementContainer(item->isContainer()); + + if (!item->includeFile().isEmpty()) { + DomHeader *header = new DomHeader; + const IncludeSpecification spec = includeSpecification(item->includeFile()); + header->setText(spec.first); + if (spec.second == IncludeGlobal) { + header->setAttributeLocation(u"global"_s); + } + custom_widget->setElementHeader(header); + custom_widget->setElementExtends(item->extends()); + } + + if (isInternalWidgetDataBase) { + WidgetDataBaseItem *internalItem = static_cast(item); + const QStringList fakeSlots = internalItem->fakeSlots(); + const QStringList fakeSignals = internalItem->fakeSignals(); + if (!fakeSlots.isEmpty() || !fakeSignals.isEmpty()) { + DomSlots *domSlots = new DomSlots(); + domSlots->setElementSlot(fakeSlots); + domSlots->setElementSignal(fakeSignals); + custom_widget->setElementSlots(domSlots); + } + const QString addPageMethod = internalItem->addPageMethod(); + if (!addPageMethod.isEmpty()) + custom_widget->setElementAddPageMethod(addPageMethod); + } + + orderedMap.insert(db->indexOfClassName(name), custom_widget); + } + + DomCustomWidgets *customWidgets = new DomCustomWidgets; + customWidgets->setElementCustomWidget(orderedMap.values().toVector()); + return customWidgets; +} + +bool QDesignerResource::canCompressSpacings(QObject *object) const +{ + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), object)) { + if (qobject_cast(object)) { + const int h = sheet->property(sheet->indexOf(u"horizontalSpacing"_s)).toInt(); + const int v = sheet->property(sheet->indexOf(u"verticalSpacing"_s)).toInt(); + if (h == v) + return true; + } + } + return false; +} + +QList QDesignerResource::computeProperties(QObject *object) +{ + QList properties; + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), object)) { + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core()->extensionManager(), object); + const int count = sheet->count(); + QList spacingProperties; + const bool compressSpacings = canCompressSpacings(object); + for (int index = 0; index < count; ++index) { + if (!sheet->isChanged(index) && (!dynamicSheet || !dynamicSheet->isDynamicProperty(index))) + continue; + + const QString propertyName = sheet->propertyName(index); + // Suppress windowModality in legacy forms that have it set on child widgets + if (propertyName == "windowModality"_L1 && !sheet->isVisible(index)) + continue; + + const QVariant value = sheet->property(index); + if (DomProperty *p = createProperty(object, propertyName, value)) { + if (compressSpacings && (propertyName == "horizontalSpacing"_L1 + || propertyName == "verticalSpacing"_L1)) { + spacingProperties.append(p); + } else { + properties.append(p); + } + } + } + if (compressSpacings) { + if (spacingProperties.size() == 2) { + DomProperty *spacingProperty = spacingProperties.at(0); + spacingProperty->setAttributeName(u"spacing"_s); + properties.append(spacingProperty); + delete spacingProperties.at(1); + } else { + properties += spacingProperties; + } + } + } + return properties; +} + +DomProperty *QDesignerResource::applyProperStdSetAttribute(QObject *object, const QString &propertyName, DomProperty *property) +{ + if (!property) + return nullptr; + + QExtensionManager *mgr = core()->extensionManager(); + if (const QDesignerPropertySheetExtension *sheet = qt_extension(mgr, object)) { + const QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(mgr, object); + const QDesignerPropertySheet *designerSheet = qobject_cast(core()->extensionManager()->extension(object, Q_TYPEID(QDesignerPropertySheetExtension))); + const int index = sheet->indexOf(propertyName); + if ((dynamicSheet && dynamicSheet->isDynamicProperty(index)) || (designerSheet && designerSheet->isDefaultDynamicProperty(index))) + property->setAttributeStdset(0); + } + return property; +} + +// Optimistic check for a standard setter function +static inline bool hasSetter(QDesignerFormEditorInterface *core, QObject *object, const QString &propertyName) +{ + const QDesignerMetaObjectInterface *meta = core->introspection()->metaObject(object); + const int pindex = meta->indexOfProperty(propertyName); + if (pindex == -1) + return true; + return meta->property(pindex)->hasSetter(); +} + +DomProperty *QDesignerResource::createProperty(QObject *object, const QString &propertyName, const QVariant &value) +{ + if (!checkProperty(object, propertyName)) { + return nullptr; + } + + if (value.canConvert()) { + const PropertySheetFlagValue f = qvariant_cast(value); + const auto mode = d->m_fullyQualifiedEnums + ? DesignerMetaFlags::FullyQualified : DesignerMetaFlags::Qualified; + const QString flagString = f.metaFlags.toString(f.value, mode); + if (flagString.isEmpty()) + return nullptr; + + DomProperty *p = new DomProperty; + // check if we have a standard cpp set function + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + p->setAttributeName(propertyName); + p->setElementSet(flagString); + return applyProperStdSetAttribute(object, propertyName, p); + } + if (value.canConvert()) { + const PropertySheetEnumValue e = qvariant_cast(value); + const auto mode = d->m_fullyQualifiedEnums + ? DesignerMetaEnum::FullyQualified : DesignerMetaEnum::Qualified; + bool ok; + const QString id = e.metaEnum.toString(e.value, mode, &ok); + if (!ok) + designerWarning(e.metaEnum.messageToStringFailed(e.value)); + if (id.isEmpty()) + return nullptr; + + DomProperty *p = new DomProperty; + // check if we have a standard cpp set function + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + // Map "horizontalSizeConstraint" to "sizeConstraint" for Qt 6 + if (!d->m_separateSizeConstraints && propertyName == "horizontalSizeConstraint"_L1 + && object->inherits("QLayout")) { + p->setAttributeName("sizeConstraint"_L1); + } else { + p->setAttributeName(propertyName); + } + p->setElementEnum(id); + return applyProperStdSetAttribute(object, propertyName, p); + } + if (value.canConvert()) { + const PropertySheetStringValue strVal = qvariant_cast(value); + DomProperty *p = stringToDomProperty(strVal.value(), strVal); + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + + p->setAttributeName(propertyName); + + return applyProperStdSetAttribute(object, propertyName, p); + } + if (value.canConvert()) { + const PropertySheetStringListValue listValue = qvariant_cast(value); + DomProperty *p = new DomProperty; + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + + p->setAttributeName(propertyName); + + DomStringList *domStringList = new DomStringList(); + domStringList->setElementString(listValue.value()); + translationParametersToDom(listValue, domStringList); + p->setElementStringList(domStringList); + return applyProperStdSetAttribute(object, propertyName, p); + } + if (value.canConvert()) { + const PropertySheetKeySequenceValue keyVal = qvariant_cast(value); + DomProperty *p = stringToDomProperty(keyVal.value().toString(), keyVal); + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + + p->setAttributeName(propertyName); + + return applyProperStdSetAttribute(object, propertyName, p); + } + + return applyProperStdSetAttribute(object, propertyName, QAbstractFormBuilder::createProperty(object, propertyName, value)); +} + +QStringList QDesignerResource::mergeWithLoadedPaths(const QStringList &paths) const +{ + QStringList newPaths = paths; +#ifdef OLD_RESOURCE_FORMAT + const QStringList loadedPaths = m_resourceBuilder->loadedQrcFiles(); + std::remove_copy_if(loadedPaths.cbegin(), loadedPaths.cend(), + std::back_inserter(newPaths), + [&newPaths] (const QString &path) { return newPaths.contains(path); }); +#endif + return newPaths; +} + + +void QDesignerResource::createResources(DomResources *resources) +{ + QStringList paths; + if (resources != nullptr) { + const auto &dom_include = resources->elementInclude(); + for (DomResource *res : dom_include) { + QString path = QDir::cleanPath(m_formWindow->absoluteDir().absoluteFilePath(res->attributeLocation())); + while (!QFile::exists(path)) { + QWidget *dialogParent = m_formWindow->core()->topLevel(); + const QString promptTitle = QCoreApplication::translate("qdesigner_internal::QDesignerResource", "Loading qrc file"); + const QString prompt = QCoreApplication::translate("qdesigner_internal::QDesignerResource", "The specified qrc file

%1

could not be found. Do you want to update the file location?

").arg(path); + + const QMessageBox::StandardButton answer = core()->dialogGui()->message(dialogParent, QDesignerDialogGuiInterface::ResourceLoadFailureMessage, + QMessageBox::Warning, promptTitle, prompt, QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes); + if (answer == QMessageBox::Yes) { + const QFileInfo fi(path); + const QString fileDialogTitle = QCoreApplication::translate("qdesigner_internal::QDesignerResource", "New location for %1").arg(fi.fileName()); + const QString fileDialogPattern = QCoreApplication::translate("qdesigner_internal::QDesignerResource", "Resource files (*.qrc)"); + path = core()->dialogGui()->getOpenFileName(dialogParent, fileDialogTitle, fi.absolutePath(), fileDialogPattern); + if (path.isEmpty()) + break; + m_formWindow->setProperty("_q_resourcepathchanged", QVariant(true)); + } else { + break; + } + } + if (!path.isEmpty()) { + paths << path; + m_formWindow->addResourceFile(path); + } + } + } + +#ifdef OLD_RESOURCE_FORMAT + paths = mergeWithLoadedPaths(paths); +#endif + + QtResourceSet *resourceSet = m_formWindow->resourceSet(); + if (resourceSet) { + QStringList newPaths = resourceSet->activeResourceFilePaths(); + std::remove_copy_if(paths.cbegin(), paths.cend(), + std::back_inserter(newPaths), + [&newPaths] (const QString &path) { return newPaths.contains(path); }); + resourceSet->activateResourceFilePaths(newPaths); + } else { + resourceSet = m_formWindow->core()->resourceModel()->addResourceSet(paths); + m_formWindow->setResourceSet(resourceSet); + QObject::connect(m_formWindow->core()->resourceModel(), &QtResourceModel::resourceSetActivated, + m_formWindow, &FormWindowBase::resourceSetActivated); + } +} + +DomResources *QDesignerResource::saveResources() +{ + QStringList paths; + switch (m_formWindow->resourceFileSaveMode()) { + case QDesignerFormWindowInterface::SaveAllResourceFiles: + paths = m_formWindow->activeResourceFilePaths(); + break; + case QDesignerFormWindowInterface::SaveOnlyUsedResourceFiles: + paths = m_resourceBuilder->usedQrcFiles(); + break; + case QDesignerFormWindowInterface::DontSaveResourceFiles: + break; + } + return saveResources(paths); +} + +DomResources *QDesignerResource::saveResources(const QStringList &qrcPaths) +{ + QtResourceSet *resourceSet = m_formWindow->resourceSet(); + QList dom_include; + if (resourceSet) { + const QStringList activePaths = resourceSet->activeResourceFilePaths(); + for (const QString &path : activePaths) { + if (qrcPaths.contains(path)) { + DomResource *dom_res = new DomResource; + QString conv_path = path; + if (m_resourceBuilder->isSaveRelative()) + conv_path = m_formWindow->absoluteDir().relativeFilePath(path); + conv_path.replace(QDir::separator(), u'/'); + dom_res->setAttributeLocation(conv_path); + dom_include.append(dom_res); + } + } + } + + DomResources *dom_resources = new DomResources; + dom_resources->setElementInclude(dom_include); + + return dom_resources; +} + +DomAction *QDesignerResource::createDom(QAction *action) +{ + if (!core()->metaDataBase()->item(action) || action->menu()) + return nullptr; + + return QAbstractFormBuilder::createDom(action); +} + +DomActionGroup *QDesignerResource::createDom(QActionGroup *actionGroup) +{ + if (core()->metaDataBase()->item(actionGroup) != nullptr) { + return QAbstractFormBuilder::createDom(actionGroup); + } + + return nullptr; +} + +QAction *QDesignerResource::create(DomAction *ui_action, QObject *parent) +{ + if (QAction *action = QAbstractFormBuilder::create(ui_action, parent)) { + core()->metaDataBase()->add(action); + return action; + } + + return nullptr; +} + +QActionGroup *QDesignerResource::create(DomActionGroup *ui_action_group, QObject *parent) +{ + if (QActionGroup *actionGroup = QAbstractFormBuilder::create(ui_action_group, parent)) { + core()->metaDataBase()->add(actionGroup); + return actionGroup; + } + + return nullptr; +} + +DomActionRef *QDesignerResource::createActionRefDom(QAction *action) +{ + if (!core()->metaDataBase()->item(action) + || (!action->isSeparator() && !action->menu() && action->objectName().isEmpty())) + return nullptr; + + return QAbstractFormBuilder::createActionRefDom(action); +} + +void QDesignerResource::addMenuAction(QAction *action) +{ + core()->metaDataBase()->add(action); +} + +QAction *QDesignerResource::createAction(QObject *parent, const QString &name) +{ + if (QAction *action = QAbstractFormBuilder::createAction(parent, name)) { + core()->metaDataBase()->add(action); + return action; + } + + return nullptr; +} + +QActionGroup *QDesignerResource::createActionGroup(QObject *parent, const QString &name) +{ + if (QActionGroup *actionGroup = QAbstractFormBuilder::createActionGroup(parent, name)) { + core()->metaDataBase()->add(actionGroup); + return actionGroup; + } + + return nullptr; +} + +/* Apply the attributes to a widget via property sheet where appropriate, + * that is, the sheet handles attributive fake properties */ +void QDesignerResource::applyAttributesToPropertySheet(const DomWidget *ui_widget, QWidget *widget) +{ + const DomPropertyList attributes = ui_widget->elementAttribute(); + if (attributes.isEmpty()) + return; + QDesignerPropertySheetExtension *sheet = qt_extension(m_formWindow->core()->extensionManager(), widget); + for (auto *prop : attributes) { + const QString name = prop->attributeName(); + const int index = sheet->indexOf(name); + if (index == -1) { + const QString msg = "Unable to apply attributive property '%1' to '%2'. It does not exist."_L1.arg(name, widget->objectName()); + designerWarning(msg); + } else { + sheet->setProperty(index, domPropertyToVariant(this, widget->metaObject(), prop)); + sheet->setChanged(index, true); + } + } +} + +void QDesignerResource::loadExtraInfo(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) +{ + QAbstractFormBuilder::loadExtraInfo(ui_widget, widget, parentWidget); + // Apply the page id attribute of a QWizardPage (which is an attributive fake property) + if (qobject_cast(widget)) + applyAttributesToPropertySheet(ui_widget, widget); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/qdesigner_resource.h b/src/designer/src/components/formeditor/qdesigner_resource.h new file mode 100644 index 0000000..ce48746 --- /dev/null +++ b/src/designer/src/components/formeditor/qdesigner_resource.h @@ -0,0 +1,145 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_RESOURCE_H +#define QDESIGNER_RESOURCE_H + +#include "formeditor_global.h" +#include "qsimpleresource_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class DomCustomWidget; +class DomCustomWidgets; +class DomResource; + +class QDesignerContainerExtension; +class QDesignerFormEditorInterface; +class QDesignerCustomWidgetInterface; +class QDesignerWidgetDataBaseItemInterface; +class QDesignerPropertySheetExtension; +class QDesignerDynamicPropertySheetExtension; + +class QTabWidget; +class QStackedWidget; +class QToolBox; +class QToolBar; +class QDesignerDockWidget; +class QLayoutWidget; +class QWizardPage; + +namespace qdesigner_internal { + +class FormWindow; + +class QT_FORMEDITOR_EXPORT QDesignerResource : public QEditorFormBuilder +{ +public: + explicit QDesignerResource(FormWindow *fw); + ~QDesignerResource() override; + + void save(QIODevice *dev, QWidget *widget) override; + + bool copy(QIODevice *dev, const FormBuilderClipboard &selection) override; + DomUI *copy(const FormBuilderClipboard &selection) override; + + FormBuilderClipboard paste(DomUI *ui, QWidget *widgetParent, QObject *actionParent = nullptr) override; + FormBuilderClipboard paste(QIODevice *dev, QWidget *widgetParent, QObject *actionParent = nullptr) override; + + bool saveRelative() const; + void setSaveRelative(bool relative); + + QWidget *load(QIODevice *dev, QWidget *parentWidget) override; + + DomUI *readUi(QIODevice *dev); + QWidget *loadUi(DomUI *ui, QWidget *parentWidget); + +protected: + using QEditorFormBuilder::create; + using QEditorFormBuilder::createDom; + + void saveDom(DomUI *ui, QWidget *widget) override; + QWidget *create(DomUI *ui, QWidget *parentWidget) override; + QWidget *create(DomWidget *ui_widget, QWidget *parentWidget) override; + QLayout *create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) override; + QLayoutItem *create(DomLayoutItem *ui_layoutItem, QLayout *layout, QWidget *parentWidget) override; + void applyProperties(QObject *o, const QList &properties) override; + QList computeProperties(QObject *obj) override; + DomProperty *createProperty(QObject *object, const QString &propertyName, const QVariant &value) override; + + QWidget *createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) override; + QLayout *createLayout(const QString &layoutName, QObject *parent, const QString &name) override; + void createCustomWidgets(DomCustomWidgets *) override; + void createResources(DomResources*) override; + void applyTabStops(QWidget *widget, DomTabStops *tabStops) override; + + bool addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) override; + bool addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; + + DomWidget *createDom(QWidget *widget, DomWidget *ui_parentWidget, bool recursive = true) override; + DomLayout *createDom(QLayout *layout, DomLayout *ui_layout, DomWidget *ui_parentWidget) override; + DomLayoutItem *createDom(QLayoutItem *item, DomLayout *ui_layout, DomWidget *ui_parentWidget) override; + + QAction *create(DomAction *ui_action, QObject *parent) override; + QActionGroup *create(DomActionGroup *ui_action_group, QObject *parent) override; + void addMenuAction(QAction *action) override; + + DomAction *createDom(QAction *action) override; + DomActionGroup *createDom(QActionGroup *actionGroup) override; + DomActionRef *createActionRefDom(QAction *action) override; + + QAction *createAction(QObject *parent, const QString &name) override; + QActionGroup *createActionGroup(QObject *parent, const QString &name) override; + + bool checkProperty(QObject *obj, const QString &prop) const override; + + DomWidget *saveWidget(QTabWidget *widget, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QStackedWidget *widget, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QToolBox *widget, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QWidget *widget, QDesignerContainerExtension *container, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QToolBar *toolBar, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QDesignerDockWidget *dockWidget, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QWizardPage *wizardPage, DomWidget *ui_parentWidget); + + DomCustomWidgets *saveCustomWidgets() override; + DomTabStops *saveTabStops() override; + DomResources *saveResources() override; + + void layoutInfo(DomLayout *layout, QObject *parent, int *margin, int *spacing) override; + + void loadExtraInfo(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; + + void changeObjectName(QObject *o, QString name); + DomProperty *applyProperStdSetAttribute(QObject *object, const QString &propertyName, DomProperty *property); + +private: + void applyProperty(QObject *o, DomProperty* p, const QString &propertyName, + QDesignerPropertySheetExtension *sheet, + QDesignerDynamicPropertySheetExtension *dynamicSheet = nullptr); + DomResources *saveResources(const QStringList &qrcPaths); + bool canCompressSpacings(QObject *object) const; + QStringList mergeWithLoadedPaths(const QStringList &paths) const; + void applyAttributesToPropertySheet(const DomWidget *ui_widget, QWidget *widget); + + using DomCustomWidgetList = QList; + void addCustomWidgetsToWidgetDatabase(DomCustomWidgetList& list); + FormWindow *m_formWindow; + bool m_isMainWidget; + QHash m_internal_to_qt; + QHash m_qt_to_internal; + QStack m_chain; + QHash m_usedCustomWidgets; + bool m_copyWidget; + QWidget *m_selected; + class QDesignerResourceBuilder *m_resourceBuilder; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_RESOURCE_H diff --git a/src/designer/src/components/formeditor/qlayoutwidget_propertysheet.cpp b/src/designer/src/components/formeditor/qlayoutwidget_propertysheet.cpp new file mode 100644 index 0000000..78fc443 --- /dev/null +++ b/src/designer/src/components/formeditor/qlayoutwidget_propertysheet.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qlayoutwidget_propertysheet.h" +#include "qlayout_widget_p.h" +#include "formwindow.h" +#include "formeditor.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +QLayoutWidgetPropertySheet::QLayoutWidgetPropertySheet(QLayoutWidget *object, QObject *parent) + : QDesignerPropertySheet(object, parent) +{ + clearFakeProperties(); +} + +QLayoutWidgetPropertySheet::~QLayoutWidgetPropertySheet() = default; + +bool QLayoutWidgetPropertySheet::isVisible(int index) const +{ + if (propertyGroup(index) == "Layout"_L1) + return QDesignerPropertySheet::isVisible(index); + return false; +} + +void QLayoutWidgetPropertySheet::setProperty(int index, const QVariant &value) +{ + QDesignerPropertySheet::setProperty(index, value); +} + +bool QLayoutWidgetPropertySheet::dynamicPropertiesAllowed() const +{ + return false; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/qlayoutwidget_propertysheet.h b/src/designer/src/components/formeditor/qlayoutwidget_propertysheet.h new file mode 100644 index 0000000..b01a9b1 --- /dev/null +++ b/src/designer/src/components/formeditor/qlayoutwidget_propertysheet.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QLAYOUTWIDGET_PROPERTYSHEET_H +#define QLAYOUTWIDGET_PROPERTYSHEET_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QLayoutWidgetPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit QLayoutWidgetPropertySheet(QLayoutWidget *object, QObject *parent = nullptr); + ~QLayoutWidgetPropertySheet() override; + + void setProperty(int index, const QVariant &value) override; + bool isVisible(int index) const override; + + bool dynamicPropertiesAllowed() const override; +}; + +using QLayoutWidgetPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QLAYOUTWIDGET_PROPERTYSHEET_H diff --git a/src/designer/src/components/formeditor/qmainwindow_container.cpp b/src/designer/src/components/formeditor/qmainwindow_container.cpp new file mode 100644 index 0000000..d4c85bc --- /dev/null +++ b/src/designer/src/components/formeditor/qmainwindow_container.cpp @@ -0,0 +1,181 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmainwindow_container.h" +#include "qdesigner_toolbar_p.h" +#include "formwindow.h" + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +QMainWindowContainer::QMainWindowContainer(QMainWindow *widget, QObject *parent) + : QObject(parent), + m_mainWindow(widget) +{ +} + +int QMainWindowContainer::count() const +{ + return m_widgets.size(); +} + +QWidget *QMainWindowContainer::widget(int index) const +{ + return m_widgets.value(index, nullptr); +} + +int QMainWindowContainer::currentIndex() const +{ + // QTBUG-111603, handle plugins with unmanaged central widgets + auto *cw = m_mainWindow->centralWidget(); + return cw != nullptr && m_widgets.contains(cw) ? 0 : -1; +} + +void QMainWindowContainer::setCurrentIndex(int index) +{ + Q_UNUSED(index); +} + + +namespace { + // Pair of + using ToolBarData = std::pair; + + ToolBarData toolBarData(QToolBar *me) { + const QMainWindow *mw = qobject_cast(me->parentWidget()); + if (!mw || !mw->layout() || mw->layout()->indexOf(me) == -1) { + const QVariant desiredAreaV = me->property("_q_desiredArea"); + const Qt::ToolBarArea desiredArea = desiredAreaV.canConvert() + ? desiredAreaV.value() : Qt::TopToolBarArea; + return {desiredArea, false}; + } + return ToolBarData(mw->toolBarArea(me), mw->toolBarBreak(me)); + } + +Qt::DockWidgetArea dockWidgetArea(QDockWidget *me) +{ + if (const QMainWindow *mw = qobject_cast(me->parentWidget())) { + // Make sure that me is actually managed by mw, otherwise + // QMainWindow::dockWidgetArea() will be VERY upset + QList candidates; + if (mw->layout()) { + candidates.append(mw->layout()); + candidates += mw->layout()->findChildren(); + } + for (QLayout *l : std::as_const(candidates)) { + if (l->indexOf(me) != -1) + return mw->dockWidgetArea(me); + } + } + return Qt::LeftDockWidgetArea; +} +} + +// In QMainWindowContainer::remove(), remember the dock area in a dynamic +// property so that it can used in addWidget() if that is called by undo(). +static const char dockAreaPropertyName[] = "_q_dockArea"; + +void QMainWindowContainer::addWidget(QWidget *widget) +{ + // remove all the occurrences of widget + m_widgets.removeAll(widget); + + // the + if (QToolBar *toolBar = qobject_cast(widget)) { + m_widgets.append(widget); + const ToolBarData data = toolBarData(toolBar); + m_mainWindow->addToolBar(data.first, toolBar); + if (data.second) m_mainWindow->insertToolBarBreak(toolBar); + toolBar->show(); + } + + else if (QMenuBar *menuBar = qobject_cast(widget)) { + if (menuBar != m_mainWindow->menuBar()) + m_mainWindow->setMenuBar(menuBar); + + m_widgets.append(widget); + menuBar->show(); + } + + else if (QStatusBar *statusBar = qobject_cast(widget)) { + if (statusBar != m_mainWindow->statusBar()) + m_mainWindow->setStatusBar(statusBar); + + m_widgets.append(widget); + statusBar->show(); + } + + else if (QDockWidget *dockWidget = qobject_cast(widget)) { + m_widgets.append(widget); + + Qt::DockWidgetArea area = Qt::LeftDockWidgetArea; + const auto areaProperty = widget->property(dockAreaPropertyName); + if (areaProperty.canConvert()) { + area = areaProperty.value(); + widget->setProperty(dockAreaPropertyName, {}); + } else { + area = dockWidgetArea(dockWidget); + } + + m_mainWindow->addDockWidget(area, dockWidget); + dockWidget->show(); + + if (FormWindow *fw = FormWindow::findFormWindow(m_mainWindow)) { + fw->manageWidget(widget); + } + } + + else if (widget) { + m_widgets.prepend(widget); + + if (widget != m_mainWindow->centralWidget()) { + // note that qmainwindow will delete the current central widget if you + // call setCentralWidget(), we end up with dangeling pointers in m_widgets list + m_widgets.removeAll(m_mainWindow->centralWidget()); + + widget->setParent(m_mainWindow); + m_mainWindow->setCentralWidget(widget); + } + } +} + +void QMainWindowContainer::insertWidget(int index, QWidget *widget) +{ + Q_UNUSED(index); + + addWidget(widget); +} + +void QMainWindowContainer::remove(int index) +{ + QWidget *widget = m_widgets.at(index); + if (QToolBar *toolBar = qobject_cast(widget)) { + m_mainWindow->removeToolBar(toolBar); + } else if (QMenuBar *menuBar = qobject_cast(widget)) { + menuBar->hide(); + menuBar->setParent(nullptr); + m_mainWindow->setMenuBar(nullptr); + } else if (QStatusBar *statusBar = qobject_cast(widget)) { + statusBar->hide(); + statusBar->setParent(nullptr); + m_mainWindow->setStatusBar(nullptr); + } else if (QDockWidget *dockWidget = qobject_cast(widget)) { + const auto area = m_mainWindow->dockWidgetArea(dockWidget); + dockWidget->setProperty(dockAreaPropertyName, QVariant::fromValue(area)); + m_mainWindow->removeDockWidget(dockWidget); + } + m_widgets.removeAt(index); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/qmainwindow_container.h b/src/designer/src/components/formeditor/qmainwindow_container.h new file mode 100644 index 0000000..4473cab --- /dev/null +++ b/src/designer/src/components/formeditor/qmainwindow_container.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMAINWINDOW_CONTAINER_H +#define QMAINWINDOW_CONTAINER_H + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QMainWindowContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QMainWindowContainer(QMainWindow *widget, QObject *parent = nullptr); + + int count() const override; + QWidget *widget(int index) const override; + int currentIndex() const override; + void setCurrentIndex(int index) override; + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QMainWindow *m_mainWindow; + QWidgetList m_widgets; +}; + +using QMainWindowContainerFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QMAINWINDOW_CONTAINER_H diff --git a/src/designer/src/components/formeditor/qmdiarea_container.cpp b/src/designer/src/components/formeditor/qmdiarea_container.cpp new file mode 100644 index 0000000..137bd96 --- /dev/null +++ b/src/designer/src/components/formeditor/qmdiarea_container.cpp @@ -0,0 +1,243 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmdiarea_container.h" + +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +QMdiAreaContainer::QMdiAreaContainer(QMdiArea *widget, QObject *parent) + : QObject(parent), + m_mdiArea(widget) +{ +} + +int QMdiAreaContainer::count() const +{ + return m_mdiArea->subWindowList(QMdiArea::CreationOrder).size(); +} + +QWidget *QMdiAreaContainer::widget(int index) const +{ + if (index < 0) + return nullptr; + return m_mdiArea->subWindowList(QMdiArea::CreationOrder).at(index)->widget(); +} + +int QMdiAreaContainer::currentIndex() const +{ + if (QMdiSubWindow *sub = m_mdiArea->activeSubWindow()) + return m_mdiArea->subWindowList(QMdiArea::CreationOrder).indexOf(sub); + return -1; +} + +void QMdiAreaContainer::setCurrentIndex(int index) +{ + if (index < 0) { + qDebug() << "** WARNING Attempt to QMdiAreaContainer::setCurrentIndex(-1)"; + return; + } + QMdiSubWindow *frame = m_mdiArea->subWindowList(QMdiArea::CreationOrder).at(index); + m_mdiArea->setActiveSubWindow(frame); +} + +void QMdiAreaContainer::addWidget(QWidget *widget) +{ + QMdiSubWindow *frame = m_mdiArea->addSubWindow(widget, Qt::Window); + frame->show(); + m_mdiArea->cascadeSubWindows(); + positionNewMdiChild(m_mdiArea, frame); +} + +// Semi-smart positioning of new windows: Make child fill the whole MDI window below +// cascaded other windows +void QMdiAreaContainer::positionNewMdiChild(const QWidget *area, QWidget *mdiChild) +{ + enum { MinSize = 20 }; + const QPoint pos = mdiChild->pos(); + const QSize areaSize = area->size(); + switch (QApplication::layoutDirection()) { + case Qt::LayoutDirectionAuto: + case Qt::LeftToRight: { + const QSize fullSize = QSize(areaSize.width() - pos.x(), areaSize.height() - pos.y()); + if (fullSize.width() > MinSize && fullSize.height() > MinSize) + mdiChild->resize(fullSize); + } + break; + case Qt::RightToLeft: { + const QSize fullSize = QSize(pos.x() + mdiChild->width(), areaSize.height() - pos.y()); + if (fullSize.width() > MinSize && fullSize.height() > MinSize) { + mdiChild->move(0, pos.y()); + mdiChild->resize(fullSize); + } + } + break; + } +} + +void QMdiAreaContainer::insertWidget(int, QWidget *widget) +{ + addWidget(widget); +} + +void QMdiAreaContainer::remove(int index) +{ + auto subWins = m_mdiArea->subWindowList(QMdiArea::CreationOrder); + if (index >= 0 && index < subWins.size()) { + QMdiSubWindow *f = subWins.at(index); + m_mdiArea->removeSubWindow(f->widget()); + delete f; + } +} + +// ---------- MdiAreaPropertySheet, creates fake properties: +// 1) window name (object name of child) +// 2) title (windowTitle of child). + +static constexpr auto subWindowTitleC = "activeSubWindowTitle"_L1; +static constexpr auto subWindowNameC = "activeSubWindowName"_L1; + +QMdiAreaPropertySheet::QMdiAreaPropertySheet(QWidget *mdiArea, QObject *parent) : + QDesignerPropertySheet(mdiArea, parent), + m_windowTitleProperty(u"windowTitle"_s) +{ + createFakeProperty(subWindowNameC, QString()); + createFakeProperty(subWindowTitleC, QString()); +} + +QMdiAreaPropertySheet::MdiAreaProperty QMdiAreaPropertySheet::mdiAreaProperty(const QString &name) +{ + static const QHash mdiAreaPropertyHash = { + {subWindowNameC, MdiAreaSubWindowName}, + {subWindowTitleC, MdiAreaSubWindowTitle} + }; + return mdiAreaPropertyHash.value(name, MdiAreaNone); +} + +void QMdiAreaPropertySheet::setProperty(int index, const QVariant &value) +{ + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + if (QWidget *w = currentWindow()) + w->setObjectName(value.toString()); + break; + case MdiAreaSubWindowTitle: // Forward to window title of subwindow + if (QDesignerPropertySheetExtension *cws = currentWindowSheet()) { + const int index = cws->indexOf(m_windowTitleProperty); + cws->setProperty(index, value); + cws->setChanged(index, true); + } + break; + default: + QDesignerPropertySheet::setProperty(index, value); + break; + } +} + +bool QMdiAreaPropertySheet::reset(int index) +{ + bool rc = true; + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + setProperty(index, QVariant(QString())); + setChanged(index, false); + break; + case MdiAreaSubWindowTitle: // Forward to window title of subwindow + if (QDesignerPropertySheetExtension *cws = currentWindowSheet()) { + const int index = cws->indexOf(m_windowTitleProperty); + rc = cws->reset(index); + } + break; + default: + rc = QDesignerPropertySheet::reset(index); + break; + } + return rc; +} + +QVariant QMdiAreaPropertySheet::property(int index) const +{ + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + if (QWidget *w = currentWindow()) + return w->objectName(); + return QVariant(QString()); + case MdiAreaSubWindowTitle: + if (QWidget *w = currentWindow()) + return w->windowTitle(); + return QVariant(QString()); + case MdiAreaNone: + break; + } + return QDesignerPropertySheet::property(index); +} + +bool QMdiAreaPropertySheet::isEnabled(int index) const +{ + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + case MdiAreaSubWindowTitle: + return currentWindow() != nullptr; + case MdiAreaNone: + break; + } + return QDesignerPropertySheet::isEnabled(index); +} + +bool QMdiAreaPropertySheet::isChanged(int index) const +{ + bool rc = false; + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + rc = currentWindow() != nullptr; + break; + case MdiAreaSubWindowTitle: + if (QDesignerPropertySheetExtension *cws = currentWindowSheet()) { + const int index = cws->indexOf(m_windowTitleProperty); + rc = cws->isChanged(index); + } + break; + default: + rc = QDesignerPropertySheet::isChanged(index); + break; + } + return rc; +} + +QWidget *QMdiAreaPropertySheet::currentWindow() const +{ + if (const QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), object())) { + const int ci = c->currentIndex(); + if (ci < 0) + return nullptr; + return c->widget(ci); + } + return nullptr; +} + +QDesignerPropertySheetExtension *QMdiAreaPropertySheet::currentWindowSheet() const +{ + QWidget *cw = currentWindow(); + if (cw == nullptr) + return nullptr; + return qt_extension(core()->extensionManager(), cw); +} + +bool QMdiAreaPropertySheet::checkProperty(const QString &propertyName) +{ + return mdiAreaProperty(propertyName) == MdiAreaNone; +} +} +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/qmdiarea_container.h b/src/designer/src/components/formeditor/qmdiarea_container.h new file mode 100644 index 0000000..aebd3f2 --- /dev/null +++ b/src/designer/src/components/formeditor/qmdiarea_container.h @@ -0,0 +1,80 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMDIAREA_CONTAINER_H +#define QMDIAREA_CONTAINER_H + +#include + + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Container for QMdiArea +class QMdiAreaContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QMdiAreaContainer(QMdiArea *widget, QObject *parent = nullptr); + + int count() const override; + QWidget *widget(int index) const override; + int currentIndex() const override; + void setCurrentIndex(int index) override; + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + + // Semismart positioning of a new MDI child after cascading + static void positionNewMdiChild(const QWidget *area, QWidget *mdiChild); + +private: + QMdiArea *m_mdiArea; +}; + +// PropertySheet for QMdiArea: Fakes window title and name. + +class QMdiAreaPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit QMdiAreaPropertySheet(QWidget *mdiArea, QObject *parent = nullptr); + + void setProperty(int index, const QVariant &value) override; + bool reset(int index) override; + bool isEnabled(int index) const override; + bool isChanged(int index) const override; + QVariant property(int index) const override; + + // Check whether the property is to be saved. Returns false for the page + // properties (as the property sheet has no concept of 'stored') + static bool checkProperty(const QString &propertyName); + +private: + const QString m_windowTitleProperty; + QWidget *currentWindow() const; + QDesignerPropertySheetExtension *currentWindowSheet() const; + + enum MdiAreaProperty { MdiAreaSubWindowName, MdiAreaSubWindowTitle, MdiAreaNone }; + static MdiAreaProperty mdiAreaProperty(const QString &name); +}; + +// Factories + +using QMdiAreaContainerFactory = ExtensionFactory; +using QMdiAreaPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QMDIAREA_CONTAINER_H diff --git a/src/designer/src/components/formeditor/qwizard_container.cpp b/src/designer/src/components/formeditor/qwizard_container.cpp new file mode 100644 index 0000000..99254a4 --- /dev/null +++ b/src/designer/src/components/formeditor/qwizard_container.cpp @@ -0,0 +1,185 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qwizard_container.h" + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using WizardPageList = QList; + +namespace qdesigner_internal { + +QWizardContainer::QWizardContainer(QWizard *widget, QObject *parent) : + QObject(parent), + m_wizard(widget) +{ +} + +int QWizardContainer::count() const +{ + return m_wizard->pageIds().size(); +} + +QWidget *QWizardContainer::widget(int index) const +{ + QWidget *rc = nullptr; + if (index >= 0) { + const auto idList = m_wizard->pageIds(); + if (index < idList.size()) + rc = m_wizard->page(idList.at(index)); + } + return rc; +} + +int QWizardContainer::currentIndex() const +{ + return m_wizard->pageIds().indexOf(m_wizard->currentId()); +} + +void QWizardContainer::setCurrentIndex(int index) +{ + if (index < 0 || m_wizard->pageIds().isEmpty()) + return; + + int currentIdx = currentIndex(); + + if (currentIdx == -1) { + m_wizard->restart(); + currentIdx = currentIndex(); + } + + if (currentIdx == index) + return; + + const int d = qAbs(index - currentIdx); + if (index > currentIdx) { + for (int i = 0; i < d; i++) + m_wizard->next(); + } else { + for (int i = 0; i < d; i++) + m_wizard->back(); + } +} + +static const char msgWrongType[] = "** WARNING Attempt to add oject that is not of class WizardPage to a QWizard"; + +void QWizardContainer::addWidget(QWidget *widget) +{ + QWizardPage *page = qobject_cast(widget); + if (!page) { + qWarning("%s", msgWrongType); + return; + } + m_wizard->addPage(page); + // Might be -1 after adding the first page + setCurrentIndex(m_wizard->pageIds().size() - 1); +} + +void QWizardContainer::insertWidget(int index, QWidget *widget) +{ + enum { delta = 5 }; + + QWizardPage *newPage = qobject_cast(widget); + if (!newPage) { + qWarning("%s", msgWrongType); + return; + } + + const auto idList = m_wizard->pageIds(); + const auto pageCount = idList.size(); + if (index >= pageCount) { + addWidget(widget); + return; + } + + // Insert before, reshuffle ids if required + const int idBefore = idList.at(index); + const int newId = idBefore - 1; + const bool needsShuffle = + (index == 0 && newId < 0) // At start: QWizard refuses to insert id -1 + || (index > 0 && idList.at(index - 1) == newId); // In-between + if (needsShuffle) { + // Create a gap by shuffling pages + WizardPageList pageList; + pageList.push_back(newPage); + for (qsizetype i = index; i < pageCount; ++i) { + pageList.push_back(m_wizard->page(idList.at(i))); + m_wizard->removePage(idList.at(i)); + } + int newId = idBefore + delta; + for (QWizardPage *page : std::as_const(pageList)) { + m_wizard->setPage(newId, page); + newId += delta; + } + } else { + // Gap found, just insert + m_wizard->setPage(newId, newPage); + } + // Might be at -1 after adding the first page + setCurrentIndex(index); +} + +void QWizardContainer::remove(int index) +{ + if (index < 0) + return; + + const auto idList = m_wizard->pageIds(); + if (index >= idList.size()) + return; + + m_wizard->removePage(idList.at(index)); + // goto next page, preferably + const int newSize = idList.size() - 1; + if (index < newSize) { + setCurrentIndex(index); + } else { + if (newSize > 0) + setCurrentIndex(newSize - 1); + } +} + +// ---------------- QWizardPagePropertySheet +const char *QWizardPagePropertySheet::pageIdProperty = "pageId"; + +QWizardPagePropertySheet::QWizardPagePropertySheet(QWizardPage *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_pageIdIndex(createFakeProperty(QLatin1StringView(pageIdProperty), QString())) +{ + setAttribute(m_pageIdIndex, true); +} + +bool QWizardPagePropertySheet::reset(int index) +{ + if (index == m_pageIdIndex) { + setProperty(index, QString()); + return true; + } + return QDesignerPropertySheet::reset(index); +} + +// ---------------- QWizardPropertySheet +QWizardPropertySheet::QWizardPropertySheet(QWizard *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_startId(u"startId"_s) +{ +} + +bool QWizardPropertySheet::isVisible(int index) const +{ + if (propertyName(index) == m_startId) + return false; + return QDesignerPropertySheet::isVisible(index); +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/qwizard_container.h b/src/designer/src/components/formeditor/qwizard_container.h new file mode 100644 index 0000000..5c6533f --- /dev/null +++ b/src/designer/src/components/formeditor/qwizard_container.h @@ -0,0 +1,86 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QWIZARD_CONTAINER_H +#define QWIZARD_CONTAINER_H + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QWizardPage; + +namespace qdesigner_internal { + +// Container for QWizard. Care must be taken to position +// the QWizard at some valid page after removal/insertion +// as it is not used to having its pages ripped out. +class QWizardContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QWizardContainer(QWizard *widget, QObject *parent = nullptr); + + int count() const override; + QWidget *widget(int index) const override; + int currentIndex() const override; + void setCurrentIndex(int index) override; + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QWizard *m_wizard; +}; + +// QWizardPagePropertySheet: Introduces a attribute string fake property +// "pageId" that allows for specifying enumeration values (uic only). +// This breaks the pattern of having a "currentSth" property for the +// container, but was deemed to make sense here since the Page has +// its own "title" properties. +class QWizardPagePropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT +public: + explicit QWizardPagePropertySheet(QWizardPage *object, QObject *parent = nullptr); + + bool reset(int index) override; + + static const char *pageIdProperty; + +private: + const int m_pageIdIndex; +}; + +// QWizardPropertySheet: Hides the "startId" property. It cannot be used +// as QWizard cannot handle setting it as a property before the actual +// page is added. + +class QWizardPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT +public: + explicit QWizardPropertySheet(QWizard *object, QObject *parent = nullptr); + bool isVisible(int index) const override; + +private: + const QString m_startId; +}; + +// Factories +using QWizardPropertySheetFactory = QDesignerPropertySheetFactory; +using QWizardPagePropertySheetFactory = QDesignerPropertySheetFactory; +using QWizardContainerFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QWIZARD_CONTAINER_H diff --git a/src/designer/src/components/formeditor/spacer_propertysheet.cpp b/src/designer/src/components/formeditor/spacer_propertysheet.cpp new file mode 100644 index 0000000..9c3da13 --- /dev/null +++ b/src/designer/src/components/formeditor/spacer_propertysheet.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "spacer_propertysheet.h" +#include "qdesigner_widget_p.h" +#include "formwindow.h" +#include "spacer_widget_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal +{ +SpacerPropertySheet::SpacerPropertySheet(Spacer *object, QObject *parent) + : QDesignerPropertySheet(object, parent) +{ + clearFakeProperties(); +} + +SpacerPropertySheet::~SpacerPropertySheet() = default; + +bool SpacerPropertySheet::isVisible(int index) const +{ + return propertyGroup(index) == "Spacer"_L1; +} + +void SpacerPropertySheet::setProperty(int index, const QVariant &value) +{ + QDesignerPropertySheet::setProperty(index, value); +} + +bool SpacerPropertySheet::dynamicPropertiesAllowed() const +{ + return false; +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/spacer_propertysheet.h b/src/designer/src/components/formeditor/spacer_propertysheet.h new file mode 100644 index 0000000..c2525d8 --- /dev/null +++ b/src/designer/src/components/formeditor/spacer_propertysheet.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SPACER_PROPERTYSHEET_H +#define SPACER_PROPERTYSHEET_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class SpacerPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit SpacerPropertySheet(Spacer *object, QObject *parent = nullptr); + ~SpacerPropertySheet() override; + + void setProperty(int index, const QVariant &value) override; + bool isVisible(int index) const override; + + bool dynamicPropertiesAllowed() const override; +}; + +using SpacerPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SPACER_PROPERTYSHEET_H diff --git a/src/designer/src/components/formeditor/templateoptionspage.cpp b/src/designer/src/components/formeditor/templateoptionspage.cpp new file mode 100644 index 0000000..2063972 --- /dev/null +++ b/src/designer/src/components/formeditor/templateoptionspage.cpp @@ -0,0 +1,147 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "templateoptionspage.h" +#include "ui_templateoptionspage.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// ----------------- TemplateOptionsWidget +TemplateOptionsWidget::TemplateOptionsWidget(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_core(core), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::TemplateOptionsWidget) +{ + m_ui->setupUi(this); + + m_ui->m_addTemplatePathButton->setIcon( + qdesigner_internal::createIconSet("plus.png"_L1)); + m_ui->m_removeTemplatePathButton->setIcon( + qdesigner_internal::createIconSet("minus.png"_L1)); + + connect(m_ui->m_templatePathListWidget, &QListWidget::itemSelectionChanged, + this, &TemplateOptionsWidget::templatePathSelectionChanged); + connect(m_ui->m_addTemplatePathButton, &QAbstractButton::clicked, + this, &TemplateOptionsWidget::addTemplatePath); + connect(m_ui->m_removeTemplatePathButton, &QAbstractButton::clicked, + this, &TemplateOptionsWidget::removeTemplatePath); +} + +TemplateOptionsWidget::~TemplateOptionsWidget() +{ + delete m_ui; +} + +QStringList TemplateOptionsWidget::templatePaths() const +{ + QStringList rc; + const int count = m_ui->m_templatePathListWidget->count(); + for (int i = 0; i < count; i++) { + rc += m_ui->m_templatePathListWidget->item(i)->text(); + } + return rc; +} + +void TemplateOptionsWidget::setTemplatePaths(const QStringList &l) +{ + // add paths and select 0 + m_ui->m_templatePathListWidget->clear(); + if (l.isEmpty()) { + // disable button + templatePathSelectionChanged(); + } else { + for (const auto &s : l) + m_ui->m_templatePathListWidget->addItem(s); + m_ui->m_templatePathListWidget->setCurrentItem(m_ui->m_templatePathListWidget->item(0)); + } +} + +void TemplateOptionsWidget::addTemplatePath() +{ + const QString templatePath = chooseTemplatePath(m_core, this); + if (templatePath.isEmpty()) + return; + + const auto existing + = m_ui->m_templatePathListWidget->findItems(templatePath, Qt::MatchExactly); + if (!existing.isEmpty()) + return; + + QListWidgetItem *newItem = new QListWidgetItem(templatePath); + m_ui->m_templatePathListWidget->addItem(newItem); + m_ui->m_templatePathListWidget->setCurrentItem(newItem); +} + +void TemplateOptionsWidget::removeTemplatePath() +{ + const auto selectedPaths = m_ui->m_templatePathListWidget->selectedItems(); + if (selectedPaths.isEmpty()) + return; + delete selectedPaths.constFirst(); +} + +void TemplateOptionsWidget::templatePathSelectionChanged() +{ + const auto selectedPaths = m_ui->m_templatePathListWidget->selectedItems(); + m_ui->m_removeTemplatePathButton->setEnabled(!selectedPaths.isEmpty()); +} + +QString TemplateOptionsWidget::chooseTemplatePath(QDesignerFormEditorInterface *core, QWidget *parent) +{ + QString rc = core->dialogGui()->getExistingDirectory(parent, + tr("Pick a directory to save templates in")); + if (rc.isEmpty()) + return rc; + + if (rc.endsWith(QDir::separator())) + rc.remove(rc.size() - 1, 1); + return rc; +} + +// ----------------- TemplateOptionsPage +TemplateOptionsPage::TemplateOptionsPage(QDesignerFormEditorInterface *core) : + m_core(core) +{ +} + +QString TemplateOptionsPage::name() const +{ + //: Tab in preferences dialog + return QCoreApplication::translate("TemplateOptionsPage", "Template Paths"); +} + +QWidget *TemplateOptionsPage::createPage(QWidget *parent) +{ + m_widget = new TemplateOptionsWidget(m_core, parent); + m_initialTemplatePaths = QDesignerSharedSettings(m_core).additionalFormTemplatePaths(); + m_widget->setTemplatePaths(m_initialTemplatePaths); + return m_widget; +} + +void TemplateOptionsPage::apply() +{ + if (m_widget) { + const QStringList newTemplatePaths = m_widget->templatePaths(); + if (newTemplatePaths != m_initialTemplatePaths) { + QDesignerSharedSettings settings(m_core); + settings.setAdditionalFormTemplatePaths(newTemplatePaths); + m_initialTemplatePaths = newTemplatePaths; + } + } +} + +void TemplateOptionsPage::finish() +{ +} +} +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/templateoptionspage.h b/src/designer/src/components/formeditor/templateoptionspage.h new file mode 100644 index 0000000..9ce8c45 --- /dev/null +++ b/src/designer/src/components/formeditor/templateoptionspage.h @@ -0,0 +1,72 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_TEMPLATEOPTIONS_H +#define QDESIGNER_TEMPLATEOPTIONS_H + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +namespace Ui { + class TemplateOptionsWidget; +} + +/* Present the user with a list of form template paths to save + * form templates. */ +class TemplateOptionsWidget : public QWidget +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(TemplateOptionsWidget) +public: + explicit TemplateOptionsWidget(QDesignerFormEditorInterface *core, + QWidget *parent = nullptr); + ~TemplateOptionsWidget(); + + + QStringList templatePaths() const; + void setTemplatePaths(const QStringList &l); + + static QString chooseTemplatePath(QDesignerFormEditorInterface *core, QWidget *parent); + +private slots: + void addTemplatePath(); + void removeTemplatePath(); + void templatePathSelectionChanged(); + +private: + QDesignerFormEditorInterface *m_core; + Ui::TemplateOptionsWidget *m_ui; +}; + +class TemplateOptionsPage : public QDesignerOptionsPageInterface +{ + Q_DISABLE_COPY_MOVE(TemplateOptionsPage) +public: + explicit TemplateOptionsPage(QDesignerFormEditorInterface *core); + + QString name() const override; + QWidget *createPage(QWidget *parent) override; + void apply() override; + void finish() override; + +private: + QDesignerFormEditorInterface *m_core; + QStringList m_initialTemplatePaths; + QPointer m_widget; +}; + +} + +QT_END_NAMESPACE + +#endif // QDESIGNER_TEMPLATEOPTIONS_H diff --git a/src/designer/src/components/formeditor/templateoptionspage.ui b/src/designer/src/components/formeditor/templateoptionspage.ui new file mode 100644 index 0000000..3427ffe --- /dev/null +++ b/src/designer/src/components/formeditor/templateoptionspage.ui @@ -0,0 +1,59 @@ + + qdesigner_internal::TemplateOptionsWidget + + + + 0 + 0 + 376 + 387 + + + + Form + + + + + + Additional Template Paths + + + + + + + + + ... + + + + + + + ... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + diff --git a/src/designer/src/components/formeditor/tool_widgeteditor.cpp b/src/designer/src/components/formeditor/tool_widgeteditor.cpp new file mode 100644 index 0000000..fd019b7 --- /dev/null +++ b/src/designer/src/components/formeditor/tool_widgeteditor.cpp @@ -0,0 +1,343 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tool_widgeteditor.h" +#include "formwindow.h" + +// sdk +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +WidgetEditorTool::WidgetEditorTool(FormWindow *formWindow) + : QDesignerFormWindowToolInterface(formWindow), + m_formWindow(formWindow), + m_action(new QAction(tr("Edit Widgets"), this)), + m_specialDockDrag(false) +{ +} + +QAction *WidgetEditorTool::action() const +{ + return m_action; +} + +WidgetEditorTool::~WidgetEditorTool() = default; + +QDesignerFormEditorInterface *WidgetEditorTool::core() const +{ + return m_formWindow->core(); +} + +QDesignerFormWindowInterface *WidgetEditorTool::formWindow() const +{ + return m_formWindow; +} + +// separators in QMainWindow are no longer widgets +bool WidgetEditorTool::mainWindowSeparatorEvent(QWidget *widget, QEvent *event) +{ + QMainWindow *mw = qobject_cast(widget); + if (mw == nullptr) + return false; + + if (event->type() != QEvent::MouseButtonPress + && event->type() != QEvent::MouseMove + && event->type() != QEvent::MouseButtonRelease) + return false; + + QMouseEvent *e = static_cast(event); + + if (event->type() == QEvent::MouseButtonPress) { + if (mw->isSeparator(e->position().toPoint())) { + m_separator_drag_mw = mw; + return true; + } + return false; + } + + if (event->type() == QEvent::MouseMove) + return m_separator_drag_mw == mw; + + if (event->type() == QEvent::MouseButtonRelease) { + if (m_separator_drag_mw != mw) + return false; + m_separator_drag_mw = nullptr; + return true; + } + + return false; +} + +bool WidgetEditorTool::isPassiveInteractor(QWidget *widget, QEvent *event) +{ + auto *widgetFactory = core()->widgetFactory(); + return widgetFactory->isPassiveInteractor(widget) || mainWindowSeparatorEvent(widget, event); +} + +bool WidgetEditorTool::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + switch (event->type()) { + case QEvent::Resize: + case QEvent::Move: + m_formWindow->updateSelection(widget); + break; + + case QEvent::FocusOut: + case QEvent::FocusIn: // Popup cancelled over a form widget: Reset its focus frame + return widget != m_formWindow && widget != m_formWindow->mainContainer() + && !isPassiveInteractor(widget, event); + + case QEvent::Wheel: // Prevent spinboxes and combos from reacting + if (widget == m_formWindow->formContainer() || widget == m_formWindow + || widget == m_formWindow->mainContainer()) { // Allow scrolling the form with wheel. + return false; + } + return !isPassiveInteractor(widget, event); + + case QEvent::KeyPress: + return !isPassiveInteractor(widget, event) + && handleKeyPressEvent(widget, managedWidget, static_cast(event)); + + case QEvent::KeyRelease: + return !isPassiveInteractor(widget, event) + && handleKeyReleaseEvent(widget, managedWidget, static_cast(event)); + + case QEvent::MouseMove: + return !isPassiveInteractor(widget, event) + && handleMouseMoveEvent(widget, managedWidget, static_cast(event)); + + case QEvent::MouseButtonPress: + return !isPassiveInteractor(widget, event) + && handleMousePressEvent(widget, managedWidget, static_cast(event)); + + case QEvent::MouseButtonRelease: + return !isPassiveInteractor(widget, event) + && handleMouseReleaseEvent(widget, managedWidget, static_cast(event)); + + case QEvent::MouseButtonDblClick: + return !isPassiveInteractor(widget, event) + && handleMouseButtonDblClickEvent(widget, managedWidget, static_cast(event)); + + case QEvent::ContextMenu: + return !isPassiveInteractor(widget, event) + && handleContextMenu(widget, managedWidget, static_cast(event)); + + case QEvent::DragEnter: + return handleDragEnterMoveEvent(widget, managedWidget, static_cast(event), true); + case QEvent::DragMove: + return handleDragEnterMoveEvent(widget, managedWidget, static_cast(event), false); + case QEvent::DragLeave: + return handleDragLeaveEvent(widget, managedWidget, static_cast(event)); + case QEvent::Drop: + return handleDropEvent(widget, managedWidget, static_cast(event)); + default: + break; + + } // end switch + + return false; +} + +// ### remove me + +bool WidgetEditorTool::handleContextMenu(QWidget *widget, QWidget *managedWidget, QContextMenuEvent *e) +{ + return m_formWindow->handleContextMenu(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleMouseButtonDblClickEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e) +{ + return m_formWindow->handleMouseButtonDblClickEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleMousePressEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e) +{ + return m_formWindow->handleMousePressEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleMouseMoveEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e) +{ + return m_formWindow->handleMouseMoveEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleMouseReleaseEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e) +{ + return m_formWindow->handleMouseReleaseEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleKeyPressEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e) +{ + return m_formWindow->handleKeyPressEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleKeyReleaseEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e) +{ + return m_formWindow->handleKeyReleaseEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handlePaintEvent(QWidget *widget, QWidget *managedWidget, QPaintEvent *e) +{ + Q_UNUSED(widget); + Q_UNUSED(managedWidget); + Q_UNUSED(e); + + return false; +} + +void WidgetEditorTool::detectDockDrag(const QDesignerMimeData *mimeData) +{ + m_specialDockDrag = false; + if (!mimeData) + return; + + QMainWindow *mw = qobject_cast(m_formWindow->mainContainer()); + if (!mw) + return; + + const auto item_list = mimeData->items(); + + for (QDesignerDnDItemInterface *item : item_list) { + if (item->decoration() && item->decoration()->property("_q_dockDrag").toBool()) + m_specialDockDrag = true; + + } +} + +bool WidgetEditorTool::handleDragEnterMoveEvent(QWidget *widget, QWidget * /*managedWidget*/, QDragMoveEvent *e, bool isEnter) +{ + const QDesignerMimeData *mimeData = qobject_cast(e->mimeData()); + if (!mimeData) + return false; + + if (!m_formWindow->hasFeature(QDesignerFormWindowInterface::EditFeature)) { + e->ignore(); + return true; + } + + if (isEnter) + detectDockDrag(mimeData); + + + QPoint globalPos = QPoint(0, 0); + if (m_specialDockDrag) { + m_lastDropTarget = nullptr; + QMainWindow *mw = qobject_cast(m_formWindow->mainContainer()); + if (mw) + m_lastDropTarget = mw->centralWidget(); + } else { + // If custom widgets have acceptDrops=true, the event occurs for them + const QPoint formPos = widget != m_formWindow ? widget->mapTo(m_formWindow, e->position().toPoint()) : e->position().toPoint(); + globalPos = m_formWindow->mapToGlobal(formPos); + const FormWindowBase::WidgetUnderMouseMode wum = mimeData->items().size() == 1 ? FormWindowBase::FindSingleSelectionDropTarget : FormWindowBase::FindMultiSelectionDropTarget; + QWidget *dropTarget = m_formWindow->widgetUnderMouse(formPos, wum); + if (m_lastDropTarget && dropTarget != m_lastDropTarget) + m_formWindow->highlightWidget(m_lastDropTarget, m_lastDropTarget->mapFromGlobal(globalPos), FormWindow::Restore); + m_lastDropTarget = dropTarget; + } + + if (m_lastDropTarget) + m_formWindow->highlightWidget(m_lastDropTarget, m_lastDropTarget->mapFromGlobal(globalPos), FormWindow::Highlight); + + if (isEnter || m_lastDropTarget) + mimeData->acceptEvent(e); + else + e->ignore(); + return true; +} + +bool WidgetEditorTool::handleDropEvent(QWidget *widget, QWidget *, QDropEvent *e) +{ + const QDesignerMimeData *mimeData = qobject_cast(e->mimeData()); + if (!mimeData) + return false; + + if (!m_lastDropTarget || + !m_formWindow->hasFeature(QDesignerFormWindowInterface::EditFeature)) { + e->ignore(); + return true; + } + // FormWindow determines the position from the decoration. + const QPoint globalPos = widget->mapToGlobal(e->position().toPoint()); + mimeData->moveDecoration(globalPos); + if (m_specialDockDrag) { + if (!m_formWindow->dropDockWidget(mimeData->items().at(0), globalPos)) { + e->ignore(); + return true; + } + } else if (!m_formWindow->dropWidgets(mimeData->items(), m_lastDropTarget, globalPos)) { + e->ignore(); + return true; + } + mimeData->acceptEvent(e); + return true; +} + +bool WidgetEditorTool::restoreDropHighlighting() +{ + if (!m_lastDropTarget) + return false; + + m_formWindow->highlightWidget(m_lastDropTarget, m_lastDropTarget->mapFromGlobal(QCursor::pos()), FormWindow::Restore); + m_lastDropTarget = nullptr; + return true; +} + +bool WidgetEditorTool::handleDragLeaveEvent(QWidget *, QWidget *, QDragLeaveEvent *event) +{ + if (restoreDropHighlighting()) { + event->accept(); + return true; + } + return false; +} + +QWidget *WidgetEditorTool::editor() const +{ + Q_ASSERT(formWindow() != nullptr); + return formWindow()->mainContainer(); +} + +void WidgetEditorTool::activated() +{ + if (core()->widgetBox()) + core()->widgetBox()->setEnabled(true); + + if (m_formWindow == nullptr) + return; + + const QWidgetList &sel = m_formWindow->selectedWidgets(); + for (QWidget *w : sel) + m_formWindow->raiseSelection(w); +} + +void WidgetEditorTool::deactivated() +{ + if (core()->widgetBox()) + core()->widgetBox()->setEnabled(false); + + if (m_formWindow == nullptr) + return; + + m_formWindow->clearSelection(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/tool_widgeteditor.h b/src/designer/src/components/formeditor/tool_widgeteditor.h new file mode 100644 index 0000000..5e22224 --- /dev/null +++ b/src/designer/src/components/formeditor/tool_widgeteditor.h @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TOOL_WIDGETEDITOR_H +#define TOOL_WIDGETEDITOR_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; +class QMainWindow; + +namespace qdesigner_internal { + +class FormWindow; +class QDesignerMimeData; + +class WidgetEditorTool: public QDesignerFormWindowToolInterface +{ + Q_OBJECT +public: + explicit WidgetEditorTool(FormWindow *formWindow); + ~WidgetEditorTool() override; + + QDesignerFormEditorInterface *core() const override; + QDesignerFormWindowInterface *formWindow() const override; + QWidget *editor() const override; + QAction *action() const override; + + void activated() override; + void deactivated() override; + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) override; + + bool handleContextMenu(QWidget *widget, QWidget *managedWidget, QContextMenuEvent *e); + bool handleMouseButtonDblClickEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMousePressEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMouseMoveEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMouseReleaseEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleKeyPressEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e); + bool handleKeyReleaseEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e); + bool handlePaintEvent(QWidget *widget, QWidget *managedWidget, QPaintEvent *e); + + bool handleDragEnterMoveEvent(QWidget *widget, QWidget *managedWidget, QDragMoveEvent *e, bool isEnter); + bool handleDragLeaveEvent(QWidget *widget, QWidget *managedWidget, QDragLeaveEvent *e); + bool handleDropEvent(QWidget *widget, QWidget *managedWidget, QDropEvent *e); + +private: + bool restoreDropHighlighting(); + void detectDockDrag(const QDesignerMimeData *mimeData); + + FormWindow *m_formWindow; + QAction *m_action; + + bool mainWindowSeparatorEvent(QWidget *widget, QEvent *event); + bool isPassiveInteractor(QWidget *widget, QEvent *event); + QPointer m_separator_drag_mw; + QPointer m_lastDropTarget; + bool m_specialDockDrag; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TOOL_WIDGETEDITOR_H diff --git a/src/designer/src/components/formeditor/widgetselection.cpp b/src/designer/src/components/formeditor/widgetselection.cpp new file mode 100644 index 0000000..75d8a5f --- /dev/null +++ b/src/designer/src/components/formeditor/widgetselection.cpp @@ -0,0 +1,720 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetselection.h" +#include "formwindow.h" +#include "formwindowmanager.h" + +// sdk +#include +#include + +// shared +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +enum { debugWidgetSelection = 0 }; + +// Return the layout the widget is in +template +static inline Layout *managedLayoutOf(const QDesignerFormEditorInterface *core, + QWidget *w, + const Layout * /* vs6dummy */ = nullptr) +{ + if (QWidget *p = w->parentWidget()) + if (QLayout *l = LayoutInfo::managedLayout(core, p)) + return qobject_cast(l); + return nullptr; +} + +// ----------- WidgetHandle +WidgetHandle::WidgetHandle(FormWindow *parent, WidgetHandle::Type t, WidgetSelection *s) : + InvisibleWidget(parent->formContainer()), + m_widget(nullptr), + m_type(t), + m_formWindow( parent), + m_sel(s), + m_active(true) +{ + setMouseTracking(false); + setAutoFillBackground(true); + + setBackgroundRole(m_active ? QPalette::Text : QPalette::Dark); + setFixedSize(6, 6); + + updateCursor(); +} + +void WidgetHandle::updateCursor() +{ +#if QT_CONFIG(cursor) + if (!m_active) { + setCursor(Qt::ArrowCursor); + return; + } + + switch (m_type) { + case LeftTop: + setCursor(Qt::SizeFDiagCursor); + break; + case Top: + setCursor(Qt::SizeVerCursor); + break; + case RightTop: + setCursor(Qt::SizeBDiagCursor); + break; + case Right: + setCursor(Qt::SizeHorCursor); + break; + case RightBottom: + setCursor(Qt::SizeFDiagCursor); + break; + case Bottom: + setCursor(Qt::SizeVerCursor); + break; + case LeftBottom: + setCursor(Qt::SizeBDiagCursor); + break; + case Left: + setCursor(Qt::SizeHorCursor); + break; + default: + Q_ASSERT(0); + } +#endif +} + +QDesignerFormEditorInterface *WidgetHandle::core() const +{ + if (m_formWindow) + return m_formWindow->core(); + + return nullptr; +} + +void WidgetHandle::setActive(bool a) +{ + m_active = a; + setBackgroundRole(m_active ? QPalette::Text : QPalette::Dark); + updateCursor(); +} + +void WidgetHandle::setWidget(QWidget *w) +{ + m_widget = w; +} + +void WidgetHandle::paintEvent(QPaintEvent *) +{ + QDesignerFormWindowManagerInterface *m = m_formWindow->core()->formWindowManager(); + + QStylePainter p(this); + if (m_formWindow->currentWidget() == m_widget) { + p.setPen(m->activeFormWindow() == m_formWindow ? Qt::blue : Qt::red); + p.drawRect(0, 0, width() - 1, height() - 1); + } +} + +void WidgetHandle::mousePressEvent(QMouseEvent *e) +{ + e->accept(); + + if (!m_formWindow->hasFeature(FormWindow::EditFeature)) + return; + + if (!(m_widget && e->button() == Qt::LeftButton)) + return; + + if (!(m_active)) + return; + + QWidget *container = m_widget->parentWidget(); + + m_origPressPos = container->mapFromGlobal(e->globalPosition().toPoint()); + m_geom = m_origGeom = m_widget->geometry(); + + switch (WidgetSelection::widgetState(m_formWindow->core(), m_widget)) { + case WidgetSelection::UnlaidOut: + case WidgetSelection::LaidOut: + m_formWindow->setHandleOperation(FormWindow::ResizeHandleOperation); + break; + case WidgetSelection::ManagedGridLayout: + case WidgetSelection::ManagedFormLayout: + m_formWindow->setHandleOperation(FormWindow::ChangeLayoutSpanHandleOperation); + break; + } +} + +void WidgetHandle::mouseMoveEvent(QMouseEvent *e) +{ + if (!(m_widget && m_active && e->buttons() & Qt::LeftButton)) + return; + + e->accept(); + + QWidget *container = m_widget->parentWidget(); + + const QPoint rp = container->mapFromGlobal(e->globalPosition().toPoint()); + const QPoint d = rp - m_origPressPos; + + const QRect pr = container->rect(); + + qdesigner_internal::Grid grid; + if (const qdesigner_internal::FormWindowBase *fwb = qobject_cast(m_formWindow)) + grid = fwb->designerGrid(); + + switch (m_type) { + + case LeftTop: { + if (rp.x() > pr.width() - 2 * width() || rp.y() > pr.height() - 2 * height()) + return; + + int w = m_origGeom.width() - d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + int h = m_origGeom.height() - d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + const int dx = m_widget->width() - w; + const int dy = m_widget->height() - h; + + trySetGeometry(m_widget, m_widget->x() + dx, m_widget->y() + dy, w, h); + } break; + + case Top: { + if (rp.y() > pr.height() - 2 * height()) + return; + + int h = m_origGeom.height() - d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + const int dy = m_widget->height() - h; + trySetGeometry(m_widget, m_widget->x(), m_widget->y() + dy, m_widget->width(), h); + } break; + + case RightTop: { + if (rp.x() < 2 * width() || rp.y() > pr.height() - 2 * height()) + return; + + int h = m_origGeom.height() - d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + const int dy = m_widget->height() - h; + + int w = m_origGeom.width() + d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + trySetGeometry(m_widget, m_widget->x(), m_widget->y() + dy, w, h); + } break; + + case Right: { + if (rp.x() < 2 * width()) + return; + + int w = m_origGeom.width() + d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + tryResize(m_widget, w, m_widget->height()); + } break; + + case RightBottom: { + if (rp.x() < 2 * width() || rp.y() < 2 * height()) + return; + + int w = m_origGeom.width() + d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + int h = m_origGeom.height() + d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + tryResize(m_widget, w, h); + } break; + + case Bottom: { + if (rp.y() < 2 * height()) + return; + + int h = m_origGeom.height() + d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + tryResize(m_widget, m_widget->width(), h); + } break; + + case LeftBottom: { + if (rp.x() > pr.width() - 2 * width() || rp.y() < 2 * height()) + return; + + int w = m_origGeom.width() - d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + int h = m_origGeom.height() + d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + int dx = m_widget->width() - w; + + trySetGeometry(m_widget, m_widget->x() + dx, m_widget->y(), w, h); + } break; + + case Left: { + if (rp.x() > pr.width() - 2 * width()) + return; + + int w = m_origGeom.width() - d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + const int dx = m_widget->width() - w; + + trySetGeometry(m_widget, m_widget->x() + dx, m_widget->y(), w, m_widget->height()); + } break; + + default: break; + + } // end switch + + m_sel->updateGeometry(); + + if (LayoutInfo::layoutType(m_formWindow->core(), m_widget) != LayoutInfo::NoLayout) + m_formWindow->updateChildSelections(m_widget); +} + +void WidgetHandle::mouseReleaseEvent(QMouseEvent *e) +{ + m_formWindow->setHandleOperation(FormWindow::NoHandleOperation); + + if (e->button() != Qt::LeftButton || !m_active) + return; + + e->accept(); + + if (!m_formWindow->hasFeature(FormWindow::EditFeature)) + return; + + switch (WidgetSelection::widgetState(m_formWindow->core(), m_widget)) { + case WidgetSelection::UnlaidOut: + if (m_geom != m_widget->geometry()) { + SetPropertyCommand *cmd = new SetPropertyCommand(m_formWindow); + cmd->init(m_widget, u"geometry"_s, m_widget->geometry()); + cmd->setOldValue(m_origGeom); + m_formWindow->commandHistory()->push(cmd); + m_formWindow->emitSelectionChanged(); + } + break; + case WidgetSelection::LaidOut: + break; + case WidgetSelection::ManagedGridLayout: + changeGridLayoutItemSpan(); + break; + case WidgetSelection::ManagedFormLayout: + changeFormLayoutItemSpan(); + break; + } +} + +// Match the left/right widget handle mouse movements to form layout span-changing operations +static inline int formLayoutLeftHandleOperation(int dx, unsigned possibleOperations) +{ + if (dx < 0) { + if (possibleOperations & ChangeFormLayoutItemRoleCommand::FieldToSpanning) + return ChangeFormLayoutItemRoleCommand::FieldToSpanning; + return 0; + } + if (possibleOperations & ChangeFormLayoutItemRoleCommand::SpanningToField) + return ChangeFormLayoutItemRoleCommand::SpanningToField; + return 0; +} + +static inline int formLayoutRightHandleOperation(int dx, unsigned possibleOperations) +{ + if (dx < 0) { + if (possibleOperations & ChangeFormLayoutItemRoleCommand::SpanningToLabel) + return ChangeFormLayoutItemRoleCommand::SpanningToLabel; + return 0; + } + if (possibleOperations & ChangeFormLayoutItemRoleCommand::LabelToSpanning) + return ChangeFormLayoutItemRoleCommand::LabelToSpanning; + return 0; +} + +// Change form layout item horizontal span +void WidgetHandle::changeFormLayoutItemSpan() +{ + QUndoCommand *cmd = nullptr; + // Figure out command according to the movement + const int dx = m_widget->geometry().center().x() - m_origGeom.center().x(); + if (qAbs(dx) >= QApplication::startDragDistance()) { + int operation = 0; + if (const unsigned possibleOperations = ChangeFormLayoutItemRoleCommand::possibleOperations(m_formWindow->core(), m_widget)) { + switch (m_type) { + case WidgetHandle::Left: + operation = formLayoutLeftHandleOperation(dx, possibleOperations); + break; + case WidgetHandle::Right: + operation = formLayoutRightHandleOperation(dx, possibleOperations); + break; + default: + break; + } + if (operation) { + ChangeFormLayoutItemRoleCommand *fcmd = new ChangeFormLayoutItemRoleCommand(m_formWindow); + fcmd->init(m_widget, static_cast(operation)); + cmd = fcmd; + } + } + } + if (cmd) { + m_formWindow->commandHistory()->push(cmd); + } else { + // Cancelled/Invalid. Restore the size of the widget. + if (QFormLayout *form = managedLayoutOf(m_formWindow->core(), m_widget)) { + form->invalidate(); + form->activate(); + m_formWindow->clearSelection(false); + m_formWindow->selectWidget(m_widget); + } + } +} + +void WidgetHandle::changeGridLayoutItemSpan() +{ + QDesignerLayoutDecorationExtension *deco = qt_extension(core()->extensionManager(), m_widget->parentWidget()); + if (!deco) + return; + QGridLayout *grid = managedLayoutOf(m_formWindow->core(), m_widget); + if (!grid) + return; + + const int index = deco->indexOf(m_widget); + const QRect info = deco->itemInfo(index); + const int top = deco->findItemAt(info.top() - 1, info.left()); + const int left = deco->findItemAt(info.top(), info.left() - 1); + const int bottom = deco->findItemAt(info.bottom() + 1, info.left()); + const int right = deco->findItemAt(info.top(), info.right() + 1); + + const QPoint pt = m_origGeom.center() - m_widget->geometry().center(); + + ChangeLayoutItemGeometry *cmd = nullptr; + + switch (m_type) { + default: + break; + + case WidgetHandle::Top: { + if (pt.y() < 0 && info.height() > 1) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y() + 1, info.x(), info.height() - 1, info.width()); + } else if (pt.y() > 0 && top != -1 && grid->itemAt(top)->spacerItem()) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y() - 1, info.x(), info.height() + 1, info.width()); + } + } + break; + + case WidgetHandle::Left: { + if (pt.x() < 0 && info.width() > 1) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x() + 1, info.height(), info.width() - 1); + } else if (pt.x() > 0 && left != -1 && grid->itemAt(left)->spacerItem()) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x() - 1, info.height(), info.width() + 1); + } + } + break; + + case WidgetHandle::Right: { + if (pt.x() > 0 && info.width() > 1) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x(), info.height(), info.width() - 1); + } else if (pt.x() < 0 && right != -1 && grid->itemAt(right)->spacerItem()) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x(), info.height(), info.width() + 1); + } + } + break; + + case WidgetHandle::Bottom: { + if (pt.y() > 0 && info.height() > 1) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x(), info.height() - 1, info.width()); + } else if (pt.y() < 0 && bottom != -1 && grid->itemAt(bottom)->spacerItem()) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x(), info.height() + 1, info.width()); + } + } + break; + } + + if (cmd != nullptr) { + m_formWindow->commandHistory()->push(cmd); + } else { + grid->invalidate(); + grid->activate(); + m_formWindow->clearSelection(false); + m_formWindow->selectWidget(m_widget); + } +} + +void WidgetHandle::trySetGeometry(QWidget *w, int x, int y, int width, int height) +{ + if (!m_formWindow->hasFeature(FormWindow::EditFeature)) + return; + + int minw = w->minimumSize().width(); + minw = qMax(minw, 2 * m_formWindow->grid().x()); + + int minh = w->minimumSize().height(); + minh = qMax(minh, 2 * m_formWindow->grid().y()); + + if (qMax(minw, width) > w->maximumWidth() || + qMax(minh, height) > w->maximumHeight()) + return; + + if (width < minw && x != w->x()) + x -= minw - width; + + if (height < minh && y != w->y()) + y -= minh - height; + + w->setGeometry(x, y, qMax(minw, width), qMax(minh, height)); +} + +void WidgetHandle::tryResize(QWidget *w, int width, int height) +{ + int minw = w->minimumSize().width(); + minw = qMax(minw, 16); + + int minh = w->minimumSize().height(); + minh = qMax(minh, 16); + + w->resize(qMax(minw, width), qMax(minh, height)); +} + +// ------------------ WidgetSelection + +WidgetSelection::WidgetState WidgetSelection::widgetState(const QDesignerFormEditorInterface *core, QWidget *w) +{ + bool isManaged; + const LayoutInfo::Type lt = LayoutInfo::laidoutWidgetType(core, w, &isManaged); + if (lt == LayoutInfo::NoLayout) + return UnlaidOut; + if (!isManaged) + return LaidOut; + switch (lt) { + case LayoutInfo::Grid: + return ManagedGridLayout; + case LayoutInfo::Form: + return ManagedFormLayout; + default: + break; + } + return LaidOut; +} + +WidgetSelection::WidgetSelection(FormWindow *parent) : + m_widget(nullptr), + m_formWindow(parent) +{ + for (int i = WidgetHandle::LeftTop; i < WidgetHandle::TypeCount; ++i) + m_handles[i] = new WidgetHandle(m_formWindow, static_cast(i), this); + hide(); +} + +void WidgetSelection::setWidget(QWidget *w) +{ + if (m_widget != nullptr) + m_widget->removeEventFilter(this); + + if (w == nullptr) { + hide(); + m_widget = nullptr; + return; + } + + m_widget = w; + + m_widget->installEventFilter(this); + + updateActive(); + + updateGeometry(); + show(); +} + +void WidgetSelection::updateActive() +{ + const WidgetState ws = widgetState(m_formWindow->core(), m_widget); + bool active[WidgetHandle::TypeCount]; + std::fill(active, active + WidgetHandle::TypeCount, false); + // Determine active handles + switch (ws) { + case UnlaidOut: + std::fill(active, active + WidgetHandle::TypeCount, true); + break; + case ManagedGridLayout: // Grid: Allow changing span + active[WidgetHandle::Left] = active[WidgetHandle::Top] = active[WidgetHandle::Right] = active[WidgetHandle::Bottom] = true; + break; + case ManagedFormLayout: // Form: Allow changing column span + if (const unsigned operation = ChangeFormLayoutItemRoleCommand::possibleOperations(m_formWindow->core(), m_widget)) { + active[WidgetHandle::Left] = operation & (ChangeFormLayoutItemRoleCommand::SpanningToField|ChangeFormLayoutItemRoleCommand::FieldToSpanning); + active[WidgetHandle::Right] = operation & (ChangeFormLayoutItemRoleCommand::SpanningToLabel|ChangeFormLayoutItemRoleCommand::LabelToSpanning); + } + break; + default: + break; + } + + for (int i = WidgetHandle::LeftTop; i < WidgetHandle::TypeCount; ++i) + if (WidgetHandle *h = m_handles[i]) { + h->setWidget(m_widget); + h->setActive(active[i]); + } +} + +bool WidgetSelection::isUsed() const +{ + return m_widget != nullptr; +} + +void WidgetSelection::updateGeometry() +{ + if (!m_widget || !m_widget->parentWidget()) + return; + + QPoint p = m_widget->parentWidget()->mapToGlobal(m_widget->pos()); + p = m_formWindow->formContainer()->mapFromGlobal(p); + const QRect r(p, m_widget->size()); + + const int w = 6; + const int h = 6; + + for (int i = WidgetHandle::LeftTop; i < WidgetHandle::TypeCount; ++i) { + WidgetHandle *hndl = m_handles[ i ]; + if (!hndl) + continue; + switch (i) { + case WidgetHandle::LeftTop: + hndl->move(r.x() - w / 2, r.y() - h / 2); + break; + case WidgetHandle::Top: + hndl->move(r.x() + r.width() / 2 - w / 2, r.y() - h / 2); + break; + case WidgetHandle::RightTop: + hndl->move(r.x() + r.width() - w / 2, r.y() - h / 2); + break; + case WidgetHandle::Right: + hndl->move(r.x() + r.width() - w / 2, r.y() + r.height() / 2 - h / 2); + break; + case WidgetHandle::RightBottom: + hndl->move(r.x() + r.width() - w / 2, r.y() + r.height() - h / 2); + break; + case WidgetHandle::Bottom: + hndl->move(r.x() + r.width() / 2 - w / 2, r.y() + r.height() - h / 2); + break; + case WidgetHandle::LeftBottom: + hndl->move(r.x() - w / 2, r.y() + r.height() - h / 2); + break; + case WidgetHandle::Left: + hndl->move(r.x() - w / 2, r.y() + r.height() / 2 - h / 2); + break; + default: + break; + } + } +} + +void WidgetSelection::hide() +{ + for (WidgetHandle *h : m_handles) { + if (h) + h->hide(); + } +} + +void WidgetSelection::show() +{ + for (WidgetHandle *h : m_handles) { + if (h) { + h->show(); + h->raise(); + } + } +} + +void WidgetSelection::update() +{ + for (WidgetHandle *h : m_handles) { + if (h) + h->update(); + } +} + +QWidget *WidgetSelection::widget() const +{ + return m_widget; +} + +QDesignerFormEditorInterface *WidgetSelection::core() const +{ + if (m_formWindow) + return m_formWindow->core(); + + return nullptr; +} + +bool WidgetSelection::eventFilter(QObject *object, QEvent *event) +{ + if (object != widget()) + return false; + + switch (event->type()) { + default: break; + + case QEvent::Move: + case QEvent::Resize: + updateGeometry(); + break; + case QEvent::ZOrderChange: + show(); + break; + } // end switch + + return false; +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/formeditor/widgetselection.h b/src/designer/src/components/formeditor/widgetselection.h new file mode 100644 index 0000000..b5a6ae2 --- /dev/null +++ b/src/designer/src/components/formeditor/widgetselection.h @@ -0,0 +1,107 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETSELECTION_H +#define WIDGETSELECTION_H + +#include "formeditor_global.h" +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QMouseEvent; +class QPaintEvent; + +namespace qdesigner_internal { + +class FormWindow; +class WidgetSelection; + +class QT_FORMEDITOR_EXPORT WidgetHandle: public InvisibleWidget +{ + Q_OBJECT +public: + enum Type + { + LeftTop, + Top, + RightTop, + Right, + RightBottom, + Bottom, + LeftBottom, + Left, + + TypeCount + }; + + WidgetHandle(FormWindow *parent, Type t, WidgetSelection *s); + void setWidget(QWidget *w); + void setActive(bool a); + void updateCursor(); + + void setEnabled(bool) {} + + QDesignerFormEditorInterface *core() const; + +protected: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + +private: + void changeGridLayoutItemSpan(); + void changeFormLayoutItemSpan(); + void trySetGeometry(QWidget *w, int x, int y, int width, int height); + void tryResize(QWidget *w, int width, int height); + +private: + QWidget *m_widget; + const Type m_type; + QPoint m_origPressPos; + FormWindow *m_formWindow; + WidgetSelection *m_sel; + QRect m_geom, m_origGeom; + bool m_active; +}; + +class QT_FORMEDITOR_EXPORT WidgetSelection: public QObject +{ + Q_OBJECT +public: + WidgetSelection(FormWindow *parent); + + void setWidget(QWidget *w); + bool isUsed() const; + + void updateActive(); + void updateGeometry(); + void hide(); + void show(); + void update(); + + QWidget *widget() const; + + QDesignerFormEditorInterface *core() const; + + bool eventFilter(QObject *object, QEvent *event) override; + + enum WidgetState { UnlaidOut, LaidOut, ManagedGridLayout, ManagedFormLayout }; + static WidgetState widgetState(const QDesignerFormEditorInterface *core, QWidget *w); + +private: + WidgetHandle *m_handles[WidgetHandle::TypeCount]; + QPointer m_widget; + FormWindow *m_formWindow; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETSELECTION_H diff --git a/src/designer/src/components/lib/CMakeLists.txt b/src/designer/src/components/lib/CMakeLists.txt new file mode 100644 index 0000000..ecff291 --- /dev/null +++ b/src/designer/src/components/lib/CMakeLists.txt @@ -0,0 +1,439 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## DesignerComponentsPrivate Module: +##################################################################### + +qt_internal_add_module(DesignerComponentsPrivate + INTERNAL_MODULE + SOURCES + lib_pch.h + ../buddyeditor/buddyeditor.cpp ../buddyeditor/buddyeditor.h + ../buddyeditor/buddyeditor_global.h + ../buddyeditor/buddyeditor_plugin.cpp ../buddyeditor/buddyeditor_plugin.h + ../buddyeditor/buddyeditor_tool.cpp ../buddyeditor/buddyeditor_tool.h + ../formeditor/default_actionprovider.cpp ../formeditor/default_actionprovider.h + ../formeditor/default_container.cpp ../formeditor/default_container.h + ../formeditor/default_layoutdecoration.cpp ../formeditor/default_layoutdecoration.h + ../formeditor/deviceprofiledialog.cpp ../formeditor/deviceprofiledialog.h + ../formeditor/dpi_chooser.cpp ../formeditor/dpi_chooser.h + ../formeditor/embeddedoptionspage.cpp ../formeditor/embeddedoptionspage.h + ../formeditor/formeditor.cpp ../formeditor/formeditor.h + ../formeditor/formeditor_global.h + ../formeditor/formeditor_optionspage.cpp ../formeditor/formeditor_optionspage.h + ../formeditor/formwindow.cpp ../formeditor/formwindow.h + ../formeditor/formwindow_dnditem.cpp ../formeditor/formwindow_dnditem.h + ../formeditor/formwindow_widgetstack.cpp ../formeditor/formwindow_widgetstack.h + ../formeditor/formwindowcursor.cpp ../formeditor/formwindowcursor.h + ../formeditor/formwindowmanager.cpp ../formeditor/formwindowmanager.h + ../formeditor/formwindowsettings.cpp ../formeditor/formwindowsettings.h + ../formeditor/itemview_propertysheet.cpp ../formeditor/itemview_propertysheet.h + ../formeditor/layout_propertysheet.cpp ../formeditor/layout_propertysheet.h + ../formeditor/line_propertysheet.cpp ../formeditor/line_propertysheet.h + ../formeditor/previewactiongroup.cpp ../formeditor/previewactiongroup.h + ../formeditor/qdesigner_resource.cpp ../formeditor/qdesigner_resource.h + ../formeditor/qlayoutwidget_propertysheet.cpp ../formeditor/qlayoutwidget_propertysheet.h + ../formeditor/qmainwindow_container.cpp ../formeditor/qmainwindow_container.h + ../formeditor/qmdiarea_container.cpp ../formeditor/qmdiarea_container.h + ../formeditor/qwizard_container.cpp ../formeditor/qwizard_container.h + ../formeditor/spacer_propertysheet.cpp ../formeditor/spacer_propertysheet.h + ../formeditor/templateoptionspage.cpp ../formeditor/templateoptionspage.h + ../formeditor/tool_widgeteditor.cpp ../formeditor/tool_widgeteditor.h + ../formeditor/widgetselection.cpp ../formeditor/widgetselection.h + ../objectinspector/objectinspector.cpp ../objectinspector/objectinspector.h + ../objectinspector/objectinspector_global.h + ../objectinspector/objectinspectormodel.cpp ../objectinspector/objectinspectormodel_p.h + ../propertyeditor/brushpropertymanager.cpp ../propertyeditor/brushpropertymanager.h + ../propertyeditor/designerpropertymanager.cpp ../propertyeditor/designerpropertymanager.h + ../propertyeditor/fontpropertymanager.cpp ../propertyeditor/fontpropertymanager.h + ../propertyeditor/newdynamicpropertydialog.cpp ../propertyeditor/newdynamicpropertydialog.h + ../propertyeditor/paletteeditor.cpp ../propertyeditor/paletteeditor.h + ../propertyeditor/paletteeditorbutton.cpp ../propertyeditor/paletteeditorbutton.h + ../propertyeditor/pixmapeditor.cpp ../propertyeditor/pixmapeditor.h + ../propertyeditor/previewframe.cpp ../propertyeditor/previewframe.h + ../propertyeditor/previewwidget.cpp ../propertyeditor/previewwidget.h + ../propertyeditor/propertyeditor.cpp ../propertyeditor/propertyeditor.h + ../propertyeditor/propertyeditor_global.h + ../propertyeditor/qlonglongvalidator.cpp ../propertyeditor/qlonglongvalidator.h + ../propertyeditor/resetdecorator.cpp ../propertyeditor/resetdecorator.h + ../propertyeditor/texteditor.cpp ../propertyeditor/texteditor.h + ../propertyeditor/stringlisteditor.cpp ../propertyeditor/stringlisteditor.h + ../propertyeditor/stringlisteditorbutton.cpp ../propertyeditor/stringlisteditorbutton.h + ../signalsloteditor/connectdialog.cpp ../signalsloteditor/connectdialog_p.h + ../signalsloteditor/signalslot_utils.cpp ../signalsloteditor/signalslot_utils_p.h + ../signalsloteditor/signalsloteditor.cpp ../signalsloteditor/signalsloteditor.h ../signalsloteditor/signalsloteditor_p.h + ../signalsloteditor/signalsloteditor_global.h + ../signalsloteditor/signalsloteditor_plugin.cpp ../signalsloteditor/signalsloteditor_plugin.h + ../signalsloteditor/signalsloteditor_tool.cpp ../signalsloteditor/signalsloteditor_tool.h + ../signalsloteditor/signalsloteditorwindow.cpp ../signalsloteditor/signalsloteditorwindow.h + ../tabordereditor/tabordereditor.cpp ../tabordereditor/tabordereditor.h + ../tabordereditor/tabordereditor_global.h + ../tabordereditor/tabordereditor_plugin.cpp ../tabordereditor/tabordereditor_plugin.h + ../tabordereditor/tabordereditor_tool.cpp ../tabordereditor/tabordereditor_tool.h + ../taskmenu/button_taskmenu.cpp ../taskmenu/button_taskmenu.h + ../taskmenu/combobox_taskmenu.cpp ../taskmenu/combobox_taskmenu.h + ../taskmenu/containerwidget_taskmenu.cpp ../taskmenu/containerwidget_taskmenu.h + ../taskmenu/groupbox_taskmenu.cpp ../taskmenu/groupbox_taskmenu.h + ../taskmenu/inplace_editor.cpp ../taskmenu/inplace_editor.h + ../taskmenu/inplace_widget_helper.cpp ../taskmenu/inplace_widget_helper.h + ../taskmenu/itemlisteditor.cpp ../taskmenu/itemlisteditor.h + ../taskmenu/label_taskmenu.cpp ../taskmenu/label_taskmenu.h + ../taskmenu/layouttaskmenu.cpp ../taskmenu/layouttaskmenu.h + ../taskmenu/lineedit_taskmenu.cpp ../taskmenu/lineedit_taskmenu.h + ../taskmenu/listwidget_taskmenu.cpp ../taskmenu/listwidget_taskmenu.h + ../taskmenu/listwidgeteditor.cpp ../taskmenu/listwidgeteditor.h + ../taskmenu/menutaskmenu.cpp ../taskmenu/menutaskmenu.h + ../taskmenu/tablewidget_taskmenu.cpp ../taskmenu/tablewidget_taskmenu.h + ../taskmenu/tablewidgeteditor.cpp ../taskmenu/tablewidgeteditor.h + ../taskmenu/taskmenu_component.cpp ../taskmenu/taskmenu_component.h + ../taskmenu/textedit_taskmenu.cpp ../taskmenu/textedit_taskmenu.h + ../taskmenu/toolbar_taskmenu.cpp ../taskmenu/toolbar_taskmenu.h + ../taskmenu/treewidget_taskmenu.cpp ../taskmenu/treewidget_taskmenu.h + ../taskmenu/treewidgeteditor.cpp ../taskmenu/treewidgeteditor.h + ../widgetbox/widgetbox.cpp ../widgetbox/widgetbox.h + ../widgetbox/widgetbox_dnditem.cpp ../widgetbox/widgetbox_dnditem.h + ../widgetbox/widgetbox_global.h + ../widgetbox/widgetboxcategorylistview.cpp ../widgetbox/widgetboxcategorylistview.h + ../widgetbox/widgetboxtreewidget.cpp ../widgetbox/widgetboxtreewidget.h + qdesigner_components.cpp + NO_UNITY_BUILD_SOURCES + ../tabordereditor/tabordereditor.cpp # redefinition of 'QMetaTypeId>' (from qdesigner_resource.cpp) + ../formeditor/formwindow.cpp # explicit specialization of 'QMetaTypeId' after instantiation + DEFINES + QDESIGNER_COMPONENTS_LIBRARY + QT_STATICPLUGIN + INCLUDE_DIRECTORIES + . + .. + ../../../../shared/tools/shared/qtpropertybrowser + ../../lib/components + ../../lib/extension + ../../lib/sdk + ../../lib/shared + ../buddyeditor + ../formeditor + ../objectinspector + ../propertyeditor ../propertyeditor + ../signalsloteditor + ../tabordereditor + ../taskmenu + ../widgetbox + LIBRARIES + Qt::Xml + PUBLIC_LIBRARIES + Qt::Core + Qt::DesignerPrivate + Qt::GuiPrivate + Qt::WidgetsPrivate + Qt::Xml + ENABLE_AUTOGEN_TOOLS + uic + PRECOMPILED_HEADER + "lib_pch.h" + NO_GENERATE_CPP_EXPORTS +) + +set(ui_sources + ../formeditor/deviceprofiledialog.ui + ../formeditor/formwindowsettings.ui + ../formeditor/templateoptionspage.ui + ../propertyeditor/newdynamicpropertydialog.ui + ../propertyeditor/paletteeditor.ui + ../propertyeditor/previewwidget.ui + ../propertyeditor/stringlisteditor.ui + ../signalsloteditor/connectdialog.ui + ../taskmenu/itemlisteditor.ui + ../taskmenu/tablewidgeteditor.ui + ../taskmenu/treewidgeteditor.ui +) + +# Work around QTBUG-95305 +if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" AND CMAKE_CROSS_CONFIGS) + qt6_wrap_ui(ui_sources_processed ${ui_sources}) +else() + set(ui_sources_processed ${ui_sources}) +endif() +target_sources(DesignerComponentsPrivate PRIVATE ${ui_sources_processed}) + +# Resources: +set(propertyeditor_resource_files + "../propertyeditor/fontmapping.xml" +) + +qt_internal_add_resource(DesignerComponentsPrivate "propertyeditor" + PREFIX + "/qt-project.org/propertyeditor" + BASE + "../propertyeditor" + FILES + ${propertyeditor_resource_files} +) +set(formeditor_resource_files + "../formeditor/images/color.png" + "../formeditor/images/configure.png" + "../formeditor/images/downplus.png" + "../formeditor/images/dropdownbutton.png" + "../formeditor/images/edit.png" + "../formeditor/images/editdelete-16.png" + "../formeditor/images/emptyicon.png" + "../formeditor/images/filenew-16.png" + "../formeditor/images/fileopen-16.png" + "../formeditor/images/leveldown.png" + "../formeditor/images/levelup.png" + "../formeditor/images/mac/adjustsize.png" + "../formeditor/images/mac/back.png" + "../formeditor/images/mac/buddytool.png" + "../formeditor/images/mac/down.png" + "../formeditor/images/mac/editbreaklayout.png" + "../formeditor/images/mac/editcopy.png" + "../formeditor/images/mac/editcut.png" + "../formeditor/images/mac/editdelete.png" + "../formeditor/images/mac/editform.png" + "../formeditor/images/mac/editgrid.png" + "../formeditor/images/mac/edithlayout.png" + "../formeditor/images/mac/edithlayoutsplit.png" + "../formeditor/images/mac/editlower.png" + "../formeditor/images/mac/editpaste.png" + "../formeditor/images/mac/editraise.png" + "../formeditor/images/mac/editvlayout.png" + "../formeditor/images/mac/editvlayoutsplit.png" + "../formeditor/images/mac/filenew.png" + "../formeditor/images/mac/fileopen.png" + "../formeditor/images/mac/filesave.png" + "../formeditor/images/mac/forward.png" + "../formeditor/images/mac/insertimage.png" + "../formeditor/images/mac/minus.png" + "../formeditor/images/mac/plus.png" + "../formeditor/images/mac/redo.png" + "../formeditor/images/mac/signalslottool.png" + "../formeditor/images/mac/simplifyrichtext.png" + "../formeditor/images/mac/tabordertool.png" + "../formeditor/images/mac/textanchor.png" + "../formeditor/images/mac/textbold.png" + "../formeditor/images/mac/textcenter.png" + "../formeditor/images/mac/textitalic.png" + "../formeditor/images/mac/textjustify.png" + "../formeditor/images/mac/textleft.png" + "../formeditor/images/mac/textright.png" + "../formeditor/images/mac/textsubscript.png" + "../formeditor/images/mac/textsuperscript.png" + "../formeditor/images/mac/textunder.png" + "../formeditor/images/mac/undo.png" + "../formeditor/images/mac/up.png" + "../formeditor/images/mac/widgettool.png" + "../formeditor/images/minus-16.png" + "../formeditor/images/prefix-add.png" + "../formeditor/images/qtlogo128x128.png" + "../formeditor/images/qtlogo16x16.png" + "../formeditor/images/qtlogo24x24.png" + "../formeditor/images/qtlogo32x32.png" + "../formeditor/images/qtlogo64x64.png" + "../formeditor/images/reload.png" + "../formeditor/images/resetproperty.png" + "../formeditor/images/righttoleft.png" + "../formeditor/images/sort.png" + "../formeditor/images/submenu.png" + "../formeditor/images/widgets/calendarwidget.png" + "../formeditor/images/widgets/checkbox.png" + "../formeditor/images/widgets/columnview.png" + "../formeditor/images/widgets/combobox.png" + "../formeditor/images/widgets/commandlinkbutton.png" + "../formeditor/images/widgets/dateedit.png" + "../formeditor/images/widgets/datetimeedit.png" + "../formeditor/images/widgets/dial.png" + "../formeditor/images/widgets/dialogbuttonbox.png" + "../formeditor/images/widgets/dockwidget.png" + "../formeditor/images/widgets/doublespinbox.png" + "../formeditor/images/widgets/fontcombobox.png" + "../formeditor/images/widgets/frame.png" + "../formeditor/images/widgets/graphicsview.png" + "../formeditor/images/widgets/groupbox.png" + "../formeditor/images/widgets/hscrollbar.png" + "../formeditor/images/widgets/hslider.png" + "../formeditor/images/widgets/label.png" + "../formeditor/images/widgets/lcdnumber.png" + "../formeditor/images/widgets/line.png" + "../formeditor/images/widgets/lineedit.png" + "../formeditor/images/widgets/listbox.png" + "../formeditor/images/widgets/listview.png" + "../formeditor/images/widgets/mdiarea.png" + "../formeditor/images/widgets/plaintextedit.png" + "../formeditor/images/widgets/progress.png" + "../formeditor/images/widgets/pushbutton.png" + "../formeditor/images/widgets/radiobutton.png" + "../formeditor/images/widgets/scrollarea.png" + "../formeditor/images/widgets/spacer.png" + "../formeditor/images/widgets/spinbox.png" + "../formeditor/images/widgets/table.png" + "../formeditor/images/widgets/tabwidget.png" + "../formeditor/images/widgets/textedit.png" + "../formeditor/images/widgets/timeedit.png" + "../formeditor/images/widgets/toolbox.png" + "../formeditor/images/widgets/toolbutton.png" + "../formeditor/images/widgets/vline.png" + "../formeditor/images/widgets/vscrollbar.png" + "../formeditor/images/widgets/vslider.png" + "../formeditor/images/widgets/vspacer.png" + "../formeditor/images/widgets/widget.png" + "../formeditor/images/widgets/widgetstack.png" + "../formeditor/images/win/adjustsize.png" + "../formeditor/images/win/back.png" + "../formeditor/images/win/buddytool.png" + "../formeditor/images/win/down.png" + "../formeditor/images/win/editbreaklayout.png" + "../formeditor/images/win/editcopy.png" + "../formeditor/images/win/editcut.png" + "../formeditor/images/win/editdelete.png" + "../formeditor/images/win/editform.png" + "../formeditor/images/win/editgrid.png" + "../formeditor/images/win/edithlayout.png" + "../formeditor/images/win/edithlayoutsplit.png" + "../formeditor/images/win/editlower.png" + "../formeditor/images/win/editpaste.png" + "../formeditor/images/win/editraise.png" + "../formeditor/images/win/editvlayout.png" + "../formeditor/images/win/editvlayoutsplit.png" + "../formeditor/images/win/filenew.png" + "../formeditor/images/win/fileopen.png" + "../formeditor/images/win/filesave.png" + "../formeditor/images/win/forward.png" + "../formeditor/images/win/insertimage.png" + "../formeditor/images/win/minus.png" + "../formeditor/images/win/plus.png" + "../formeditor/images/win/redo.png" + "../formeditor/images/win/signalslottool.png" + "../formeditor/images/win/simplifyrichtext.png" + "../formeditor/images/win/tabordertool.png" + "../formeditor/images/win/textanchor.png" + "../formeditor/images/win/textbold.png" + "../formeditor/images/win/textcenter.png" + "../formeditor/images/win/textitalic.png" + "../formeditor/images/win/textjustify.png" + "../formeditor/images/win/textleft.png" + "../formeditor/images/win/textright.png" + "../formeditor/images/win/textsubscript.png" + "../formeditor/images/win/textsuperscript.png" + "../formeditor/images/win/textunder.png" + "../formeditor/images/win/undo.png" + "../formeditor/images/win/up.png" + "../formeditor/images/win/widgettool.png" +) + +qt_internal_add_resource(DesignerComponentsPrivate "formeditor" + PREFIX + "/qt-project.org/formeditor" + BASE + "../formeditor" + FILES + ${formeditor_resource_files} +) +set(formeditor1_resource_files + "../formeditor/defaultbrushes.xml" +) + +qt_internal_add_resource(DesignerComponentsPrivate "formeditor1" + PREFIX + "/qt-project.org/brushes" + BASE + "../formeditor" + FILES + ${formeditor1_resource_files} +) +set(widgetbox_resource_files + "../widgetbox/widgetbox.xml" +) + +qt_internal_add_resource(DesignerComponentsPrivate "widgetbox" + PREFIX + "/qt-project.org/widgetbox" + BASE + "../widgetbox" + FILES + ${widgetbox_resource_files} +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(DesignerComponentsPrivate CONDITION NOT QT_BUILD_SHARED_LIBS + DEFINES + QT_DESIGNER_STATIC + INCLUDE_DIRECTORIES + ../../../../shared/findwidget + ../../../../shared/qtgradienteditor + ../../../../shared/qtpropertybrowser +) + +qt_internal_extend_target(DesignerComponentsPrivate CONDITION QT_BUILD_SHARED_LIBS + SOURCES + ../../../../shared/findwidget/abstractfindwidget.cpp ../../../../shared/findwidget/abstractfindwidget_p.h + ../../../../shared/findwidget/itemviewfindwidget.cpp ../../../../shared/findwidget/itemviewfindwidget_p.h + ../../../../shared/findwidget/texteditfindwidget.cpp ../../../../shared/findwidget/texteditfindwidget_p.h + ../../../../shared/qtgradienteditor/qtcolorbutton.cpp ../../../../shared/qtgradienteditor/qtcolorbutton_p.h + ../../../../shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp ../../../../shared/qtpropertybrowser/qtbuttonpropertybrowser_p.h + ../../../../shared/qtpropertybrowser/qteditorfactory.cpp ../../../../shared/qtpropertybrowser/qteditorfactory_p.h + ../../../../shared/qtpropertybrowser/qtgroupboxpropertybrowser.cpp ../../../../shared/qtpropertybrowser/qtgroupboxpropertybrowser_p.h + ../../../../shared/qtpropertybrowser/qtpropertybrowser.cpp ../../../../shared/qtpropertybrowser/qtpropertybrowser_p.h + ../../../../shared/qtpropertybrowser/qtpropertybrowserutils.cpp ../../../../shared/qtpropertybrowser/qtpropertybrowserutils_p.h + ../../../../shared/qtpropertybrowser/qtpropertymanager.cpp ../../../../shared/qtpropertybrowser/qtpropertymanager_p.h + ../../../../shared/qtpropertybrowser/qttreepropertybrowser.cpp ../../../../shared/qtpropertybrowser/qttreepropertybrowser_p.h + ../../../../shared/qtpropertybrowser/qtvariantproperty.cpp ../../../../shared/qtpropertybrowser/qtvariantproperty_p.h + INCLUDE_DIRECTORIES + ../../../../shared/findwidget + ../../../../shared/qtgradienteditor + ../../../../shared/qtpropertybrowser +) + +if(QT_BUILD_SHARED_LIBS) + # Resources: + set(findwidget_resource_files + "../../../../shared/findwidget/images/mac/closetab.png" + "../../../../shared/findwidget/images/mac/next.png" + "../../../../shared/findwidget/images/mac/previous.png" + "../../../../shared/findwidget/images/mac/searchfind.png" + "../../../../shared/findwidget/images/win/closetab.png" + "../../../../shared/findwidget/images/win/next.png" + "../../../../shared/findwidget/images/win/previous.png" + "../../../../shared/findwidget/images/win/searchfind.png" + "../../../../shared/findwidget/images/wrap.png" + ) + + qt_internal_add_resource(DesignerComponentsPrivate "findwidget" + PREFIX + "/qt-project.org/shared" + BASE + "../../../../shared/findwidget" + FILES + ${findwidget_resource_files} + ) + set(qtpropertybrowser_resource_files + "../../../../shared/qtpropertybrowser/images/cursor-arrow.png" + "../../../../shared/qtpropertybrowser/images/cursor-busy.png" + "../../../../shared/qtpropertybrowser/images/cursor-closedhand.png" + "../../../../shared/qtpropertybrowser/images/cursor-cross.png" + "../../../../shared/qtpropertybrowser/images/cursor-forbidden.png" + "../../../../shared/qtpropertybrowser/images/cursor-hand.png" + "../../../../shared/qtpropertybrowser/images/cursor-hsplit.png" + "../../../../shared/qtpropertybrowser/images/cursor-ibeam.png" + "../../../../shared/qtpropertybrowser/images/cursor-openhand.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizeall.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizeb.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizef.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizeh.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizev.png" + "../../../../shared/qtpropertybrowser/images/cursor-uparrow.png" + "../../../../shared/qtpropertybrowser/images/cursor-vsplit.png" + "../../../../shared/qtpropertybrowser/images/cursor-wait.png" + "../../../../shared/qtpropertybrowser/images/cursor-whatsthis.png" + ) + + qt_internal_add_resource(DesignerComponentsPrivate "qtpropertybrowser" + PREFIX + "/qt-project.org/qtpropertybrowser" + BASE + "../../../../shared/qtpropertybrowser" + FILES + ${qtpropertybrowser_resource_files} + ) +endif() diff --git a/src/designer/src/components/lib/lib_pch.h b/src/designer/src/components/lib/lib_pch.h new file mode 100644 index 0000000..6befeb7 --- /dev/null +++ b/src/designer/src/components/lib/lib_pch.h @@ -0,0 +1,7 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#if defined __cplusplus +#include +#include +#endif diff --git a/src/designer/src/components/lib/qdesigner_components.cpp b/src/designer/src/components/lib/qdesigner_components.cpp new file mode 100644 index 0000000..d19e5b2 --- /dev/null +++ b/src/designer/src/components/lib/qdesigner_components.cpp @@ -0,0 +1,235 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "qtresourceview_p.h" +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +Q_IMPORT_PLUGIN(SignalSlotEditorPlugin) +Q_IMPORT_PLUGIN(BuddyEditorPlugin) +Q_IMPORT_PLUGIN(TabOrderEditorPlugin) + +static void initResources() +{ + // Q_INIT_RESOURCE only usable in functions in global namespace + Q_INIT_RESOURCE(formeditor); + Q_INIT_RESOURCE(widgetbox); + Q_INIT_RESOURCE(propertyeditor); +} + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +/*! + \class QDesignerComponents + \brief The QDesignerComponents class provides a central resource for the various components + used in the \QD user interface. + \inmodule QtDesigner + \internal + + The QDesignerComponents class is a factory for each of the standard components present + in the \QD user interface. It is mostly useful for developers who want to implement + a standalone form editing environment using \QD's components, or who need to integrate + \QD's components into an existing integrated development environment (IDE). + + \sa QDesignerFormEditorInterface, QDesignerObjectInspectorInterface, + QDesignerPropertyEditorInterface, QDesignerWidgetBoxInterface +*/ + +/*! + Initializes the resources used by the components.*/ +void QDesignerComponents::initializeResources() +{ + initResources(); +} + +/*! + Initializes the plugins used by the components.*/ +void QDesignerComponents::initializePlugins(QDesignerFormEditorInterface *core) +{ + QDesignerIntegration::initializePlugins(core); +} + +// ### fixme Qt 7 createFormEditorWithPluginPaths->createFormEditor + +/*! + Constructs a form editor interface with the given \a parent.*/ +QDesignerFormEditorInterface *QDesignerComponents::createFormEditor(QObject *parent) +{ + return createFormEditorWithPluginPaths({}, parent); +} + +/*! + Constructs a form editor interface with the given \a pluginPaths and the \a parent. + \since 6.7 +*/ +QDesignerFormEditorInterface * + QDesignerComponents::createFormEditorWithPluginPaths(const QStringList &pluginPaths, + QObject *parent) +{ + return new qdesigner_internal::FormEditor(pluginPaths, parent); +} + +/*! + Returns a new task menu with the given \a parent for the \a core interface.*/ +QObject *QDesignerComponents::createTaskMenu(QDesignerFormEditorInterface *core, QObject *parent) +{ + return new qdesigner_internal::TaskMenuComponent(core, parent); +} + +static inline int qtMajorVersion(int qtVersion) { return qtVersion >> 16; } +static inline int qtMinorVersion(int qtVersion) { return (qtVersion >> 8) & 0xFF; } +static inline void setMinorVersion(int minorVersion, int *qtVersion) +{ + *qtVersion &= ~0xFF00; + *qtVersion |= minorVersion << 8; +} + +// Build the version-dependent name of the user widget box file, '$HOME.designer/widgetbox4.4.xml' +static inline QString widgetBoxFileName(int qtVersion, const QDesignerLanguageExtension *lang = nullptr) +{ + QString rc; { + QTextStream str(&rc); + str << QDir::homePath() << QDir::separator() << ".designer" << QDir::separator() + << "widgetbox"; + // The naming convention using the version was introduced with 4.4 + const int major = qtMajorVersion(qtVersion); + const int minor = qtMinorVersion(qtVersion); + if (major >= 4 && minor >= 4) + str << major << '.' << minor; + if (lang) + str << '.' << lang->uiExtension(); + str << ".xml"; + } + return rc; +} + +/*! + Returns a new widget box interface with the given \a parent for the \a core interface.*/ +QDesignerWidgetBoxInterface *QDesignerComponents::createWidgetBox(QDesignerFormEditorInterface *core, QWidget *parent) +{ + qdesigner_internal::WidgetBox *widgetBox = new qdesigner_internal::WidgetBox(core, parent); + + const QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core); + + do { + if (lang) { + const QString languageWidgetBox = lang->widgetBoxContents(); + if (!languageWidgetBox.isEmpty()) { + widgetBox->loadContents(lang->widgetBoxContents()); + break; + } + } + + widgetBox->setFileName(u":/qt-project.org/widgetbox/widgetbox.xml"_s); + widgetBox->load(); + } while (false); + + const QString userWidgetBoxFile = widgetBoxFileName(QT_VERSION, lang); + + widgetBox->setFileName(userWidgetBoxFile); + if (!QFileInfo::exists(userWidgetBoxFile)) { + // check previous version, that is, are we running the new version for the first time + // If so, try to copy the old widget box file + if (const int minv = qtMinorVersion(QT_VERSION)) { + int oldVersion = QT_VERSION; + setMinorVersion(minv - 1, &oldVersion); + const QString oldWidgetBoxFile = widgetBoxFileName(oldVersion, lang); + if (QFileInfo::exists(oldWidgetBoxFile)) + QFile::copy(oldWidgetBoxFile, userWidgetBoxFile); + } + } + widgetBox->load(); + + return widgetBox; +} + +/*! + Returns a new property editor interface with the given \a parent for the \a core interface.*/ +QDesignerPropertyEditorInterface *QDesignerComponents::createPropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::PropertyEditor(core, parent); +} + +/*! + Returns a new object inspector interface with the given \a parent for the \a core interface.*/ +QDesignerObjectInspectorInterface *QDesignerComponents::createObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::ObjectInspector(core, parent); +} + +/*! + Returns a new action editor interface with the given \a parent for the \a core interface.*/ +QDesignerActionEditorInterface *QDesignerComponents::createActionEditor(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::ActionEditor(core, parent); +} + +/*! + Returns a new resource editor with the given \a parent for the \a core interface.*/ +QWidget *QDesignerComponents::createResourceEditor(QDesignerFormEditorInterface *core, QWidget *parent) +{ + if (QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) { + QWidget *w = lang->createResourceBrowser(parent); + if (w) + return w; + } + QtResourceView *resourceView = new QtResourceView(core, parent); + resourceView->setResourceModel(core->resourceModel()); + resourceView->setSettingsKey(u"ResourceBrowser"_s); + // Note for integrators: make sure you call createResourceEditor() after you instantiated your subclass of designer integration + // (designer doesn't do that since by default editing resources is enabled) + const QDesignerIntegrationInterface *integration = core->integration(); + if (integration && !integration->hasFeature(QDesignerIntegrationInterface::ResourceEditorFeature)) + resourceView->setResourceEditingEnabled(false); + return resourceView; +} + +/*! + Returns a new signal-slot editor with the given \a parent for the \a core interface.*/ +QWidget *QDesignerComponents::createSignalSlotEditor(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::SignalSlotEditorWindow(core, parent); +} + +/*! + Returns the default plugin paths of Qt Widgets Designer's plugin manager. + + \return Plugin paths + \since 6.7 +*/ +QStringList QDesignerComponents::defaultPluginPaths() +{ + return QDesignerPluginManager::defaultPluginPaths(); +} + +QT_END_NAMESPACE + diff --git a/src/designer/src/components/objectinspector/objectinspector.cpp b/src/designer/src/components/objectinspector/objectinspector.cpp new file mode 100644 index 0000000..1809606 --- /dev/null +++ b/src/designer/src/components/objectinspector/objectinspector.cpp @@ -0,0 +1,824 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "objectinspector.h" +#include "objectinspectormodel_p.h" +#include "formwindow.h" + +// sdk +#include +#include +#include +#include +#include +#include +#include +#include + +// shared +#include +#include +#include +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + // Selections: Basically, ObjectInspector has to ensure a consistent + // selection, that is, either form-managed widgets (represented + // by the cursor interface selection), or unmanaged widgets/objects, + // for example actions, container pages, menu bars, tool bars + // and the like. The selection state of the latter is managed only in the object inspector. + // As soon as a managed widget is selected, unmanaged objects + // have to be unselected + // Normally, an empty selection is not allowed, the main container + // should be selected in this case (applyCursorSelection()). + // An exception is when clearSelection is called directly for example + // by the action editor that puts an unassociated action into the property + // editor. A hack exists to avoid the update in this case. + + enum SelectionType { + NoSelection, + // A QObject that has a meta database entry + QObjectSelection, + // Unmanaged widget, menu bar or the like + UnmanagedWidgetSelection, + // A widget managed by the form window cursor + ManagedWidgetSelection }; +} + +static inline SelectionType selectionType(const QDesignerFormWindowInterface *fw, QObject *o) +{ + if (!o->isWidgetType()) + return fw->core()->metaDataBase()->item(o) ? QObjectSelection : NoSelection; + return fw->isManaged(qobject_cast(o)) ? ManagedWidgetSelection : UnmanagedWidgetSelection; +} + +// Return an offset for dropping (when dropping widgets on the object +// inspector, we fake a position on the form based on the widget dropped on). +// Position the dropped widget with form grid offset to avoid overlapping unless we +// drop on a layout. Position doesn't matter in the layout case +// and this enables us to drop on a squeezed layout widget of size zero + +static inline QPoint dropPointOffset(const qdesigner_internal::FormWindowBase *fw, const QWidget *dropTarget) +{ + if (!dropTarget || dropTarget->layout()) + return QPoint(0, 0); + return QPoint(fw->designerGrid().deltaX(), fw->designerGrid().deltaY()); +} + +namespace qdesigner_internal { +// Delegate with object name validator for the object name column +class ObjectInspectorDelegate : public QStyledItemDelegate { +public: + using QStyledItemDelegate::QStyledItemDelegate; + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +QWidget *ObjectInspectorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & option, const QModelIndex &index) const +{ + if (index.column() != ObjectInspectorModel::ObjectNameColumn) + return QStyledItemDelegate::createEditor(parent, option, index); + // Object name editor + const bool isMainContainer = !index.parent().isValid(); + return new TextPropertyEditor(parent, TextPropertyEditor::EmbeddingTreeView, + isMainContainer ? ValidationObjectNameScope : ValidationObjectName); +} + +// ------------ ObjectInspectorTreeView: +// - Makes the Space key start editing +// - Suppresses a range selection by dragging or Shift-up/down, which does not really work due +// to the need to maintain a consistent selection. + +class ObjectInspectorTreeView : public QTreeView { +public: + using QTreeView::QTreeView; + +protected: + void mouseMoveEvent (QMouseEvent * event) override; + void keyPressEvent(QKeyEvent *event) override; + +}; + +void ObjectInspectorTreeView::mouseMoveEvent(QMouseEvent *event) +{ + event->ignore(); // suppress a range selection by dragging +} + +void ObjectInspectorTreeView::keyPressEvent(QKeyEvent *event) +{ + bool handled = false; + switch (event->key()) { + case Qt::Key_Up: + case Qt::Key_Down: // suppress shift-up/down range selection + if (event->modifiers() & Qt::ShiftModifier) { + event->ignore(); + handled = true; + } + break; + case Qt::Key_Space: { // Space pressed: Start editing + const QModelIndex index = currentIndex(); + if (index.isValid() && index.column() == 0 && !model()->hasChildren(index) && model()->flags(index) & Qt::ItemIsEditable) { + event->accept(); + handled = true; + edit(index); + } + } + break; + default: + break; + } + if (!handled) + QTreeView::keyPressEvent(event); +} + +// ------------ ObjectInspectorPrivate + +class ObjectInspector::ObjectInspectorPrivate { + Q_DISABLE_COPY_MOVE(ObjectInspectorPrivate) +public: + ObjectInspectorPrivate(QDesignerFormEditorInterface *core); + ~ObjectInspectorPrivate(); + + QLineEdit *filterLineEdit() const { return m_filterLineEdit; } + QTreeView *treeView() const { return m_treeView; } + QDesignerFormEditorInterface *core() const { return m_core; } + const QPointer &formWindow() const { return m_formWindow; } + + void clear(); + void setFormWindow(QDesignerFormWindowInterface *fwi); + + QWidget *managedWidgetAt(const QPoint &global_mouse_pos); + + void restoreDropHighlighting(); + void handleDragEnterMoveEvent(const QWidget *objectInspectorWidget, QDragMoveEvent * event, bool isDragEnter); + void dropEvent (QDropEvent * event); + + void clearSelection(); + bool selectObject(QObject *o); + void slotSelectionChanged(const QItemSelection & selected, const QItemSelection &deselected); + void getSelection(Selection &s) const; + + QModelIndexList indexesOf(QObject *o) const; + QObject *objectAt(const QModelIndex &index) const; + QObjectList indexesToObjects(const QModelIndexList &indexes) const; + + void slotHeaderDoubleClicked(int column) { m_treeView->resizeColumnToContents(column); } + void slotPopupContextMenu(QWidget *parent, const QPoint &pos); + +private: + void setFormWindowBlocked(QDesignerFormWindowInterface *fwi); + void applyCursorSelection(); + void synchronizeSelection(const QItemSelection & selected, const QItemSelection &deselected); + bool checkManagedWidgetSelection(const QModelIndexList &selection); + void showContainersCurrentPage(QWidget *widget); + + enum SelectionFlags { AddToSelection = 1, MakeCurrent = 2}; + void selectIndexRange(const QModelIndexList &indexes, unsigned flags); + + QDesignerFormEditorInterface *m_core; + QLineEdit *m_filterLineEdit; + QTreeView *m_treeView; + ObjectInspectorModel *m_model; + QSortFilterProxyModel *m_filterModel; + QPointer m_formWindow; + QPointer m_formFakeDropTarget; + bool m_withinClearSelection; +}; + +ObjectInspector::ObjectInspectorPrivate::ObjectInspectorPrivate(QDesignerFormEditorInterface *core) : + m_core(core), + m_filterLineEdit(new QLineEdit), + m_treeView(new ObjectInspectorTreeView), + m_model(new ObjectInspectorModel(m_treeView)), + m_filterModel(new QSortFilterProxyModel(m_treeView)), + m_withinClearSelection(false) +{ + m_filterModel->setRecursiveFilteringEnabled(true); + m_filterLineEdit->setPlaceholderText(ObjectInspector::tr("Filter")); + m_filterLineEdit->setClearButtonEnabled(true); + connect(m_filterLineEdit, &QLineEdit::textChanged, + m_filterModel, &QSortFilterProxyModel::setFilterFixedString); + // Filtering text collapses nodes, expand on clear. + connect(m_filterLineEdit, &QLineEdit::textChanged, + m_core, [this] (const QString &text) { + if (text.isEmpty()) + this->m_treeView->expandAll(); + }); + m_filterModel->setSourceModel(m_model); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_treeView->setModel(m_filterModel); + m_treeView->setSortingEnabled(true); + m_treeView->sortByColumn(0, Qt::AscendingOrder); + m_treeView->setItemDelegate(new ObjectInspectorDelegate); + m_treeView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_treeView->header()->setSectionResizeMode(1, QHeaderView::Stretch); + m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_treeView->setAlternatingRowColors(true); + m_treeView->setTextElideMode (Qt::ElideMiddle); + + m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); +} + +ObjectInspector::ObjectInspectorPrivate::~ObjectInspectorPrivate() +{ + delete m_treeView->itemDelegate(); +} + +void ObjectInspector::ObjectInspectorPrivate::clearSelection() +{ + m_withinClearSelection = true; + m_treeView->clearSelection(); + m_withinClearSelection = false; +} + +QWidget *ObjectInspector::ObjectInspectorPrivate::managedWidgetAt(const QPoint &global_mouse_pos) +{ + if (!m_formWindow) + return nullptr; + + const QPoint pos = m_treeView->viewport()->mapFromGlobal(global_mouse_pos); + QObject *o = objectAt(m_treeView->indexAt(pos)); + + if (!o || !o->isWidgetType()) + return nullptr; + + QWidget *rc = qobject_cast(o); + if (!m_formWindow->isManaged(rc)) + return nullptr; + return rc; +} + +void ObjectInspector::ObjectInspectorPrivate::showContainersCurrentPage(QWidget *widget) +{ + if (!widget) + return; + + FormWindow *fw = FormWindow::findFormWindow(widget); + if (!fw) + return; + + QWidget *w = widget->parentWidget(); + bool macroStarted = false; + // Find a multipage container (tab widgets, etc.) in the hierarchy and set the right page. + while (w != nullptr) { + if (fw->isManaged(w) && !qobject_cast(w)) { // Rule out unmanaged internal scroll areas, for example, on QToolBoxes. + if (QDesignerContainerExtension *c = qt_extension(m_core->extensionManager(), w)) { + const int count = c->count(); + if (count > 1 && !c->widget(c->currentIndex())->isAncestorOf(widget)) { + for (int i = 0; i < count; i++) + if (c->widget(i)->isAncestorOf(widget)) { + if (!macroStarted) { + macroStarted = true; + fw->beginCommand(tr("Change Current Page")); + } + ChangeCurrentPageCommand *cmd = new ChangeCurrentPageCommand(fw); + cmd->init(w, i); + fw->commandHistory()->push(cmd); + break; + } + } + } + } + w = w->parentWidget(); + } + if (macroStarted) + fw->endCommand(); +} + +void ObjectInspector::ObjectInspectorPrivate::restoreDropHighlighting() +{ + if (m_formFakeDropTarget) { + if (m_formWindow) { + m_formWindow->highlightWidget(m_formFakeDropTarget, QPoint(5, 5), FormWindow::Restore); + } + m_formFakeDropTarget = nullptr; + } +} + +void ObjectInspector::ObjectInspectorPrivate::handleDragEnterMoveEvent(const QWidget *objectInspectorWidget, QDragMoveEvent * event, bool isDragEnter) +{ + if (!m_formWindow) { + event->ignore(); + return; + } + + const QDesignerMimeData *mimeData = qobject_cast(event->mimeData()); + if (!mimeData) { + event->ignore(); + return; + } + + QWidget *dropTarget = nullptr; + QPoint fakeDropTargetOffset = QPoint(0, 0); + if (QWidget *managedWidget = managedWidgetAt(objectInspectorWidget->mapToGlobal(event->position().toPoint()))) { + fakeDropTargetOffset = dropPointOffset(m_formWindow, managedWidget); + // pretend we drag over the managed widget on the form + const QPoint fakeFormPos = m_formWindow->mapFromGlobal(managedWidget->mapToGlobal(fakeDropTargetOffset)); + const FormWindowBase::WidgetUnderMouseMode wum = mimeData->items().size() == 1 ? FormWindowBase::FindSingleSelectionDropTarget : FormWindowBase::FindMultiSelectionDropTarget; + dropTarget = m_formWindow->widgetUnderMouse(fakeFormPos, wum); + } + + if (m_formFakeDropTarget && dropTarget != m_formFakeDropTarget) + m_formWindow->highlightWidget(m_formFakeDropTarget, fakeDropTargetOffset, FormWindow::Restore); + + m_formFakeDropTarget = dropTarget; + if (m_formFakeDropTarget) + m_formWindow->highlightWidget(m_formFakeDropTarget, fakeDropTargetOffset, FormWindow::Highlight); + + // Do not refuse drag enter even if the area is not droppable + if (isDragEnter || m_formFakeDropTarget) + mimeData->acceptEvent(event); + else + event->ignore(); +} +void ObjectInspector::ObjectInspectorPrivate::dropEvent (QDropEvent * event) +{ + if (!m_formWindow || !m_formFakeDropTarget) { + event->ignore(); + return; + } + + const QDesignerMimeData *mimeData = qobject_cast(event->mimeData()); + if (!mimeData) { + event->ignore(); + return; + } + const QPoint fakeGlobalDropFormPos = m_formFakeDropTarget->mapToGlobal(dropPointOffset(m_formWindow , m_formFakeDropTarget)); + mimeData->moveDecoration(fakeGlobalDropFormPos + mimeData->hotSpot()); + if (!m_formWindow->dropWidgets(mimeData->items(), m_formFakeDropTarget, fakeGlobalDropFormPos)) { + event->ignore(); + return; + } + mimeData->acceptEvent(event); +} + +QModelIndexList ObjectInspector::ObjectInspectorPrivate::indexesOf(QObject *o) const +{ + QModelIndexList result; + const auto srcIndexes = m_model->indexesOf(o); + if (!srcIndexes.isEmpty()) { + result.reserve(srcIndexes.size()); + for (const auto &srcIndex : srcIndexes) + result.append(m_filterModel->mapFromSource(srcIndex)); + } + return result; +} + +QObject *ObjectInspector::ObjectInspectorPrivate::objectAt(const QModelIndex &index) const +{ + return m_model->objectAt(m_filterModel->mapToSource(index)); +} + +bool ObjectInspector::ObjectInspectorPrivate::selectObject(QObject *o) +{ + if (!m_core->metaDataBase()->item(o)) + return false; + + using ModelIndexSet = QSet; + + const QModelIndexList objectIndexes = indexesOf(o); + if (objectIndexes.isEmpty()) + return false; + + QItemSelectionModel *selectionModel = m_treeView->selectionModel(); + const auto currentSelectedItemList = selectionModel->selectedRows(0); + const ModelIndexSet currentSelectedItems(currentSelectedItemList.cbegin(), currentSelectedItemList.cend()); + + // Change in selection? + if (!currentSelectedItems.isEmpty() + && currentSelectedItems == ModelIndexSet(objectIndexes.cbegin(), objectIndexes.cend())) { + return true; + } + + // do select and update + selectIndexRange(objectIndexes, MakeCurrent); + return true; +} + +void ObjectInspector::ObjectInspectorPrivate::selectIndexRange(const QModelIndexList &indexes, unsigned flags) +{ + if (indexes.isEmpty()) + return; + + QItemSelectionModel::SelectionFlags selectFlags = QItemSelectionModel::Select|QItemSelectionModel::Rows; + if (!(flags & AddToSelection)) + selectFlags |= QItemSelectionModel::Clear; + if (flags & MakeCurrent) + selectFlags |= QItemSelectionModel::Current; + + QItemSelectionModel *selectionModel = m_treeView->selectionModel(); + for (const auto &mi : indexes) { + if (mi.column() == 0) { + selectionModel->select(mi, selectFlags); + selectFlags &= ~(QItemSelectionModel::Clear|QItemSelectionModel::Current); + } + } + if (flags & MakeCurrent) + m_treeView->scrollTo(indexes.constFirst(), QAbstractItemView::EnsureVisible); +} + +void ObjectInspector::ObjectInspectorPrivate::clear() +{ + m_formFakeDropTarget = nullptr; + m_formWindow = nullptr; +} + +// Form window cursor is in state 'main container only' +static inline bool mainContainerIsCurrent(const QDesignerFormWindowInterface *fw) +{ + const QDesignerFormWindowCursorInterface *cursor = fw->cursor(); + if (cursor->selectedWidgetCount() > 1) + return false; + const QWidget *current = cursor->current(); + return current == fw || current == fw->mainContainer(); +} + +void ObjectInspector::ObjectInspectorPrivate::setFormWindow(QDesignerFormWindowInterface *fwi) +{ + const bool blocked = m_treeView->selectionModel()->blockSignals(true); + { + UpdateBlocker ub(m_treeView); + setFormWindowBlocked(fwi); + } + + m_treeView->update(); + m_treeView->selectionModel()->blockSignals(blocked); +} + +void ObjectInspector::ObjectInspectorPrivate::setFormWindowBlocked(QDesignerFormWindowInterface *fwi) +{ + FormWindowBase *fw = qobject_cast(fwi); + const bool formWindowChanged = m_formWindow != fw; + + m_formWindow = fw; + + const int oldWidth = m_treeView->columnWidth(0); + const int xoffset = m_treeView->horizontalScrollBar()->value(); + const int yoffset = m_treeView->verticalScrollBar()->value(); + + if (formWindowChanged) + m_formFakeDropTarget = nullptr; + + switch (m_model->update(m_formWindow)) { + case ObjectInspectorModel::NoForm: + clear(); + return; + case ObjectInspectorModel::Rebuilt: // Complete rebuild: Just apply cursor selection + applyCursorSelection(); + m_treeView->expandAll(); + if (formWindowChanged) { + m_treeView->resizeColumnToContents(0); + } else { + m_treeView->setColumnWidth(0, oldWidth); + m_treeView->horizontalScrollBar()->setValue(xoffset); + m_treeView->verticalScrollBar()->setValue(yoffset); + } + break; + case ObjectInspectorModel::Updated: { + // Same structure (property changed or click on the form) + // We maintain a selection of unmanaged objects + // only if the cursor is in state "mainContainer() == current". + // and we have a non-managed selection. + // Else we take over the cursor selection. + bool applySelection = !mainContainerIsCurrent(m_formWindow); + if (!applySelection) { + const QModelIndexList currentIndexes = m_treeView->selectionModel()->selectedRows(0); + if (currentIndexes.isEmpty()) { + applySelection = true; + } else { + applySelection = selectionType(m_formWindow, objectAt(currentIndexes.constFirst())) == ManagedWidgetSelection; + } + } + if (applySelection) + applyCursorSelection(); + } + break; + } +} + +// Apply selection of form window cursor to object inspector, set current +void ObjectInspector::ObjectInspectorPrivate::applyCursorSelection() +{ + const QDesignerFormWindowCursorInterface *cursor = m_formWindow->cursor(); + const int count = cursor->selectedWidgetCount(); + if (!count) + return; + + // Set the current widget first which also clears the selection + QWidget *currentWidget = cursor->current(); + if (currentWidget) + selectIndexRange(indexesOf(currentWidget), MakeCurrent); + else + m_treeView->selectionModel()->clearSelection(); + + for (int i = 0;i < count; i++) { + QWidget *widget = cursor->selectedWidget(i); + if (widget != currentWidget) + selectIndexRange(indexesOf(widget), AddToSelection); + } +} + +// Synchronize managed widget in the form (select in cursor). Block updates +static int selectInCursor(FormWindowBase *fw, const QObjectList &objects, bool value) +{ + int rc = 0; + const bool blocked = fw->blockSelectionChanged(true); + for (auto *o : objects) { + if (selectionType(fw, o) == ManagedWidgetSelection) { + fw->selectWidget(static_cast(o), value); + rc++; + } + } + fw->blockSelectionChanged(blocked); + return rc; +} + +void ObjectInspector::ObjectInspectorPrivate::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + if (m_formWindow) { + synchronizeSelection(selected, deselected); + QMetaObject::invokeMethod(m_core->formWindowManager(), "slotUpdateActions"); + } +} + +// Convert indexes to object vectors taking into account that +// some index lists are multicolumn ranges +QObjectList ObjectInspector::ObjectInspectorPrivate::indexesToObjects(const QModelIndexList &indexes) const +{ + QObjectList rc; + if (indexes.isEmpty()) + return rc; + rc.reserve(indexes.size()); + for (const auto &mi : indexes) { + if (mi.column() == 0) + rc.append(objectAt(mi)); + } + return rc; +} + +// Check if any managed widgets are selected. If so, iterate over +// selection and deselect all unmanaged objects +bool ObjectInspector::ObjectInspectorPrivate::checkManagedWidgetSelection(const QModelIndexList &rowSelection) +{ + bool isManagedWidgetSelection = false; + QItemSelectionModel *selectionModel = m_treeView->selectionModel(); + for (const auto &mi : rowSelection) { + QObject *object = objectAt(mi); + if (selectionType(m_formWindow, object) == ManagedWidgetSelection) { + isManagedWidgetSelection = true; + break; + } + } + + if (!isManagedWidgetSelection) + return false; + // Need to unselect unmanaged ones + const bool blocked = selectionModel->blockSignals(true); + for (const auto &mi : rowSelection) { + QObject *object = objectAt(mi); + if (selectionType(m_formWindow, object) != ManagedWidgetSelection) + selectionModel->select(mi, QItemSelectionModel::Deselect|QItemSelectionModel::Rows); + } + selectionModel->blockSignals(blocked); + return true; +} + +void ObjectInspector::ObjectInspectorPrivate::synchronizeSelection(const QItemSelection & selectedSelection, const QItemSelection &deselectedSelection) +{ + // Synchronize form window cursor. + const QObjectList deselected = indexesToObjects(deselectedSelection.indexes()); + const QObjectList newlySelected = indexesToObjects(selectedSelection.indexes()); + + const QModelIndexList currentSelectedIndexes = m_treeView->selectionModel()->selectedRows(0); + + int deselectedManagedWidgetCount = 0; + if (!deselected.isEmpty()) + deselectedManagedWidgetCount = selectInCursor(m_formWindow, deselected, false); + + if (newlySelected.isEmpty()) { // Nothing selected + if (currentSelectedIndexes.isEmpty()) // Do not allow a null-selection, reset to main container + m_formWindow->clearSelection(!m_withinClearSelection); + return; + } + + const int selectManagedWidgetCount = selectInCursor(m_formWindow, newlySelected, true); + // Check consistency: Make sure either managed widgets or unmanaged objects are selected. + // No newly-selected managed widgets: Unless there are ones in the (old) current selection, + // select the unmanaged object + if (selectManagedWidgetCount == 0) { + if (checkManagedWidgetSelection(currentSelectedIndexes)) { + // Managed selection exists, refuse and update if necessary + if (deselectedManagedWidgetCount != 0 || selectManagedWidgetCount != 0) + m_formWindow->emitSelectionChanged(); + return; + } + // And now for the unmanaged selection + m_formWindow->clearSelection(false); + QObject *unmanagedObject = newlySelected.constFirst(); + m_core->propertyEditor()->setObject(unmanagedObject); + m_core->propertyEditor()->setEnabled(true); + // open container page if it is a single widget + if (newlySelected.size() == 1 && unmanagedObject->isWidgetType()) + showContainersCurrentPage(static_cast(unmanagedObject)); + return; + } + // Open container page if it is a single widget + if (newlySelected.size() == 1) { + QObject *object = newlySelected.constFirst(); + if (object->isWidgetType()) + showContainersCurrentPage(static_cast(object)); + } + + // A managed widget was newly selected. Make sure there are no unmanaged objects + // in the whole unless just single selection + if (currentSelectedIndexes.size() > selectManagedWidgetCount) + checkManagedWidgetSelection(currentSelectedIndexes); + // Update form + if (deselectedManagedWidgetCount != 0 || selectManagedWidgetCount != 0) + m_formWindow->emitSelectionChanged(); +} + + +void ObjectInspector::ObjectInspectorPrivate::getSelection(Selection &s) const +{ + s.clear(); + + if (!m_formWindow) + return; + + const QModelIndexList currentSelectedIndexes = m_treeView->selectionModel()->selectedRows(0); + if (currentSelectedIndexes.isEmpty()) + return; + + // sort objects + for (const QModelIndex &index : currentSelectedIndexes) { + if (QObject *object = objectAt(index)) { + switch (selectionType(m_formWindow, object)) { + case NoSelection: + break; + case QObjectSelection: + // It is actually possible to select an action twice if it is in a menu bar + // and in a tool bar. + if (!s.objects.contains(object)) + s.objects.push_back(object); + break; + case UnmanagedWidgetSelection: + s.unmanaged.push_back(qobject_cast(object)); + break; + case ManagedWidgetSelection: + s.managed.push_back(qobject_cast(object)); + break; + } + } + } +} + +// Utility to create a task menu +static inline QMenu *createTaskMenu(QObject *object, QDesignerFormWindowInterface *fw) +{ + // 1) Objects + if (!object->isWidgetType()) + return FormWindowBase::createExtensionTaskMenu(fw, object, false); + // 2) Unmanaged widgets + QWidget *w = static_cast(object); + if (!fw->isManaged(w)) + return FormWindowBase::createExtensionTaskMenu(fw, w, false); + // 3) Mananaged widgets + if (qdesigner_internal::FormWindowBase *fwb = qobject_cast(fw)) + return fwb->initializePopupMenu(w); + return nullptr; +} + +void ObjectInspector::ObjectInspectorPrivate::slotPopupContextMenu(QWidget * /*parent*/, const QPoint &pos) +{ + if (m_formWindow == nullptr || m_formWindow->currentTool() != 0) + return; + + if (QObject *object = objectAt(m_treeView->indexAt(pos))) { + if (QMenu *menu = createTaskMenu(object, m_formWindow)) { + menu->exec(m_treeView->viewport()->mapToGlobal(pos)); + delete menu; + } + } +} + +// ------------ ObjectInspector +ObjectInspector::ObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent) : + QDesignerObjectInspector(parent), + m_impl(new ObjectInspectorPrivate(core)) +{ + QVBoxLayout *vbox = new QVBoxLayout(this); + vbox->setContentsMargins(QMargins()); + + vbox->addWidget(m_impl->filterLineEdit()); + QTreeView *treeView = m_impl->treeView(); + vbox->addWidget(treeView); + + connect(treeView, &QWidget::customContextMenuRequested, + this, &ObjectInspector::slotPopupContextMenu); + + connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &ObjectInspector::slotSelectionChanged); + + connect(treeView->header(), &QHeaderView::sectionDoubleClicked, + this, &ObjectInspector::slotHeaderDoubleClicked); + setAcceptDrops(true); +} + +ObjectInspector::~ObjectInspector() +{ + delete m_impl; +} + +QDesignerFormEditorInterface *ObjectInspector::core() const +{ + return m_impl->core(); +} + +void ObjectInspector::slotPopupContextMenu(const QPoint &pos) +{ + m_impl->slotPopupContextMenu(this, pos); +} + +void ObjectInspector::setFormWindow(QDesignerFormWindowInterface *fwi) +{ + m_impl->setFormWindow(fwi); +} + +void ObjectInspector::slotSelectionChanged(const QItemSelection & selected, const QItemSelection &deselected) +{ + m_impl->slotSelectionChanged(selected, deselected); +} + +void ObjectInspector::getSelection(Selection &s) const +{ + m_impl->getSelection(s); +} + +bool ObjectInspector::selectObject(QObject *o) +{ + return m_impl->selectObject(o); +} + +void ObjectInspector::clearSelection() +{ + m_impl->clearSelection(); +} + +void ObjectInspector::slotHeaderDoubleClicked(int column) +{ + m_impl->slotHeaderDoubleClicked(column); +} + +void ObjectInspector::mainContainerChanged() +{ + // Invalidate references to objects kept in items + if (sender() == m_impl->formWindow()) + setFormWindow(nullptr); +} + +void ObjectInspector::dragEnterEvent (QDragEnterEvent * event) +{ + m_impl->handleDragEnterMoveEvent(this, event, true); +} + +void ObjectInspector::dragMoveEvent(QDragMoveEvent * event) +{ + m_impl->handleDragEnterMoveEvent(this, event, false); +} + +void ObjectInspector::dragLeaveEvent(QDragLeaveEvent * /* event*/) +{ + m_impl->restoreDropHighlighting(); +} + +void ObjectInspector::dropEvent (QDropEvent * event) +{ + m_impl->dropEvent(event); + +QT_END_NAMESPACE +} +} diff --git a/src/designer/src/components/objectinspector/objectinspector.h b/src/designer/src/components/objectinspector/objectinspector.h new file mode 100644 index 0000000..c856d7d --- /dev/null +++ b/src/designer/src/components/objectinspector/objectinspector.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OBJECTINSPECTOR_H +#define OBJECTINSPECTOR_H + +#include "objectinspector_global.h" +#include "qdesigner_objectinspector_p.h" + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class QItemSelection; + +namespace qdesigner_internal { + +class QT_OBJECTINSPECTOR_EXPORT ObjectInspector: public QDesignerObjectInspector +{ + Q_OBJECT +public: + explicit ObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~ObjectInspector() override; + + QDesignerFormEditorInterface *core() const override; + + void getSelection(Selection &s) const override; + bool selectObject(QObject *o) override; + void clearSelection() override; + + void setFormWindow(QDesignerFormWindowInterface *formWindow) override; + +public slots: + void mainContainerChanged() override; + +private slots: + void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void slotPopupContextMenu(const QPoint &pos); + void slotHeaderDoubleClicked(int column); + +protected: + void dragEnterEvent (QDragEnterEvent * event) override; + void dragMoveEvent(QDragMoveEvent * event) override; + void dragLeaveEvent(QDragLeaveEvent * event) override; + void dropEvent (QDropEvent * event) override; + +private: + class ObjectInspectorPrivate; + ObjectInspectorPrivate *m_impl; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // OBJECTINSPECTOR_H diff --git a/src/designer/src/components/objectinspector/objectinspector_global.h b/src/designer/src/components/objectinspector/objectinspector_global.h new file mode 100644 index 0000000..6a11855 --- /dev/null +++ b/src/designer/src/components/objectinspector/objectinspector_global.h @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OBJECTINSPECTOR_GLOBAL_H +#define OBJECTINSPECTOR_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_WIN +#ifdef QT_OBJECTINSPECTOR_LIBRARY +# define QT_OBJECTINSPECTOR_EXPORT +#else +# define QT_OBJECTINSPECTOR_EXPORT +#endif +#else +#define QT_OBJECTINSPECTOR_EXPORT +#endif + +QT_END_NAMESPACE + +#endif // OBJECTINSPECTOR_GLOBAL_H diff --git a/src/designer/src/components/objectinspector/objectinspectormodel.cpp b/src/designer/src/components/objectinspector/objectinspectormodel.cpp new file mode 100644 index 0000000..5168d4f --- /dev/null +++ b/src/designer/src/components/objectinspector/objectinspectormodel.cpp @@ -0,0 +1,463 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "objectinspectormodel_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { DataRole = 1000 }; +} + +static inline QObject *objectOfItem(const QStandardItem *item) { + return qvariant_cast(item->data(DataRole)); +} + +static bool sameIcon(const QIcon &i1, const QIcon &i2) +{ + if (i1.isNull() && i2.isNull()) + return true; + if (i1.isNull() != i2.isNull()) + return false; + return i1.cacheKey() == i2.cacheKey(); +} + +static inline bool isNameColumnEditable(const QObject *o) +{ + if (auto *action = qobject_cast(o)) + return !action->isSeparator(); + return true; +} + +static qdesigner_internal::ObjectData::StandardItemList createModelRow(const QObject *o) +{ + qdesigner_internal::ObjectData::StandardItemList rc; + const Qt::ItemFlags baseFlags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsEnabled; + for (int i = 0; i < qdesigner_internal::ObjectInspectorModel::NumColumns; i++) { + QStandardItem *item = new QStandardItem; + Qt::ItemFlags flags = baseFlags; + if (i == qdesigner_internal::ObjectInspectorModel::ObjectNameColumn && isNameColumnEditable(o)) + flags |= Qt::ItemIsEditable; + item->setFlags(flags); + rc += item; + } + return rc; +} + +static inline bool isQLayoutWidget(const QObject *o) +{ + return o->metaObject() == &QLayoutWidget::staticMetaObject; +} + +namespace qdesigner_internal { + + // context kept while building a model, just there to reduce string allocations + struct ModelRecursionContext { + explicit ModelRecursionContext(QDesignerFormEditorInterface *core, const QString &sepName); + + const QString designerPrefix; + const QString separator; + + QDesignerFormEditorInterface *core; + const QDesignerWidgetDataBaseInterface *db; + const QDesignerMetaDataBaseInterface *mdb; + }; + + ModelRecursionContext::ModelRecursionContext(QDesignerFormEditorInterface *c, const QString &sepName) : + designerPrefix(u"QDesigner"_s), + separator(sepName), + core(c), + db(c->widgetDataBase()), + mdb(c->metaDataBase()) + { + } + + // ------------ ObjectData/ ObjectModel: + // Whenever the selection changes, ObjectInspector::setFormWindow is + // called. To avoid rebuilding the tree every time (loosing expanded state) + // a model is first built from the object tree by recursion. + // As a tree is difficult to represent, a flat list of entries (ObjectData) + // containing object and parent object is used. + // ObjectData has an overloaded operator== that compares the object pointers. + // Structural changes which cause a rebuild can be detected by + // comparing the lists of ObjectData. If it is the same, only the item data (class name [changed by promotion], + // object name and icon) are checked and the existing items are updated. + + ObjectData::ObjectData() = default; + + ObjectData::ObjectData(QObject *parent, QObject *object, const ModelRecursionContext &ctx) : + m_parent(parent), + m_object(object), + m_className(QLatin1StringView(object->metaObject()->className())), + m_objectName(object->objectName()) + { + + // 1) set entry + if (object->isWidgetType()) { + initWidget(static_cast(object), ctx); + } else { + initObject(ctx); + } + if (m_className.startsWith(ctx.designerPrefix)) + m_className.remove(1, ctx.designerPrefix.size() - 1); + } + + void ObjectData::initObject(const ModelRecursionContext &ctx) + { + // Check objects: Action? + if (const QAction *act = qobject_cast(m_object)) { + if (act->isSeparator()) { // separator is reserved + m_objectName = ctx.separator; + m_type = SeparatorAction; + } else { + m_type = Action; + } + m_classIcon = act->icon(); + } else { + m_type = Object; + } + } + + void ObjectData::initWidget(QWidget *w, const ModelRecursionContext &ctx) + { + // Check for extension container, QLayoutwidget, or normal container + bool isContainer = false; + if (const QDesignerWidgetDataBaseItemInterface *widgetItem = ctx.db->item(ctx.db->indexOfObject(w, true))) { + m_classIcon = widgetItem->icon(); + m_className = widgetItem->name(); + isContainer = widgetItem->isContainer(); + } + + // We might encounter temporary states with no layouts when re-layouting. + // Just default to Widget handling for the moment. + if (isQLayoutWidget(w)) { + if (const QLayout *layout = w->layout()) { + m_type = LayoutWidget; + m_managedLayoutType = LayoutInfo::layoutType(ctx.core, layout); + m_className = QLatin1StringView(layout->metaObject()->className()); + m_objectName = layout->objectName(); + } + return; + } + + if (qt_extension(ctx.core->extensionManager(), w)) { + m_type = ExtensionContainer; + return; + } + if (isContainer) { + m_type = LayoutableContainer; + m_managedLayoutType = LayoutInfo::managedLayoutType(ctx.core, w); + return; + } + m_type = ChildWidget; + } + + bool ObjectData::equals(const ObjectData & me) const + { + return m_parent == me.m_parent && m_object == me.m_object; + } + + unsigned ObjectData::compare(const ObjectData & rhs) const + { + unsigned rc = 0; + if (m_className != rhs.m_className) + rc |= ClassNameChanged; + if (m_objectName != rhs.m_objectName) + rc |= ObjectNameChanged; + if (!sameIcon(m_classIcon, rhs.m_classIcon)) + rc |= ClassIconChanged; + if (m_type != rhs.m_type) + rc |= TypeChanged; + if (m_managedLayoutType != rhs.m_managedLayoutType) + rc |= LayoutTypeChanged; + return rc; + } + + void ObjectData::setItemsDisplayData(const StandardItemList &row, const ObjectInspectorIcons &icons, unsigned mask) const + { + if (mask & ObjectNameChanged) + row[ObjectInspectorModel::ObjectNameColumn]->setText(m_objectName); + if (mask & ClassNameChanged) { + row[ObjectInspectorModel::ClassNameColumn]->setText(m_className); + row[ObjectInspectorModel::ClassNameColumn]->setToolTip(m_className); + } + // Set a layout icon only for containers. Note that QLayoutWidget don't have + // real class icons + if (mask & (ClassIconChanged|TypeChanged|LayoutTypeChanged)) { + switch (m_type) { + case LayoutWidget: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + break; + case LayoutableContainer: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(m_classIcon); + break; + default: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(QIcon()); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(m_classIcon); + break; + } + } + } + + void ObjectData::setItems(const StandardItemList &row, const ObjectInspectorIcons &icons) const + { + const QVariant object = QVariant::fromValue(m_object); + row[ObjectInspectorModel::ObjectNameColumn]->setData(object, DataRole); + row[ObjectInspectorModel::ClassNameColumn]->setData(object, DataRole); + setItemsDisplayData(row, icons, ClassNameChanged|ObjectNameChanged|ClassIconChanged|TypeChanged|LayoutTypeChanged); + } + + // Recursive routine that creates the model by traversing the form window object tree. + void createModelRecursion(const QDesignerFormWindowInterface *fwi, + QObject *parent, + QObject *object, + ObjectModel &model, + const ModelRecursionContext &ctx) + { + using ButtonGroupList = QList; + // 1) Create entry + const ObjectData entry(parent, object, ctx); + model.push_back(entry); + + // 2) recurse over widget children via container extension or children list + const QDesignerContainerExtension *containerExtension = nullptr; + if (entry.type() == ObjectData::ExtensionContainer) { + containerExtension = qt_extension(fwi->core()->extensionManager(), object); + Q_ASSERT(containerExtension); + const int count = containerExtension->count(); + for (int i=0; i < count; ++i) { + QObject *page = containerExtension->widget(i); + Q_ASSERT(page != nullptr); + createModelRecursion(fwi, object, page, model, ctx); + } + } + + if (!object->children().isEmpty()) { + ButtonGroupList buttonGroups; + for (QObject *childObject : object->children()) { + // Managed child widgets unless we had a container extension + if (childObject->isWidgetType()) { + if (!containerExtension) { + QWidget *widget = qobject_cast(childObject); + if (fwi->isManaged(widget)) + createModelRecursion(fwi, object, widget, model, ctx); + } + } else { + if (ctx.mdb->item(childObject)) { + if (auto bg = qobject_cast(childObject)) + buttonGroups.push_back(bg); + } // Has MetaDataBase entry + } + } + // Add button groups + if (!buttonGroups.isEmpty()) { + for (QButtonGroup *group : std::as_const(buttonGroups)) + createModelRecursion(fwi, object, group, model, ctx); + } + } // has children + if (object->isWidgetType()) { + // Add actions + const auto actions = static_cast(object)->actions(); + for (QAction *action : actions) { + if (ctx.mdb->item(action)) { + QObject *childObject = action; + if (auto menu = action->menu()) + childObject = menu; + createModelRecursion(fwi, object, childObject, model, ctx); + } + } + } + } + + // ------------ ObjectInspectorModel + ObjectInspectorModel::ObjectInspectorModel(QObject *parent) : + QStandardItemModel(0, NumColumns, parent) + { + QStringList headers; + headers += QCoreApplication::translate("ObjectInspectorModel", "Object"); + headers += QCoreApplication::translate("ObjectInspectorModel", "Class"); + Q_ASSERT(headers.size() == NumColumns); + setColumnCount(NumColumns); + setHorizontalHeaderLabels(headers); + // Icons + m_icons.layoutIcons[LayoutInfo::NoLayout] = createIconSet("editbreaklayout.png"_L1); + m_icons.layoutIcons[LayoutInfo::HSplitter] = createIconSet("edithlayoutsplit.png"_L1); + m_icons.layoutIcons[LayoutInfo::VSplitter] = createIconSet("editvlayoutsplit.png"_L1); + m_icons.layoutIcons[LayoutInfo::HBox] = createIconSet("edithlayout.png"_L1); + m_icons.layoutIcons[LayoutInfo::VBox] = createIconSet("editvlayout.png"_L1); + m_icons.layoutIcons[LayoutInfo::Grid] = createIconSet("editgrid.png"_L1); + m_icons.layoutIcons[LayoutInfo::Form] = createIconSet("editform.png"_L1); + } + + void ObjectInspectorModel::clearItems() + { + beginResetModel(); + m_objectIndexMultiMap.clear(); + m_model.clear(); + endResetModel(); // force editors to be closed in views + removeRow(0); + } + + ObjectInspectorModel::UpdateResult ObjectInspectorModel::update(QDesignerFormWindowInterface *fw) + { + QWidget *mainContainer = fw ? fw->mainContainer() : nullptr; + if (!mainContainer) { + clearItems(); + m_formWindow = nullptr; + return NoForm; + } + m_formWindow = fw; + // Build new model and compare to previous one. If the structure is + // identical, just update, else rebuild + ObjectModel newModel; + + static const QString separator = QCoreApplication::translate("ObjectInspectorModel", "separator"); + const ModelRecursionContext ctx(fw->core(), separator); + createModelRecursion(fw, nullptr, mainContainer, newModel, ctx); + + if (newModel == m_model) { + updateItemContents(m_model, newModel); + return Updated; + } + + rebuild(newModel); + m_model = newModel; + return Rebuilt; + } + + QObject *ObjectInspectorModel::objectAt(const QModelIndex &index) const + { + if (index.isValid()) + if (const QStandardItem *item = itemFromIndex(index)) + return objectOfItem(item); + return nullptr; + } + + // Missing Qt API: get a row + ObjectInspectorModel::StandardItemList ObjectInspectorModel::rowAt(QModelIndex index) const + { + StandardItemList rc; + while (true) { + rc += itemFromIndex(index); + const int nextColumn = index.column() + 1; + if (nextColumn >= NumColumns) + break; + index = index.sibling(index.row(), nextColumn); + } + return rc; + } + + // Rebuild the tree in case the model has completely changed. + void ObjectInspectorModel::rebuild(const ObjectModel &newModel) + { + clearItems(); + if (newModel.isEmpty()) + return; + + const auto mcend = newModel.cend(); + auto it = newModel.cbegin(); + // Set up root element + StandardItemList rootRow = createModelRow(it->object()); + it->setItems(rootRow, m_icons); + appendRow(rootRow); + m_objectIndexMultiMap.insert(it->object(), indexFromItem(rootRow.constFirst())); + for (++it; it != mcend; ++it) { + // Add to parent item, found via map + const QModelIndex parentIndex = m_objectIndexMultiMap.value(it->parent(), QModelIndex()); + Q_ASSERT(parentIndex.isValid()); + QStandardItem *parentItem = itemFromIndex(parentIndex); + StandardItemList row = createModelRow(it->object()); + it->setItems(row, m_icons); + parentItem->appendRow(row); + m_objectIndexMultiMap.insert(it->object(), indexFromItem(row.constFirst())); + } + } + + // Update item data in case the model has the same structure + void ObjectInspectorModel::updateItemContents(ObjectModel &oldModel, const ObjectModel &newModel) + { + // Change text and icon. Keep a set of changed object + // as for example actions might occur several times in the tree. + using QObjectSet = QSet; + + QObjectSet changedObjects; + + const auto size = newModel.size(); + Q_ASSERT(oldModel.size() == size); + for (qsizetype i = 0; i < size; ++i) { + const ObjectData &newEntry = newModel.at(i); + ObjectData &entry = oldModel[i]; + // Has some data changed? + if (const unsigned changedMask = entry.compare(newEntry)) { + entry = newEntry; + QObject * o = entry.object(); + if (!changedObjects.contains(o)) { + changedObjects.insert(o); + const QModelIndexList indexes = m_objectIndexMultiMap.values(o); + for (const QModelIndex &index : indexes) + entry.setItemsDisplayData(rowAt(index), m_icons, changedMask); + } + } + } + } + + QVariant ObjectInspectorModel::data(const QModelIndex &index, int role) const + { + const QVariant rc = QStandardItemModel::data(index, role); + // Return if the string is empty for the display role + // only (else, editing starts with ). + if (role == Qt::DisplayRole && rc.metaType().id() == QMetaType::QString) { + const QString s = rc.toString(); + if (s.isEmpty()) { + static const QString noName = QCoreApplication::translate("ObjectInspectorModel", ""); + return QVariant(noName); + } + } + return rc; + } + + bool ObjectInspectorModel::setData(const QModelIndex &index, const QVariant &value, int role) + { + if (role != Qt::EditRole || !m_formWindow) + return false; + + QObject *object = objectAt(index); + if (!object) + return false; + // Is this a layout widget? + const QString nameProperty = isQLayoutWidget(object) ? u"layoutName"_s : u"objectName"_s; + m_formWindow->commandHistory()->push(createTextPropertyCommand(nameProperty, value.toString(), object, m_formWindow)); + return true; + } +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/objectinspector/objectinspectormodel_p.h b/src/designer/src/components/objectinspector/objectinspectormodel_p.h new file mode 100644 index 0000000..5ba4b83 --- /dev/null +++ b/src/designer/src/components/objectinspector/objectinspectormodel_p.h @@ -0,0 +1,131 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef OBJECTINSPECTORMODEL_H +#define OBJECTINSPECTORMODEL_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + + // Data structure containing the fixed item type icons + struct ObjectInspectorIcons { + QIcon layoutIcons[LayoutInfo::UnknownLayout + 1]; + }; + + struct ModelRecursionContext; + + // Data structure representing one item of the object inspector. + class ObjectData { + public: + enum Type { + Object, + Action, + SeparatorAction, + ChildWidget, // A child widget + LayoutableContainer, // A container that can be laid out + LayoutWidget, // A QLayoutWidget + ExtensionContainer // QTabWidget and the like, container extension + }; + + using StandardItemList = QList; + + explicit ObjectData(QObject *parent, QObject *object, const ModelRecursionContext &ctx); + ObjectData(); + + inline Type type() const { return m_type; } + inline QObject *object() const { return m_object; } + inline QObject *parent() const { return m_parent; } + inline QString objectName() const { return m_objectName; } + + bool equals(const ObjectData & me) const; + + enum ChangedMask { ClassNameChanged = 1, ObjectNameChanged = 2, + ClassIconChanged = 4, TypeChanged = 8, + LayoutTypeChanged = 16}; + + unsigned compare(const ObjectData & me) const; + + // Initially set up a row + void setItems(const StandardItemList &row, const ObjectInspectorIcons &icons) const; + // Update row data according to change mask + void setItemsDisplayData(const StandardItemList &row, const ObjectInspectorIcons &icons, unsigned mask) const; + + private: + friend bool comparesEqual(const ObjectData &lhs, const ObjectData &rhs) noexcept + { + return lhs.m_parent == rhs.m_parent && lhs.m_object == rhs.m_object; + } + Q_DECLARE_EQUALITY_COMPARABLE(ObjectData) + + void initObject(const ModelRecursionContext &ctx); + void initWidget(QWidget *w, const ModelRecursionContext &ctx); + + QObject *m_parent = nullptr; + QObject *m_object = nullptr; + Type m_type = Object; + QString m_className; + QString m_objectName; + QIcon m_classIcon; + LayoutInfo::Type m_managedLayoutType = LayoutInfo::NoLayout; + }; + + using ObjectModel = QList; + + // QStandardItemModel for ObjectInspector. Uses ObjectData/ObjectModel + // internally for its updates. + class ObjectInspectorModel : public QStandardItemModel { + public: + using StandardItemList = QList; + enum { ObjectNameColumn, ClassNameColumn, NumColumns }; + + explicit ObjectInspectorModel(QObject *parent); + + enum UpdateResult { NoForm, Rebuilt, Updated }; + UpdateResult update(QDesignerFormWindowInterface *fw); + + const QModelIndexList indexesOf(QObject *o) const { return m_objectIndexMultiMap.values(o); } + QObject *objectAt(const QModelIndex &index) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + private: + void rebuild(const ObjectModel &newModel); + void updateItemContents(ObjectModel &oldModel, const ObjectModel &newModel); + void clearItems(); + StandardItemList rowAt(QModelIndex index) const; + + ObjectInspectorIcons m_icons; + QMultiMap m_objectIndexMultiMap; + ObjectModel m_model; + QPointer m_formWindow; + }; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // OBJECTINSPECTORMODEL_H diff --git a/src/designer/src/components/propertyeditor/brushpropertymanager.cpp b/src/designer/src/components/propertyeditor/brushpropertymanager.cpp new file mode 100644 index 0000000..503e07e --- /dev/null +++ b/src/designer/src/components/propertyeditor/brushpropertymanager.cpp @@ -0,0 +1,271 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "brushpropertymanager.h" +#include "qtpropertymanager_p.h" +#include "designerpropertymanager.h" +#include "qtpropertybrowserutils_p.h" + +#include +#include +#include + +static const char *brushStyles[] = { +QT_TRANSLATE_NOOP("BrushPropertyManager", "No brush"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Solid"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 1"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 2"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 3"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 4"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 5"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 6"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 7"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Horizontal"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Vertical"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Cross"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Backward diagonal"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Forward diagonal"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Crossing diagonal"), +}; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +BrushPropertyManager::BrushPropertyManager() = default; + +int BrushPropertyManager::brushStyleToIndex(Qt::BrushStyle st) +{ + switch (st) { + case Qt::NoBrush: return 0; + case Qt::SolidPattern: return 1; + case Qt::Dense1Pattern: return 2; + case Qt::Dense2Pattern: return 3; + case Qt::Dense3Pattern: return 4; + case Qt::Dense4Pattern: return 5; + case Qt::Dense5Pattern: return 6; + case Qt::Dense6Pattern: return 7; + case Qt::Dense7Pattern: return 8; + case Qt::HorPattern: return 9; + case Qt::VerPattern: return 10; + case Qt::CrossPattern: return 11; + case Qt::BDiagPattern: return 12; + case Qt::FDiagPattern: return 13; + case Qt::DiagCrossPattern: return 14; + default: break; + } + return 0; +} + +Qt::BrushStyle BrushPropertyManager::brushStyleIndexToStyle(int brushStyleIndex) +{ + switch (brushStyleIndex) { + case 0: return Qt::NoBrush; + case 1: return Qt::SolidPattern; + case 2: return Qt::Dense1Pattern; + case 3: return Qt::Dense2Pattern; + case 4: return Qt::Dense3Pattern; + case 5: return Qt::Dense4Pattern; + case 6: return Qt::Dense5Pattern; + case 7: return Qt::Dense6Pattern; + case 8: return Qt::Dense7Pattern; + case 9: return Qt::HorPattern; + case 10: return Qt::VerPattern; + case 11: return Qt::CrossPattern; + case 12: return Qt::BDiagPattern; + case 13: return Qt::FDiagPattern; + case 14: return Qt::DiagCrossPattern; + } + return Qt::NoBrush; +} + +static void clearBrushIcons(); + +namespace { +class EnumIndexIconMap : public QMap +{ +public: + EnumIndexIconMap() + { + qAddPostRoutine(clearBrushIcons); + } +}; +} + +Q_GLOBAL_STATIC(EnumIndexIconMap, brushIcons) + +static void clearBrushIcons() +{ + brushIcons()->clear(); +} + +const QMap &BrushPropertyManager::brushStyleIcons() +{ + // Create a map of icons for the brush style editor + if (brushIcons()->empty()) { + const int brushStyleCount = sizeof(brushStyles)/sizeof(const char *); + QBrush brush(Qt::black); + for (int i = 0; i < brushStyleCount; i++) { + const Qt::BrushStyle style = brushStyleIndexToStyle(i); + brush.setStyle(style); + brushIcons()->insert(i, QtPropertyBrowserUtils::brushValueIcon(brush)); + } + } + return *(brushIcons()); +} + +QString BrushPropertyManager::brushStyleIndexToString(int brushStyleIndex) +{ + const int brushStyleCount = sizeof(brushStyles)/sizeof(const char *); + return brushStyleIndex < brushStyleCount ? QCoreApplication::translate("BrushPropertyManager", brushStyles[brushStyleIndex]) : QString(); +} + +BrushPropertyManager::~BrushPropertyManager() = default; + +void BrushPropertyManager::initializeProperty(QtVariantPropertyManager *vm, QtProperty *property, int enumTypeId) +{ + m_brushValues.insert(property, QBrush()); + // style + QtVariantProperty *styleSubProperty = vm->addProperty(enumTypeId, QCoreApplication::translate("BrushPropertyManager", "Style")); + property->addSubProperty(styleSubProperty); + QStringList styles; + for (const char *brushStyle : brushStyles) + styles.push_back(QCoreApplication::translate("BrushPropertyManager", brushStyle)); + styleSubProperty->setAttribute(u"enumNames"_s, styles); + styleSubProperty->setAttribute(u"enumIcons"_s, QVariant::fromValue(brushStyleIcons())); + m_brushPropertyToStyleSubProperty.insert(property, styleSubProperty); + m_brushStyleSubPropertyToProperty.insert(styleSubProperty, property); + // color + QtVariantProperty *colorSubProperty = + vm->addProperty(QMetaType::QColor, QCoreApplication::translate("BrushPropertyManager", "Color")); + property->addSubProperty(colorSubProperty); + m_brushPropertyToColorSubProperty.insert(property, colorSubProperty); + m_brushColorSubPropertyToProperty.insert(colorSubProperty, property); +} + +bool BrushPropertyManager::uninitializeProperty(QtProperty *property) +{ + const auto brit = m_brushValues.find(property); // Brushes + if (brit == m_brushValues.end()) + return false; + m_brushValues.erase(brit); + // style + const auto styleIt = m_brushPropertyToStyleSubProperty.find(property); + if (styleIt != m_brushPropertyToStyleSubProperty.end()) { + QtProperty *styleProp = styleIt .value(); + m_brushStyleSubPropertyToProperty.remove(styleProp); + m_brushPropertyToStyleSubProperty.erase(styleIt ); + delete styleProp; + } + // color + const auto colorIt = m_brushPropertyToColorSubProperty.find(property); + if (colorIt != m_brushPropertyToColorSubProperty.end()) { + QtProperty *colorProp = colorIt .value(); + m_brushColorSubPropertyToProperty.remove(colorProp); + m_brushPropertyToColorSubProperty.erase(colorIt ); + delete colorProp; + } + return true; +} + +void BrushPropertyManager::slotPropertyDestroyed(QtProperty *property) +{ + auto subit = m_brushStyleSubPropertyToProperty.find(property); + if (subit != m_brushStyleSubPropertyToProperty.end()) { + m_brushPropertyToStyleSubProperty[subit.value()] = 0; + m_brushStyleSubPropertyToProperty.erase(subit); + } + subit = m_brushColorSubPropertyToProperty.find(property); + if (subit != m_brushColorSubPropertyToProperty.end()) { + m_brushPropertyToColorSubProperty[subit.value()] = 0; + m_brushColorSubPropertyToProperty.erase(subit); + } +} + + +int BrushPropertyManager::valueChanged(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value) +{ + switch (value.metaType().id()) { + case QMetaType::Int: // Style subproperty? + if (QtProperty *brushProperty = m_brushStyleSubPropertyToProperty.value(property, 0)) { + const QBrush oldValue = m_brushValues.value(brushProperty); + QBrush newBrush = oldValue; + const int index = value.toInt(); + newBrush.setStyle(brushStyleIndexToStyle(index)); + if (newBrush == oldValue) + return DesignerPropertyManager::Unchanged; + vm->variantProperty(brushProperty)->setValue(newBrush); + return DesignerPropertyManager::Changed; + } + break; + case QMetaType::QColor: // Color subproperty? + if (QtProperty *brushProperty = m_brushColorSubPropertyToProperty.value(property, 0)) { + const QBrush oldValue = m_brushValues.value(brushProperty); + QBrush newBrush = oldValue; + newBrush.setColor(qvariant_cast(value)); + if (newBrush == oldValue) + return DesignerPropertyManager::Unchanged; + vm->variantProperty(brushProperty)->setValue(newBrush); + return DesignerPropertyManager::Changed; + } + break; + default: + break; + } + return DesignerPropertyManager::NoMatch; +} + +int BrushPropertyManager::setValue(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value) +{ + if (value.metaType().id() != QMetaType::QBrush) + return DesignerPropertyManager::NoMatch; + const auto brit = m_brushValues.find(property); + if (brit == m_brushValues.end()) + return DesignerPropertyManager::NoMatch; + + const QBrush newBrush = qvariant_cast(value); + if (newBrush == brit.value()) + return DesignerPropertyManager::Unchanged; + brit.value() = newBrush; + if (QtProperty *styleProperty = m_brushPropertyToStyleSubProperty.value(property)) + vm->variantProperty(styleProperty)->setValue(brushStyleToIndex(newBrush.style())); + if (QtProperty *colorProperty = m_brushPropertyToColorSubProperty.value(property)) + vm->variantProperty(colorProperty)->setValue(newBrush.color()); + + return DesignerPropertyManager::Changed; +} + +bool BrushPropertyManager::valueText(const QtProperty *property, QString *text) const +{ + const auto brit = m_brushValues.constFind(property); + if (brit == m_brushValues.constEnd()) + return false; + const QBrush &brush = brit.value(); + const QString styleName = brushStyleIndexToString(brushStyleToIndex(brush.style())); + *text = QCoreApplication::translate("BrushPropertyManager", "[%1, %2]") + .arg(styleName, QtPropertyBrowserUtils::colorValueText(brush.color())); + return true; +} + +bool BrushPropertyManager::valueIcon(const QtProperty *property, QIcon *icon) const +{ + const auto brit = m_brushValues.constFind(property); + if (brit == m_brushValues.constEnd()) + return false; + *icon = QtPropertyBrowserUtils::brushValueIcon(brit.value()); + return true; +} + +bool BrushPropertyManager::value(const QtProperty *property, QVariant *v) const +{ + const auto brit = m_brushValues.constFind(property); + if (brit == m_brushValues.constEnd()) + return false; + v->setValue(brit.value()); + return true; +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/brushpropertymanager.h b/src/designer/src/components/propertyeditor/brushpropertymanager.h new file mode 100644 index 0000000..8bfe7f5 --- /dev/null +++ b/src/designer/src/components/propertyeditor/brushpropertymanager.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BRUSHPROPERTYMANAGER_H +#define BRUSHPROPERTYMANAGER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtProperty; +class QtVariantPropertyManager; + +class QString; +class QVariant; + +namespace qdesigner_internal { + +// BrushPropertyManager: A mixin for DesignerPropertyManager that manages brush properties. + +class BrushPropertyManager { +public: + Q_DISABLE_COPY_MOVE(BrushPropertyManager); + + BrushPropertyManager(); + ~BrushPropertyManager(); + + void initializeProperty(QtVariantPropertyManager *vm, QtProperty *property, int enumTypeId); + bool uninitializeProperty(QtProperty *property); + + // Call from slotValueChanged(). + int valueChanged(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value); + int setValue(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value); + + bool valueText(const QtProperty *property, QString *text) const; + bool valueIcon(const QtProperty *property, QIcon *icon) const; + bool value(const QtProperty *property, QVariant *v) const; + + // Call from QtPropertyManager's propertyDestroyed signal + void slotPropertyDestroyed(QtProperty *property); + +private: + static int brushStyleToIndex(Qt::BrushStyle st); + static Qt::BrushStyle brushStyleIndexToStyle(int brushStyleIndex); + static QString brushStyleIndexToString(int brushStyleIndex); + + static const QMap &brushStyleIcons(); + + using PropertyToPropertyMap = QHash; + PropertyToPropertyMap m_brushPropertyToStyleSubProperty; + PropertyToPropertyMap m_brushPropertyToColorSubProperty; + PropertyToPropertyMap m_brushStyleSubPropertyToProperty; + PropertyToPropertyMap m_brushColorSubPropertyToProperty; + + QHash m_brushValues; +}; + +} + +QT_END_NAMESPACE + +#endif // BRUSHPROPERTYMANAGER_H diff --git a/src/designer/src/components/propertyeditor/designerpropertymanager.cpp b/src/designer/src/components/propertyeditor/designerpropertymanager.cpp new file mode 100644 index 0000000..2c94f02 --- /dev/null +++ b/src/designer/src/components/propertyeditor/designerpropertymanager.cpp @@ -0,0 +1,2236 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "designerpropertymanager.h" +#include "qtpropertymanager_p.h" +#include "paletteeditorbutton.h" +#include "pixmapeditor.h" +#include "qlonglongvalidator.h" +#include "texteditor.h" +#include "resetdecorator.h" +#include "stringlisteditorbutton.h" +#include "qtresourceview_p.h" +#include "qtpropertybrowserutils_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto resettableAttributeC = "resettable"_L1; +static constexpr auto flagsAttributeC = "flags"_L1; +static constexpr auto validationModesAttributeC = "validationMode"_L1; +static constexpr auto superPaletteAttributeC = "superPalette"_L1; +static constexpr auto defaultResourceAttributeC = "defaultResource"_L1; +static constexpr auto fontAttributeC = "font"_L1; +static constexpr auto themeAttributeC = "theme"_L1; +static constexpr auto themeEnumAttributeC = "themeEnum"_L1; + +class DesignerFlagPropertyType +{ +}; + + +class DesignerAlignmentPropertyType +{ +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(DesignerFlagPropertyType) +Q_DECLARE_METATYPE(DesignerAlignmentPropertyType) + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +template +void TranslatablePropertyManager::initialize(QtVariantPropertyManager *m, + QtProperty *property, + const PropertySheetValue &value) +{ + m_values.insert(property, value); + + QtVariantProperty *translatable = m->addProperty(QMetaType::Bool, DesignerPropertyManager::tr("translatable")); + translatable->setValue(value.translatable()); + m_valueToTranslatable.insert(property, translatable); + m_translatableToValue.insert(translatable, property); + property->addSubProperty(translatable); + + if (!DesignerPropertyManager::useIdBasedTranslations()) { + QtVariantProperty *disambiguation = + m->addProperty(QMetaType::QString, DesignerPropertyManager::tr("disambiguation")); + disambiguation->setValue(value.disambiguation()); + m_valueToDisambiguation.insert(property, disambiguation); + m_disambiguationToValue.insert(disambiguation, property); + property->addSubProperty(disambiguation); + } + + QtVariantProperty *comment = m->addProperty(QMetaType::QString, DesignerPropertyManager::tr("comment")); + comment->setValue(value.comment()); + m_valueToComment.insert(property, comment); + m_commentToValue.insert(comment, property); + property->addSubProperty(comment); + + if (DesignerPropertyManager::useIdBasedTranslations()) { + QtVariantProperty *id = m->addProperty(QMetaType::QString, DesignerPropertyManager::tr("id")); + id->setValue(value.id()); + m_valueToId.insert(property, id); + m_idToValue.insert(id, property); + property->addSubProperty(id); + } +} + +template +bool TranslatablePropertyManager::uninitialize(QtProperty *property) +{ + if (QtProperty *comment = m_valueToComment.value(property)) { + delete comment; + m_commentToValue.remove(comment); + } else { + return false; + } + if (QtProperty *translatable = m_valueToTranslatable.value(property)) { + delete translatable; + m_translatableToValue.remove(translatable); + } + if (QtProperty *disambiguation = m_valueToDisambiguation.value(property)) { + delete disambiguation; + m_disambiguationToValue.remove(disambiguation); + } + if (QtProperty *id = m_valueToId.value(property)) { + delete id; + m_idToValue.remove(id); + } + + m_values.remove(property); + m_valueToComment.remove(property); + m_valueToTranslatable.remove(property); + m_valueToDisambiguation.remove(property); + m_valueToId.remove(property); + return true; +} + +template +bool TranslatablePropertyManager::destroy(QtProperty *subProperty) +{ + const auto commentToValueIt = m_commentToValue.find(subProperty); + if (commentToValueIt != m_commentToValue.end()) { + m_valueToComment.remove(commentToValueIt.value()); + m_commentToValue.erase(commentToValueIt); + return true; + } + const auto translatableToValueIt = m_translatableToValue.find(subProperty); + if (translatableToValueIt != m_translatableToValue.end()) { + m_valueToTranslatable.remove(translatableToValueIt.value()); + m_translatableToValue.erase(translatableToValueIt); + return true; + } + const auto disambiguationToValueIt = m_disambiguationToValue.find(subProperty); + if (disambiguationToValueIt != m_disambiguationToValue.end()) { + m_valueToDisambiguation.remove(disambiguationToValueIt.value()); + m_disambiguationToValue.erase(disambiguationToValueIt); + return true; + } + const auto idToValueIt = m_idToValue.find(subProperty); + if (idToValueIt != m_idToValue.end()) { + m_valueToId.remove(idToValueIt.value()); + m_idToValue.erase(idToValueIt); + return true; + } + return false; +} + +template +int TranslatablePropertyManager::valueChanged(QtVariantPropertyManager *m, + QtProperty *propertyIn, + const QVariant &value) +{ + if (QtProperty *property = m_translatableToValue.value(propertyIn, 0)) { + const PropertySheetValue oldValue = m_values.value(property); + PropertySheetValue newValue = oldValue; + newValue.setTranslatable(value.toBool()); + if (newValue != oldValue) { + m->variantProperty(property)->setValue(QVariant::fromValue(newValue)); + return DesignerPropertyManager::Changed; + } + return DesignerPropertyManager::Unchanged; + } + if (QtProperty *property = m_commentToValue.value(propertyIn)) { + const PropertySheetValue oldValue = m_values.value(property); + PropertySheetValue newValue = oldValue; + newValue.setComment(value.toString()); + if (newValue != oldValue) { + m->variantProperty(property)->setValue(QVariant::fromValue(newValue)); + return DesignerPropertyManager::Changed; + } + return DesignerPropertyManager::Unchanged; + } + if (QtProperty *property = m_disambiguationToValue.value(propertyIn, 0)) { + const PropertySheetValue oldValue = m_values.value(property); + PropertySheetValue newValue = oldValue; + newValue.setDisambiguation(value.toString()); + if (newValue != oldValue) { + m->variantProperty(property)->setValue(QVariant::fromValue(newValue)); + return DesignerPropertyManager::Changed; + } + return DesignerPropertyManager::Unchanged; + } + if (QtProperty *property = m_idToValue.value(propertyIn)) { + const PropertySheetValue oldValue = m_values.value(property); + PropertySheetValue newValue = oldValue; + newValue.setId(value.toString()); + if (newValue != oldValue) { + m->variantProperty(property)->setValue(QVariant::fromValue(newValue)); + return DesignerPropertyManager::Changed; + } + return DesignerPropertyManager::Unchanged; + } + return DesignerPropertyManager::NoMatch; +} + +template +int TranslatablePropertyManager::setValue(QtVariantPropertyManager *m, + QtProperty *property, + int expectedTypeId, + const QVariant &variantValue) +{ + const auto it = m_values.find(property); + if (it == m_values.end()) + return DesignerPropertyManager::NoMatch; + if (variantValue.userType() != expectedTypeId) + return DesignerPropertyManager::NoMatch; + const PropertySheetValue value = qvariant_cast(variantValue); + if (value == it.value()) + return DesignerPropertyManager::Unchanged; + if (QtVariantProperty *comment = m->variantProperty(m_valueToComment.value(property))) + comment->setValue(value.comment()); + if (QtVariantProperty *translatable = m->variantProperty(m_valueToTranslatable.value(property))) + translatable->setValue(value.translatable()); + if (QtVariantProperty *disambiguation = m->variantProperty(m_valueToDisambiguation.value(property))) + disambiguation->setValue(value.disambiguation()); + if (QtVariantProperty *id = m->variantProperty(m_valueToId.value(property))) + id->setValue(value.id()); + it.value() = value; + return DesignerPropertyManager::Changed; +} + +template +bool TranslatablePropertyManager::value(const QtProperty *property, QVariant *rc) const +{ + const auto it = m_values.constFind(property); + if (it == m_values.constEnd()) + return false; + *rc = QVariant::fromValue(it.value()); + return true; +} + +// ------------ DesignerPropertyManager: + +DesignerPropertyManager::DesignerPropertyManager(QDesignerFormEditorInterface *core, QObject *parent) : + QtVariantPropertyManager(parent), + m_changingSubValue(false), + m_core(core), + m_object(nullptr), + m_sourceOfChange(nullptr) +{ + connect(this, &QtVariantPropertyManager::valueChanged, + this, &DesignerPropertyManager::slotValueChanged); + connect(this, & QtAbstractPropertyManager::propertyDestroyed, + this, &DesignerPropertyManager::slotPropertyDestroyed); +} + +DesignerPropertyManager::~DesignerPropertyManager() +{ + clear(); +} + +bool DesignerPropertyManager::m_IdBasedTranslations = false; + +template +static int bitCount(IntT mask) +{ + int count = 0; + for (; mask; count++) + mask &= mask - 1; // clear the least significant bit set + return count; +} + +int DesignerPropertyManager::alignToIndexH(uint align) const +{ + if (align & Qt::AlignLeft) + return 0; + if (align & Qt::AlignHCenter) + return 1; + if (align & Qt::AlignRight) + return 2; + if (align & Qt::AlignJustify) + return 3; + return 0; +} + +int DesignerPropertyManager::alignToIndexV(uint align) const +{ + if (align & Qt::AlignTop) + return 0; + if (align & Qt::AlignVCenter) + return 1; + if (align & Qt::AlignBottom) + return 2; + return 1; +} + +uint DesignerPropertyManager::indexHToAlign(int idx) const +{ + switch (idx) { + case 0: return Qt::AlignLeft; + case 1: return Qt::AlignHCenter; + case 2: return Qt::AlignRight; + case 3: return Qt::AlignJustify; + default: break; + } + return Qt::AlignLeft; +} + +uint DesignerPropertyManager::indexVToAlign(int idx) const +{ + switch (idx) { + case 0: return Qt::AlignTop; + case 1: return Qt::AlignVCenter; + case 2: return Qt::AlignBottom; + default: break; + } + return Qt::AlignVCenter; +} + +QString DesignerPropertyManager::indexHToString(int idx) const +{ + switch (idx) { + case 0: return tr("AlignLeft"); + case 1: return tr("AlignHCenter"); + case 2: return tr("AlignRight"); + case 3: return tr("AlignJustify"); + default: break; + } + return tr("AlignLeft"); +} + +QString DesignerPropertyManager::indexVToString(int idx) const +{ + switch (idx) { + case 0: return tr("AlignTop"); + case 1: return tr("AlignVCenter"); + case 2: return tr("AlignBottom"); + default: break; + } + return tr("AlignVCenter"); +} + +void DesignerPropertyManager::slotValueChanged(QtProperty *property, const QVariant &value) +{ + if (m_changingSubValue) + return; + bool enableSubPropertyHandling = true; + + // Find a matching manager + int subResult = m_stringManager.valueChanged(this, property, value); + if (subResult == NoMatch) + subResult = m_keySequenceManager.valueChanged(this, property, value); + if (subResult == NoMatch) + subResult = m_stringListManager.valueChanged(this, property, value); + if (subResult == NoMatch) + subResult = m_brushManager.valueChanged(this, property, value); + if (subResult == NoMatch) + subResult = m_fontManager.valueChanged(this, property, value); + if (subResult != NoMatch) { + if (subResult == Changed) + emit valueChanged(property, value, enableSubPropertyHandling); + return; + } + + auto *parentItem = property->parentProperty(); + if (m_flagValues.contains(parentItem)) { + auto *flagProperty = parentItem; + const auto subFlags = m_propertyToFlags.value(flagProperty); + const qsizetype subFlagCount = subFlags.size(); + // flag changed + const bool subValue = variantProperty(property)->value().toBool(); + const qsizetype subIndex = subFlags.indexOf(property); + if (subIndex < 0) + return; + + uint newValue = 0; + + m_changingSubValue = true; + + FlagData data = m_flagValues.value(flagProperty); + const auto values = data.values; + // Compute new value, without including (additional) supermasks + if (values.at(subIndex) == 0) { + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + subFlag->setValue(i == subIndex); + } + } else { + if (subValue) + newValue = values.at(subIndex); // value mask of subValue + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + if (subFlag->value().toBool() && bitCount(values.at(i)) == 1) + newValue |= values.at(i); + } + if (newValue == 0) { + // Uncheck all items except 0-mask + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + subFlag->setValue(values.at(i) == 0); + } + } else if (newValue == data.val) { + if (!subValue && bitCount(values.at(subIndex)) > 1) { + // We unchecked something, but the original value still holds + variantProperty(property)->setValue(true); + } + } else { + // Make sure 0-mask is not selected + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + if (values.at(i) == 0) + subFlag->setValue(false); + } + // Check/uncheck proper masks + if (subValue) { + // Make sure submasks and supermasks are selected + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + const uint vi = values.at(i); + if ((vi != 0) && ((vi & newValue) == vi) && !subFlag->value().toBool()) + subFlag->setValue(true); + } + } else { + // Make sure supermasks are not selected if they're no longer valid + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + const uint vi = values.at(i); + if (subFlag->value().toBool() && ((vi & newValue) != vi)) + subFlag->setValue(false); + } + } + } + } + m_changingSubValue = false; + data.val = newValue; + QVariant v; + v.setValue(data.val); + variantProperty(flagProperty)->setValue(v); + } else if (QtProperty *alignProperty = m_alignHToProperty.value(property, 0)) { + const uint v = m_alignValues.value(alignProperty); + const uint newValue = indexHToAlign(value.toInt()) | indexVToAlign(alignToIndexV(v)); + if (v == newValue) + return; + + variantProperty(alignProperty)->setValue(newValue); + } else if (QtProperty *alignProperty = m_alignVToProperty.value(property, 0)) { + const uint v = m_alignValues.value(alignProperty); + const uint newValue = indexVToAlign(value.toInt()) | indexHToAlign(alignToIndexH(v)); + if (v == newValue) + return; + + variantProperty(alignProperty)->setValue(newValue); + } else if (m_iconValues.contains(parentItem)) { + QtProperty *iProperty = parentItem; + QtVariantProperty *iconProperty = variantProperty(iProperty); + PropertySheetIconValue icon = qvariant_cast(iconProperty->value()); + const auto itState = m_iconSubPropertyToState.constFind(property); + if (itState != m_iconSubPropertyToState.constEnd()) { + const auto pair = m_iconSubPropertyToState.value(property); + icon.setPixmap(pair.first, pair.second, qvariant_cast(value)); + } else if (attributeValue(property, themeEnumAttributeC).toBool()) { + icon.setThemeEnum(value.toInt()); + } else { // must be theme property + icon.setTheme(value.toString()); + } + QtProperty *origSourceOfChange = m_sourceOfChange; + if (!origSourceOfChange) + m_sourceOfChange = property; + iconProperty->setValue(QVariant::fromValue(icon)); + if (!origSourceOfChange) + m_sourceOfChange = origSourceOfChange; + } else if (m_iconValues.contains(property)) { + enableSubPropertyHandling = m_sourceOfChange; + } + emit valueChanged(property, value, enableSubPropertyHandling); +} + +void DesignerPropertyManager::slotPropertyDestroyed(QtProperty *property) +{ + auto *parentItem = property->parentProperty(); + if (m_flagValues.contains(parentItem)) { + auto *flagProperty = parentItem; + const auto it = m_propertyToFlags.find(flagProperty); + auto &propertyList = it.value(); + propertyList.replace(propertyList.indexOf(property), 0); + } else if (QtProperty *alignProperty = m_alignHToProperty.value(property, 0)) { + m_propertyToAlignH.remove(alignProperty); + m_alignHToProperty.remove(property); + } else if (QtProperty *alignProperty = m_alignVToProperty.value(property, 0)) { + m_propertyToAlignV.remove(alignProperty); + m_alignVToProperty.remove(property); + } else if (m_stringManager.destroy(property) + || m_stringListManager.destroy(property) + || m_keySequenceManager.destroy(property)) { + } else if (m_iconValues.contains(parentItem)) { + auto *iconProperty = parentItem; + if (m_propertyToTheme.value(iconProperty) == property) { + m_propertyToTheme.remove(iconProperty); + } else if (m_propertyToThemeEnum.value(iconProperty) == property) { + m_propertyToThemeEnum.remove(iconProperty); + } else { + const auto it = m_propertyToIconSubProperties.find(iconProperty); + const auto state = m_iconSubPropertyToState.value(property); + auto &propertyList = it.value(); + propertyList.remove(state); + m_iconSubPropertyToState.remove(property); + } + } else { + m_fontManager.slotPropertyDestroyed(property); + m_brushManager.slotPropertyDestroyed(property); + } + m_alignDefault.remove(property); +} + +QStringList DesignerPropertyManager::attributes(int propertyType) const +{ + if (!isPropertyTypeSupported(propertyType)) + return QStringList(); + + QStringList list = QtVariantPropertyManager::attributes(propertyType); + if (propertyType == designerFlagTypeId()) { + list.append(flagsAttributeC); + } else if (propertyType == designerPixmapTypeId()) { + list.append(defaultResourceAttributeC); + } else if (propertyType == designerIconTypeId()) { + list.append(defaultResourceAttributeC); + } else if (propertyType == designerStringTypeId() || propertyType == QMetaType::QString) { + list.append(validationModesAttributeC); + list.append(fontAttributeC); + list.append(themeAttributeC); + } else if (propertyType == QMetaType::QPalette) { + list.append(superPaletteAttributeC); + } else if (propertyType == QMetaType::Int) { + list.append(themeEnumAttributeC); + } + list.append(resettableAttributeC); + return list; +} + +int DesignerPropertyManager::attributeType(int propertyType, const QString &attribute) const +{ + if (!isPropertyTypeSupported(propertyType)) + return 0; + + if (propertyType == designerFlagTypeId() && attribute == flagsAttributeC) + return designerFlagListTypeId(); + if (propertyType == designerPixmapTypeId() && attribute == defaultResourceAttributeC) + return QMetaType::QPixmap; + if (propertyType == designerIconTypeId() && attribute == defaultResourceAttributeC) + return QMetaType::QIcon; + if (attribute == resettableAttributeC) + return QMetaType::Bool; + if (propertyType == designerStringTypeId() || propertyType == QMetaType::QString) { + if (attribute == validationModesAttributeC) + return QMetaType::Int; + if (attribute == fontAttributeC) + return QMetaType::QFont; + if (attribute == themeAttributeC) + return QMetaType::Bool; + } + if (propertyType == QMetaType::QPalette && attribute == superPaletteAttributeC) + return QMetaType::QPalette; + + return QtVariantPropertyManager::attributeType(propertyType, attribute); +} + +QVariant DesignerPropertyManager::attributeValue(const QtProperty *property, const QString &attribute) const +{ + if (attribute == resettableAttributeC) { + const auto it = m_resetMap.constFind(property); + if (it != m_resetMap.constEnd()) + return it.value(); + } + + if (attribute == flagsAttributeC) { + const auto it = m_flagValues.constFind(property); + if (it != m_flagValues.constEnd()) { + QVariant v; + v.setValue(it.value().flags); + return v; + } + } + if (attribute == validationModesAttributeC) { + const auto it = m_stringAttributes.constFind(property); + if (it != m_stringAttributes.constEnd()) + return it.value(); + } + + if (attribute == fontAttributeC) { + const auto it = m_stringFontAttributes.constFind(property); + if (it != m_stringFontAttributes.constEnd()) + return it.value(); + } + + if (attribute == themeAttributeC) { + const auto it = m_stringThemeAttributes.constFind(property); + if (it != m_stringThemeAttributes.constEnd()) + return it.value(); + } + + if (attribute == themeEnumAttributeC) { + const auto it = m_intThemeEnumAttributes.constFind(property); + if (it != m_intThemeEnumAttributes.constEnd()) + return it.value(); + } + + if (attribute == superPaletteAttributeC) { + const auto it = m_paletteValues.constFind(property); + if (it != m_paletteValues.cend()) + return it.value().superPalette; + } + + if (attribute == defaultResourceAttributeC) { + const auto itPix = m_defaultPixmaps.constFind(property); + if (itPix != m_defaultPixmaps.constEnd()) + return itPix.value(); + + const auto itIcon = m_defaultIcons.constFind(property); + if (itIcon != m_defaultIcons.constEnd()) + return itIcon.value(); + } + + if (attribute == alignDefaultAttribute()) { + Qt::Alignment v = m_alignDefault.value(property, + Qt::Alignment(Qt::AlignLeading | Qt::AlignHCenter)); + return QVariant(uint(v)); + } + + return QtVariantPropertyManager::attributeValue(property, attribute); +} + +void DesignerPropertyManager::setAttribute(QtProperty *property, + const QString &attribute, const QVariant &value) +{ + if (attribute == resettableAttributeC && m_resetMap.contains(property)) { + if (value.userType() != QMetaType::Bool) + return; + const bool val = value.toBool(); + const auto it = m_resetMap.find(property); + if (it.value() == val) + return; + it.value() = val; + emit attributeChanged(variantProperty(property), attribute, value); + return; + } + if (attribute == flagsAttributeC && m_flagValues.contains(property)) { + if (value.userType() != designerFlagListTypeId()) + return; + + const DesignerFlagList flags = qvariant_cast(value); + const auto fit = m_flagValues.find(property); + FlagData data = fit.value(); + if (data.flags == flags) + return; + + const auto pfit = m_propertyToFlags.find(property); + qDeleteAll(std::as_const(pfit.value())); + pfit.value().clear(); + + QList values; + + for (const auto &pair : flags) { + const QString flagName = pair.first; + QtProperty *prop = addProperty(QMetaType::Bool); + prop->setPropertyName(flagName); + property->addSubProperty(prop); + m_propertyToFlags[property].append(prop); + values.append(pair.second); + } + + data.val = 0; + data.flags = flags; + data.values = values; + + fit.value() = data; + + QVariant v; + v.setValue(flags); + emit attributeChanged(property, attribute, v); + + emit propertyChanged(property); + emit QtVariantPropertyManager::valueChanged(property, data.val); + } else if (attribute == validationModesAttributeC && m_stringAttributes.contains(property)) { + if (value.userType() != QMetaType::Int) + return; + + const auto it = m_stringAttributes.find(property); + const int oldValue = it.value(); + + const int newValue = value.toInt(); + + if (oldValue == newValue) + return; + + it.value() = newValue; + + emit attributeChanged(property, attribute, newValue); + } else if (attribute == fontAttributeC && m_stringFontAttributes.contains(property)) { + if (value.userType() != QMetaType::QFont) + return; + + const auto it = m_stringFontAttributes.find(property); + const QFont oldValue = it.value(); + + const QFont newValue = qvariant_cast(value); + + if (oldValue == newValue) + return; + + it.value() = newValue; + + emit attributeChanged(property, attribute, newValue); + } else if (attribute == themeAttributeC && m_stringThemeAttributes.contains(property)) { + if (value.userType() != QMetaType::Bool) + return; + + const auto it = m_stringThemeAttributes.find(property); + const bool oldValue = it.value(); + + const bool newValue = value.toBool(); + + if (oldValue == newValue) + return; + + it.value() = newValue; + + emit attributeChanged(property, attribute, newValue); + } else if (attribute == themeEnumAttributeC && m_intThemeEnumAttributes.contains(property)) { + if (value.userType() != QMetaType::Bool) + return; + + const auto it = m_intThemeEnumAttributes.find(property); + const bool oldValue = it.value(); + + const bool newValue = value.toBool(); + + if (oldValue == newValue) + return; + + it.value() = newValue; + + emit attributeChanged(property, attribute, newValue); + } else if (attribute == superPaletteAttributeC && m_paletteValues.contains(property)) { + if (value.userType() != QMetaType::QPalette) + return; + + QPalette superPalette = qvariant_cast(value); + + const auto it = m_paletteValues.find(property); + PaletteData data = it.value(); + if (data.superPalette == superPalette) + return; + + data.superPalette = superPalette; + // resolve here + const auto mask = data.val.resolveMask(); + data.val = data.val.resolve(superPalette); + data.val.setResolveMask(mask); + + it.value() = data; + + QVariant v; + v.setValue(superPalette); + emit attributeChanged(property, attribute, v); + + emit propertyChanged(property); + emit QtVariantPropertyManager::valueChanged(property, data.val); // if resolve was done, this is also for consistency + } else if (attribute == defaultResourceAttributeC && m_defaultPixmaps.contains(property)) { + if (value.userType() != QMetaType::QPixmap) + return; + + QPixmap defaultPixmap = qvariant_cast(value); + + const auto it = m_defaultPixmaps.find(property); + QPixmap oldDefaultPixmap = it.value(); + if (defaultPixmap.cacheKey() == oldDefaultPixmap.cacheKey()) + return; + + it.value() = defaultPixmap; + + QVariant v = QVariant::fromValue(defaultPixmap); + emit attributeChanged(property, attribute, v); + + emit propertyChanged(property); + } else if (attribute == defaultResourceAttributeC && m_defaultIcons.contains(property)) { + if (value.userType() != QMetaType::QIcon) + return; + + QIcon defaultIcon = qvariant_cast(value); + + const auto it = m_defaultIcons.find(property); + QIcon oldDefaultIcon = it.value(); + if (defaultIcon.cacheKey() == oldDefaultIcon.cacheKey()) + return; + + it.value() = defaultIcon; + + qdesigner_internal::PropertySheetIconValue icon = m_iconValues.value(property); + if (icon.paths().isEmpty()) { + const auto &subIconProperties = m_propertyToIconSubProperties.value(property); + for (auto itSub = subIconProperties.cbegin(), end = subIconProperties.cend(); itSub != end; ++itSub) { + const auto pair = itSub.key(); + QtProperty *subProp = itSub.value(); + setAttribute(subProp, defaultResourceAttributeC, + defaultIcon.pixmap(16, 16, pair.first, pair.second)); + } + } + + QVariant v = QVariant::fromValue(defaultIcon); + emit attributeChanged(property, attribute, v); + + emit propertyChanged(property); + } else if (attribute == alignDefaultAttribute()) { + m_alignDefault[property] = Qt::Alignment(value.toUInt()); + } + QtVariantPropertyManager::setAttribute(property, attribute, value); +} + +int DesignerPropertyManager::designerFlagTypeId() +{ + static const int rc = qMetaTypeId(); + return rc; +} + +int DesignerPropertyManager::designerFlagListTypeId() +{ + static const int rc = qMetaTypeId(); + return rc; +} + +int DesignerPropertyManager::designerAlignmentTypeId() +{ + static const int rc = qMetaTypeId(); + return rc; +} + +int DesignerPropertyManager::designerPixmapTypeId() +{ + return qMetaTypeId(); +} + +int DesignerPropertyManager::designerIconTypeId() +{ + return qMetaTypeId(); +} + +int DesignerPropertyManager::designerStringTypeId() +{ + return qMetaTypeId(); +} + +int DesignerPropertyManager::designerStringListTypeId() +{ + return qMetaTypeId(); +} + +int DesignerPropertyManager::designerKeySequenceTypeId() +{ + return qMetaTypeId(); +} + +QString DesignerPropertyManager::alignDefaultAttribute() +{ + return u"alignDefault"_s; +} + +uint DesignerPropertyManager::alignDefault(const QtVariantProperty *prop) +{ + return prop->attributeValue(DesignerPropertyManager::alignDefaultAttribute()).toUInt(); +} + +bool DesignerPropertyManager::isPropertyTypeSupported(int propertyType) const +{ + switch (propertyType) { + case QMetaType::QPalette: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::QUrl: + case QMetaType::QByteArray: + case QMetaType::QStringList: + case QMetaType::QBrush: + return true; + default: + break; + } + + if (propertyType == designerFlagTypeId()) + return true; + if (propertyType == designerAlignmentTypeId()) + return true; + if (propertyType == designerPixmapTypeId()) + return true; + if (propertyType == designerIconTypeId()) + return true; + if (propertyType == designerStringTypeId() || propertyType == designerStringListTypeId()) + return true; + if (propertyType == designerKeySequenceTypeId()) + return true; + + return QtVariantPropertyManager::isPropertyTypeSupported(propertyType); +} + +QString DesignerPropertyManager::valueText(const QtProperty *property) const +{ + if (m_flagValues.contains(property)) { + const FlagData data = m_flagValues.value(property); + const uint v = data.val; + QString valueStr; + for (const DesignerIntPair &p : data.flags) { + const uint val = p.second; + const bool checked = (val == 0) ? (v == 0) : ((val & v) == val); + if (checked) { + if (!valueStr.isEmpty()) + valueStr += u'|'; + valueStr += p.first; + } + } + return valueStr; + } + if (m_alignValues.contains(property)) { + const uint v = m_alignValues.value(property); + return tr("%1, %2").arg(indexHToString(alignToIndexH(v)), + indexVToString(alignToIndexV(v))); + } + if (m_paletteValues.contains(property)) { + const PaletteData data = m_paletteValues.value(property); + const auto mask = data.val.resolveMask(); + if (mask) + return tr("Customized (%n roles)", nullptr, bitCount(mask)); + static const QString inherited = tr("Inherited"); + return inherited; + } + if (m_iconValues.contains(property)) + return PixmapEditor::displayText(m_iconValues.value(property)); + if (m_pixmapValues.contains(property)) { + const QString path = m_pixmapValues.value(property).path(); + if (path.isEmpty()) + return QString(); + return QFileInfo(path).fileName(); + } + if (m_intValues.contains(property)) { + const auto value = m_intValues.value(property); + if (m_intThemeEnumAttributes.value(property)) + return IconThemeEnumEditor::iconName(value); + return QString::number(value); + } + if (m_uintValues.contains(property)) + return QString::number(m_uintValues.value(property)); + if (m_longLongValues.contains(property)) + return QString::number(m_longLongValues.value(property)); + if (m_uLongLongValues.contains(property)) + return QString::number(m_uLongLongValues.value(property)); + if (m_urlValues.contains(property)) + return m_urlValues.value(property).toString(); + if (m_byteArrayValues.contains(property)) + return QString::fromUtf8(m_byteArrayValues.value(property)); + const int vType = QtVariantPropertyManager::valueType(property); + if (vType == QMetaType::QString || vType == designerStringTypeId()) { + const QString str = (QtVariantPropertyManager::valueType(property) == QMetaType::QString) + ? value(property).toString() : qvariant_cast(value(property)).value(); + const int validationMode = attributeValue(property, validationModesAttributeC).toInt(); + return TextPropertyEditor::stringToEditorString(str, static_cast(validationMode)); + } + if (vType == QMetaType::QStringList || vType == designerStringListTypeId()) { + QVariant v = value(property); + const QStringList list = v.metaType().id() == QMetaType::QStringList + ? v.toStringList() : qvariant_cast(v).value(); + return list.join("; "_L1); + } + if (vType == designerKeySequenceTypeId()) { + return qvariant_cast(value(property)).value().toString(QKeySequence::NativeText); + } + if (vType == QMetaType::Bool) { + return QString(); + } + + QString rc; + if (m_brushManager.valueText(property, &rc)) + return rc; + return QtVariantPropertyManager::valueText(property); +} + +void DesignerPropertyManager::reloadResourceProperties() +{ + DesignerIconCache *iconCache = nullptr; + for (auto itIcon = m_iconValues.cbegin(), end = m_iconValues.cend(); itIcon!= end; ++itIcon) { + auto *property = itIcon.key(); + const PropertySheetIconValue &icon = itIcon.value(); + + QIcon defaultIcon = m_defaultIcons.value(property); + if (!icon.paths().isEmpty()) { + if (!iconCache) { + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + qdesigner_internal::FormWindowBase *fwb = qobject_cast(formWindow); + iconCache = fwb->iconCache(); + } + if (iconCache) + defaultIcon = iconCache->icon(icon); + } + + const auto &subProperties = m_propertyToIconSubProperties.value(property); + for (auto itSub = subProperties.cbegin(), end = subProperties.cend(); itSub != end; ++itSub) { + const auto pair = itSub.key(); + QtVariantProperty *subProperty = variantProperty(itSub.value()); + subProperty->setAttribute(defaultResourceAttributeC, + defaultIcon.pixmap(16, 16, pair.first, pair.second)); + } + + auto *ncProperty = const_cast(property); + emit propertyChanged(ncProperty); + emit QtVariantPropertyManager::valueChanged(ncProperty, QVariant::fromValue(itIcon.value())); + } + for (auto itPix = m_pixmapValues.cbegin(), end = m_pixmapValues.cend(); itPix != end; ++itPix) { + auto *property = const_cast(itPix.key()); + emit propertyChanged(property); + emit QtVariantPropertyManager::valueChanged(property, QVariant::fromValue(itPix.value())); + } +} + +QIcon DesignerPropertyManager::valueIcon(const QtProperty *property) const +{ + if (m_iconValues.contains(property)) { + if (!property->isModified()) + return m_defaultIcons.value(property).pixmap(16, 16); + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + qdesigner_internal::FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + return fwb->iconCache()->icon(m_iconValues.value(property)).pixmap(16, 16); + } else if (m_pixmapValues.contains(property)) { + if (!property->isModified()) + return m_defaultPixmaps.value(property); + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + qdesigner_internal::FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + return fwb->pixmapCache()->pixmap(m_pixmapValues.value(property)); + } else if (m_stringThemeAttributes.value(property, false)) { + return QIcon::fromTheme(value(property).toString()); + } else { + QIcon rc; + if (m_brushManager.valueIcon(property, &rc)) + return rc; + } + + return QtVariantPropertyManager::valueIcon(property); +} + +QVariant DesignerPropertyManager::value(const QtProperty *property) const +{ + if (m_flagValues.contains(property)) + return m_flagValues.value(property).val; + if (m_alignValues.contains(property)) + return m_alignValues.value(property); + if (m_paletteValues.contains(property)) + return m_paletteValues.value(property).val; + if (m_iconValues.contains(property)) + return QVariant::fromValue(m_iconValues.value(property)); + if (m_pixmapValues.contains(property)) + return QVariant::fromValue(m_pixmapValues.value(property)); + QVariant rc; + if (m_stringManager.value(property, &rc) + || m_keySequenceManager.value(property, &rc) + || m_stringListManager.value(property, &rc) + || m_brushManager.value(property, &rc)) + return rc; + if (m_intValues.contains(property)) + return m_intValues.value(property); + if (m_uintValues.contains(property)) + return m_uintValues.value(property); + if (m_longLongValues.contains(property)) + return m_longLongValues.value(property); + if (m_uLongLongValues.contains(property)) + return m_uLongLongValues.value(property); + if (m_urlValues.contains(property)) + return m_urlValues.value(property); + if (m_byteArrayValues.contains(property)) + return m_byteArrayValues.value(property); + + return QtVariantPropertyManager::value(property); +} + +int DesignerPropertyManager::valueType(int propertyType) const +{ + switch (propertyType) { + case QMetaType::QPalette: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::QUrl: + case QMetaType::QByteArray: + case QMetaType::QStringList: + case QMetaType::QBrush: + return propertyType; + default: + break; + } + if (propertyType == designerFlagTypeId()) + return QMetaType::UInt; + if (propertyType == designerAlignmentTypeId()) + return QMetaType::UInt; + if (propertyType == designerPixmapTypeId()) + return propertyType; + if (propertyType == designerIconTypeId()) + return propertyType; + if (propertyType == designerStringTypeId() || propertyType == designerStringListTypeId()) + return propertyType; + if (propertyType == designerKeySequenceTypeId()) + return propertyType; + return QtVariantPropertyManager::valueType(propertyType); +} + +void DesignerPropertyManager::setValue(QtProperty *property, const QVariant &value) +{ + int subResult = m_stringManager.setValue(this, property, designerStringTypeId(), value); + if (subResult == NoMatch) + subResult = m_stringListManager.setValue(this, property, designerStringListTypeId(), value); + if (subResult == NoMatch) + subResult = m_keySequenceManager.setValue(this, property, designerKeySequenceTypeId(), value); + if (subResult == NoMatch) + subResult = m_brushManager.setValue(this, property, value); + if (subResult != NoMatch) { + if (subResult == Changed) { + emit QtVariantPropertyManager::valueChanged(property, value); + emit propertyChanged(property); + } + return; + } + + const auto fit = m_flagValues.find(property); + + if (fit != m_flagValues.end()) { + if (value.metaType().id() != QMetaType::UInt && !value.canConvert()) + return; + + const uint v = value.toUInt(); + + FlagData data = fit.value(); + if (data.val == v) + return; + + // set Value + + const auto values = data.values; + const auto subFlags = m_propertyToFlags.value(property); + const qsizetype subFlagCount = subFlags.size(); + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + const uint val = values.at(i); + const bool checked = (val == 0) ? (v == 0) : ((val & v) == val); + subFlag->setValue(checked); + } + + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + const uint val = values.at(i); + const bool checked = (val == 0) ? (v == 0) : ((val & v) == val); + bool enabled = true; + if (val == 0) { + if (checked) + enabled = false; + } else if (bitCount(val) > 1) { + // Disabled if all flags contained in the mask are checked + uint currentMask = 0; + for (qsizetype j = 0; j < subFlagCount; ++j) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(j)); + if (bitCount(values.at(j)) == 1) + currentMask |= subFlag->value().toBool() ? values.at(j) : 0; + } + if ((currentMask & values.at(i)) == values.at(i)) + enabled = false; + } + subFlag->setEnabled(enabled); + } + + data.val = v; + fit.value() = data; + + emit QtVariantPropertyManager::valueChanged(property, data.val); + emit propertyChanged(property); + + return; + } + if (m_alignValues.contains(property)) { + if (value.metaType().id() != QMetaType::UInt && !value.canConvert()) + return; + + const uint v = value.toUInt(); + + uint val = m_alignValues.value(property); + + if (val == v) + return; + + QtVariantProperty *alignH = variantProperty(m_propertyToAlignH.value(property)); + QtVariantProperty *alignV = variantProperty(m_propertyToAlignV.value(property)); + + if (alignH) + alignH->setValue(alignToIndexH(v)); + if (alignV) + alignV->setValue(alignToIndexV(v)); + + m_alignValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_paletteValues.contains(property)) { + if (value.metaType().id() != QMetaType::QPalette && !value.canConvert()) + return; + + QPalette p = qvariant_cast(value); + + PaletteData data = m_paletteValues.value(property); + + const auto mask = p.resolveMask(); + p = p.resolve(data.superPalette); + p.setResolveMask(mask); + + if (data.val == p && data.val.resolveMask() == p.resolveMask()) + return; + + data.val = p; + m_paletteValues[property] = data; + + emit QtVariantPropertyManager::valueChanged(property, data.val); + emit propertyChanged(property); + + return; + } + if (m_iconValues.contains(property)) { + if (value.userType() != designerIconTypeId()) + return; + + const PropertySheetIconValue icon = qvariant_cast(value); + + const PropertySheetIconValue oldIcon = m_iconValues.value(property); + if (icon == oldIcon) + return; + + m_iconValues[property] = icon; + + QIcon defaultIcon = m_defaultIcons.value(property); + if (!icon.paths().isEmpty()) { + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + qdesigner_internal::FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + defaultIcon = fwb->iconCache()->icon(icon); + } + + const auto &iconPaths = icon.paths(); + + const auto &subProperties = m_propertyToIconSubProperties.value(property); + for (auto itSub = subProperties.cbegin(), end = subProperties.cend(); itSub != end; ++itSub) { + const auto pair = itSub.key(); + QtVariantProperty *subProperty = variantProperty(itSub.value()); + bool hasPath = iconPaths.contains(pair); + subProperty->setModified(hasPath); + subProperty->setValue(QVariant::fromValue(iconPaths.value(pair))); + subProperty->setAttribute(defaultResourceAttributeC, + defaultIcon.pixmap(16, 16, pair.first, pair.second)); + } + QtVariantProperty *themeSubProperty = variantProperty(m_propertyToTheme.value(property)); + if (themeSubProperty) { + const QString theme = icon.theme(); + themeSubProperty->setModified(!theme.isEmpty()); + themeSubProperty->setValue(theme); + } + QtVariantProperty *themeEnumSubProperty = variantProperty(m_propertyToThemeEnum.value(property)); + if (themeEnumSubProperty) { + const int themeEnum = icon.themeEnum(); + themeEnumSubProperty->setModified(themeEnum != -1); + themeEnumSubProperty->setValue(QVariant(themeEnum)); + } + + emit QtVariantPropertyManager::valueChanged(property, QVariant::fromValue(icon)); + emit propertyChanged(property); + + QString toolTip; + const auto itNormalOff = iconPaths.constFind({QIcon::Normal, QIcon::Off}); + if (itNormalOff != iconPaths.constEnd()) + toolTip = itNormalOff.value().path(); + // valueText() only show the file name; show full path as ToolTip. + property->setToolTip(QDir::toNativeSeparators(toolTip)); + + return; + } + if (m_pixmapValues.contains(property)) { + if (value.userType() != designerPixmapTypeId()) + return; + + const PropertySheetPixmapValue pixmap = qvariant_cast(value); + + const PropertySheetPixmapValue oldPixmap = m_pixmapValues.value(property); + if (pixmap == oldPixmap) + return; + + m_pixmapValues[property] = pixmap; + + emit QtVariantPropertyManager::valueChanged(property, QVariant::fromValue(pixmap)); + emit propertyChanged(property); + + // valueText() only show the file name; show full path as ToolTip. + property->setToolTip(QDir::toNativeSeparators(pixmap.path())); + + return; + } + if (m_intValues.contains(property)) { + if (value.metaType().id() != QMetaType::Int && !value.canConvert()) + return; + + const int v = value.toInt(nullptr); + + const int oldValue = m_intValues.value(property); + if (v == oldValue) + return; + + m_intValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_uintValues.contains(property)) { + if (value.metaType().id() != QMetaType::UInt && !value.canConvert()) + return; + + const uint v = value.toUInt(nullptr); + + const uint oldValue = m_uintValues.value(property); + if (v == oldValue) + return; + + m_uintValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_longLongValues.contains(property)) { + if (value.metaType().id() != QMetaType::LongLong && !value.canConvert()) + return; + + const qlonglong v = value.toLongLong(nullptr); + + const qlonglong oldValue = m_longLongValues.value(property); + if (v == oldValue) + return; + + m_longLongValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_uLongLongValues.contains(property)) { + if (value.metaType().id() != QMetaType::ULongLong && !value.canConvert()) + return; + + qulonglong v = value.toULongLong(nullptr); + + qulonglong oldValue = m_uLongLongValues.value(property); + if (v == oldValue) + return; + + m_uLongLongValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_urlValues.contains(property)) { + if (value.metaType().id() != QMetaType::QUrl && !value.canConvert()) + return; + + const QUrl v = value.toUrl(); + + const QUrl oldValue = m_urlValues.value(property); + if (v == oldValue) + return; + + m_urlValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_byteArrayValues.contains(property)) { + if (value.metaType().id() != QMetaType::QByteArray && !value.canConvert()) + return; + + const QByteArray v = value.toByteArray(); + + const QByteArray oldValue = m_byteArrayValues.value(property); + if (v == oldValue) + return; + + m_byteArrayValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + m_fontManager.setValue(this, property, value); + QtVariantPropertyManager::setValue(property, value); + if (QtVariantPropertyManager::valueType(property) == QMetaType::Bool) + property->setToolTip(QtVariantPropertyManager::valueText(property)); +} + +void DesignerPropertyManager::initializeProperty(QtProperty *property) +{ + static bool creatingIconProperties = false; + + m_resetMap[property] = false; + + const int type = propertyType(property); + m_fontManager.preInitializeProperty(property, type, m_resetMap); + switch (type) { + case QMetaType::QPalette: + m_paletteValues[property] = PaletteData(); + break; + case QMetaType::QString: + m_stringAttributes[property] = ValidationSingleLine; + m_stringFontAttributes[property] = QApplication::font(); + m_stringThemeAttributes[property] = false; + break; + case QMetaType::Int: + if (creatingIconProperties) { + m_intValues[property] = 0; + m_intThemeEnumAttributes[property] = false; + } + break; + case QMetaType::UInt: + m_uintValues[property] = 0; + break; + case QMetaType::LongLong: + m_longLongValues[property] = 0; + break; + case QMetaType::ULongLong: + m_uLongLongValues[property] = 0; + break; + case QMetaType::QUrl: + m_urlValues[property] = QUrl(); + break; + case QMetaType::QByteArray: + m_byteArrayValues[property] = QByteArray(); + break; + case QMetaType::QBrush: + m_brushManager.initializeProperty(this, property, enumTypeId()); + break; + default: + if (type == designerFlagTypeId()) { + m_flagValues[property] = FlagData(); + m_propertyToFlags[property] = QList(); + } else if (type == designerAlignmentTypeId()) { + const uint align = Qt::AlignLeft | Qt::AlignVCenter; + m_alignValues[property] = align; + + QtVariantProperty *alignH = addProperty(enumTypeId(), tr("Horizontal")); + QStringList namesH; + namesH << indexHToString(0) << indexHToString(1) << indexHToString(2) << indexHToString(3); + alignH->setAttribute(u"enumNames"_s, namesH); + alignH->setValue(alignToIndexH(align)); + m_propertyToAlignH[property] = alignH; + m_alignHToProperty[alignH] = property; + property->addSubProperty(alignH); + + QtVariantProperty *alignV = addProperty(enumTypeId(), tr("Vertical")); + QStringList namesV; + namesV << indexVToString(0) << indexVToString(1) << indexVToString(2); + alignV->setAttribute(u"enumNames"_s, namesV); + alignV->setValue(alignToIndexV(align)); + m_propertyToAlignV[property] = alignV; + m_alignVToProperty[alignV] = property; + property->addSubProperty(alignV); + } else if (type == designerPixmapTypeId()) { + m_pixmapValues[property] = PropertySheetPixmapValue(); + m_defaultPixmaps[property] = QPixmap(); + } else if (type == designerIconTypeId()) { + creatingIconProperties = true; + m_iconValues[property] = PropertySheetIconValue(); + m_defaultIcons[property] = QIcon(); + + QtVariantProperty *themeEnumProp = addProperty(QMetaType::Int, tr("Theme")); + m_intValues[themeEnumProp] = -1; + themeEnumProp->setAttribute(themeEnumAttributeC, true); + m_propertyToThemeEnum[property] = themeEnumProp; + m_resetMap[themeEnumProp] = true; + property->addSubProperty(themeEnumProp); + + QtVariantProperty *themeProp = addProperty(QMetaType::QString, tr("XDG Theme")); + themeProp->setAttribute(themeAttributeC, true); + m_propertyToTheme[property] = themeProp; + m_resetMap[themeProp] = true; + property->addSubProperty(themeProp); + + createIconSubProperty(property, QIcon::Normal, QIcon::Off, tr("Normal Off")); + createIconSubProperty(property, QIcon::Normal, QIcon::On, tr("Normal On")); + createIconSubProperty(property, QIcon::Disabled, QIcon::Off, tr("Disabled Off")); + createIconSubProperty(property, QIcon::Disabled, QIcon::On, tr("Disabled On")); + createIconSubProperty(property, QIcon::Active, QIcon::Off, tr("Active Off")); + createIconSubProperty(property, QIcon::Active, QIcon::On, tr("Active On")); + createIconSubProperty(property, QIcon::Selected, QIcon::Off, tr("Selected Off")); + createIconSubProperty(property, QIcon::Selected, QIcon::On, tr("Selected On")); + creatingIconProperties = false; + } else if (type == designerStringTypeId()) { + m_stringManager.initialize(this, property, PropertySheetStringValue()); + m_stringAttributes.insert(property, ValidationMultiLine); + m_stringFontAttributes.insert(property, QApplication::font()); + m_stringThemeAttributes.insert(property, false); + } else if (type == designerStringListTypeId()) { + m_stringListManager.initialize(this, property, PropertySheetStringListValue()); + } else if (type == designerKeySequenceTypeId()) { + m_keySequenceManager.initialize(this, property, PropertySheetKeySequenceValue()); + } + } + + QtVariantPropertyManager::initializeProperty(property); + m_fontManager.postInitializeProperty(this, property, type, DesignerPropertyManager::enumTypeId()); + if (type == QMetaType::Double) + setAttribute(property, u"decimals"_s, 6); +} + +void DesignerPropertyManager::createIconSubProperty(QtProperty *iconProperty, QIcon::Mode mode, QIcon::State state, const QString &subName) +{ + const auto pair = std::make_pair(mode, state); + QtVariantProperty *subProp = addProperty(DesignerPropertyManager::designerPixmapTypeId(), subName); + m_propertyToIconSubProperties[iconProperty][pair] = subProp; + m_iconSubPropertyToState[subProp] = pair; + m_resetMap[subProp] = true; + iconProperty->addSubProperty(subProp); +} + +void DesignerPropertyManager::uninitializeProperty(QtProperty *property) +{ + m_resetMap.remove(property); + + const auto pfit = m_propertyToFlags.find(property); + if (pfit != m_propertyToFlags.end()) { + qDeleteAll(std::as_const(pfit.value())); + m_propertyToFlags.erase(pfit); + } + m_flagValues.remove(property); + + QtProperty *alignH = m_propertyToAlignH.value(property); + if (alignH) { + delete alignH; + m_alignHToProperty.remove(alignH); + } + QtProperty *alignV = m_propertyToAlignV.value(property); + if (alignV) { + delete alignV; + m_alignVToProperty.remove(alignV); + } + + m_stringManager.uninitialize(property); + m_stringListManager.uninitialize(property); + m_keySequenceManager.uninitialize(property); + + if (QtProperty *iconTheme = m_propertyToTheme.value(property)) + delete iconTheme; + + if (QtProperty *iconThemeEnum = m_propertyToThemeEnum.value(property)) + delete iconThemeEnum; + + m_propertyToAlignH.remove(property); + m_propertyToAlignV.remove(property); + + m_stringAttributes.remove(property); + m_stringFontAttributes.remove(property); + + m_paletteValues.remove(property); + + m_iconValues.remove(property); + m_defaultIcons.remove(property); + + m_pixmapValues.remove(property); + m_defaultPixmaps.remove(property); + + const auto &iconSubProperties = m_propertyToIconSubProperties.value(property); + for (auto itIcon = iconSubProperties.cbegin(), end = iconSubProperties.cend(); itIcon != end; ++itIcon) { + QtProperty *subIcon = itIcon.value(); + delete subIcon; + m_iconSubPropertyToState.remove(subIcon); + } + m_propertyToIconSubProperties.remove(property); + m_iconSubPropertyToState.remove(property); + + m_intValues.remove(property); + m_uintValues.remove(property); + m_longLongValues.remove(property); + m_uLongLongValues.remove(property); + m_urlValues.remove(property); + m_byteArrayValues.remove(property); + + m_fontManager.uninitializeProperty(property); + m_brushManager.uninitializeProperty(property); + + QtVariantPropertyManager::uninitializeProperty(property); +} + +bool DesignerPropertyManager::resetTextAlignmentProperty(QtProperty *property) +{ + const auto it = m_alignDefault.constFind(property); + if (it == m_alignDefault.cend()) + return false; + QtVariantProperty *alignProperty = variantProperty(property); + alignProperty->setValue(DesignerPropertyManager::alignDefault(alignProperty)); + alignProperty->setModified(false); + return true; +} + +bool DesignerPropertyManager::resetFontSubProperty(QtProperty *property) +{ + return m_fontManager.resetFontSubProperty(this, property); +} + +bool DesignerPropertyManager::resetIconSubProperty(QtProperty *property) +{ + auto *parentItem = property->parentProperty(); + if (!m_iconValues.contains(parentItem)) + return false; + + if (m_pixmapValues.contains(property)) { + QtVariantProperty *pixmapProperty = variantProperty(property); + pixmapProperty->setValue(QVariant::fromValue(PropertySheetPixmapValue())); + return true; + } + if (attributeValue(property, themeAttributeC).toBool()) { + QtVariantProperty *themeProperty = variantProperty(property); + themeProperty->setValue(QString()); + return true; + } + if (attributeValue(property, themeEnumAttributeC).toBool()) { + QtVariantProperty *themeEnumProperty = variantProperty(property); + themeEnumProperty->setValue(-1); + return true; + } + + return false; +} + +// -------- DesignerEditorFactory +DesignerEditorFactory::DesignerEditorFactory(QDesignerFormEditorInterface *core, QObject *parent) : + QtVariantEditorFactory(parent), + m_resetDecorator(new ResetDecorator(core, this)), + m_core(core) +{ + connect(m_resetDecorator, &ResetDecorator::resetProperty, + this, &DesignerEditorFactory::resetProperty); +} + +DesignerEditorFactory::~DesignerEditorFactory() = default; + +void DesignerEditorFactory::setSpacing(int spacing) +{ + m_spacing = spacing; + m_resetDecorator->setSpacing(spacing); +} + +void DesignerEditorFactory::setFormWindowBase(qdesigner_internal::FormWindowBase *fwb) +{ + m_fwb = fwb; + DesignerPixmapCache *cache = nullptr; + if (fwb) + cache = fwb->pixmapCache(); + for (auto it = m_editorToPixmapProperty.cbegin(), end = m_editorToPixmapProperty.cend(); it != end; ++it) + it.key()->setPixmapCache(cache); + for (auto it = m_editorToIconProperty.cbegin(), end = m_editorToIconProperty.cend(); it != end; ++it) + it.key()->setPixmapCache(cache); +} + +void DesignerEditorFactory::connectPropertyManager(QtVariantPropertyManager *manager) +{ + m_resetDecorator->connectPropertyManager(manager); + connect(manager, &QtVariantPropertyManager::attributeChanged, + this, &DesignerEditorFactory::slotAttributeChanged); + connect(manager, &QtVariantPropertyManager::valueChanged, + this, &DesignerEditorFactory::slotValueChanged); + connect(manager, &QtVariantPropertyManager::propertyChanged, + this, &DesignerEditorFactory::slotPropertyChanged); + QtVariantEditorFactory::connectPropertyManager(manager); +} + +void DesignerEditorFactory::disconnectPropertyManager(QtVariantPropertyManager *manager) +{ + m_resetDecorator->disconnectPropertyManager(manager); + disconnect(manager, &QtVariantPropertyManager::attributeChanged, + this, &DesignerEditorFactory::slotAttributeChanged); + disconnect(manager, &QtVariantPropertyManager::valueChanged, + this, &DesignerEditorFactory::slotValueChanged); + disconnect(manager, &QtVariantPropertyManager::propertyChanged, + this, &DesignerEditorFactory::slotPropertyChanged); + QtVariantEditorFactory::disconnectPropertyManager(manager); +} + +// A helper that calls a setter with a value on a pointer list of editor objects. +// Could use QList instead of EditorContainer/Editor, but that crashes VS 6. +template +static inline void applyToEditors(const EditorContainer &list, void (Editor::*setter)(SetterParameter), const Value &value) +{ + if (list.isEmpty()) { + return; + } + for (auto it = list.constBegin(), end = list.constEnd(); it != end; ++it) { + Editor &editor = *(*it); + (editor.*setter)(value); + } +} + +void DesignerEditorFactory::slotAttributeChanged(QtProperty *property, const QString &attribute, const QVariant &value) +{ + QtVariantPropertyManager *manager = propertyManager(property); + const int type = manager->propertyType(property); + if (type == DesignerPropertyManager::designerPixmapTypeId() && attribute == defaultResourceAttributeC) { + const QPixmap pixmap = qvariant_cast(value); + applyToEditors(m_pixmapPropertyToEditors.value(property), &PixmapEditor::setDefaultPixmap, pixmap); + } else if (type == DesignerPropertyManager::designerStringTypeId() || type == QMetaType::QString) { + if (attribute == validationModesAttributeC) { + const TextPropertyValidationMode validationMode = static_cast(value.toInt()); + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setTextPropertyValidationMode, validationMode); + } + if (attribute == fontAttributeC) { + const QFont font = qvariant_cast(value); + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setRichTextDefaultFont, font); + } + if (attribute == themeAttributeC) { + const bool themeEnabled = value.toBool(); + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setIconThemeModeEnabled, themeEnabled); + } + } else if (type == QMetaType::QPalette && attribute == superPaletteAttributeC) { + const QPalette palette = qvariant_cast(value); + applyToEditors(m_palettePropertyToEditors.value(property), &PaletteEditorButton::setSuperPalette, palette); + } +} + +void DesignerEditorFactory::slotPropertyChanged(QtProperty *property) +{ + QtVariantPropertyManager *manager = propertyManager(property); + const int type = manager->propertyType(property); + if (type == DesignerPropertyManager::designerIconTypeId()) { + QIcon defaultPixmap; + if (!property->isModified()) { + const auto attributeValue = manager->attributeValue(property, defaultResourceAttributeC); + defaultPixmap = attributeValue.value(); + } else if (m_fwb) { + const auto value = manager->value(property); + defaultPixmap = m_fwb->iconCache()->icon(value.value()); + } + const auto editors = m_iconPropertyToEditors.value(property); + for (PixmapEditor *editor : editors) + editor->setDefaultPixmapIcon(defaultPixmap); + } +} + +void DesignerEditorFactory::slotValueChanged(QtProperty *property, const QVariant &value) +{ + if (m_changingPropertyValue) + return; + + QtVariantPropertyManager *manager = propertyManager(property); + const int type = manager->propertyType(property); + switch (type) { + case QMetaType::QString: + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setText, value.toString()); + break; + case QMetaType::QPalette: + applyToEditors(m_palettePropertyToEditors.value(property), &PaletteEditorButton::setPalette, qvariant_cast(value)); + break; + case QMetaType::Int: { + auto it = m_intPropertyToComboEditors.constFind(property); + if (it != m_intPropertyToComboEditors.cend()) + applyToEditors(it.value(), &QComboBox::setCurrentIndex, value.toInt()); + } + break; + case QMetaType::UInt: + applyToEditors(m_uintPropertyToEditors.value(property), &QLineEdit::setText, QString::number(value.toUInt())); + break; + case QMetaType::LongLong: + applyToEditors(m_longLongPropertyToEditors.value(property), &QLineEdit::setText, QString::number(value.toLongLong())); + break; + case QMetaType::ULongLong: + applyToEditors(m_uLongLongPropertyToEditors.value(property), &QLineEdit::setText, QString::number(value.toULongLong())); + break; + case QMetaType::QUrl: + applyToEditors(m_urlPropertyToEditors.value(property), &TextEditor::setText, value.toUrl().toString()); + break; + case QMetaType::QByteArray: + applyToEditors(m_byteArrayPropertyToEditors.value(property), &TextEditor::setText, QString::fromUtf8(value.toByteArray())); + break; + case QMetaType::QStringList: + applyToEditors(m_stringListPropertyToEditors.value(property), &StringListEditorButton::setStringList, value.toStringList()); + break; + default: + if (type == DesignerPropertyManager::designerIconTypeId()) { + PropertySheetIconValue iconValue = qvariant_cast(value); + applyToEditors(m_iconPropertyToEditors.value(property), &PixmapEditor::setTheme, iconValue.theme()); + applyToEditors(m_iconPropertyToEditors.value(property), &PixmapEditor::setThemeEnum, iconValue.themeEnum()); + applyToEditors(m_iconPropertyToEditors.value(property), &PixmapEditor::setPath, iconValue.pixmap(QIcon::Normal, QIcon::Off).path()); + } else if (type == DesignerPropertyManager::designerPixmapTypeId()) { + applyToEditors(m_pixmapPropertyToEditors.value(property), &PixmapEditor::setPath, qvariant_cast(value).path()); + } else if (type == DesignerPropertyManager::designerStringTypeId()) { + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setText, qvariant_cast(value).value()); + } else if (type == DesignerPropertyManager::designerStringListTypeId()) { + applyToEditors(m_stringListPropertyToEditors.value(property), &StringListEditorButton::setStringList, qvariant_cast(value).value()); + } else if (type == DesignerPropertyManager::designerKeySequenceTypeId()) { + applyToEditors(m_keySequencePropertyToEditors.value(property), &QKeySequenceEdit::setKeySequence, qvariant_cast(value).value()); + } + break; + } +} + +TextEditor *DesignerEditorFactory::createTextEditor(QWidget *parent, TextPropertyValidationMode vm, const QString &value) +{ + TextEditor *rc = new TextEditor(m_core, parent); + rc->setText(value); + rc->setSpacing(m_spacing); + rc->setTextPropertyValidationMode(vm); + connect(rc, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + return rc; +} + +QWidget *DesignerEditorFactory::createEditor(QtVariantPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + QWidget *editor = nullptr; + const int type = manager->propertyType(property); + switch (type) { + case QMetaType::Bool: { + editor = QtVariantEditorFactory::createEditor(manager, property, parent); + QtBoolEdit *boolEdit = qobject_cast(editor); + if (boolEdit) + boolEdit->setTextVisible(false); + } + break; + case QMetaType::QString: { + const int itvm = manager->attributeValue(property, validationModesAttributeC).toInt(); + const auto tvm = static_cast(itvm); + TextEditor *ed = createTextEditor(parent, tvm, manager->value(property).toString()); + const QVariant richTextDefaultFont = manager->attributeValue(property, fontAttributeC); + if (richTextDefaultFont.metaType().id() == QMetaType::QFont) + ed->setRichTextDefaultFont(qvariant_cast(richTextDefaultFont)); + const bool themeEnabled = manager->attributeValue(property, themeAttributeC).toBool(); + ed->setIconThemeModeEnabled(themeEnabled); + m_stringPropertyToEditors[property].append(ed); + m_editorToStringProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &TextEditor::textChanged, this, &DesignerEditorFactory::slotStringTextChanged); + editor = ed; + } + break; + case QMetaType::QPalette: { + PaletteEditorButton *ed = new PaletteEditorButton(m_core, qvariant_cast(manager->value(property)), parent); + ed->setSuperPalette(qvariant_cast(manager->attributeValue(property, superPaletteAttributeC))); + m_palettePropertyToEditors[property].append(ed); + m_editorToPaletteProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &PaletteEditorButton::paletteChanged, this, &DesignerEditorFactory::slotPaletteChanged); + editor = ed; + } + break; + case QMetaType::Int: + if (manager->attributeValue(property, themeEnumAttributeC).toBool()) { + auto *ed = IconThemeEnumEditor::createComboBox(parent); + ed->setCurrentIndex(manager->value(property).toInt()); + connect(ed, &QComboBox::currentIndexChanged, this, + &DesignerEditorFactory::slotIntChanged); + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + m_intPropertyToComboEditors[property].append(ed); + m_comboEditorToIntProperty.insert(ed, property); + editor = ed; + } else { + editor = QtVariantEditorFactory::createEditor(manager, property, parent); + } + break; + case QMetaType::UInt: { + QLineEdit *ed = new QLineEdit(parent); + ed->setValidator(new QULongLongValidator(0, UINT_MAX, ed)); + ed->setText(QString::number(manager->value(property).toUInt())); + m_uintPropertyToEditors[property].append(ed); + m_editorToUintProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &QLineEdit::textChanged, this, &DesignerEditorFactory::slotUintChanged); + editor = ed; + } + break; + case QMetaType::LongLong: { + QLineEdit *ed = new QLineEdit(parent); + ed->setValidator(new QLongLongValidator(ed)); + ed->setText(QString::number(manager->value(property).toLongLong())); + m_longLongPropertyToEditors[property].append(ed); + m_editorToLongLongProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &QLineEdit::textChanged, this, &DesignerEditorFactory::slotLongLongChanged); + editor = ed; + } + break; + case QMetaType::ULongLong: { + QLineEdit *ed = new QLineEdit(parent); + ed->setValidator(new QULongLongValidator(ed)); + ed->setText(QString::number(manager->value(property).toULongLong())); + m_uLongLongPropertyToEditors[property].append(ed); + m_editorToULongLongProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &QLineEdit::textChanged, this, &DesignerEditorFactory::slotULongLongChanged); + editor = ed; + } + break; + case QMetaType::QUrl: { + TextEditor *ed = createTextEditor(parent, ValidationURL, manager->value(property).toUrl().toString()); + ed->setUpdateMode(TextPropertyEditor::UpdateOnFinished); + m_urlPropertyToEditors[property].append(ed); + m_editorToUrlProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &TextEditor::textChanged, this, &DesignerEditorFactory::slotUrlChanged); + editor = ed; + } + break; + case QMetaType::QByteArray: { + TextEditor *ed = createTextEditor(parent, ValidationMultiLine, QString::fromUtf8(manager->value(property).toByteArray())); + m_byteArrayPropertyToEditors[property].append(ed); + m_editorToByteArrayProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &TextEditor::textChanged, this, &DesignerEditorFactory::slotByteArrayChanged); + editor = ed; + } + break; + default: + if (type == DesignerPropertyManager::designerPixmapTypeId()) { + PixmapEditor *ed = new PixmapEditor(m_core, parent); + ed->setPixmapCache(m_fwb->pixmapCache()); + ed->setPath(qvariant_cast(manager->value(property)).path()); + ed->setDefaultPixmap(qvariant_cast(manager->attributeValue(property, defaultResourceAttributeC))); + ed->setSpacing(m_spacing); + m_pixmapPropertyToEditors[property].append(ed); + m_editorToPixmapProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &PixmapEditor::pathChanged, this, &DesignerEditorFactory::slotPixmapChanged); + editor = ed; + } else if (type == DesignerPropertyManager::designerIconTypeId()) { + PixmapEditor *ed = new PixmapEditor(m_core, parent); + ed->setPixmapCache(m_fwb->pixmapCache()); + ed->setIconThemeModeEnabled(true); + PropertySheetIconValue value = qvariant_cast(manager->value(property)); + ed->setTheme(value.theme()); + ed->setThemeEnum(value.themeEnum()); + ed->setPath(value.pixmap(QIcon::Normal, QIcon::Off).path()); + QIcon defaultPixmap; + if (!property->isModified()) + defaultPixmap = qvariant_cast(manager->attributeValue(property, defaultResourceAttributeC)); + else if (m_fwb) + defaultPixmap = m_fwb->iconCache()->icon(value); + ed->setDefaultPixmapIcon(defaultPixmap); + ed->setSpacing(m_spacing); + m_iconPropertyToEditors[property].append(ed); + m_editorToIconProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &PixmapEditor::pathChanged, this, &DesignerEditorFactory::slotIconChanged); + connect(ed, &PixmapEditor::themeChanged, this, &DesignerEditorFactory::slotIconThemeChanged); + connect(ed, &PixmapEditor::themeEnumChanged, this, &DesignerEditorFactory::slotIconThemeEnumChanged); + editor = ed; + } else if (type == DesignerPropertyManager::designerStringTypeId()) { + const TextPropertyValidationMode tvm = static_cast(manager->attributeValue(property, validationModesAttributeC).toInt()); + TextEditor *ed = createTextEditor(parent, tvm, qvariant_cast(manager->value(property)).value()); + const QVariant richTextDefaultFont = manager->attributeValue(property, fontAttributeC); + if (richTextDefaultFont.metaType().id() == QMetaType::QFont) + ed->setRichTextDefaultFont(qvariant_cast(richTextDefaultFont)); + m_stringPropertyToEditors[property].append(ed); + m_editorToStringProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &TextEditor::textChanged, this, &DesignerEditorFactory::slotStringTextChanged); + editor = ed; + } else if (type == DesignerPropertyManager::designerStringListTypeId() || type == QMetaType::QStringList) { + const QVariant variantValue = manager->value(property); + const QStringList value = type == QMetaType::QStringList + ? variantValue.toStringList() : qvariant_cast(variantValue).value(); + StringListEditorButton *ed = new StringListEditorButton(value, parent); + m_stringListPropertyToEditors[property].append(ed); + m_editorToStringListProperty.insert(ed, property); + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &StringListEditorButton::stringListChanged, this, &DesignerEditorFactory::slotStringListChanged); + editor = ed; + } else if (type == DesignerPropertyManager::designerKeySequenceTypeId()) { + QKeySequenceEdit *ed = new QKeySequenceEdit(parent); + ed->setKeySequence(qvariant_cast(manager->value(property)).value()); + m_keySequencePropertyToEditors[property].append(ed); + m_editorToKeySequenceProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &QKeySequenceEdit::keySequenceChanged, this, &DesignerEditorFactory::slotKeySequenceChanged); + editor = ed; + } else { + editor = QtVariantEditorFactory::createEditor(manager, property, parent); + } + break; + } + return m_resetDecorator->editor(editor, + manager->variantProperty(property)->attributeValue(resettableAttributeC).toBool(), + manager, property, parent); +} + +template +bool removeEditor(QObject *object, + QHash> *propertyToEditors, + QHash *editorToProperty) +{ + if (!propertyToEditors) + return false; + if (!editorToProperty) + return false; + for (auto e2pIt = editorToProperty->begin(), end = editorToProperty->end(); e2pIt != end; ++e2pIt) { + Editor editor = e2pIt.key(); + if (editor == object) { + const auto p2eIt = propertyToEditors->find(e2pIt.value()); + if (p2eIt != propertyToEditors->end()) { + p2eIt.value().removeAll(editor); + if (p2eIt.value().isEmpty()) + propertyToEditors->erase(p2eIt); + } + editorToProperty->erase(e2pIt); + return true; + } + } + return false; +} + +void DesignerEditorFactory::slotEditorDestroyed(QObject *object) +{ + if (removeEditor(object, &m_stringPropertyToEditors, &m_editorToStringProperty)) + return; + if (removeEditor(object, &m_keySequencePropertyToEditors, &m_editorToKeySequenceProperty)) + return; + if (removeEditor(object, &m_palettePropertyToEditors, &m_editorToPaletteProperty)) + return; + if (removeEditor(object, &m_pixmapPropertyToEditors, &m_editorToPixmapProperty)) + return; + if (removeEditor(object, &m_iconPropertyToEditors, &m_editorToIconProperty)) + return; + if (removeEditor(object, &m_uintPropertyToEditors, &m_editorToUintProperty)) + return; + if (removeEditor(object, &m_longLongPropertyToEditors, &m_editorToLongLongProperty)) + return; + if (removeEditor(object, &m_intPropertyToComboEditors, &m_comboEditorToIntProperty)) + return; + if (removeEditor(object, &m_uLongLongPropertyToEditors, &m_editorToULongLongProperty)) + return; + if (removeEditor(object, &m_urlPropertyToEditors, &m_editorToUrlProperty)) + return; + if (removeEditor(object, &m_byteArrayPropertyToEditors, &m_editorToByteArrayProperty)) + return; + if (removeEditor(object, &m_stringListPropertyToEditors, &m_editorToStringListProperty)) + return; +} + +template +bool updateManager(QtVariantEditorFactory *factory, bool *changingPropertyValue, + const QHash &editorToProperty, QWidget *editor, const QVariant &value) +{ + if (!editor) + return false; + for (auto it = editorToProperty.cbegin(), end = editorToProperty.cend(); it != end; ++it) { + if (it.key() == editor) { + QtProperty *prop = it.value(); + QtVariantPropertyManager *manager = factory->propertyManager(prop); + *changingPropertyValue = true; + manager->variantProperty(prop)->setValue(value); + *changingPropertyValue = false; + return true; + } + } + return false; +} + +void DesignerEditorFactory::slotUintChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToUintProperty, qobject_cast(sender()), value.toUInt()); +} + +void DesignerEditorFactory::slotLongLongChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToLongLongProperty, qobject_cast(sender()), value.toLongLong()); +} + +void DesignerEditorFactory::slotIntChanged(int v) +{ + updateManager(this, &m_changingPropertyValue, m_comboEditorToIntProperty, + qobject_cast(sender()), v); +} + +void DesignerEditorFactory::slotULongLongChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToULongLongProperty, qobject_cast(sender()), value.toULongLong()); +} + +void DesignerEditorFactory::slotUrlChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToUrlProperty, qobject_cast(sender()), QUrl(value)); +} + +void DesignerEditorFactory::slotByteArrayChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToByteArrayProperty, qobject_cast(sender()), value.toUtf8()); +} + +template +QtProperty *findPropertyForEditor(const QHash &editorMap, + const QObject *sender) +{ + for (auto it = editorMap.constBegin(), cend = editorMap.constEnd(); it != cend; ++it) + if (it.key() == sender) + return it.value(); + return nullptr; +} + +void DesignerEditorFactory::slotStringTextChanged(const QString &value) +{ + if (QtProperty *prop = findPropertyForEditor(m_editorToStringProperty, sender())) { + QtVariantPropertyManager *manager = propertyManager(prop); + QtVariantProperty *varProp = manager->variantProperty(prop); + QVariant val = varProp->value(); + if (val.userType() == DesignerPropertyManager::designerStringTypeId()) { + PropertySheetStringValue strVal = qvariant_cast(val); + strVal.setValue(value); + // Disable translation if no translation subproperties exist. + if (varProp->subProperties().isEmpty()) + strVal.setTranslatable(false); + val = QVariant::fromValue(strVal); + } else { + val = QVariant(value); + } + m_changingPropertyValue = true; + manager->variantProperty(prop)->setValue(val); + m_changingPropertyValue = false; + } +} + +void DesignerEditorFactory::slotKeySequenceChanged(const QKeySequence &value) +{ + if (QtProperty *prop = findPropertyForEditor(m_editorToKeySequenceProperty, sender())) { + QtVariantPropertyManager *manager = propertyManager(prop); + QtVariantProperty *varProp = manager->variantProperty(prop); + QVariant val = varProp->value(); + if (val.userType() == DesignerPropertyManager::designerKeySequenceTypeId()) { + PropertySheetKeySequenceValue keyVal = qvariant_cast(val); + keyVal.setValue(value); + val = QVariant::fromValue(keyVal); + } else { + val = QVariant::fromValue(value); + } + m_changingPropertyValue = true; + manager->variantProperty(prop)->setValue(val); + m_changingPropertyValue = false; + } +} + +void DesignerEditorFactory::slotPaletteChanged(const QPalette &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToPaletteProperty, qobject_cast(sender()), QVariant::fromValue(value)); +} + +void DesignerEditorFactory::slotPixmapChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToPixmapProperty, qobject_cast(sender()), + QVariant::fromValue(PropertySheetPixmapValue(value))); +} + +void DesignerEditorFactory::slotIconChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToIconProperty, qobject_cast(sender()), + QVariant::fromValue(PropertySheetIconValue(PropertySheetPixmapValue(value)))); +} + +void DesignerEditorFactory::slotIconThemeChanged(const QString &value) +{ + PropertySheetIconValue icon; + icon.setTheme(value); + updateManager(this, &m_changingPropertyValue, m_editorToIconProperty, qobject_cast(sender()), + QVariant::fromValue(icon)); +} + +void DesignerEditorFactory::slotIconThemeEnumChanged(int value) +{ + PropertySheetIconValue icon; + icon.setThemeEnum(value); + updateManager(this, &m_changingPropertyValue, m_editorToIconProperty, + qobject_cast(sender()), QVariant::fromValue(icon)); +} + +void DesignerEditorFactory::slotStringListChanged(const QStringList &value) +{ + if (QtProperty *prop = findPropertyForEditor(m_editorToStringListProperty, sender())) { + QtVariantPropertyManager *manager = propertyManager(prop); + QtVariantProperty *varProp = manager->variantProperty(prop); + QVariant val = varProp->value(); + if (val.userType() == DesignerPropertyManager::designerStringListTypeId()) { + PropertySheetStringListValue listValue = qvariant_cast(val); + listValue.setValue(value); + // Disable translation if no translation subproperties exist. + if (varProp->subProperties().isEmpty()) + listValue.setTranslatable(false); + val = QVariant::fromValue(listValue); + } else { + val = QVariant(value); + } + m_changingPropertyValue = true; + manager->variantProperty(prop)->setValue(val); + m_changingPropertyValue = false; + } +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/designerpropertymanager.h b/src/designer/src/components/propertyeditor/designerpropertymanager.h new file mode 100644 index 0000000..8d2cc4b --- /dev/null +++ b/src/designer/src/components/propertyeditor/designerpropertymanager.h @@ -0,0 +1,281 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DESIGNERPROPERTYMANAGER_H +#define DESIGNERPROPERTYMANAGER_H + +#include "qtvariantproperty_p.h" +#include "brushpropertymanager.h" +#include "fontpropertymanager.h" + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using DesignerIntPair = std::pair; +using DesignerFlagList = QList; + +class QComboBox; +class QDesignerFormEditorInterface; +class QLineEdit; +class QUrl; +class QKeySequenceEdit; + +namespace qdesigner_internal +{ + +class TextEditor; +class PaletteEditorButton; +class PixmapEditor; +class ResetDecorator; +class StringListEditorButton; +class FormWindowBase; + +// Helper for handling sub-properties of properties inheriting PropertySheetTranslatableData +// (translatable, disambiguation, comment). +template +class TranslatablePropertyManager +{ +public: + void initialize(QtVariantPropertyManager *m, QtProperty *property, const PropertySheetValue &value); + bool uninitialize(QtProperty *property); + bool destroy(QtProperty *subProperty); + + bool value(const QtProperty *property, QVariant *rc) const; + int valueChanged(QtVariantPropertyManager *m, QtProperty *property, + const QVariant &value); + + int setValue(QtVariantPropertyManager *m, QtProperty *property, + int expectedTypeId, const QVariant &value); + +private: + QHash m_values; + QHash m_valueToComment; + QHash m_valueToTranslatable; + QHash m_valueToDisambiguation; + QHash m_valueToId; + + QHash m_commentToValue; + QHash m_translatableToValue; + QHash m_disambiguationToValue; + QHash m_idToValue; +}; + +class DesignerPropertyManager : public QtVariantPropertyManager +{ + Q_OBJECT +public: + enum ValueChangedResult { NoMatch, Unchanged, Changed }; + + explicit DesignerPropertyManager(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~DesignerPropertyManager(); + + QStringList attributes(int propertyType) const override; + int attributeType(int propertyType, const QString &attribute) const override; + + QVariant attributeValue(const QtProperty *property, const QString &attribute) const override; + bool isPropertyTypeSupported(int propertyType) const override; + QVariant value(const QtProperty *property) const override; + int valueType(int propertyType) const override; + QString valueText(const QtProperty *property) const override; + QIcon valueIcon(const QtProperty *property) const override; + + bool resetTextAlignmentProperty(QtProperty *property); + bool resetFontSubProperty(QtProperty *property); + bool resetIconSubProperty(QtProperty *subProperty); + + void reloadResourceProperties(); + + static int designerFlagTypeId(); + static int designerFlagListTypeId(); + static int designerAlignmentTypeId(); + static int designerPixmapTypeId(); + static int designerIconTypeId(); + static int designerStringTypeId(); + static int designerStringListTypeId(); + static int designerKeySequenceTypeId(); + + void setObject(QObject *object) { m_object = object; } + + static void setUseIdBasedTranslations(bool v) + { m_IdBasedTranslations = v; } + static bool useIdBasedTranslations() + { return m_IdBasedTranslations; } + + static QString alignDefaultAttribute(); + + static uint alignDefault(const QtVariantProperty *prop); + +public Q_SLOTS: + void setAttribute(QtProperty *property, const QString &attribute, const QVariant &value) override; + void setValue(QtProperty *property, const QVariant &value) override; +Q_SIGNALS: + // sourceOfChange - a subproperty (or just property) which caused a change + //void valueChanged(QtProperty *property, const QVariant &value, QtProperty *sourceOfChange); + void valueChanged(QtProperty *property, const QVariant &value, bool enableSubPropertyHandling); +protected: + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private Q_SLOTS: + void slotValueChanged(QtProperty *property, const QVariant &value); + void slotPropertyDestroyed(QtProperty *property); +private: + void createIconSubProperty(QtProperty *iconProperty, QIcon::Mode mode, QIcon::State state, const QString &subName); + + QHash m_resetMap; + + struct FlagData + { + uint val{0}; + DesignerFlagList flags; + QList values; + }; + + QHash m_flagValues; + QHash> m_propertyToFlags; + + int alignToIndexH(uint align) const; + int alignToIndexV(uint align) const; + uint indexHToAlign(int idx) const; + uint indexVToAlign(int idx) const; + QString indexHToString(int idx) const; + QString indexVToString(int idx) const; + QHash m_alignValues; + using PropertyToPropertyMap = QHash; + PropertyToPropertyMap m_propertyToAlignH; + PropertyToPropertyMap m_propertyToAlignV; + PropertyToPropertyMap m_alignHToProperty; + PropertyToPropertyMap m_alignVToProperty; + QHash m_alignDefault; + + QHash, QtProperty *>> m_propertyToIconSubProperties; + QHash> m_iconSubPropertyToState; + PropertyToPropertyMap m_propertyToTheme; + PropertyToPropertyMap m_propertyToThemeEnum; + + TranslatablePropertyManager m_stringManager; + TranslatablePropertyManager m_keySequenceManager; + TranslatablePropertyManager m_stringListManager; + + struct PaletteData + { + QPalette val; + QPalette superPalette; + }; + QHash m_paletteValues; + + QHash m_pixmapValues; + QHash m_iconValues; + + QHash m_intValues; + QHash m_uintValues; + QHash m_longLongValues; + QHash m_uLongLongValues; + QHash m_urlValues; + QHash m_byteArrayValues; + + QHash m_stringAttributes; + QHash m_stringFontAttributes; + QHash m_stringThemeAttributes; + QHash m_intThemeEnumAttributes; + + BrushPropertyManager m_brushManager; + FontPropertyManager m_fontManager; + + QHash m_defaultPixmaps; + QHash m_defaultIcons; + + bool m_changingSubValue; + QDesignerFormEditorInterface *m_core; + + QObject *m_object; + + QtProperty *m_sourceOfChange; + static bool m_IdBasedTranslations; +}; + +class DesignerEditorFactory : public QtVariantEditorFactory +{ + Q_OBJECT +public: + explicit DesignerEditorFactory(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~DesignerEditorFactory(); + void setSpacing(int spacing); + void setFormWindowBase(FormWindowBase *fwb); +signals: + void resetProperty(QtProperty *property); +protected: + void connectPropertyManager(QtVariantPropertyManager *manager) override; + QWidget *createEditor(QtVariantPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtVariantPropertyManager *manager) override; +private slots: + void slotEditorDestroyed(QObject *object); + void slotAttributeChanged(QtProperty *property, const QString &attribute, const QVariant &value); + void slotPropertyChanged(QtProperty *property); + void slotValueChanged(QtProperty *property, const QVariant &value); + void slotStringTextChanged(const QString &value); + void slotKeySequenceChanged(const QKeySequence &value); + void slotPaletteChanged(const QPalette &value); + void slotPixmapChanged(const QString &value); + void slotIconChanged(const QString &value); + void slotIconThemeChanged(const QString &value); + void slotIconThemeEnumChanged(int value); + void slotUintChanged(const QString &value); + void slotIntChanged(int); + void slotLongLongChanged(const QString &value); + void slotULongLongChanged(const QString &value); + void slotUrlChanged(const QString &value); + void slotByteArrayChanged(const QString &value); + void slotStringListChanged(const QStringList &value); +private: + TextEditor *createTextEditor(QWidget *parent, TextPropertyValidationMode vm, const QString &value); + + ResetDecorator *m_resetDecorator; + bool m_changingPropertyValue = false; + QDesignerFormEditorInterface *m_core; + FormWindowBase *m_fwb = nullptr; + + int m_spacing = -1; + + QHash> m_stringPropertyToEditors; + QHash m_editorToStringProperty; + QHash> m_keySequencePropertyToEditors; + QHash m_editorToKeySequenceProperty; + QHash> m_palettePropertyToEditors; + QHash m_editorToPaletteProperty; + QHash> m_pixmapPropertyToEditors; + QHash m_editorToPixmapProperty; + QHash> m_iconPropertyToEditors; + QHash m_editorToIconProperty; + QHash> m_intPropertyToComboEditors; + QHash m_comboEditorToIntProperty; + QHash> m_uintPropertyToEditors; + QHash m_editorToUintProperty; + QHash> m_longLongPropertyToEditors; + QHash m_editorToLongLongProperty; + QHash> m_uLongLongPropertyToEditors; + QHash m_editorToULongLongProperty; + QHash> m_urlPropertyToEditors; + QHash m_editorToUrlProperty; + QHash> m_byteArrayPropertyToEditors; + QHash m_editorToByteArrayProperty; + QHash> m_stringListPropertyToEditors; + QHash m_editorToStringListProperty; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(DesignerIntPair) +Q_DECLARE_METATYPE(DesignerFlagList) + +#endif + diff --git a/src/designer/src/components/propertyeditor/fontmapping.xml b/src/designer/src/components/propertyeditor/fontmapping.xml new file mode 100644 index 0000000..d7a716e --- /dev/null +++ b/src/designer/src/components/propertyeditor/fontmapping.xml @@ -0,0 +1,35 @@ + + + + +]> + + +DejaVu SansDejaVu Sans [&qe;] +DejaVu SansDejaVu Sans [&qe;] +DejaVu SansDejaVu Sans [&qe;] +DejaVu SansDejaVu Sans [&qe;] +DejaVu Sans MonoDejaVu Sans Mono [&qe;] +DejaVu Sans MonoDejaVu Sans Mono [&qe;] +DejaVu Sans MonoDejaVu Sans Mono [&qe;] +DejaVu Sans MonoDejaVu Sans Mono [&qe;] +DejaVu SerifDejaVu Serif [&qe;] +DejaVu SerifDejaVu Serif [&qe;] +DejaVu SerifDejaVu Serif [&qe;] +DejaVu SerifDejaVu Serif [&qe;] +Bitstream Vera SansBitstream Vera Sans [&qe;] +Bitstream Vera SansBitstream Vera Sans [&qe;] +Bitstream Vera SansBitstream Vera Sans [&qe;] +Bitstream Vera SansBitstream Vera Sans [&qe;] +Bitstream Vera Sans MonoBitstream Vera Sans Mono [&qe;] +Bitstream Vera Sans MonoBitstream Vera Sans Mono [&qe;] +Bitstream Vera Sans MonoBitstream Vera Sans Mono [&qe;] +Bitstream Vera Sans MonoBitstream Vera Sans Mono [&qe;] +Bitstream Vera SerifBitstream Vera Serif [&qe;] +Bitstream Vera SerifBitstream Vera Serif [&qe;] + diff --git a/src/designer/src/components/propertyeditor/fontpropertymanager.cpp b/src/designer/src/components/propertyeditor/fontpropertymanager.cpp new file mode 100644 index 0000000..8011d47 --- /dev/null +++ b/src/designer/src/components/propertyeditor/fontpropertymanager.cpp @@ -0,0 +1,452 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "fontpropertymanager.h" +#include "qtpropertymanager_p.h" +#include "designerpropertymanager.h" +#include "qtpropertybrowserutils_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + + using DisambiguatedTranslation = std::pair; + + static const char *aliasingC[] = { + QT_TRANSLATE_NOOP("FontPropertyManager", "PreferDefault"), + QT_TRANSLATE_NOOP("FontPropertyManager", "NoAntialias"), + QT_TRANSLATE_NOOP("FontPropertyManager", "PreferAntialias") + }; + + static const DisambiguatedTranslation hintingPreferenceC[] = { + QT_TRANSLATE_NOOP3("FontPropertyManager", "PreferDefaultHinting", "QFont::StyleStrategy combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "PreferNoHinting", "QFont::StyleStrategy combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "PreferVerticalHinting", "QFont::StyleStrategy combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "PreferFullHinting", "QFont::StyleStrategy combo") + }; + + FontPropertyManager::FontPropertyManager() + { + for (const auto *a : aliasingC) + m_aliasingEnumNames.append(QCoreApplication::translate("FontPropertyManager", a)); + + for (const auto &h : hintingPreferenceC) + m_hintingPreferenceEnumNames.append(QCoreApplication::translate("FontPropertyManager", h.first, h.second)); + + QString errorMessage; + if (!readFamilyMapping(&m_familyMappings, &errorMessage)) { + designerWarning(errorMessage); + } + + } + + void FontPropertyManager::preInitializeProperty(QtProperty *property, + int type, + ResetMap &resetMap) + { + if (m_createdFontProperty) { + auto it = m_propertyToFontSubProperties.find(m_createdFontProperty); + if (it == m_propertyToFontSubProperties.end()) + it = m_propertyToFontSubProperties.insert(m_createdFontProperty, PropertyList()); + const int index = it.value().size(); + m_fontSubPropertyToFlag.insert(property, index); + it.value().push_back(property); + resetMap[property] = true; + } + + if (type == QMetaType::QFont) + m_createdFontProperty = property; + } + + // Map the font family names to display names retrieved from the XML configuration + static QStringList designerFamilyNames(QStringList families, const FontPropertyManager::NameMap &nm) + { + if (nm.isEmpty()) + return families; + + const auto ncend = nm.constEnd(); + for (auto it = families.begin(), end = families.end(); it != end; ++it) { + const auto nit = nm.constFind(*it); + if (nit != ncend) + *it = nit.value(); + } + return families; + } + + void FontPropertyManager::postInitializeProperty(QtVariantPropertyManager *vm, + QtProperty *property, + int type, + int enumTypeId) + { + if (type != QMetaType::QFont) + return; + + // This will cause a recursion + QtVariantProperty *antialiasing = vm->addProperty(enumTypeId, QCoreApplication::translate("FontPropertyManager", "Antialiasing")); + const QFont font = qvariant_cast(vm->variantProperty(property)->value()); + + antialiasing->setAttribute(u"enumNames"_s, m_aliasingEnumNames); + antialiasing->setValue(antialiasingToIndex(font.styleStrategy())); + property->addSubProperty(antialiasing); + + m_propertyToAntialiasing[property] = antialiasing; + m_antialiasingToProperty[antialiasing] = property; + + QtVariantProperty *hintingPreference = vm->addProperty(enumTypeId, QCoreApplication::translate("FontPropertyManager", "HintingPreference")); + hintingPreference->setAttribute(u"enumNames"_s, m_hintingPreferenceEnumNames); + hintingPreference->setValue(hintingPreferenceToIndex(font.hintingPreference())); + property->addSubProperty(hintingPreference); + + m_propertyToHintingPreference[property] = hintingPreference; + m_hintingPreferenceToProperty[hintingPreference] = property; + + // Fiddle family names + if (!m_familyMappings.isEmpty()) { + const auto it = m_propertyToFontSubProperties.find(m_createdFontProperty); + QtVariantProperty *familyProperty = vm->variantProperty(it.value().constFirst()); + const QString enumNamesAttribute = u"enumNames"_s; + QStringList plainFamilyNames = familyProperty->attributeValue(enumNamesAttribute).toStringList(); + // Did someone load fonts or something? + if (m_designerFamilyNames.size() != plainFamilyNames.size()) + m_designerFamilyNames = designerFamilyNames(plainFamilyNames, m_familyMappings); + familyProperty->setAttribute(enumNamesAttribute, m_designerFamilyNames); + } + // Next + m_createdFontProperty = nullptr; + } + + bool FontPropertyManager::uninitializeProperty(QtProperty *property) + { + const auto ait = m_propertyToAntialiasing.find(property); + if (ait != m_propertyToAntialiasing.end()) { + QtProperty *antialiasing = ait.value(); + m_antialiasingToProperty.remove(antialiasing); + m_propertyToAntialiasing.erase(ait); + delete antialiasing; + } + + const auto hit = m_propertyToHintingPreference.find(property); + if (hit != m_propertyToHintingPreference.end()) { + QtProperty *hintingPreference = hit.value(); + m_hintingPreferenceToProperty.remove(hintingPreference); + m_propertyToHintingPreference.erase(hit); + delete hintingPreference; + } + + const auto sit = m_propertyToFontSubProperties.find(property); + if (sit == m_propertyToFontSubProperties.end()) + return false; + + m_propertyToFontSubProperties.erase(sit); + m_fontSubPropertyToFlag.remove(property); + + return true; + } + + void FontPropertyManager::slotPropertyDestroyed(QtProperty *property) + { + removeAntialiasingProperty(property); + removeHintingPreferenceProperty(property); + } + + void FontPropertyManager::removeAntialiasingProperty(QtProperty *property) + { + const auto ait = m_antialiasingToProperty.find(property); + if (ait == m_antialiasingToProperty.end()) + return; + m_propertyToAntialiasing[ait.value()] = 0; + m_antialiasingToProperty.erase(ait); + } + + void FontPropertyManager::removeHintingPreferenceProperty(QtProperty *property) + { + const auto hit = m_hintingPreferenceToProperty.find(property); + if (hit == m_hintingPreferenceToProperty.end()) + return; + m_propertyToHintingPreference[hit.value()] = nullptr; + m_hintingPreferenceToProperty.erase(hit); + } + + bool FontPropertyManager::resetFontSubProperty(QtVariantPropertyManager *vm, QtProperty *property) + { + auto *parentItem = property->parentProperty(); + if (!m_propertyToFontSubProperties.contains(parentItem)) + return false; + + QtVariantProperty *fontProperty = vm->variantProperty(parentItem); + + QVariant v = fontProperty->value(); + QFont font = qvariant_cast(v); + unsigned mask = font.resolveMask(); + const unsigned flag = fontFlag(m_fontSubPropertyToFlag.value(property)); + + mask &= ~flag; + font.setResolveMask(mask); + v.setValue(font); + fontProperty->setValue(v); + return true; + } + + int FontPropertyManager::antialiasingToIndex(QFont::StyleStrategy antialias) + { + switch (antialias) { + case QFont::PreferDefault: return 0; + case QFont::NoAntialias: return 1; + case QFont::PreferAntialias: return 2; + default: break; + } + return 0; + } + + QFont::StyleStrategy FontPropertyManager::indexToAntialiasing(int idx) + { + switch (idx) { + case 0: return QFont::PreferDefault; + case 1: return QFont::NoAntialias; + case 2: return QFont::PreferAntialias; + } + return QFont::PreferDefault; + } + + int FontPropertyManager::hintingPreferenceToIndex(QFont::HintingPreference h) + { + switch (h) { + case QFont::PreferDefaultHinting: + return 0; + case QFont::PreferNoHinting: + return 1; + case QFont::PreferVerticalHinting: + return 2; + case QFont::PreferFullHinting: + return 3; + } + return 0; + } + + QFont::HintingPreference FontPropertyManager::indexToHintingPreference(int idx) + { + switch (idx) { + case 0: + return QFont::PreferDefaultHinting; + case 1: + return QFont::PreferNoHinting; + case 2: + return QFont::PreferVerticalHinting; + case 3: + return QFont::PreferFullHinting; + } + return QFont::PreferDefaultHinting; + } + + unsigned FontPropertyManager::fontFlag(int idx) + { + switch (idx) { + case 0: + return QFont::FamilyResolved | QFont::FamiliesResolved; + case 1: + return QFont::SizeResolved; + case 2: + case 7: + return QFont::WeightResolved; + case 3: + return QFont::StyleResolved; + case 4: + return QFont::UnderlineResolved; + case 5: + return QFont::StrikeOutResolved; + case 6: + return QFont::KerningResolved; + case 8: + return QFont::StyleStrategyResolved; + case 9: + return QFont::HintingPreferenceResolved; + } + return 0; + } + + int FontPropertyManager::valueChanged(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value) + { + if (auto *antialiasingProperty = m_antialiasingToProperty.value(property, nullptr)) + return antialiasingValueChanged(vm, antialiasingProperty, value); + + if (auto *hintingPreferenceProperty = m_hintingPreferenceToProperty.value(property, nullptr)) + return hintingPreferenceValueChanged(vm, hintingPreferenceProperty, value); + + if (m_propertyToFontSubProperties.contains(property)) + updateModifiedState(property, value); + + return DesignerPropertyManager::NoMatch; + } + + int FontPropertyManager::antialiasingValueChanged(QtVariantPropertyManager *vm, + QtProperty *antialiasingProperty, + const QVariant &value) + { + QtVariantProperty *fontProperty = vm->variantProperty(antialiasingProperty); + const QFont::StyleStrategy newValue = indexToAntialiasing(value.toInt()); + + QFont font = qvariant_cast(fontProperty->value()); + const QFont::StyleStrategy oldValue = font.styleStrategy(); + if (newValue == oldValue) + return DesignerPropertyManager::Unchanged; + + font.setStyleStrategy(newValue); + fontProperty->setValue(QVariant::fromValue(font)); + return DesignerPropertyManager::Changed; + } + + int FontPropertyManager::hintingPreferenceValueChanged(QtVariantPropertyManager *vm, + QtProperty *hintingPreferenceProperty, + const QVariant &value) + { + QtVariantProperty *fontProperty = vm->variantProperty(hintingPreferenceProperty); + const QFont::HintingPreference newValue = indexToHintingPreference(value.toInt()); + + QFont font = qvariant_cast(fontProperty->value()); + const QFont::HintingPreference oldValue = font.hintingPreference(); + if (newValue == oldValue) + return DesignerPropertyManager::Unchanged; + + font.setHintingPreference(newValue); + fontProperty->setValue(QVariant::fromValue(font)); + return DesignerPropertyManager::Changed; + } + + void FontPropertyManager::updateModifiedState(QtProperty *property, const QVariant &value) + { + const auto it = m_propertyToFontSubProperties.find(property); + if (it == m_propertyToFontSubProperties.end()) + return; + + const PropertyList &subProperties = it.value(); + + QFont font = qvariant_cast(value); + const unsigned mask = font.resolveMask(); + + const int count = subProperties.size(); + for (int index = 0; index < count; index++) { + const unsigned flag = fontFlag(index); + subProperties.at(index)->setModified(mask & flag); + } + } + + void FontPropertyManager::setValue(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value) + { + updateModifiedState(property, value); + + if (QtProperty *antialiasingProperty = m_propertyToAntialiasing.value(property, 0)) { + QtVariantProperty *antialiasing = vm->variantProperty(antialiasingProperty); + if (antialiasing) { + QFont font = qvariant_cast(value); + antialiasing->setValue(antialiasingToIndex(font.styleStrategy())); + } + } + + if (QtProperty *hintingPreferenceProperty = m_propertyToHintingPreference.value(property, nullptr)) { + if (auto *hintingPreference = vm->variantProperty(hintingPreferenceProperty)) { + QFont font = qvariant_cast(value); + hintingPreference->setValue(hintingPreferenceToIndex(font.hintingPreference())); + } + } + + } + + /* Parse a mappings file of the form: + * + * DejaVu SansDejaVu Sans [CE] + * ... which is used to display on which platforms fonts are available.*/ + +static constexpr auto rootTagC = "fontmappings"_L1; +static constexpr auto mappingTagC = "mapping"_L1; +static constexpr auto familyTagC = "family"_L1; +static constexpr auto displayTagC = "display"_L1; + + static QString msgXmlError(const QXmlStreamReader &r, const QString& fileName) + { + return u"An error has been encountered at line %1 of %2: %3:"_s.arg(r.lineNumber()).arg(fileName, r.errorString()); + } + + /* Switch stages when encountering a start element (state table) */ + enum ParseStage { ParseBeginning, ParseWithinRoot, ParseWithinMapping, ParseWithinFamily, + ParseWithinDisplay, ParseError }; + + static ParseStage nextStage(ParseStage currentStage, QStringView startElement) + { + switch (currentStage) { + case ParseBeginning: + return startElement == rootTagC ? ParseWithinRoot : ParseError; + case ParseWithinRoot: + case ParseWithinDisplay: // Next mapping, was in + return startElement == mappingTagC ? ParseWithinMapping : ParseError; + case ParseWithinMapping: + return startElement == familyTagC ? ParseWithinFamily : ParseError; + case ParseWithinFamily: + return startElement == displayTagC ? ParseWithinDisplay : ParseError; + case ParseError: + break; + } + return ParseError; + } + + bool FontPropertyManager::readFamilyMapping(NameMap *rc, QString *errorMessage) + { + rc->clear(); + const QString fileName = u":/qt-project.org/propertyeditor/fontmapping.xml"_s; + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + *errorMessage = "Unable to open %1: %2"_L1.arg(fileName, file.errorString()); + return false; + } + + QXmlStreamReader reader(&file); + QXmlStreamReader::TokenType token; + + QString family; + ParseStage stage = ParseBeginning; + do { + token = reader.readNext(); + switch (token) { + case QXmlStreamReader::Invalid: + *errorMessage = msgXmlError(reader, fileName); + return false; + case QXmlStreamReader::StartElement: + stage = nextStage(stage, reader.name()); + switch (stage) { + case ParseError: + reader.raiseError("Unexpected element <%1>."_L1.arg(reader.name())); + *errorMessage = msgXmlError(reader, fileName); + return false; + case ParseWithinFamily: + family = reader.readElementText(); + break; + case ParseWithinDisplay: + rc->insert(family, reader.readElementText()); + break; + default: + break; + } + break; + default: + break; + } + } while (token != QXmlStreamReader::EndDocument); + return true; + } + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/fontpropertymanager.h b/src/designer/src/components/propertyeditor/fontpropertymanager.h new file mode 100644 index 0000000..f2d92e0 --- /dev/null +++ b/src/designer/src/components/propertyeditor/fontpropertymanager.h @@ -0,0 +1,97 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FONTPROPERTYMANAGER_H +#define FONTPROPERTYMANAGER_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtProperty; +class QtVariantPropertyManager; + +class QString; +class QVariant; + +namespace qdesigner_internal { + +/* FontPropertyManager: A mixin for DesignerPropertyManager that manages font + * properties. Adds an antialiasing subproperty and reset flags/mask handling + * for the other subproperties. It also modifies the font family + * enumeration names, which it reads from an XML mapping file that + * contains annotations indicating the platform the font is available on. */ + +class FontPropertyManager { + Q_DISABLE_COPY_MOVE(FontPropertyManager) + +public: + FontPropertyManager(); + + using ResetMap = QHash; + using NameMap = QMap; + + // Call before QtVariantPropertyManager::initializeProperty. + void preInitializeProperty(QtProperty *property, int type, ResetMap &resetMap); + // Call after QtVariantPropertyManager::initializeProperty. This will trigger + // a recursion for the sub properties + void postInitializeProperty(QtVariantPropertyManager *vm, QtProperty *property, int type, int enumTypeId); + + bool uninitializeProperty(QtProperty *property); + + // Call from QtPropertyManager's propertyDestroyed signal + void slotPropertyDestroyed(QtProperty *property); + + bool resetFontSubProperty(QtVariantPropertyManager *vm, QtProperty *subProperty); + + // Call from slotValueChanged(), returns DesignerPropertyManager::ValueChangedResult + int valueChanged(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value); + + // Call from setValue() before calling setValue() on QtVariantPropertyManager. + void setValue(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value); + + static bool readFamilyMapping(NameMap *rc, QString *errorMessage); + +private: + using PropertyToPropertyMap = QHash; + using PropertyList = QList; + + void removeAntialiasingProperty(QtProperty *); + void removeHintingPreferenceProperty(QtProperty *); + int antialiasingValueChanged(QtVariantPropertyManager *vm, + QtProperty *antialiasingProperty, const QVariant &value); + int hintingPreferenceValueChanged(QtVariantPropertyManager *vm, + QtProperty *hintingPreferenceProperty, + const QVariant &value); + void updateModifiedState(QtProperty *property, const QVariant &value); + static int antialiasingToIndex(QFont::StyleStrategy antialias); + static QFont::StyleStrategy indexToAntialiasing(int idx); + static int hintingPreferenceToIndex(QFont::HintingPreference h); + static QFont::HintingPreference indexToHintingPreference(int idx); + + static unsigned fontFlag(int idx); + + PropertyToPropertyMap m_propertyToAntialiasing; + PropertyToPropertyMap m_antialiasingToProperty; + PropertyToPropertyMap m_propertyToHintingPreference; + PropertyToPropertyMap m_hintingPreferenceToProperty; + + + QHash m_propertyToFontSubProperties; + QHash m_fontSubPropertyToFlag; + QtProperty *m_createdFontProperty = nullptr; + QStringList m_aliasingEnumNames; + QStringList m_hintingPreferenceEnumNames; + // Font families with Designer annotations + QStringList m_designerFamilyNames; + NameMap m_familyMappings; +}; + +} + +QT_END_NAMESPACE + +#endif // FONTPROPERTYMANAGER_H diff --git a/src/designer/src/components/propertyeditor/newdynamicpropertydialog.cpp b/src/designer/src/components/propertyeditor/newdynamicpropertydialog.cpp new file mode 100644 index 0000000..a8972c3 --- /dev/null +++ b/src/designer/src/components/propertyeditor/newdynamicpropertydialog.cpp @@ -0,0 +1,162 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "newdynamicpropertydialog.h" +#include "ui_newdynamicpropertydialog.h" +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +NewDynamicPropertyDialog::NewDynamicPropertyDialog(QDesignerDialogGuiInterface *dialogGui, + QWidget *parent) : + QDialog(parent), + m_dialogGui(dialogGui), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::NewDynamicPropertyDialog) +{ + m_ui->setupUi(this); + connect(m_ui->m_lineEdit, &QLineEdit::textChanged, this, &NewDynamicPropertyDialog::nameChanged); + connect(m_ui->m_buttonBox, &QDialogButtonBox::clicked, + this, &NewDynamicPropertyDialog::buttonBoxClicked); + + m_ui->m_comboBox->addItem(u"String"_s, + QVariant(QMetaType(QMetaType::QString))); + m_ui->m_comboBox->addItem(u"StringList"_s, + QVariant(QMetaType(QMetaType::QStringList))); + m_ui->m_comboBox->addItem(u"Char"_s, + QVariant(QMetaType(QMetaType::QChar))); + m_ui->m_comboBox->addItem(u"ByteArray"_s, + QVariant(QMetaType(QMetaType::QByteArray))); + m_ui->m_comboBox->addItem(u"Url"_s, + QVariant(QMetaType(QMetaType::QUrl))); + m_ui->m_comboBox->addItem(u"Bool"_s, + QVariant(QMetaType(QMetaType::Bool))); + m_ui->m_comboBox->addItem(u"Int"_s, + QVariant(QMetaType(QMetaType::Int))); + m_ui->m_comboBox->addItem(u"UInt"_s, + QVariant(QMetaType(QMetaType::UInt))); + m_ui->m_comboBox->addItem(u"LongLong"_s, + QVariant(QMetaType(QMetaType::LongLong))); + m_ui->m_comboBox->addItem(u"ULongLong"_s, + QVariant(QMetaType(QMetaType::ULongLong))); + m_ui->m_comboBox->addItem(u"Double"_s, + QVariant(QMetaType(QMetaType::Double))); + m_ui->m_comboBox->addItem(u"Size"_s, + QVariant(QMetaType(QMetaType::QSize))); + m_ui->m_comboBox->addItem(u"SizeF"_s, + QVariant(QMetaType(QMetaType::QSizeF))); + m_ui->m_comboBox->addItem(u"Point"_s, + QVariant(QMetaType(QMetaType::QPoint))); + m_ui->m_comboBox->addItem(u"PointF"_s, + QVariant(QMetaType(QMetaType::QPointF))); + m_ui->m_comboBox->addItem(u"Rect"_s, + QVariant(QMetaType(QMetaType::QRect))); + m_ui->m_comboBox->addItem(u"RectF"_s, + QVariant(QMetaType(QMetaType::QRectF))); + m_ui->m_comboBox->addItem(u"Date"_s, + QVariant(QMetaType(QMetaType::QDate))); + m_ui->m_comboBox->addItem(u"Time"_s, + QVariant(QMetaType(QMetaType::QTime))); + m_ui->m_comboBox->addItem(u"DateTime"_s, + QVariant(QMetaType(QMetaType::QDateTime))); + m_ui->m_comboBox->addItem(u"Font"_s, + QVariant(QMetaType(QMetaType::QFont))); + m_ui->m_comboBox->addItem(u"Palette"_s, + QVariant(QMetaType(QMetaType::QPalette))); + m_ui->m_comboBox->addItem(u"Color"_s, + QVariant(QMetaType(QMetaType::QColor))); + m_ui->m_comboBox->addItem(u"Pixmap"_s, + QVariant(QMetaType(QMetaType::QPixmap))); + m_ui->m_comboBox->addItem(u"Icon"_s, + QVariant(QMetaType(QMetaType::QIcon))); + m_ui->m_comboBox->addItem(u"Cursor"_s, + QVariant(QMetaType(QMetaType::QCursor))); + m_ui->m_comboBox->addItem(u"SizePolicy"_s, + QVariant(QMetaType(QMetaType::QSizePolicy))); + m_ui->m_comboBox->addItem(u"KeySequence"_s, + QVariant(QMetaType(QMetaType::QKeySequence))); + + m_ui->m_comboBox->setCurrentIndex(0); // String + setOkButtonEnabled(false); +} + +void NewDynamicPropertyDialog::setOkButtonEnabled(bool e) +{ + m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(e); +} + +NewDynamicPropertyDialog::~NewDynamicPropertyDialog() +{ + delete m_ui; +} + +void NewDynamicPropertyDialog::setReservedNames(const QStringList &names) +{ + m_reservedNames = names; +} + +void NewDynamicPropertyDialog::setPropertyType(int t) +{ + const int index = m_ui->m_comboBox->findData(QVariant(QMetaType(t))); + if (index != -1) + m_ui->m_comboBox->setCurrentIndex(index); +} + +QString NewDynamicPropertyDialog::propertyName() const +{ + return m_ui->m_lineEdit->text(); +} + +QVariant NewDynamicPropertyDialog::propertyValue() const +{ + const int index = m_ui->m_comboBox->currentIndex(); + if (index == -1) + return QVariant(); + return m_ui->m_comboBox->itemData(index); +} + +void NewDynamicPropertyDialog::information(const QString &message) +{ + m_dialogGui->message(this, QDesignerDialogGuiInterface::PropertyEditorMessage, QMessageBox::Information, tr("Set Property Name"), message); +} + +void NewDynamicPropertyDialog::nameChanged(const QString &s) +{ + setOkButtonEnabled(!s.isEmpty()); +} + +bool NewDynamicPropertyDialog::validatePropertyName(const QString& name) +{ + if (m_reservedNames.contains(name)) { + information(tr("The current object already has a property named '%1'.\nPlease select another, unique one.").arg(name)); + return false; + } + if (!QDesignerPropertySheet::internalDynamicPropertiesEnabled() && name.startsWith("_q_"_L1)) { + information(tr("The '_q_' prefix is reserved for the Qt library.\nPlease select another name.")); + return false; + } + return true; +} + +void NewDynamicPropertyDialog::buttonBoxClicked(QAbstractButton *btn) +{ + const int role = m_ui->m_buttonBox->buttonRole(btn); + switch (role) { + case QDialogButtonBox::RejectRole: + reject(); + break; + case QDialogButtonBox::AcceptRole: + if (validatePropertyName(propertyName())) + accept(); + break; + } +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/newdynamicpropertydialog.h b/src/designer/src/components/propertyeditor/newdynamicpropertydialog.h new file mode 100644 index 0000000..0142e44 --- /dev/null +++ b/src/designer/src/components/propertyeditor/newdynamicpropertydialog.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NEWDYNAMICPROPERTYDIALOG_P_H +#define NEWDYNAMICPROPERTYDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "propertyeditor_global.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QAbstractButton; +class QDesignerDialogGuiInterface; + +namespace qdesigner_internal { + +namespace Ui +{ + class NewDynamicPropertyDialog; +} + +class QT_PROPERTYEDITOR_EXPORT NewDynamicPropertyDialog: public QDialog +{ + Q_OBJECT +public: + explicit NewDynamicPropertyDialog(QDesignerDialogGuiInterface *dialogGui, QWidget *parent = nullptr); + ~NewDynamicPropertyDialog(); + + void setReservedNames(const QStringList &names); + void setPropertyType(int t); + + QString propertyName() const; + QVariant propertyValue() const; + +private slots: + + void buttonBoxClicked(QAbstractButton *btn); + void nameChanged(const QString &); + +private: + bool validatePropertyName(const QString& name); + void setOkButtonEnabled(bool e); + void information(const QString &message); + + QDesignerDialogGuiInterface *m_dialogGui; + Ui::NewDynamicPropertyDialog *m_ui; + QStringList m_reservedNames; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // NEWDYNAMICPROPERTYDIALOG_P_H diff --git a/src/designer/src/components/propertyeditor/newdynamicpropertydialog.ui b/src/designer/src/components/propertyeditor/newdynamicpropertydialog.ui new file mode 100644 index 0000000..dec3bb3 --- /dev/null +++ b/src/designer/src/components/propertyeditor/newdynamicpropertydialog.ui @@ -0,0 +1,109 @@ + + qdesigner_internal::NewDynamicPropertyDialog + + + + 0 + 0 + 340 + 118 + + + + Create Dynamic Property + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + + 220 + 0 + + + + + + + + + 0 + 0 + + + + Property Name + + + + + + + + + + + + horizontalSpacer + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + Property Type + + + + + + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + diff --git a/src/designer/src/components/propertyeditor/paletteeditor.cpp b/src/designer/src/components/propertyeditor/paletteeditor.cpp new file mode 100644 index 0000000..72f737f --- /dev/null +++ b/src/designer/src/components/propertyeditor/paletteeditor.cpp @@ -0,0 +1,775 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "paletteeditor.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +# include +#endif +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +enum { BrushRole = 33 }; + +PaletteEditor::PaletteEditor(QDesignerFormEditorInterface *core, QWidget *parent) : + QDialog(parent), + m_paletteModel(new PaletteModel(this)), + m_core(core) +{ + ui.setupUi(this); + auto saveButton = ui.buttonBox->addButton(tr("Save..."), QDialogButtonBox::ActionRole); + connect(saveButton, &QPushButton::clicked, this, &PaletteEditor::save); + auto loadButton = ui.buttonBox->addButton(tr("Load..."), QDialogButtonBox::ActionRole); + connect(loadButton, &QPushButton::clicked, this, &PaletteEditor::load); + + connect(ui.buildButton, &QtColorButton::colorChanged, + this, &PaletteEditor::buildButtonColorChanged); + connect(ui.activeRadio, &QAbstractButton::clicked, + this, &PaletteEditor::activeRadioClicked); + connect(ui.inactiveRadio, &QAbstractButton::clicked, + this, &PaletteEditor::inactiveRadioClicked); + connect(ui.disabledRadio, &QAbstractButton::clicked, + this, &PaletteEditor::disabledRadioClicked); + connect(ui.computeRadio, &QAbstractButton::clicked, + this, &PaletteEditor::computeRadioClicked); + connect(ui.detailsRadio, &QAbstractButton::clicked, + this, &PaletteEditor::detailsRadioClicked); + + ui.paletteView->setModel(m_paletteModel); + ui.previewGroupBox->setTitle(tr("Preview (%1)").arg(style()->objectName())); + updatePreviewPalette(); + updateStyledButton(); + ui.paletteView->setModel(m_paletteModel); + ColorDelegate *delegate = new ColorDelegate(core, this); + ui.paletteView->setItemDelegate(delegate); + ui.paletteView->setEditTriggers(QAbstractItemView::AllEditTriggers); + connect(m_paletteModel, &PaletteModel::paletteChanged, + this, &PaletteEditor::paletteChanged); + ui.paletteView->setSelectionBehavior(QAbstractItemView::SelectRows); + ui.paletteView->setDragEnabled(true); + ui.paletteView->setDropIndicatorShown(true); + ui.paletteView->setRootIsDecorated(false); + ui.paletteView->setColumnHidden(2, true); + ui.paletteView->setColumnHidden(3, true); + ui.paletteView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui.paletteView, &QWidget::customContextMenuRequested, + this, &PaletteEditor::viewContextMenuRequested); + + const auto itemRect = ui.paletteView->visualRect(m_paletteModel->index(0, 0)); + const int minHeight = qMin(itemRect.height() * QPalette::NColorRoles, + (screen()->geometry().height() * 2) / 3); + ui.paletteView->setMinimumSize({itemRect.width() * 4, minHeight}); +} + +PaletteEditor::~PaletteEditor() = default; + +QPalette PaletteEditor::palette() const +{ + return m_editPalette; +} + +void PaletteEditor::setPalette(const QPalette &palette) +{ + m_editPalette = palette; + for (int r = 0; r < static_cast(QPalette::NColorRoles); ++r) { + for (int g = 0; g < static_cast(QPalette::NColorGroups); ++g) { + const auto role = static_cast(r); + const auto group = static_cast(g); + if (!palette.isBrushSet(group, role)) + m_editPalette.setBrush(group, role, m_parentPalette.brush(group, role)); + } + } + m_editPalette.setResolveMask(palette.resolveMask()); + updatePreviewPalette(); + updateStyledButton(); + m_paletteUpdated = true; + if (!m_modelUpdated) + m_paletteModel->setPalette(m_editPalette, m_parentPalette); + m_paletteUpdated = false; +} + +void PaletteEditor::setPalette(const QPalette &palette, const QPalette &parentPalette) +{ + m_parentPalette = parentPalette; + setPalette(palette); +} + +void PaletteEditor::buildButtonColorChanged() +{ + buildPalette(); +} + +void PaletteEditor::activeRadioClicked() +{ + m_currentColorGroup = QPalette::Active; + updatePreviewPalette(); +} + +void PaletteEditor::inactiveRadioClicked() +{ + m_currentColorGroup = QPalette::Inactive; + updatePreviewPalette(); +} + +void PaletteEditor::disabledRadioClicked() +{ + m_currentColorGroup = QPalette::Disabled; + updatePreviewPalette(); +} + +void PaletteEditor::computeRadioClicked() +{ + if (m_compute) + return; + ui.paletteView->setColumnHidden(2, true); + ui.paletteView->setColumnHidden(3, true); + m_compute = true; + m_paletteModel->setCompute(true); +} + +void PaletteEditor::detailsRadioClicked() +{ + if (!m_compute) + return; + const int w = ui.paletteView->columnWidth(1); + ui.paletteView->setColumnHidden(2, false); + ui.paletteView->setColumnHidden(3, false); + QHeaderView *header = ui.paletteView->header(); + header->resizeSection(1, w / 3); + header->resizeSection(2, w / 3); + header->resizeSection(3, w / 3); + m_compute = false; + m_paletteModel->setCompute(false); +} + +void PaletteEditor::paletteChanged(const QPalette &palette) +{ + m_modelUpdated = true; + if (!m_paletteUpdated) + setPalette(palette); + m_modelUpdated = false; +} + +void PaletteEditor::buildPalette() +{ + const QColor btn = ui.buildButton->color(); + const QPalette temp = QPalette(btn); + setPalette(temp); +} + +void PaletteEditor::updatePreviewPalette() +{ + const QPalette::ColorGroup g = currentColorGroup(); + // build the preview palette + const QPalette currentPalette = palette(); + QPalette previewPalette; + for (int i = QPalette::WindowText; i < QPalette::NColorRoles; i++) { + const QPalette::ColorRole r = static_cast(i); + const QBrush &br = currentPalette.brush(g, r); + previewPalette.setBrush(QPalette::Active, r, br); + previewPalette.setBrush(QPalette::Inactive, r, br); + previewPalette.setBrush(QPalette::Disabled, r, br); + } + ui.previewFrame->setPreviewPalette(previewPalette); + + const bool enabled = g != QPalette::Disabled; + ui.previewFrame->setEnabled(enabled); + ui.previewFrame->setSubWindowActive(g != QPalette::Inactive); +} + +void PaletteEditor::updateStyledButton() +{ + ui.buildButton->setColor(palette().color(QPalette::Active, QPalette::Button)); +} + +QPalette PaletteEditor::getPalette(QDesignerFormEditorInterface *core, QWidget* parent, const QPalette &init, + const QPalette &parentPal, int *ok) +{ + PaletteEditor dlg(core, parent); + QPalette parentPalette(parentPal); + for (int r = 0; r < static_cast(QPalette::NColorRoles); ++r) { + for (int g = 0; g < static_cast(QPalette::NColorGroups); ++g) { + const auto role = static_cast(r); + const auto group = static_cast(g); + if (!init.isBrushSet(group, role)) + parentPalette.setBrush(group, role, init.brush(group, role)); + } + } + dlg.setPalette(init, parentPalette); + + const int result = dlg.exec(); + if (ok) *ok = result; + + return result == QDialog::Accepted ? dlg.palette() : init; +} + +void PaletteEditor::viewContextMenuRequested(QPoint pos) +{ + const auto index = ui.paletteView->indexAt(pos); + if (!index.isValid()) + return; + auto brush = m_paletteModel->brushAt(index); + const auto color = brush.color(); + if (!m_contextMenu) { + m_contextMenu = new QMenu(this); + m_lighterAction = m_contextMenu->addAction(tr("Lighter")); + m_darkerAction = m_contextMenu->addAction(tr("Darker")); + m_copyColorAction = m_contextMenu->addAction(QString()); + } + const auto rgb = color.rgb() & 0xffffffu; + const bool isBlack = rgb == 0u; + m_lighterAction->setEnabled(rgb != 0xffffffu); + m_darkerAction->setDisabled(isBlack); + m_copyColorAction->setText(tr("Copy color %1").arg(color.name())); + auto action = m_contextMenu->exec(ui.paletteView->viewport()->mapToGlobal(pos)); + if (!action) + return; + if (action == m_copyColorAction) { +#if QT_CONFIG(clipboard) + QGuiApplication::clipboard()->setText(color.name()); +#endif + return; + } + // Fall through to darker/lighter. Note: black cannot be made lighter due + // to QTBUG-9343. + enum : int { factor = 120 }; + const QColor newColor = action == m_darkerAction + ? color.darker(factor) + : (isBlack ? QColor(0x404040u) : color.lighter(factor)); + brush.setColor(newColor); + m_paletteModel->setData(index, QVariant(brush), BrushRole); +} + +static inline QString paletteFilter() +{ + return PaletteEditor::tr("QPalette UI file (*.xml)"); +} + +static bool savePalette(const QString &fileName, const QPalette &pal, QString *errorMessage) +{ + QSaveFile file; + file.setFileName(fileName); + if (!file.open(QIODevice::WriteOnly)) { + *errorMessage = PaletteEditor::tr("Cannot open %1 for writing: %2") + .arg(QDir::toNativeSeparators(fileName), file.errorString()); + return false; + } + { + QScopedPointer domPalette(QFormBuilderExtra::savePalette(pal)); + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(1); + writer.writeStartDocument(); + domPalette->write(writer); + writer.writeEndDocument(); + } + const bool result = file.commit(); + if (!result) { + *errorMessage = PaletteEditor::tr("Cannot write %1: %2") + .arg(QDir::toNativeSeparators(fileName), file.errorString()); + } + return result; +} + +static QString msgCannotReadPalette(const QString &fileName, const QXmlStreamReader &reader, + const QString &why) +{ + return PaletteEditor::tr("Cannot read palette from %1:%2:%3") + .arg(QDir::toNativeSeparators(fileName)).arg(reader.lineNumber()).arg(why); +} + +static inline QString msgCannotReadPalette(const QString &fileName, const QXmlStreamReader &reader) +{ + return msgCannotReadPalette(fileName, reader, reader.errorString()); +} + +static bool loadPalette(const QString &fileName, QPalette *pal, QString *errorMessage) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + *errorMessage = PaletteEditor::tr("Cannot open %1 for reading: %2") + .arg(QDir::toNativeSeparators(fileName), file.errorString()); + return false; + } + QXmlStreamReader reader(&file); + if (!reader.readNextStartElement()) { + *errorMessage = msgCannotReadPalette(fileName, reader); + return false; + } + if (reader.name() != "palette"_L1) { + const auto why = PaletteEditor::tr("Invalid element \"%1\", expected \"palette\".") + .arg(reader.name().toString()); + *errorMessage = msgCannotReadPalette(fileName, reader, why); + return false; + } + QScopedPointer domPalette(new DomPalette); + domPalette->read(reader); + if (reader.hasError()) { + *errorMessage = msgCannotReadPalette(fileName, reader); + return false; + } + *pal = QFormBuilderExtra::loadPalette(domPalette.data()); + return true; +} + +void PaletteEditor::save() +{ + QFileDialog dialog(this, tr("Save Palette"), QString(), paletteFilter()); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDefaultSuffix(u"xml"_s); + while (dialog.exec() == QDialog::Accepted) { + QString errorMessage; + if (savePalette(dialog.selectedFiles().constFirst(), palette(), &errorMessage)) + break; + QMessageBox::warning(this, tr("Error Writing Palette"), errorMessage); + } +} + +void PaletteEditor::load() +{ + QFileDialog dialog(this, tr("Load Palette"), QString(), paletteFilter()); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + while (dialog.exec() == QDialog::Accepted) { + QPalette pal; + QString errorMessage; + if (loadPalette(dialog.selectedFiles().constFirst(), &pal, &errorMessage)) { + setPalette(pal); + break; + } + QMessageBox::warning(this, tr("Error Reading Palette"), errorMessage); + } +} + +////////////////////// +// Column 0: Role name and reset button. Uses a boolean value indicating +// whether the role is modified for the edit role. +// Column 1: Color group Active +// Column 2: Color group Inactive (visibility depending on m_compute/detail radio group) +// Column 3: Color group Disabled + +PaletteModel::PaletteModel(QObject *parent) : + QAbstractTableModel(parent) +{ + const QMetaObject *meta = metaObject(); + const int index = meta->indexOfProperty("colorRole"); + const QMetaProperty p = meta->property(index); + const QMetaEnum e = p.enumerator(); + m_roleEntries.reserve(QPalette::NColorRoles); + for (int r = QPalette::WindowText; r < QPalette::NColorRoles; r++) { + const auto role = static_cast(r); + if (role != QPalette::NoRole) + m_roleEntries.append({QLatin1StringView(e.key(r)), role}); + } +} + +int PaletteModel::rowCount(const QModelIndex &) const +{ + return m_roleEntries.size(); +} + +int PaletteModel::columnCount(const QModelIndex &) const +{ + return 4; +} + +QBrush PaletteModel::brushAt(const QModelIndex &index) const +{ + return m_palette.brush(columnToGroup(index.column()), roleAt(index.row())); +} + +// Palette resolve mask with all group bits for a row/role +quint64 PaletteModel::rowMask(const QModelIndex &index) const +{ + return paletteResolveMask(roleAt(index.row())); +} + +QVariant PaletteModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.row() < 0 || index.row() >= m_roleEntries.size()) + return QVariant(); + if (index.column() < 0 || index.column() >= 4) + return QVariant(); + + if (index.column() == 0) { // Role name/bold print if changed + if (role == Qt::DisplayRole) + return m_roleEntries.at(index.row()).name; + if (role == Qt::EditRole) + return (rowMask(index) & m_palette.resolveMask()) != 0; + return QVariant(); + } + if (role == Qt::ToolTipRole) + return brushAt(index).color().name(); + if (role == BrushRole) + return brushAt(index); + return QVariant(); +} + +bool PaletteModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + const int row = index.row(); + const auto colorRole = roleAt(row); + + if (index.column() != 0 && role == BrushRole) { + const QBrush br = qvariant_cast(value); + const QPalette::ColorGroup g = columnToGroup(index.column()); + m_palette.setBrush(g, colorRole, br); + + QModelIndex idxBegin = PaletteModel::index(row, 0); + QModelIndex idxEnd = PaletteModel::index(row, 3); + if (m_compute) { + m_palette.setBrush(QPalette::Inactive, colorRole, br); + switch (colorRole) { + case QPalette::WindowText: + case QPalette::Text: + case QPalette::ButtonText: + case QPalette::Base: + break; + case QPalette::Dark: + m_palette.setBrush(QPalette::Disabled, QPalette::WindowText, br); + m_palette.setBrush(QPalette::Disabled, QPalette::Dark, br); + m_palette.setBrush(QPalette::Disabled, QPalette::Text, br); + m_palette.setBrush(QPalette::Disabled, QPalette::ButtonText, br); + idxBegin = PaletteModel::index(0, 0); + idxEnd = PaletteModel::index(m_roleEntries.size() - 1, 3); + break; + case QPalette::Window: + m_palette.setBrush(QPalette::Disabled, QPalette::Base, br); + m_palette.setBrush(QPalette::Disabled, QPalette::Window, br); + idxBegin = PaletteModel::index(rowOf(QPalette::Base), 0); + break; + case QPalette::Highlight: + //m_palette.setBrush(QPalette::Disabled, QPalette::Highlight, c.dark(120)); + break; + default: + m_palette.setBrush(QPalette::Disabled, colorRole, br); + break; + } + } + emit paletteChanged(m_palette); + emit dataChanged(idxBegin, idxEnd); + return true; + } + if (index.column() == 0 && role == Qt::EditRole) { + auto mask = m_palette.resolveMask(); + const bool isMask = qvariant_cast(value); + const auto bitMask = rowMask(index); + if (isMask) { + mask |= bitMask; + } else { + m_palette.setBrush(QPalette::Active, colorRole, + m_parentPalette.brush(QPalette::Active, colorRole)); + m_palette.setBrush(QPalette::Inactive, colorRole, + m_parentPalette.brush(QPalette::Inactive, colorRole)); + m_palette.setBrush(QPalette::Disabled, colorRole, + m_parentPalette.brush(QPalette::Disabled, colorRole)); + + mask &= ~bitMask; + } + m_palette.setResolveMask(mask); + emit paletteChanged(m_palette); + const QModelIndex idxEnd = PaletteModel::index(row, 3); + emit dataChanged(index, idxEnd); + return true; + } + return false; +} + +Qt::ItemFlags PaletteModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemIsEnabled; + return Qt::ItemIsEditable | Qt::ItemIsEnabled; +} + +QVariant PaletteModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + if (section == 0) + return tr("Color Role"); + if (section == groupToColumn(QPalette::Active)) + return tr("Active"); + if (section == groupToColumn(QPalette::Inactive)) + return tr("Inactive"); + if (section == groupToColumn(QPalette::Disabled)) + return tr("Disabled"); + } + return QVariant(); +} + +QPalette PaletteModel::getPalette() const +{ + return m_palette; +} + +void PaletteModel::setPalette(const QPalette &palette, const QPalette &parentPalette) +{ + m_parentPalette = parentPalette; + m_palette = palette; + const QModelIndex idxBegin = index(0, 0); + const QModelIndex idxEnd = index(m_roleEntries.size() - 1, 3); + emit dataChanged(idxBegin, idxEnd); +} + +QPalette::ColorGroup PaletteModel::columnToGroup(int index) const +{ + if (index == 1) + return QPalette::Active; + if (index == 2) + return QPalette::Inactive; + return QPalette::Disabled; +} + +int PaletteModel::groupToColumn(QPalette::ColorGroup group) const +{ + if (group == QPalette::Active) + return 1; + if (group == QPalette::Inactive) + return 2; + return 3; +} + +int PaletteModel::rowOf(QPalette::ColorRole role) const +{ + for (qsizetype row = 0, size = m_roleEntries.size(); row < size; ++row) { + if (m_roleEntries.at(row).role == role) + return row; + } + return -1; +} + +////////////////////////// + +BrushEditor::BrushEditor(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_button(new QtColorButton(this)), + m_core(core) +{ + QLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->addWidget(m_button); + connect(m_button, &QtColorButton::colorChanged, this, &BrushEditor::brushChanged); + setFocusProxy(m_button); +} + +void BrushEditor::setBrush(const QBrush &brush) +{ + m_button->setColor(brush.color()); + m_changed = false; +} + +QBrush BrushEditor::brush() const +{ + return QBrush(m_button->color()); +} + +void BrushEditor::brushChanged() +{ + m_changed = true; + emit changed(this); +} + +bool BrushEditor::changed() const +{ + return m_changed; +} + +////////////////////////// + +RoleEditor::RoleEditor(QWidget *parent) : + QWidget(parent), + m_label(new QLabel(this)) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + + layout->addWidget(m_label); + m_label->setAutoFillBackground(true); + m_label->setIndent(3); // ### hardcode it should have the same value of textMargin in QItemDelegate + setFocusProxy(m_label); + + QToolButton *button = new QToolButton(this); + button->setToolButtonStyle(Qt::ToolButtonIconOnly); + button->setIcon(createIconSet("resetproperty.png"_L1)); + button->setIconSize(QSize(8,8)); + button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding)); + layout->addWidget(button); + connect(button, &QAbstractButton::clicked, this, &RoleEditor::emitResetProperty); +} + +void RoleEditor::setLabel(const QString &label) +{ + m_label->setText(label); +} + +void RoleEditor::setEdited(bool on) +{ + QFont font; + if (on) + font.setBold(on); + m_label->setFont(font); + m_edited = on; +} + +bool RoleEditor::edited() const +{ + return m_edited; +} + +void RoleEditor::emitResetProperty() +{ + setEdited(false); + emit changed(this); +} + +////////////////////////// +ColorDelegate::ColorDelegate(QDesignerFormEditorInterface *core, QObject *parent) : + QStyledItemDelegate(parent), + m_core(core) +{ +} + +QWidget *ColorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, + const QModelIndex &index) const +{ + QWidget *ed = nullptr; + if (index.column() == 0) { + RoleEditor *editor = new RoleEditor(parent); + connect(editor, &RoleEditor::changed, this, &ColorDelegate::commitData); + //editor->setFocusPolicy(Qt::NoFocus); + //editor->installEventFilter(const_cast(this)); + ed = editor; + } else { + BrushEditor *editor = new BrushEditor(m_core, parent); + connect(editor, QOverload::of(&BrushEditor::changed), + this, &ColorDelegate::commitData); + editor->setFocusPolicy(Qt::NoFocus); + editor->installEventFilter(const_cast(this)); + ed = editor; + } + return ed; +} + +void ColorDelegate::setEditorData(QWidget *ed, const QModelIndex &index) const +{ + if (index.column() == 0) { + const bool mask = qvariant_cast(index.model()->data(index, Qt::EditRole)); + RoleEditor *editor = static_cast(ed); + editor->setEdited(mask); + const QString colorName = qvariant_cast(index.model()->data(index, Qt::DisplayRole)); + editor->setLabel(colorName); + } else { + const QBrush br = qvariant_cast(index.model()->data(index, BrushRole)); + BrushEditor *editor = static_cast(ed); + editor->setBrush(br); + } +} + +void ColorDelegate::setModelData(QWidget *ed, QAbstractItemModel *model, + const QModelIndex &index) const +{ + if (index.column() == 0) { + RoleEditor *editor = static_cast(ed); + const bool mask = editor->edited(); + model->setData(index, mask, Qt::EditRole); + } else { + BrushEditor *editor = static_cast(ed); + if (editor->changed()) { + QBrush br = editor->brush(); + model->setData(index, br, BrushRole); + } + } +} + +void ColorDelegate::updateEditorGeometry(QWidget *ed, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::updateEditorGeometry(ed, option, index); + ed->setGeometry(ed->geometry().adjusted(0, 0, -1, -1)); +} + +void ColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, + const QModelIndex &index) const +{ + QStyleOptionViewItem option = opt; + const bool mask = qvariant_cast(index.model()->data(index, Qt::EditRole)); + if (index.column() == 0 && mask) { + option.font.setBold(true); + } + QBrush br = qvariant_cast(index.model()->data(index, BrushRole)); + if (br.style() == Qt::LinearGradientPattern || + br.style() == Qt::RadialGradientPattern || + br.style() == Qt::ConicalGradientPattern) { + painter->save(); + painter->translate(option.rect.x(), option.rect.y()); + painter->scale(option.rect.width(), option.rect.height()); + QGradient gr = *(br.gradient()); + gr.setCoordinateMode(QGradient::LogicalMode); + br = QBrush(gr); + painter->fillRect(0, 0, 1, 1, br); + painter->restore(); + } else { + painter->save(); + painter->setBrushOrigin(option.rect.x(), option.rect.y()); + painter->fillRect(option.rect, br); + painter->restore(); + } + QStyledItemDelegate::paint(painter, option, index); + + + const QColor color = static_cast(QApplication::style()->styleHint(QStyle::SH_Table_GridLineColor, &option)); + const QPen oldPen = painter->pen(); + painter->setPen(QPen(color)); + + painter->drawLine(option.rect.right(), option.rect.y(), + option.rect.right(), option.rect.bottom()); + painter->drawLine(option.rect.x(), option.rect.bottom(), + option.rect.right(), option.rect.bottom()); + painter->setPen(oldPen); +} + +QSize ColorDelegate::sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &index) const +{ + return QStyledItemDelegate::sizeHint(opt, index) + QSize(4, 4); +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/paletteeditor.h b/src/designer/src/components/propertyeditor/paletteeditor.h new file mode 100644 index 0000000..e172682 --- /dev/null +++ b/src/designer/src/components/propertyeditor/paletteeditor.h @@ -0,0 +1,187 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PALETTEEDITOR_H +#define PALETTEEDITOR_H + +#include "ui_paletteeditor.h" +#include + +QT_BEGIN_NAMESPACE + +class QAction; +class QListView; +class QMenu; +class QLabel; +class QtColorButton; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class PaletteEditor: public QDialog +{ + Q_OBJECT +public: + ~PaletteEditor() override; + + static QPalette getPalette(QDesignerFormEditorInterface *core, + QWidget* parent, const QPalette &init = QPalette(), + const QPalette &parentPal = QPalette(), int *result = nullptr); + + QPalette palette() const; + void setPalette(const QPalette &palette); + void setPalette(const QPalette &palette, const QPalette &parentPalette); + +private slots: + + void buildButtonColorChanged(); + void activeRadioClicked(); + void inactiveRadioClicked(); + void disabledRadioClicked(); + void computeRadioClicked(); + void detailsRadioClicked(); + + void paletteChanged(const QPalette &palette); + void viewContextMenuRequested(QPoint pos); + void save(); + void load(); + +protected: + +private: + PaletteEditor(QDesignerFormEditorInterface *core, QWidget *parent); + void buildPalette(); + + void updatePreviewPalette(); + void updateStyledButton(); + + QPalette::ColorGroup currentColorGroup() const + { return m_currentColorGroup; } + + Ui::PaletteEditor ui; + QPalette m_editPalette; + QPalette m_parentPalette; + class PaletteModel *m_paletteModel; + QDesignerFormEditorInterface *m_core; + QAction *m_lighterAction = nullptr; + QAction *m_darkerAction = nullptr; + QAction *m_copyColorAction = nullptr; + QMenu *m_contextMenu = nullptr; + QPalette::ColorGroup m_currentColorGroup = QPalette::Active; + bool m_modelUpdated = false; + bool m_paletteUpdated = false; + bool m_compute = true; +}; + + +class PaletteModel : public QAbstractTableModel +{ + Q_OBJECT + Q_PROPERTY(QPalette::ColorRole colorRole READ colorRole) +public: + explicit PaletteModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + QPalette getPalette() const; + void setPalette(const QPalette &palette, const QPalette &parentPalette); + + QBrush brushAt(const QModelIndex &index) const; + + QPalette::ColorRole colorRole() const { return QPalette::NoRole; } + void setCompute(bool on) { m_compute = on; } + + quint64 rowMask(const QModelIndex &index) const; + +signals: + void paletteChanged(const QPalette &palette); +private: + struct RoleEntry + { + QString name; + QPalette::ColorRole role; + }; + + QPalette::ColorGroup columnToGroup(int index) const; + int groupToColumn(QPalette::ColorGroup group) const; + QPalette::ColorRole roleAt(int row) const { return m_roleEntries.at(row).role; } + int rowOf(QPalette::ColorRole role) const; + + QPalette m_palette; + QPalette m_parentPalette; + QList m_roleEntries; + bool m_compute = true; +}; + +class BrushEditor : public QWidget +{ + Q_OBJECT +public: + explicit BrushEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + + void setBrush(const QBrush &brush); + QBrush brush() const; + bool changed() const; +signals: + void changed(QWidget *widget); +private slots: + void brushChanged(); +private: + QtColorButton *m_button; + bool m_changed = false; + QDesignerFormEditorInterface *m_core; +}; + +class RoleEditor : public QWidget +{ + Q_OBJECT +public: + explicit RoleEditor(QWidget *parent = nullptr); + + void setLabel(const QString &label); + void setEdited(bool on); + bool edited() const; +signals: + void changed(QWidget *widget); +private slots: + void emitResetProperty(); +private: + QLabel *m_label; + bool m_edited = false; +}; + +class ColorDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit ColorDelegate(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void setEditorData(QWidget *ed, const QModelIndex &index) const override; + void setModelData(QWidget *ed, QAbstractItemModel *model, + const QModelIndex &index) const override; + + void updateEditorGeometry(QWidget *ed, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void paint(QPainter *painter, const QStyleOptionViewItem &opt, + const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &index) const override; +private: + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PALETTEEDITOR_H diff --git a/src/designer/src/components/propertyeditor/paletteeditor.ui b/src/designer/src/components/propertyeditor/paletteeditor.ui new file mode 100644 index 0000000..0a6aeaa --- /dev/null +++ b/src/designer/src/components/propertyeditor/paletteeditor.ui @@ -0,0 +1,237 @@ + + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::PaletteEditor + + + + 0 + 0 + 918 + 599 + + + + + 0 + 0 + + + + Edit Palette + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Tune Palette + + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 200 + + + + + + + + Show Details + + + + + + + Compute Details + + + true + + + + + + + Quick + + + + + + + + + + + 0 + 0 + + + + Preview + + + + 8 + + + 8 + + + 8 + + + 8 + + + 6 + + + + + Disabled + + + + + + + Inactive + + + + + + + Active + + + true + + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + QtColorButton + QToolButton +
qtcolorbutton_p.h
+
+ + qdesigner_internal::PreviewFrame + QWidget +
previewframe.h
+
+
+ + + + buttonBox + accepted() + qdesigner_internal::PaletteEditor + accept() + + + 180 + 331 + + + 134 + 341 + + + + + buttonBox + rejected() + qdesigner_internal::PaletteEditor + reject() + + + 287 + 329 + + + 302 + 342 + + + + +
diff --git a/src/designer/src/components/propertyeditor/paletteeditorbutton.cpp b/src/designer/src/components/propertyeditor/paletteeditorbutton.cpp new file mode 100644 index 0000000..4f6661b --- /dev/null +++ b/src/designer/src/components/propertyeditor/paletteeditorbutton.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "paletteeditorbutton.h" +#include "paletteeditor.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +PaletteEditorButton::PaletteEditorButton(QDesignerFormEditorInterface *core, const QPalette &palette, QWidget *parent) + : QToolButton(parent), + m_palette(palette) +{ + m_core = core; + setFocusPolicy(Qt::NoFocus); + setText(tr("Change Palette")); + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + + connect(this, &QAbstractButton::clicked, this, &PaletteEditorButton::showPaletteEditor); +} + +PaletteEditorButton::~PaletteEditorButton() = default; + +void PaletteEditorButton::setPalette(const QPalette &palette) +{ + m_palette = palette; +} + +void PaletteEditorButton::setSuperPalette(const QPalette &palette) +{ + m_superPalette = palette; +} + +void PaletteEditorButton::showPaletteEditor() +{ + int result; + QPalette pal = PaletteEditor::getPalette(m_core, nullptr, m_palette, m_superPalette, &result); + if (result == QDialog::Accepted) { + m_palette = pal; + emit paletteChanged(m_palette); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/paletteeditorbutton.h b/src/designer/src/components/propertyeditor/paletteeditorbutton.h new file mode 100644 index 0000000..01b9c21 --- /dev/null +++ b/src/designer/src/components/propertyeditor/paletteeditorbutton.h @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PALETTEEDITORBUTTON_H +#define PALETTEEDITORBUTTON_H + +#include "propertyeditor_global.h" + +#include +#include + +#include "abstractformeditor.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QT_PROPERTYEDITOR_EXPORT PaletteEditorButton: public QToolButton +{ + Q_OBJECT +public: + PaletteEditorButton(QDesignerFormEditorInterface *core, const QPalette &palette, QWidget *parent = nullptr); + ~PaletteEditorButton() override; + + void setSuperPalette(const QPalette &palette); + inline QPalette palette() const + { return m_palette; } + +signals: + void paletteChanged(const QPalette &palette); + +public slots: + void setPalette(const QPalette &palette); + +private slots: + void showPaletteEditor(); + +private: + QPalette m_palette; + QPalette m_superPalette; + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PALETTEEDITORBUTTON_H diff --git a/src/designer/src/components/propertyeditor/pixmapeditor.cpp b/src/designer/src/components/propertyeditor/pixmapeditor.cpp new file mode 100644 index 0000000..312fd13 --- /dev/null +++ b/src/designer/src/components/propertyeditor/pixmapeditor.cpp @@ -0,0 +1,420 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "pixmapeditor.h" +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr QSize ICON_SIZE{16, 16}; + +namespace qdesigner_internal { + +static void createIconThemeDialog(QDialog *topLevel, const QString &labelText, + QWidget *themeEditor) +{ + QVBoxLayout *layout = new QVBoxLayout(topLevel); + QLabel *label = new QLabel(labelText, topLevel); + QDialogButtonBox *buttons = new QDialogButtonBox(topLevel); + buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QObject::connect(buttons, &QDialogButtonBox::accepted, topLevel, &QDialog::accept); + QObject::connect(buttons, &QDialogButtonBox::rejected, topLevel, &QDialog::reject); + + layout->addWidget(label); + layout->addWidget(themeEditor); + layout->addWidget(buttons); +} + +IconThemeDialog::IconThemeDialog(QWidget *parent) + : QDialog(parent) +{ + setWindowTitle(tr("Set Icon From XDG Theme")); + m_editor = new IconThemeEditor(this); + createIconThemeDialog(this, tr("Select icon name from XDG theme:"), m_editor); +} + +std::optional IconThemeDialog::getTheme(QWidget *parent, const QString &theme) +{ + IconThemeDialog dlg(parent); + dlg.m_editor->setTheme(theme); + if (dlg.exec() == QDialog::Accepted) + return dlg.m_editor->theme(); + return std::nullopt; +} + +IconThemeEnumDialog::IconThemeEnumDialog(QWidget *parent) + : QDialog(parent) +{ + setWindowTitle(tr("Set Icon From Theme")); + m_editor = new IconThemeEnumEditor(this); + createIconThemeDialog(this, tr("Select icon name from theme:"), m_editor); +} + +std::optional IconThemeEnumDialog::getTheme(QWidget *parent, int theme) +{ + IconThemeEnumDialog dlg(parent); + dlg.m_editor->setThemeEnum(theme); + if (dlg.exec() == QDialog::Accepted) + return dlg.m_editor->themeEnum(); + return std::nullopt; +} + +PixmapEditor::PixmapEditor(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_iconThemeModeEnabled(false), + m_core(core), + m_pixmapLabel(new QLabel(this)), + m_pathLabel(new QLabel(this)), + m_button(new QToolButton(this)), + m_resourceAction(new QAction(tr("Choose Resource..."), this)), + m_fileAction(new QAction(tr("Choose File..."), this)), + m_themeEnumAction(new QAction(tr("Set Icon From Theme..."), this)), + m_themeAction(new QAction(tr("Set Icon From XDG Theme..."), this)), + m_copyAction(new QAction(createIconSet(QIcon::ThemeIcon::EditCopy, "editcopy.png"_L1), + tr("Copy Path"), this)), + m_pasteAction(new QAction(createIconSet(QIcon::ThemeIcon::EditPaste, "editpaste.png"_L1), + tr("Paste Path"), this)), + m_layout(new QHBoxLayout(this)), + m_pixmapCache(nullptr) +{ + m_layout->addWidget(m_pixmapLabel); + m_layout->addWidget(m_pathLabel); + m_button->setText(tr("...")); + m_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + m_button->setFixedWidth(30); + m_button->setPopupMode(QToolButton::MenuButtonPopup); + m_layout->addWidget(m_button); + m_layout->setContentsMargins(QMargins()); + m_layout->setSpacing(0); + m_pixmapLabel->setFixedWidth(ICON_SIZE.width()); + m_pixmapLabel->setAlignment(Qt::AlignCenter); + m_pathLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed)); + m_themeAction->setVisible(false); + m_themeEnumAction->setVisible(false); + + QMenu *menu = new QMenu(this); + menu->addAction(m_resourceAction); + menu->addAction(m_fileAction); + menu->addAction(m_themeEnumAction); + menu->addAction(m_themeAction); + + m_button->setMenu(menu); + m_button->setText(tr("...")); + + connect(m_button, &QAbstractButton::clicked, this, &PixmapEditor::defaultActionActivated); + connect(m_resourceAction, &QAction::triggered, this, &PixmapEditor::resourceActionActivated); + connect(m_fileAction, &QAction::triggered, this, &PixmapEditor::fileActionActivated); + connect(m_themeEnumAction, &QAction::triggered, this, &PixmapEditor::themeEnumActionActivated); + connect(m_themeAction, &QAction::triggered, this, &PixmapEditor::themeActionActivated); +#if QT_CONFIG(clipboard) + connect(m_copyAction, &QAction::triggered, this, &PixmapEditor::copyActionActivated); + connect(m_pasteAction, &QAction::triggered, this, &PixmapEditor::pasteActionActivated); +#endif + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored)); + setFocusProxy(m_button); + +#if QT_CONFIG(clipboard) + connect(QApplication::clipboard(), &QClipboard::dataChanged, + this, &PixmapEditor::clipboardDataChanged); + clipboardDataChanged(); +#endif +} + +void PixmapEditor::setPixmapCache(DesignerPixmapCache *cache) +{ + m_pixmapCache = cache; +} + +void PixmapEditor::setIconThemeModeEnabled(bool enabled) +{ + if (m_iconThemeModeEnabled == enabled) + return; + m_iconThemeModeEnabled = enabled; + m_themeAction->setVisible(enabled); + m_themeEnumAction->setVisible(enabled); +} + +void PixmapEditor::setSpacing(int spacing) +{ + m_layout->setSpacing(spacing); +} + +void PixmapEditor::setPath(const QString &path) +{ + m_path = path; + updateLabels(); +} + +void PixmapEditor::setTheme(const QString &theme) +{ + m_theme = theme; + updateLabels(); +} + +QString PixmapEditor::msgThemeIcon(const QString &t) +{ + return tr("[Theme] %1").arg(t); +} + +QString PixmapEditor::msgMissingThemeIcon(const QString &t) +{ + return tr("[Theme] %1 (missing)").arg(t); +} + +void PixmapEditor::setThemeEnum(int e) +{ + m_themeEnum = e; + updateLabels(); +} + +void PixmapEditor::updateLabels() +{ + m_pathLabel->setText(displayText(m_themeEnum, m_theme, m_path)); + switch (state()) { + case State::Empty: + case State::MissingXdgTheme: + case State::MissingThemeEnum: + m_pixmapLabel->setPixmap(m_defaultPixmap); + m_copyAction->setEnabled(false); + break; + case State::ThemeEnum: + m_pixmapLabel->setPixmap(QIcon::fromTheme(static_cast(m_themeEnum)).pixmap(ICON_SIZE)); + m_copyAction->setEnabled(true); + break; + case State::XdgTheme: + m_pixmapLabel->setPixmap(QIcon::fromTheme(m_theme).pixmap(ICON_SIZE)); + m_copyAction->setEnabled(true); + break; + case State::Path: + case State::PathFallback: + if (m_pixmapCache) { + auto pixmap = m_pixmapCache->pixmap(PropertySheetPixmapValue(m_path)); + m_pixmapLabel->setPixmap(QIcon(pixmap).pixmap(ICON_SIZE)); + } + m_copyAction->setEnabled(true); + break; + } +} + +void PixmapEditor::setDefaultPixmapIcon(const QIcon &icon) +{ + m_defaultPixmap = icon.pixmap(ICON_SIZE); + if (state() == State::Empty) + m_pixmapLabel->setPixmap(m_defaultPixmap); +} + +void PixmapEditor::setDefaultPixmap(const QPixmap &pixmap) +{ + setDefaultPixmapIcon(QIcon(pixmap)); +} + +void PixmapEditor::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu menu(this); + menu.addAction(m_copyAction); + menu.addAction(m_pasteAction); + menu.exec(event->globalPos()); + event->accept(); +} + +void PixmapEditor::defaultActionActivated() +{ + if (m_iconThemeModeEnabled) { + themeEnumActionActivated(); + return; + } + // Default to resource + const PropertySheetPixmapValue::PixmapSource ps = m_path.isEmpty() + ? PropertySheetPixmapValue::ResourcePixmap + : PropertySheetPixmapValue::getPixmapSource(m_core, m_path); + switch (ps) { + case PropertySheetPixmapValue::LanguageResourcePixmap: + case PropertySheetPixmapValue::ResourcePixmap: + resourceActionActivated(); + break; + case PropertySheetPixmapValue::FilePixmap: + fileActionActivated(); + break; + } +} + +void PixmapEditor::resourceActionActivated() +{ + const QString oldPath = m_path; + const QString newPath = IconSelector::choosePixmapResource(m_core, m_core->resourceModel(), + oldPath, this); + if (!newPath.isEmpty() && newPath != oldPath) { + setTheme({}); + setThemeEnum(-1); + setPath(newPath); + emit pathChanged(newPath); + } +} + +void PixmapEditor::fileActionActivated() +{ + const QString newPath = IconSelector::choosePixmapFile(m_path, m_core->dialogGui(), this); + if (!newPath.isEmpty() && newPath != m_path) { + setTheme({}); + setThemeEnum(-1); + setPath(newPath); + emit pathChanged(newPath); + } +} + +void PixmapEditor::themeEnumActionActivated() +{ + const auto newThemeO = IconThemeEnumDialog::getTheme(this, {}); + if (newThemeO.has_value()) { + const int newTheme = newThemeO.value(); + if (newTheme != m_themeEnum) { + setThemeEnum(newTheme); + setTheme({}); + setPath({}); + emit themeEnumChanged(newTheme); + } + } +} + +void PixmapEditor::themeActionActivated() +{ + const auto newThemeO = IconThemeDialog::getTheme(this, m_theme); + if (newThemeO.has_value()) { + const QString newTheme = newThemeO.value(); + if (newTheme != m_theme) { + setTheme(newTheme); + setThemeEnum(-1); + setPath({}); + emit themeChanged(newTheme); + } + } +} + +PixmapEditor::State PixmapEditor::stateFromData(int themeEnum, const QString &xdgTheme, + const QString &path) +{ + if (themeEnum != -1) { + if (QIcon::hasThemeIcon(static_cast(themeEnum))) + return State::ThemeEnum; + return path.isEmpty() ? State::MissingThemeEnum : State::PathFallback; + } + if (!xdgTheme.isEmpty()) { + if (QIcon::hasThemeIcon(xdgTheme)) + return State::XdgTheme; + return path.isEmpty() ? State::MissingXdgTheme : State::PathFallback; + } + return path.isEmpty() ? State::Empty : State::Path; +} + +PixmapEditor::State PixmapEditor::state() const +{ + return stateFromData(m_themeEnum, m_theme, m_path); +} + +QString PixmapEditor::displayText(int themeEnum, const QString &xdgTheme, const QString &path) +{ + switch (stateFromData(themeEnum, xdgTheme, path)) { + case State::ThemeEnum: + return msgThemeIcon(IconThemeEnumEditor::iconName(themeEnum)); + case State::MissingThemeEnum: + return msgMissingThemeIcon(IconThemeEnumEditor::iconName(themeEnum)); + case State::XdgTheme: + return msgThemeIcon(xdgTheme); + case State::MissingXdgTheme: + return msgMissingThemeIcon(xdgTheme); + case State::Path: + return QFileInfo(path).fileName(); + case State::PathFallback: + return tr("%1 (fallback)").arg(QFileInfo(path).fileName()); + case State::Empty: + break; + } + return {}; +} + +QString PixmapEditor::displayText(const PropertySheetIconValue &icon) +{ + const auto &paths = icon.paths(); + const auto &it = paths.constFind({QIcon::Normal, QIcon::Off}); + const QString path = it != paths.constEnd() ? it.value().path() : QString{}; + return displayText(icon.themeEnum(), icon.theme(), path); +} + +#if QT_CONFIG(clipboard) +void PixmapEditor::copyActionActivated() +{ + QClipboard *clipboard = QApplication::clipboard(); + switch (state()) { + case State::ThemeEnum: + case State::MissingThemeEnum: + clipboard->setText(IconThemeEnumEditor::iconName(m_themeEnum)); + break; + case State::XdgTheme: + case State::MissingXdgTheme: + clipboard->setText(m_theme); + break; + case State::Path: + case State::PathFallback: + clipboard->setText(m_path); + break; + case State::Empty: + break; + } +} + +void PixmapEditor::pasteActionActivated() +{ + QClipboard *clipboard = QApplication::clipboard(); + QString subtype = u"plain"_s; + QString text = clipboard->text(subtype); + if (!text.isNull()) { + QStringList list = text.split(u'\n'); + if (!list.isEmpty()) { + text = list.at(0); + if (m_iconThemeModeEnabled && QIcon::hasThemeIcon(text)) { + setTheme(text); + setPath(QString()); + emit themeChanged(text); + } else { + setPath(text); + setTheme(QString()); + emit pathChanged(text); + } + } + } +} + +void PixmapEditor::clipboardDataChanged() +{ + QClipboard *clipboard = QApplication::clipboard(); + QString subtype = u"plain"_s; + const QString text = clipboard->text(subtype); + m_pasteAction->setEnabled(!text.isNull()); +} +#endif // QT_CONFIG(clipboard) + +} // qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/pixmapeditor.h b/src/designer/src/components/propertyeditor/pixmapeditor.h new file mode 100644 index 0000000..9ca7305 --- /dev/null +++ b/src/designer/src/components/propertyeditor/pixmapeditor.h @@ -0,0 +1,128 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PIXMAPEDITOR_H +#define PIXMAPEDITOR_H + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QLabel; +class QHBoxLayout; +class QToolButton; + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class DesignerPixmapCache; +class IconThemeEditor; +class IconThemeEnumEditor; +class PropertySheetIconValue; + +class IconThemeDialog : public QDialog +{ + Q_OBJECT +public: + static std::optional getTheme(QWidget *parent, const QString &theme); +private: + explicit IconThemeDialog(QWidget *parent); + IconThemeEditor *m_editor; +}; + +class IconThemeEnumDialog : public QDialog +{ + Q_OBJECT +public: + static std::optional getTheme(QWidget *parent, int theme); + +private: + IconThemeEnumDialog(QWidget *parent); + IconThemeEnumEditor *m_editor; +}; + +class PixmapEditor : public QWidget +{ + Q_OBJECT +public: + explicit PixmapEditor(QDesignerFormEditorInterface *core, QWidget *parent); + + void setSpacing(int spacing); + void setPixmapCache(DesignerPixmapCache *cache); + void setIconThemeModeEnabled(bool enabled); + + static QString msgThemeIcon(const QString &t); + static QString msgMissingThemeIcon(const QString &t); + static QString displayText(const PropertySheetIconValue &icon); + +public slots: + void setPath(const QString &path); + void setTheme(const QString &theme); + void setThemeEnum(int e); + void setDefaultPixmap(const QPixmap &pixmap); + void setDefaultPixmapIcon(const QIcon &icon); + +signals: + void pathChanged(const QString &path); + void themeEnumChanged(int themeEnum); + void themeChanged(const QString &theme); + +protected: + void contextMenuEvent(QContextMenuEvent *event) override; + +private slots: + void defaultActionActivated(); + void resourceActionActivated(); + void fileActionActivated(); + void themeEnumActionActivated(); + void themeActionActivated(); +#if QT_CONFIG(clipboard) + void copyActionActivated(); + void pasteActionActivated(); + void clipboardDataChanged(); +#endif +private: + enum class State { + Empty, + ThemeEnum, + MissingThemeEnum, + XdgTheme, + MissingXdgTheme, + Path, + PathFallback // Non-existent theme icon, falling back to path + }; + + static State stateFromData(int themeEnum, const QString &xdgTheme, const QString &path); + State state() const; + static QString displayText(int themeEnum, const QString &xdgTheme, const QString &path); + + void updateLabels(); + bool m_iconThemeModeEnabled; + QDesignerFormEditorInterface *m_core; + QLabel *m_pixmapLabel; + QLabel *m_pathLabel; + QToolButton *m_button; + QAction *m_resourceAction; + QAction *m_fileAction; + QAction *m_themeEnumAction; + QAction *m_themeAction; + QAction *m_copyAction; + QAction *m_pasteAction; + QHBoxLayout *m_layout; + QPixmap m_defaultPixmap; + QString m_path; + QString m_theme; + int m_themeEnum = -1; + DesignerPixmapCache *m_pixmapCache; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PIXMAPEDITOR_H diff --git a/src/designer/src/components/propertyeditor/previewframe.cpp b/src/designer/src/components/propertyeditor/previewframe.cpp new file mode 100644 index 0000000..04a3141 --- /dev/null +++ b/src/designer/src/components/propertyeditor/previewframe.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "previewframe.h" +#include "previewwidget.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + + class PreviewMdiArea: public QMdiArea { + public: + PreviewMdiArea(QWidget *parent = nullptr) : QMdiArea(parent) {} + protected: + bool viewportEvent(QEvent *event) override; + }; + + bool PreviewMdiArea::viewportEvent (QEvent * event) { + if (event->type() != QEvent::Paint) + return QMdiArea::viewportEvent (event); + QWidget *paintWidget = viewport(); + QPainter p(paintWidget); + p.fillRect(rect(), paintWidget->palette().color(backgroundRole()).darker()); + p.setPen(QPen(Qt::white)); + //: Palette editor background + p.drawText(0, height() / 2, width(), height(), Qt::AlignHCenter, + QCoreApplication::translate("qdesigner_internal::PreviewMdiArea", "The moose in the noose\nate the goose who was loose.")); + return true; + } + +PreviewFrame::PreviewFrame(QWidget *parent) : + QFrame(parent), + m_mdiArea(new PreviewMdiArea(this)) +{ + m_mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + setLineWidth(1); + + QVBoxLayout *vbox = new QVBoxLayout(this); + vbox->setContentsMargins(QMargins()); + vbox->addWidget(m_mdiArea); + + setMinimumSize(ensureMdiSubWindow()->minimumSizeHint()); +} + +void PreviewFrame::setPreviewPalette(const QPalette &pal) +{ + ensureMdiSubWindow()->setPalette(pal); +} + +void PreviewFrame::setSubWindowActive(bool active) +{ + m_mdiArea->setActiveSubWindow (active ? ensureMdiSubWindow() : nullptr); +} + +QMdiSubWindow *PreviewFrame::ensureMdiSubWindow() +{ + if (!m_mdiSubWindow) { + PreviewWidget *previewWidget = new PreviewWidget(m_mdiArea); + m_mdiSubWindow = m_mdiArea->addSubWindow(previewWidget, Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint); + m_mdiSubWindow->move(10,10); + m_mdiSubWindow->showMaximized(); + } + + const Qt::WindowStates state = m_mdiSubWindow->windowState(); + if (state & Qt::WindowMinimized) + m_mdiSubWindow->setWindowState(state & ~Qt::WindowMinimized); + + return m_mdiSubWindow; +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/previewframe.h b/src/designer/src/components/propertyeditor/previewframe.h new file mode 100644 index 0000000..f368bb8 --- /dev/null +++ b/src/designer/src/components/propertyeditor/previewframe.h @@ -0,0 +1,38 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PREVIEWFRAME_H +#define PREVIEWFRAME_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QMdiArea; +class QMdiSubWindow; + +namespace qdesigner_internal { + +class PreviewFrame: public QFrame +{ + Q_OBJECT +public: + explicit PreviewFrame(QWidget *parent); + + void setPreviewPalette(const QPalette &palette); + void setSubWindowActive(bool active); + +private: + // The user can on some platforms close the mdi child by invoking the system menu. + // Ensure a child is present. + QMdiSubWindow *ensureMdiSubWindow(); + QMdiArea *m_mdiArea; + QPointer m_mdiSubWindow; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/components/propertyeditor/previewwidget.cpp b/src/designer/src/components/propertyeditor/previewwidget.cpp new file mode 100644 index 0000000..722338d --- /dev/null +++ b/src/designer/src/components/propertyeditor/previewwidget.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "previewwidget.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +PreviewWidget::PreviewWidget(QWidget *parent) + : QWidget(parent) +{ + ui.setupUi(this); + ui.treeWidget->expandAll(); + auto model = ui.treeWidget->model(); + ui.treeWidget->setCurrentIndex(model->index(0, 0, model->index(0, 0))); + auto toolButtonMenu = new QMenu(ui.menuToolButton); + toolButtonMenu->addAction(tr("Option 1")); + toolButtonMenu->addSeparator(); + auto checkable = toolButtonMenu->addAction(tr("Checkable")); + checkable->setCheckable(true); + ui.menuToolButton->setMenu(toolButtonMenu); + ui.menuToolButton->setPopupMode(QToolButton::InstantPopup); +} + +PreviewWidget::~PreviewWidget() = default; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/previewwidget.h b/src/designer/src/components/propertyeditor/previewwidget.h new file mode 100644 index 0000000..5b129ef --- /dev/null +++ b/src/designer/src/components/propertyeditor/previewwidget.h @@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PREVIEWWIDGET_H +#define PREVIEWWIDGET_H + +#include "ui_previewwidget.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class PreviewWidget: public QWidget +{ + Q_OBJECT +public: + explicit PreviewWidget(QWidget *parent); + ~PreviewWidget() override; + +private: + Ui::PreviewWidget ui; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PREVIEWWIDGET_H diff --git a/src/designer/src/components/propertyeditor/previewwidget.ui b/src/designer/src/components/propertyeditor/previewwidget.ui new file mode 100644 index 0000000..dcbf627 --- /dev/null +++ b/src/designer/src/components/propertyeditor/previewwidget.ui @@ -0,0 +1,307 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::PreviewWidget + + + + 0 + 0 + 608 + 367 + + + + + 0 + 0 + + + + Preview Window + + + + + + Buttons + + + true + + + + + + + + RadioButton1 + + + true + + + + + + + RadioButton2 + + + + + + + RadioButton3 + + + + + + + CheckBox1 + + + true + + + + + + + Tristate CheckBox + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + PushButton + + + + + + + ToggleButton + + + true + + + true + + + false + + + + + + + + + ToolButton + + + + + + + Menu + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Item Views + + + true + + + + + + true + + + + Column 1 + + + + + Top Level 1 + + + + Nested Item 1 + + + + + Nested Item 2 + + + + + Nested Item 3 + + + + + + + + + + + + Simple Input Widgets + + + true + + + + + + + + LineEdit + + + true + + + + + + + + ComboBox + + + + + Item1 + + + + + Item2 + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + + + + + + Display Widgets + + + + + + 50 + + + Qt::Horizontal + + + + + + + QLabel + + + + + + + QFrame::StyledPanel + + + QLabel with frame + + + + + + + + + + + diff --git a/src/designer/src/components/propertyeditor/propertyeditor.cpp b/src/designer/src/components/propertyeditor/propertyeditor.cpp new file mode 100644 index 0000000..131be0f --- /dev/null +++ b/src/designer/src/components/propertyeditor/propertyeditor.cpp @@ -0,0 +1,1277 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "propertyeditor.h" + +#include "qttreepropertybrowser_p.h" +#include "qtbuttonpropertybrowser_p.h" +#include "qtvariantproperty_p.h" +#include "designerpropertymanager.h" +#include "qdesigner_propertysheet_p.h" +#include "formwindowbase_p.h" + +#include "newdynamicpropertydialog.h" +#include "dynamicpropertysheet.h" +#include "shared_enums_p.h" + +// sdk +#include +#include +#include +#include +#include +#include +// shared +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +enum SettingsView { TreeView, ButtonView }; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto SettingsGroupC = "PropertyEditor"_L1; +static constexpr auto ViewKeyC = "View"_L1; +static constexpr auto ColorKeyC = "Colored"_L1; +static constexpr auto SortedKeyC = "Sorted"_L1; +static constexpr auto ExpansionKeyC = "ExpandedItems"_L1; +static constexpr auto SplitterPositionKeyC = "SplitterPosition"_L1; + +// --------------------------------------------------------------------------------- + +namespace qdesigner_internal { + +// ----------- ElidingLabel +// QLabel does not support text eliding so we need a helper class + +class ElidingLabel : public QWidget +{ +public: + explicit ElidingLabel(const QString &text = QString(), + QWidget *parent = nullptr) : QWidget(parent), m_text(text) + { setContentsMargins(3, 2, 3, 2); } + + void setText(const QString &text) { + m_text = text; + updateGeometry(); + } + void setElidemode(Qt::TextElideMode mode) { + m_mode = mode; + updateGeometry(); + } + +protected: + QSize sizeHint() const override; + void paintEvent(QPaintEvent *e) override; + +private: + QString m_text; + Qt::TextElideMode m_mode = Qt::ElideRight; +}; + +QSize ElidingLabel::sizeHint() const +{ + QSize size = fontMetrics().boundingRect(m_text).size(); + size += QSize(contentsMargins().left() + contentsMargins().right(), + contentsMargins().top() + contentsMargins().bottom()); + return size; +} + +void ElidingLabel::paintEvent(QPaintEvent *) { + QPainter painter(this); + painter.setPen(QColor(0, 0, 0, 60)); + painter.setBrush(QColor(255, 255, 255, 40)); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + painter.setPen(palette().windowText().color()); + painter.drawText(contentsRect(), Qt::AlignLeft, + fontMetrics().elidedText(m_text, Qt::ElideRight, width(), 0)); +} + + +// ----------- PropertyEditor::Strings + +PropertyEditor::Strings::Strings() : + m_alignmentProperties{u"alignment"_s, + u"layoutLabelAlignment"_s, // QFormLayout + u"layoutFormAlignment"_s}, + m_fontProperty(u"font"_s), + m_qLayoutWidget(u"QLayoutWidget"_s), + m_designerPrefix(u"QDesigner"_s), + m_layout(u"Layout"_s), + m_validationModeAttribute(u"validationMode"_s), + m_fontAttribute(u"font"_s), + m_superPaletteAttribute(u"superPalette"_s), + m_enumNamesAttribute(u"enumNames"_s), + m_resettableAttribute(u"resettable"_s), + m_flagsAttribute(u"flags"_s) +{ +} + +// ----------- PropertyEditor + +QDesignerMetaDataBaseItemInterface* PropertyEditor::metaDataBaseItem() const +{ + QObject *o = object(); + if (!o) + return nullptr; + QDesignerMetaDataBaseInterface *db = core()->metaDataBase(); + if (!db) + return nullptr; + return db->item(o); +} + +void PropertyEditor::setupStringProperty(QtVariantProperty *property, bool isMainContainer) +{ + const StringPropertyParameters params = textPropertyValidationMode(core(), m_object, property->propertyName(), isMainContainer); + // Does a meta DB entry exist - add comment + const bool hasComment = params.second; + property->setAttribute(m_strings.m_validationModeAttribute, params.first); + // assuming comment cannot appear or disappear for the same property in different object instance + if (!hasComment) + qDeleteAll(property->subProperties()); +} + +void PropertyEditor::setupPaletteProperty(QtVariantProperty *property) +{ + QPalette superPalette = QPalette(); + QWidget *currentWidget = qobject_cast(m_object); + if (currentWidget) { + if (currentWidget->isWindow()) + superPalette = QApplication::palette(currentWidget); + else { + if (currentWidget->parentWidget()) + superPalette = currentWidget->parentWidget()->palette(); + } + } + m_updatingBrowser = true; + property->setAttribute(m_strings.m_superPaletteAttribute, superPalette); + m_updatingBrowser = false; +} + +static inline QToolButton *createDropDownButton(QAction *defaultAction, QWidget *parent = nullptr) +{ + QToolButton *rc = new QToolButton(parent); + rc->setDefaultAction(defaultAction); + rc->setPopupMode(QToolButton::InstantPopup); + return rc; +} + +PropertyEditor::PropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) : + QDesignerPropertyEditor(parent, flags), + m_core(core), + m_propertyManager(new DesignerPropertyManager(m_core, this)), + m_stackedWidget(new QStackedWidget), + m_filterWidget(new QLineEdit), + m_addDynamicAction(new QAction(createIconSet("plus.png"_L1), tr("Add Dynamic Property..."), this)), + m_removeDynamicAction(new QAction(createIconSet("minus.png"_L1), tr("Remove Dynamic Property"), this)), + m_sortingAction(new QAction(createIconSet("sort.png"_L1), tr("Sorting"), this)), + m_coloringAction(new QAction(createIconSet("color.png"_L1), tr("Color Groups"), this)), + m_treeAction(new QAction(tr("Tree View"), this)), + m_buttonAction(new QAction(tr("Drop Down Button View"), this)), + m_classLabel(new ElidingLabel) +{ + const QColor colors[] = {{255, 230, 191}, {255, 255, 191}, {191, 255, 191}, + {199, 255, 255}, {234, 191, 255}, {255, 191, 239}}; + const int darknessFactor = 250; + m_colors.reserve(std::size(colors)); + for (const QColor &c : colors) + m_colors.append({c, c.darker(darknessFactor)}); + QColor dynamicColor(191, 207, 255); + QColor layoutColor(255, 191, 191); + m_dynamicColor = {dynamicColor, dynamicColor.darker(darknessFactor)}; + m_layoutColor = {layoutColor, layoutColor.darker(darknessFactor)}; + + updateForegroundBrightness(); + + QActionGroup *actionGroup = new QActionGroup(this); + + m_treeAction->setCheckable(true); + m_treeAction->setIcon(createIconSet("widgets/listview.png"_L1)); + m_buttonAction->setCheckable(true); + m_buttonAction->setIcon(createIconSet("dropdownbutton.png"_L1)); + + actionGroup->addAction(m_treeAction); + actionGroup->addAction(m_buttonAction); + connect(actionGroup, &QActionGroup::triggered, + this, &PropertyEditor::slotViewTriggered); + + // Add actions + QActionGroup *addDynamicActionGroup = new QActionGroup(this); + connect(addDynamicActionGroup, &QActionGroup::triggered, + this, &PropertyEditor::slotAddDynamicProperty); + + QMenu *addDynamicActionMenu = new QMenu(this); + m_addDynamicAction->setMenu(addDynamicActionMenu); + m_addDynamicAction->setEnabled(false); + QAction *addDynamicAction = addDynamicActionGroup->addAction(tr("String...")); + addDynamicAction->setData(static_cast(QMetaType::QString)); + addDynamicActionMenu->addAction(addDynamicAction); + addDynamicAction = addDynamicActionGroup->addAction(tr("Bool...")); + addDynamicAction->setData(static_cast(QMetaType::Bool)); + addDynamicActionMenu->addAction(addDynamicAction); + addDynamicActionMenu->addSeparator(); + addDynamicAction = addDynamicActionGroup->addAction(tr("Other...")); + addDynamicAction->setData(static_cast(QMetaType::UnknownType)); + addDynamicActionMenu->addAction(addDynamicAction); + // remove + m_removeDynamicAction->setEnabled(false); + connect(m_removeDynamicAction, &QAction::triggered, this, &PropertyEditor::slotRemoveDynamicProperty); + // Configure + QAction *configureAction = new QAction(tr("Configure Property Editor"), this); + configureAction->setIcon(createIconSet("configure.png"_L1)); + QMenu *configureMenu = new QMenu(this); + configureAction->setMenu(configureMenu); + + m_sortingAction->setCheckable(true); + connect(m_sortingAction, &QAction::toggled, this, &PropertyEditor::slotSorting); + + m_coloringAction->setCheckable(true); + connect(m_coloringAction, &QAction::toggled, this, &PropertyEditor::slotColoring); + + configureMenu->addAction(m_sortingAction); + configureMenu->addAction(m_coloringAction); + configureMenu->addSeparator(); + configureMenu->addAction(m_treeAction); + configureMenu->addAction(m_buttonAction); + // Assemble toolbar + QToolBar *toolBar = new QToolBar; + toolBar->addWidget(m_filterWidget); + toolBar->addWidget(createDropDownButton(m_addDynamicAction)); + toolBar->addAction(m_removeDynamicAction); + toolBar->addWidget(createDropDownButton(configureAction)); + // Views + QScrollArea *buttonScroll = new QScrollArea(m_stackedWidget); + m_buttonBrowser = new QtButtonPropertyBrowser(buttonScroll); + buttonScroll->setWidgetResizable(true); + buttonScroll->setWidget(m_buttonBrowser); + m_buttonIndex = m_stackedWidget->addWidget(buttonScroll); + connect(m_buttonBrowser, &QtAbstractPropertyBrowser::currentItemChanged, + this, &PropertyEditor::slotCurrentItemChanged); + + m_treeBrowser = new QtTreePropertyBrowser(m_stackedWidget); + m_treeBrowser->setRootIsDecorated(false); + m_treeBrowser->setPropertiesWithoutValueMarked(true); + m_treeBrowser->setResizeMode(QtTreePropertyBrowser::Interactive); + m_treeIndex = m_stackedWidget->addWidget(m_treeBrowser); + connect(m_treeBrowser, &QtAbstractPropertyBrowser::currentItemChanged, + this, &PropertyEditor::slotCurrentItemChanged); + m_filterWidget->setPlaceholderText(tr("Filter")); + m_filterWidget->setClearButtonEnabled(true); + connect(m_filterWidget, &QLineEdit::textChanged, this, &PropertyEditor::setFilter); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(toolBar); + layout->addWidget(m_classLabel); + layout->addSpacerItem(new QSpacerItem(0,1)); + layout->addWidget(m_stackedWidget); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + + m_treeFactory = new DesignerEditorFactory(m_core, this); + m_treeFactory->setSpacing(0); + m_groupFactory = new DesignerEditorFactory(m_core, this); + QtVariantPropertyManager *variantManager = m_propertyManager; + m_buttonBrowser->setFactoryForManager(variantManager, m_groupFactory); + m_treeBrowser->setFactoryForManager(variantManager, m_treeFactory); + + m_stackedWidget->setCurrentIndex(m_treeIndex); + m_currentBrowser = m_treeBrowser; + m_treeAction->setChecked(true); + + connect(m_groupFactory, &DesignerEditorFactory::resetProperty, + this, &PropertyEditor::slotResetProperty); + connect(m_treeFactory, &DesignerEditorFactory::resetProperty, + this, &PropertyEditor::slotResetProperty); + connect(m_propertyManager, &DesignerPropertyManager::valueChanged, + this, &PropertyEditor::slotValueChanged); + + // retrieve initial settings + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(SettingsGroupC); + const SettingsView view = settings->value(ViewKeyC, TreeView).toInt() == TreeView ? TreeView : ButtonView; + // Coloring not available unless treeview and not sorted + m_sorting = settings->value(SortedKeyC, false).toBool(); + m_coloring = settings->value(ColorKeyC, true).toBool(); + const QVariantMap expansionState = settings->value(ExpansionKeyC, QVariantMap()).toMap(); + const int splitterPosition = settings->value(SplitterPositionKeyC, 150).toInt(); + settings->endGroup(); + // Apply settings + m_sortingAction->setChecked(m_sorting); + m_coloringAction->setChecked(m_coloring); + m_treeBrowser->setSplitterPosition(splitterPosition); + switch (view) { + case TreeView: + m_currentBrowser = m_treeBrowser; + m_stackedWidget->setCurrentIndex(m_treeIndex); + m_treeAction->setChecked(true); + break; + case ButtonView: + m_currentBrowser = m_buttonBrowser; + m_stackedWidget->setCurrentIndex(m_buttonIndex); + m_buttonAction->setChecked(true); + break; + } + // Restore expansionState from QVariant map + for (auto it = expansionState.cbegin(), cend = expansionState.cend(); it != cend; ++it) + m_expansionState.insert(it.key(), it.value().toBool()); + + updateActionsState(); +} + +PropertyEditor::~PropertyEditor() +{ + // Prevent emission of QtTreePropertyBrowser::itemChanged() when deleting + // the current item, causing asserts. + m_treeBrowser->setCurrentItem(nullptr); + storeExpansionState(); + saveSettings(); +} + +void PropertyEditor::saveSettings() const +{ + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(SettingsGroupC); + settings->setValue(ViewKeyC, QVariant(m_treeAction->isChecked() ? TreeView : ButtonView)); + settings->setValue(ColorKeyC, QVariant(m_coloring)); + settings->setValue(SortedKeyC, QVariant(m_sorting)); + // Save last expansionState as QVariant map + QVariantMap expansionState; + for (auto it = m_expansionState.cbegin(), cend = m_expansionState.cend(); it != cend; ++it) + expansionState.insert(it.key(), QVariant(it.value())); + settings->setValue(ExpansionKeyC, expansionState); + settings->setValue(SplitterPositionKeyC, m_treeBrowser->splitterPosition()); + settings->endGroup(); +} + +void PropertyEditor::setExpanded(QtBrowserItem *item, bool expanded) +{ + if (m_buttonBrowser == m_currentBrowser) + m_buttonBrowser->setExpanded(item, expanded); + else if (m_treeBrowser == m_currentBrowser) + m_treeBrowser->setExpanded(item, expanded); +} + +bool PropertyEditor::isExpanded(QtBrowserItem *item) const +{ + if (m_buttonBrowser == m_currentBrowser) + return m_buttonBrowser->isExpanded(item); + if (m_treeBrowser == m_currentBrowser) + return m_treeBrowser->isExpanded(item); + return false; +} + +void PropertyEditor::setItemVisible(QtBrowserItem *item, bool visible) +{ + if (m_currentBrowser == m_treeBrowser) { + m_treeBrowser->setItemVisible(item, visible); + } else { + qWarning("** WARNING %s is not implemented for this browser.", Q_FUNC_INFO); + } +} + +bool PropertyEditor::isItemVisible(QtBrowserItem *item) const +{ + return m_currentBrowser == m_treeBrowser ? m_treeBrowser->isItemVisible(item) : true; +} + +/* Default handling of items not found in the map: + * - Top-level items (classes) are assumed to be expanded + * - Anything below (properties) is assumed to be collapsed + * That is, the map is required, the state cannot be stored in a set */ + +void PropertyEditor::storePropertiesExpansionState(const QList &items) +{ + for (QtBrowserItem *propertyItem : items) { + if (!propertyItem->children().isEmpty()) { + QtProperty *property = propertyItem->property(); + const QString propertyName = property->propertyName(); + const auto itGroup = m_propertyToGroup.constFind(property); + if (itGroup != m_propertyToGroup.constEnd()) { + const QString key = itGroup.value() + u'|' + propertyName; + m_expansionState[key] = isExpanded(propertyItem); + } + } + } +} + +void PropertyEditor::storeExpansionState() +{ + const auto items = m_currentBrowser->topLevelItems(); + if (m_sorting) { + storePropertiesExpansionState(items); + } else { + for (QtBrowserItem *item : items) { + const QString groupName = item->property()->propertyName(); + auto propertyItems = item->children(); + if (!propertyItems.isEmpty()) + m_expansionState[groupName] = isExpanded(item); + + // properties stuff here + storePropertiesExpansionState(propertyItems); + } + } +} + +void PropertyEditor::collapseAll() +{ + const auto items = m_currentBrowser->topLevelItems(); + for (QtBrowserItem *group : items) + setExpanded(group, false); +} + +void PropertyEditor::applyPropertiesExpansionState(const QList &items) +{ + for (QtBrowserItem *propertyItem : items) { + const auto excend = m_expansionState.cend(); + QtProperty *property = propertyItem->property(); + const QString propertyName = property->propertyName(); + const auto itGroup = m_propertyToGroup.constFind(property); + if (itGroup != m_propertyToGroup.constEnd()) { + const QString key = itGroup.value() + u'|' + propertyName; + const auto pit = m_expansionState.constFind(key); + if (pit != excend) + setExpanded(propertyItem, pit.value()); + else + setExpanded(propertyItem, false); + } + } +} + +void PropertyEditor::applyExpansionState() +{ + const auto items = m_currentBrowser->topLevelItems(); + if (m_sorting) { + applyPropertiesExpansionState(items); + } else { + const auto excend = m_expansionState.cend(); + for (QtBrowserItem *item : items) { + const QString groupName = item->property()->propertyName(); + const auto git = m_expansionState.constFind(groupName); + if (git != excend) + setExpanded(item, git.value()); + else + setExpanded(item, true); + // properties stuff here + applyPropertiesExpansionState(item->children()); + } + } +} + +int PropertyEditor::applyPropertiesFilter(const QList &items) +{ + int showCount = 0; + const bool matchAll = m_filterPattern.isEmpty(); + for (QtBrowserItem *propertyItem : items) { + QtProperty *property = propertyItem->property(); + const QString propertyName = property->propertyName(); + const bool showProperty = matchAll || propertyName.contains(m_filterPattern, Qt::CaseInsensitive); + setItemVisible(propertyItem, showProperty); + if (showProperty) + showCount++; + } + return showCount; +} + +void PropertyEditor::applyFilter() +{ + const auto items = m_currentBrowser->topLevelItems(); + if (m_sorting) { + applyPropertiesFilter(items); + } else { + for (QtBrowserItem *item : items) + setItemVisible(item, applyPropertiesFilter(item->children())); + } +} + +void PropertyEditor::clearView() +{ + m_currentBrowser->clear(); +} + +bool PropertyEditor::event(QEvent *event) +{ + if (event->type() == QEvent::PaletteChange) + updateForegroundBrightness(); + + return QDesignerPropertyEditor::event(event); +} + +void PropertyEditor::updateForegroundBrightness() +{ + QColor c = palette().color(QPalette::Text); + bool newBrightness = qRound(0.3 * c.redF() + 0.59 * c.greenF() + 0.11 * c.blueF()); + + if (m_brightness == newBrightness) + return; + + m_brightness = newBrightness; + + updateColors(); +} + +QColor PropertyEditor::propertyColor(QtProperty *property) const +{ + if (!m_coloring) + return QColor(); + + QtProperty *groupProperty = property; + + const auto itProp = m_propertyToGroup.constFind(property); + if (itProp != m_propertyToGroup.constEnd()) + groupProperty = m_nameToGroup.value(itProp.value()); + + const int groupIdx = m_groups.indexOf(groupProperty); + std::pair pair; + if (groupIdx != -1) { + if (groupProperty == m_dynamicGroup) + pair = m_dynamicColor; + else if (isLayoutGroup(groupProperty)) + pair = m_layoutColor; + else + pair = m_colors[groupIdx % m_colors.size()]; + } + if (!m_brightness) + return pair.first; + return pair.second; +} + +void PropertyEditor::fillView() +{ + if (m_sorting) { + for (auto itProperty = m_nameToProperty.cbegin(), end = m_nameToProperty.cend(); itProperty != end; ++itProperty) + m_currentBrowser->addProperty(itProperty.value()); + } else { + for (QtProperty *group : std::as_const(m_groups)) { + QtBrowserItem *item = m_currentBrowser->addProperty(group); + if (m_currentBrowser == m_treeBrowser) + m_treeBrowser->setBackgroundColor(item, propertyColor(group)); + group->setModified(m_currentBrowser == m_treeBrowser); + } + } +} + +bool PropertyEditor::isLayoutGroup(QtProperty *group) const +{ + return group->propertyName() == m_strings.m_layout; +} + +void PropertyEditor::updateActionsState() +{ + m_coloringAction->setEnabled(m_treeAction->isChecked() && !m_sortingAction->isChecked()); +} + +void PropertyEditor::slotViewTriggered(QAction *action) +{ + storeExpansionState(); + collapseAll(); + { + UpdateBlocker ub(this); + clearView(); + int idx = 0; + if (action == m_treeAction) { + m_currentBrowser = m_treeBrowser; + idx = m_treeIndex; + } else if (action == m_buttonAction) { + m_currentBrowser = m_buttonBrowser; + idx = m_buttonIndex; + } + fillView(); + m_stackedWidget->setCurrentIndex(idx); + applyExpansionState(); + applyFilter(); + } + updateActionsState(); +} + +void PropertyEditor::slotSorting(bool sort) +{ + if (sort == m_sorting) + return; + + storeExpansionState(); + m_sorting = sort; + collapseAll(); + { + UpdateBlocker ub(this); + clearView(); + m_treeBrowser->setRootIsDecorated(sort); + fillView(); + applyExpansionState(); + applyFilter(); + } + updateActionsState(); +} + +void PropertyEditor::updateColors() +{ + if (m_treeBrowser && m_currentBrowser == m_treeBrowser) { + const auto items = m_treeBrowser->topLevelItems(); + for (QtBrowserItem *item : items) + m_treeBrowser->setBackgroundColor(item, propertyColor(item->property())); + } +} + +void PropertyEditor::slotColoring(bool coloring) +{ + if (coloring == m_coloring) + return; + + m_coloring = coloring; + + updateColors(); +} + +void PropertyEditor::slotAddDynamicProperty(QAction *action) +{ + if (!m_propertySheet) + return; + + const QDesignerDynamicPropertySheetExtension *dynamicSheet = + qt_extension(m_core->extensionManager(), m_object); + + if (!dynamicSheet) + return; + + QString newName; + QVariant newValue; + { // Make sure the dialog is closed before the signal is emitted. + const int type = action->data().toInt(); + NewDynamicPropertyDialog dlg(core()->dialogGui(), m_currentBrowser); + if (type != QMetaType::UnknownType) + dlg.setPropertyType(type); + + QStringList reservedNames; + const int propertyCount = m_propertySheet->count(); + for (int i = 0; i < propertyCount; i++) { + if (!dynamicSheet->isDynamicProperty(i) || m_propertySheet->isVisible(i)) + reservedNames.append(m_propertySheet->propertyName(i)); + } + dlg.setReservedNames(reservedNames); + if (dlg.exec() == QDialog::Rejected) + return; + newName = dlg.propertyName(); + newValue = dlg.propertyValue(); + } + m_recentlyAddedDynamicProperty = newName; + emit addDynamicProperty(newName, newValue); +} + +QDesignerFormEditorInterface *PropertyEditor::core() const +{ + return m_core; +} + +bool PropertyEditor::isReadOnly() const +{ + return false; +} + +void PropertyEditor::setReadOnly(bool /*readOnly*/) +{ + qDebug() << "PropertyEditor::setReadOnly() request"; +} + +void PropertyEditor::setPropertyValue(const QString &name, const QVariant &value, bool changed) +{ + const auto it = m_nameToProperty.constFind(name); + if (it == m_nameToProperty.constEnd()) + return; + QtVariantProperty *property = it.value(); + updateBrowserValue(property, value); + property->setModified(changed); +} + +/* Quick update that assumes the actual count of properties has not changed + * N/A when for example executing a layout command and margin properties appear. */ +void PropertyEditor::updatePropertySheet() +{ + if (!m_propertySheet) + return; + + updateToolBarLabel(); + + const int propertyCount = m_propertySheet->count(); + const auto npcend = m_nameToProperty.cend(); + for (int i = 0; i < propertyCount; ++i) { + const QString propertyName = m_propertySheet->propertyName(i); + const auto it = m_nameToProperty.constFind(propertyName); + if (it != npcend) + updateBrowserValue(it.value(), m_propertySheet->property(i)); + } +} + +static inline QLayout *layoutOfQLayoutWidget(QObject *o) +{ + if (o->isWidgetType() && !qstrcmp(o->metaObject()->className(), "QLayoutWidget")) + return static_cast(o)->layout(); + return nullptr; +} + +void PropertyEditor::updateToolBarLabel() +{ + QString objectName; + QString className; + if (m_object) { + if (QLayout *l = layoutOfQLayoutWidget(m_object)) + objectName = l->objectName(); + else + objectName = m_object->objectName(); + className = realClassName(m_object); + } + + m_classLabel->setVisible(!objectName.isEmpty() || !className.isEmpty()); + m_classLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + QString classLabelText; + if (!objectName.isEmpty()) + classLabelText += objectName + " : "_L1; + classLabelText += className; + + m_classLabel->setText(classLabelText); + m_classLabel->setToolTip(tr("Object: %1\nClass: %2") + .arg(objectName, className)); +} + +void PropertyEditor::updateBrowserValue(QtVariantProperty *property, const QVariant &value) +{ + QVariant v = value; + const int type = property->propertyType(); + if (type == QtVariantPropertyManager::enumTypeId()) { + const PropertySheetEnumValue e = qvariant_cast(v); + v = e.metaEnum.keys().indexOf(e.metaEnum.valueToKey(e.value)); + } else if (type == DesignerPropertyManager::designerFlagTypeId()) { + const PropertySheetFlagValue f = qvariant_cast(v); + v = QVariant(f.value); + } else if (type == DesignerPropertyManager::designerAlignmentTypeId()) { + const PropertySheetFlagValue f = qvariant_cast(v); + v = QVariant(f.value); + } + QDesignerPropertySheet *sheet = qobject_cast(m_core->extensionManager()->extension(m_object, Q_TYPEID(QDesignerPropertySheetExtension))); + int index = -1; + if (sheet) + index = sheet->indexOf(property->propertyName()); + if (sheet && m_propertyToGroup.contains(property)) { // don't do it for comments since property sheet doesn't keep them + property->setEnabled(sheet->isEnabled(index)); + } + + // Rich text string property with comment: Store/Update the font the rich text editor dialog starts out with + if (type == QMetaType::QString && !property->subProperties().isEmpty()) { + const int fontIndex = m_propertySheet->indexOf(m_strings.m_fontProperty); + if (fontIndex != -1) + property->setAttribute(m_strings.m_fontAttribute, m_propertySheet->property(fontIndex)); + } + + m_updatingBrowser = true; + property->setValue(v); + if (sheet && sheet->isResourceProperty(index)) + property->setAttribute(u"defaultResource"_s, sheet->defaultResourceProperty(index)); + m_updatingBrowser = false; +} + +int PropertyEditor::toBrowserType(const QVariant &value, const QString &propertyName) const +{ + if (value.canConvert()) { + if (m_strings.m_alignmentProperties.contains(propertyName)) + return DesignerPropertyManager::designerAlignmentTypeId(); + return DesignerPropertyManager::designerFlagTypeId(); + } + if (value.canConvert()) + return DesignerPropertyManager::enumTypeId(); + + return value.userType(); +} + +QString PropertyEditor::realClassName(QObject *object) const +{ + if (!object) + return QString(); + + QString className = QLatin1StringView(object->metaObject()->className()); + const QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase(); + if (QDesignerWidgetDataBaseItemInterface *widgetItem = db->item(db->indexOfObject(object, true))) { + className = widgetItem->name(); + + if (object->isWidgetType() && className == m_strings.m_qLayoutWidget + && static_cast(object)->layout()) { + className = QLatin1StringView(static_cast(object)->layout()->metaObject()->className()); + } + } + + if (className.startsWith(m_strings.m_designerPrefix)) + className.remove(1, m_strings.m_designerPrefix.size() - 1); + + return className; +} + +static const char *typeName(int type) +{ + if (type == qMetaTypeId()) + type = QMetaType::QString; + if (type < int(QMetaType::User)) + return QMetaType(type).name(); + if (type == qMetaTypeId()) + return "QIcon"; + if (type == qMetaTypeId()) + return "QPixmap"; + if (type == qMetaTypeId()) + return "QKeySequence"; + if (type == qMetaTypeId()) + return "QFlags"; + if (type == qMetaTypeId()) + return "enum"; + if (type == QMetaType::UnknownType) + return "invalid"; + if (type == QMetaType::User) + return "user type"; + const auto metaType = QMetaType(type); + if (metaType.isValid()) + return metaType.name(); + return nullptr; +} + +static QString msgUnsupportedType(const QString &propertyName, int type) +{ + QString rc; + QTextStream str(&rc); + const char *typeS = typeName(type); + str << "The property \"" << propertyName << "\" of type (" + << (typeS ? typeS : "unknown") << ") is not supported yet."; + return rc; +} + +static QString msgDeprecatedProperty(const QLatin1StringView version, + const QString &baseTip) +{ + return PropertyEditor::tr("Deprecated since Qt %1: %2").arg(version, baseTip); +} + +static QString basePropertyToolTip(const QString &propertyName, int type) +{ + QString result; + if (const char *typeS = typeName(type)) + result = propertyName + " ("_L1 + QLatin1StringView(typeS) + u')'; + return result; +} + +static QString propertyToolTip(const QDesignerFormEditorInterface *core, + const QString &className, + const QString &propertyName, int type) +{ + const QDesignerCustomWidgetData customData = core->pluginManager()->customWidgetData(className); + if (!customData.isNull()) { + if (QString customToolTip = customData.propertyToolTip(propertyName); !customToolTip.isEmpty()) + return customToolTip; + } + const QString base = basePropertyToolTip(propertyName, type); + // QTBUG-108199, timeSpec deprecation + if (type == QtVariantPropertyManager::enumTypeId() && propertyName == "timeSpec"_L1) + return msgDeprecatedProperty("6.9"_L1, base); + return base; +} + +void PropertyEditor::setObject(QObject *object) +{ + QDesignerFormWindowInterface *oldFormWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + // In the first setObject() call following the addition of a dynamic property, focus and edit it. + const bool editNewDynamicProperty = object != nullptr && m_object == object && !m_recentlyAddedDynamicProperty.isEmpty(); + m_object = object; + m_propertyManager->setObject(object); + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + // QTBUG-68507: Form window can be null for objects in Morph Undo macros with buddies + if (object != nullptr && formWindow == nullptr) { + formWindow = m_core->formWindowManager()->activeFormWindow(); + if (formWindow == nullptr) { + qWarning("PropertyEditor::setObject(): Unable to find form window for \"%s\".", + qPrintable(object->objectName())); + return; + } + } + FormWindowBase *fwb = qobject_cast(formWindow); + const bool idIdBasedTranslation = fwb && fwb->useIdBasedTranslations(); + const bool idIdBasedTranslationUnchanged = (idIdBasedTranslation == DesignerPropertyManager::useIdBasedTranslations()); + DesignerPropertyManager::setUseIdBasedTranslations(idIdBasedTranslation); + m_treeFactory->setFormWindowBase(fwb); + m_groupFactory->setFormWindowBase(fwb); + + storeExpansionState(); + + UpdateBlocker ub(this); + + updateToolBarLabel(); + + QMap toRemove = m_nameToProperty; + + const QDesignerDynamicPropertySheetExtension *dynamicSheet = + qt_extension(m_core->extensionManager(), m_object); + const QDesignerPropertySheet *sheet = qobject_cast(m_core->extensionManager()->extension(m_object, Q_TYPEID(QDesignerPropertySheetExtension))); + + // Optimizization: Instead of rebuilding the complete list every time, compile a list of properties to remove, + // remove them, traverse the sheet, in case property exists just set a value, otherwise - create it. + QExtensionManager *m = m_core->extensionManager(); + + m_propertySheet = qobject_cast(m->extension(object, Q_TYPEID(QDesignerPropertySheetExtension))); + if (m_propertySheet) { + const int stringTypeId = qMetaTypeId(); + const int propertyCount = m_propertySheet->count(); + for (int i = 0; i < propertyCount; ++i) { + if (!m_propertySheet->isVisible(i)) + continue; + + const QString propertyName = m_propertySheet->propertyName(i); + if (m_propertySheet->indexOf(propertyName) != i) + continue; + const QString groupName = m_propertySheet->propertyGroup(i); + const auto rit = toRemove.constFind(propertyName); + if (rit != toRemove.constEnd()) { + QtVariantProperty *property = rit.value(); + const int propertyType = property->propertyType(); + // Also remove string properties in case a change in translation mode + // occurred since different sub-properties are used (disambiguation/id). + if (m_propertyToGroup.value(property) == groupName + && (idIdBasedTranslationUnchanged || propertyType != stringTypeId) + && toBrowserType(m_propertySheet->property(i), propertyName) == propertyType) { + toRemove.remove(propertyName); + } + } + } + } + + for (auto itRemove = toRemove.cbegin(), end = toRemove.cend(); itRemove != end; ++itRemove) { + QtVariantProperty *property = itRemove.value(); + m_nameToProperty.remove(itRemove.key()); + m_propertyToGroup.remove(property); + delete property; + } + + if (oldFormWindow != formWindow) + reloadResourceProperties(); + + bool isMainContainer = false; + if (QWidget *widget = qobject_cast(object)) { + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(widget)) { + isMainContainer = (fw->mainContainer() == widget); + } + } + m_groups.clear(); + + if (m_propertySheet) { + const QString className = WidgetFactory::classNameOf(formWindow->core(), m_object); + + QtProperty *lastProperty = nullptr; + QtProperty *lastGroup = nullptr; + const int propertyCount = m_propertySheet->count(); + for (int i = 0; i < propertyCount; ++i) { + if (!m_propertySheet->isVisible(i)) + continue; + + const QString propertyName = m_propertySheet->propertyName(i); + if (m_propertySheet->indexOf(propertyName) != i) + continue; + const QVariant value = m_propertySheet->property(i); + + const int type = toBrowserType(value, propertyName); + + QtVariantProperty *property = m_nameToProperty.value(propertyName, 0); + bool newProperty = property == nullptr; + if (newProperty) { + property = m_propertyManager->addProperty(type, propertyName); + if (property) { + newProperty = true; + if (type == DesignerPropertyManager::enumTypeId()) { + const PropertySheetEnumValue e = qvariant_cast(value); + m_updatingBrowser = true; + property->setAttribute(m_strings.m_enumNamesAttribute, e.metaEnum.keys()); + m_updatingBrowser = false; + } else if (type == DesignerPropertyManager::designerFlagTypeId()) { + const PropertySheetFlagValue f = qvariant_cast(value); + QList> flags; + for (const QString &name : f.metaFlags.keys()) { + const uint val = f.metaFlags.keyToValue(name); + flags.append({name, val}); + } + m_updatingBrowser = true; + QVariant v; + v.setValue(flags); + property->setAttribute(m_strings.m_flagsAttribute, v); + m_updatingBrowser = false; + } + } + } + + if (property != nullptr) { + const bool dynamicProperty = (dynamicSheet && dynamicSheet->isDynamicProperty(i)) + || (sheet && sheet->isDefaultDynamicProperty(i)); + QString descriptionToolTip = dynamicProperty + ? basePropertyToolTip(propertyName, type) + : propertyToolTip(formWindow->core(), className, propertyName, type); + if (!descriptionToolTip.isEmpty()) + property->setDescriptionToolTip(descriptionToolTip); + switch (type) { + case QMetaType::QPalette: + setupPaletteProperty(property); + break; + case QMetaType::QKeySequence: + //addCommentProperty(property, propertyName); + break; + default: + break; + } + if (type == QMetaType::QString || type == qMetaTypeId()) + setupStringProperty(property, isMainContainer); + property->setAttribute(m_strings.m_resettableAttribute, m_propertySheet->hasReset(i)); + + const QString groupName = m_propertySheet->propertyGroup(i); + QtVariantProperty *groupProperty = nullptr; + + if (newProperty) { + auto itPrev = m_nameToProperty.insert(propertyName, property); + m_propertyToGroup[property] = groupName; + if (m_sorting) { + QtProperty *previous = nullptr; + if (itPrev != m_nameToProperty.begin()) + previous = (--itPrev).value(); + m_currentBrowser->insertProperty(property, previous); + } + } + const auto gnit = m_nameToGroup.constFind(groupName); + if (gnit != m_nameToGroup.constEnd()) { + groupProperty = gnit.value(); + } else { + groupProperty = m_propertyManager->addProperty(QtVariantPropertyManager::groupTypeId(), groupName); + QtBrowserItem *item = nullptr; + if (!m_sorting) + item = m_currentBrowser->insertProperty(groupProperty, lastGroup); + m_nameToGroup[groupName] = groupProperty; + m_groups.append(groupProperty); + if (dynamicProperty) + m_dynamicGroup = groupProperty; + if (m_currentBrowser == m_treeBrowser && item) { + m_treeBrowser->setBackgroundColor(item, propertyColor(groupProperty)); + groupProperty->setModified(true); + } + } + /* Group changed or new group. Append to last subproperty of + * that group. Note that there are cases in which a derived + * property sheet appends fake properties for the class + * which will appear after the layout group properties + * (QWizardPage). To make them appear at the end of the + * actual class group, goto last element. */ + if (lastGroup != groupProperty) { + lastGroup = groupProperty; + lastProperty = nullptr; // Append at end + const auto subProperties = lastGroup->subProperties(); + if (!subProperties.isEmpty()) + lastProperty = subProperties.constLast(); + lastGroup = groupProperty; + } + if (!m_groups.contains(groupProperty)) + m_groups.append(groupProperty); + if (newProperty) + groupProperty->insertSubProperty(property, lastProperty); + + lastProperty = property; + + updateBrowserValue(property, value); + + property->setModified(m_propertySheet->isChanged(i)); + if (propertyName == "geometry"_L1 && type == QMetaType::QRect) { + const auto &subProperties = property->subProperties(); + for (QtProperty *subProperty : subProperties) { + const QString subPropertyName = subProperty->propertyName(); + if (subPropertyName == "X"_L1 || subPropertyName == "Y"_L1) + subProperty->setEnabled(!isMainContainer); + } + } + } else { + // QTBUG-80417, suppress warning for QDateEdit::timeZone + const int typeId = value.typeId(); + if (typeId != qMetaTypeId()) + qWarning("%s", qPrintable(msgUnsupportedType(propertyName, type))); + } + } + } + QMap groups = m_nameToGroup; + for (auto itGroup = groups.cbegin(), end = groups.cend(); itGroup != end; ++itGroup) { + QtVariantProperty *groupProperty = itGroup.value(); + if (groupProperty->subProperties().isEmpty()) { + if (groupProperty == m_dynamicGroup) + m_dynamicGroup = nullptr; + delete groupProperty; + m_nameToGroup.remove(itGroup.key()); + } + } + const bool addEnabled = dynamicSheet ? dynamicSheet->dynamicPropertiesAllowed() : false; + m_addDynamicAction->setEnabled(addEnabled); + m_removeDynamicAction->setEnabled(false); + applyExpansionState(); + applyFilter(); + // In the first setObject() call following the addition of a dynamic property, focus and edit it. + if (editNewDynamicProperty) { + // Have QApplication process the events related to completely closing the modal 'add' dialog, + // otherwise, we cannot focus the property editor in docked mode. + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + editProperty(m_recentlyAddedDynamicProperty); + } + m_recentlyAddedDynamicProperty.clear(); + m_filterWidget->setEnabled(object); +} + +void PropertyEditor::reloadResourceProperties() +{ + m_updatingBrowser = true; + m_propertyManager->reloadResourceProperties(); + m_updatingBrowser = false; +} + +QtBrowserItem *PropertyEditor::nonFakePropertyBrowserItem(QtBrowserItem *item) const +{ + // Top-level properties are QObject/QWidget groups, etc. Find first item property below + // which should be nonfake + const auto topLevelItems = m_currentBrowser->topLevelItems(); + do { + if (topLevelItems.contains(item->parent())) + return item; + item = item->parent(); + } while (item); + return nullptr; +} + +QString PropertyEditor::currentPropertyName() const +{ + if (QtBrowserItem *browserItem = m_currentBrowser->currentItem()) + if (QtBrowserItem *topLevelItem = nonFakePropertyBrowserItem(browserItem)) { + return topLevelItem->property()->propertyName(); + } + return QString(); +} + +void PropertyEditor::slotResetProperty(QtProperty *property) +{ + QDesignerFormWindowInterface *form = m_core->formWindowManager()->activeFormWindow(); + if (!form) + return; + + if (m_propertyManager->resetFontSubProperty(property)) + return; + + if (m_propertyManager->resetIconSubProperty(property)) + return; + + if (m_propertyManager->resetTextAlignmentProperty(property)) + return; + + if (!m_propertyToGroup.contains(property)) + return; + + emit resetProperty(property->propertyName()); +} + +void PropertyEditor::slotValueChanged(QtProperty *property, const QVariant &value, bool enableSubPropertyHandling) +{ + if (m_updatingBrowser) + return; + + if (!m_propertySheet) + return; + + QtVariantProperty *varProp = m_propertyManager->variantProperty(property); + + if (!varProp) + return; + + if (!m_propertyToGroup.contains(property)) + return; + + if (varProp->propertyType() == QtVariantPropertyManager::enumTypeId()) { + PropertySheetEnumValue e = qvariant_cast(m_propertySheet->property(m_propertySheet->indexOf(property->propertyName()))); + const int val = value.toInt(); + const QString valName = varProp->attributeValue(m_strings.m_enumNamesAttribute).toStringList().at(val); + bool ok = false; + e.value = e.metaEnum.parseEnum(valName, &ok); + Q_ASSERT(ok); + QVariant v; + v.setValue(e); + emitPropertyValueChanged(property->propertyName(), v, true); + return; + } + + emitPropertyValueChanged(property->propertyName(), value, enableSubPropertyHandling); +} + +bool PropertyEditor::isDynamicProperty(const QtBrowserItem* item) const +{ + if (!item) + return false; + + const QDesignerDynamicPropertySheetExtension *dynamicSheet = + qt_extension(m_core->extensionManager(), m_object); + + if (!dynamicSheet) + return false; + + return m_propertyToGroup.contains(item->property()) + && dynamicSheet->isDynamicProperty(m_propertySheet->indexOf(item->property()->propertyName())); +} + +void PropertyEditor::editProperty(const QString &name) +{ + // find the browser item belonging to the property, make it current and edit it + QtBrowserItem *browserItem = nullptr; + if (QtVariantProperty *property = m_nameToProperty.value(name, 0)) { + const auto items = m_currentBrowser->items(property); + if (items.size() == 1) + browserItem = items.constFirst(); + } + if (browserItem == nullptr) + return; + m_currentBrowser->setFocus(Qt::OtherFocusReason); + if (m_currentBrowser == m_treeBrowser) { // edit is currently only supported in tree view + m_treeBrowser->editItem(browserItem); + } else { + m_currentBrowser->setCurrentItem(browserItem); + } +} + +void PropertyEditor::slotCurrentItemChanged(QtBrowserItem *item) +{ + m_removeDynamicAction->setEnabled(isDynamicProperty(item)); + +} + +void PropertyEditor::slotRemoveDynamicProperty() +{ + if (QtBrowserItem* item = m_currentBrowser->currentItem()) + if (isDynamicProperty(item)) + emit removeDynamicProperty(item->property()->propertyName()); +} + +void PropertyEditor::setFilter(const QString &pattern) +{ + m_filterPattern = pattern; + applyFilter(); +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/propertyeditor.h b/src/designer/src/components/propertyeditor/propertyeditor.h new file mode 100644 index 0000000..48cd03b --- /dev/null +++ b/src/designer/src/components/propertyeditor/propertyeditor.h @@ -0,0 +1,169 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PROPERTYEDITOR_H +#define PROPERTYEDITOR_H + +#include "propertyeditor_global.h" +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class DomProperty; +class QDesignerMetaDataBaseItemInterface; +class QDesignerPropertySheetExtension; +class QLineEdit; + +class QtAbstractPropertyBrowser; +class QtButtonPropertyBrowser; +class QtTreePropertyBrowser; +class QtProperty; +class QtVariantProperty; +class QtBrowserItem; +class QStackedWidget; + +namespace qdesigner_internal { + +class StringProperty; +class DesignerPropertyManager; +class DesignerEditorFactory; +class ElidingLabel; + +class QT_PROPERTYEDITOR_EXPORT PropertyEditor: public QDesignerPropertyEditor +{ + Q_OBJECT +public: + explicit PropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + ~PropertyEditor() override; + + QDesignerFormEditorInterface *core() const override; + + bool isReadOnly() const override; + void setReadOnly(bool readOnly) override; + void setPropertyValue(const QString &name, const QVariant &value, bool changed = true) override; + void updatePropertySheet() override; + + void setObject(QObject *object) override; + + void reloadResourceProperties() override; + + QObject *object() const override + { return m_object; } + + QString currentPropertyName() const override; + +protected: + + bool event(QEvent *event) override; + +private slots: + void slotResetProperty(QtProperty *property); + void slotValueChanged(QtProperty *property, const QVariant &value, bool enableSubPropertyHandling); + void slotViewTriggered(QAction *action); + void slotAddDynamicProperty(QAction *action); + void slotRemoveDynamicProperty(); + void slotSorting(bool sort); + void slotColoring(bool color); + void slotCurrentItemChanged(QtBrowserItem*); + void setFilter(const QString &pattern); + +private: + void updateBrowserValue(QtVariantProperty *property, const QVariant &value); + void updateToolBarLabel(); + int toBrowserType(const QVariant &value, const QString &propertyName) const; + QString removeScope(const QString &value) const; + QDesignerMetaDataBaseItemInterface *metaDataBaseItem() const; + void setupStringProperty(QtVariantProperty *property, bool isMainContainer); + void setupPaletteProperty(QtVariantProperty *property); + QString realClassName(QObject *object) const; + void storeExpansionState(); + void applyExpansionState(); + void storePropertiesExpansionState(const QList &items); + void applyPropertiesExpansionState(const QList &items); + void applyFilter(); + int applyPropertiesFilter(const QList &items); + void setExpanded(QtBrowserItem *item, bool expanded); + bool isExpanded(QtBrowserItem *item) const; + void setItemVisible(QtBrowserItem *item, bool visible); + bool isItemVisible(QtBrowserItem *item) const; + void collapseAll(); + void clearView(); + void fillView(); + bool isLayoutGroup(QtProperty *group) const; + void updateColors(); + void updateForegroundBrightness(); + QColor propertyColor(QtProperty *property) const; + void updateActionsState(); + QtBrowserItem *nonFakePropertyBrowserItem(QtBrowserItem *item) const; + void saveSettings() const; + void editProperty(const QString &name); + bool isDynamicProperty(const QtBrowserItem* item) const; + + struct Strings { + Strings(); + QSet m_alignmentProperties; + const QString m_fontProperty; + const QString m_qLayoutWidget; + const QString m_designerPrefix; + const QString m_layout; + const QString m_validationModeAttribute; + const QString m_fontAttribute; + const QString m_superPaletteAttribute; + const QString m_enumNamesAttribute; + const QString m_resettableAttribute; + const QString m_flagsAttribute; + }; + + const Strings m_strings; + QDesignerFormEditorInterface *m_core; + QDesignerPropertySheetExtension *m_propertySheet = nullptr; + QtAbstractPropertyBrowser *m_currentBrowser = nullptr; + QtButtonPropertyBrowser *m_buttonBrowser; + QtTreePropertyBrowser *m_treeBrowser = nullptr; + DesignerPropertyManager *m_propertyManager; + DesignerEditorFactory *m_treeFactory; + DesignerEditorFactory *m_groupFactory; + QPointer m_object; + QMap m_nameToProperty; + QHash m_propertyToGroup; + QMap m_nameToGroup; + QList m_groups; + QtProperty *m_dynamicGroup = nullptr; + QString m_recentlyAddedDynamicProperty; + bool m_updatingBrowser = false; + + QStackedWidget *m_stackedWidget; + QLineEdit *m_filterWidget; + int m_buttonIndex = -1; + int m_treeIndex = -1; + QAction *m_addDynamicAction; + QAction *m_removeDynamicAction; + QAction *m_sortingAction; + QAction *m_coloringAction; + QAction *m_treeAction; + QAction *m_buttonAction; + ElidingLabel *m_classLabel; + + bool m_sorting = false; + bool m_coloring = false; + + QMap m_expansionState; + + QString m_filterPattern; + QList > m_colors; + std::pair m_dynamicColor; + std::pair m_layoutColor; + + bool m_brightness = false; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PROPERTYEDITOR_H diff --git a/src/designer/src/components/propertyeditor/propertyeditor_global.h b/src/designer/src/components/propertyeditor/propertyeditor_global.h new file mode 100644 index 0000000..7172218 --- /dev/null +++ b/src/designer/src/components/propertyeditor/propertyeditor_global.h @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PROPERTYEDITOR_GLOBAL_H +#define PROPERTYEDITOR_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_WIN +#ifdef QT_PROPERTYEDITOR_LIBRARY +# define QT_PROPERTYEDITOR_EXPORT +#else +# define QT_PROPERTYEDITOR_EXPORT +#endif +#else +#define QT_PROPERTYEDITOR_EXPORT +#endif + +QT_END_NAMESPACE + +#endif // PROPERTYEDITOR_GLOBAL_H diff --git a/src/designer/src/components/propertyeditor/qlonglongvalidator.cpp b/src/designer/src/components/propertyeditor/qlonglongvalidator.cpp new file mode 100644 index 0000000..906ffdf --- /dev/null +++ b/src/designer/src/components/propertyeditor/qlonglongvalidator.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qlonglongvalidator.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// ---------------------------------------------------------------------------- +QLongLongValidator::QLongLongValidator(QObject * parent) + : QValidator(parent), + b(Q_UINT64_C(0x8000000000000000)), t(Q_UINT64_C(0x7FFFFFFFFFFFFFFF)) +{ +} + +QLongLongValidator::QLongLongValidator(qlonglong minimum, qlonglong maximum, + QObject * parent) + : QValidator(parent), b(minimum), t(maximum) +{ +} + +QLongLongValidator::~QLongLongValidator() = default; + +QValidator::State QLongLongValidator::validate(QString & input, int &) const +{ + if (input.contains(u' ')) + return Invalid; + if (input.isEmpty() || (b < 0 && input == "-"_L1)) + return Intermediate; + bool ok; + qlonglong entered = input.toLongLong(&ok); + if (!ok || (entered < 0 && b >= 0)) + return Invalid; + if (entered >= b && entered <= t) + return Acceptable; + if (entered >= 0) + return entered > t ? Invalid : Intermediate; + return entered < b ? Invalid : Intermediate; +} + +void QLongLongValidator::setRange(qlonglong bottom, qlonglong top) +{ + b = bottom; + t = top; +} + +void QLongLongValidator::setBottom(qlonglong bottom) +{ + setRange(bottom, top()); +} + +void QLongLongValidator::setTop(qlonglong top) +{ + setRange(bottom(), top); +} + + +// ---------------------------------------------------------------------------- +QULongLongValidator::QULongLongValidator(QObject * parent) + : QValidator(parent), + b(0), t(Q_UINT64_C(0xFFFFFFFFFFFFFFFF)) +{ +} + +QULongLongValidator::QULongLongValidator(qulonglong minimum, qulonglong maximum, + QObject * parent) + : QValidator(parent), b(minimum), t(maximum) +{ +} + +QULongLongValidator::~QULongLongValidator() = default; + +QValidator::State QULongLongValidator::validate(QString & input, int &) const +{ + if (input.isEmpty()) + return Intermediate; + + bool ok; + qulonglong entered = input.toULongLong(&ok); + if (input.contains(u' ') || input.contains(u'-') || !ok) + return Invalid; + + if (entered >= b && entered <= t) + return Acceptable; + + return Invalid; +} + +void QULongLongValidator::setRange(qulonglong bottom, qulonglong top) +{ + b = bottom; + t = top; +} + +void QULongLongValidator::setBottom(qulonglong bottom) +{ + setRange(bottom, top()); +} + +void QULongLongValidator::setTop(qulonglong top) +{ + setRange(bottom(), top); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/qlonglongvalidator.h b/src/designer/src/components/propertyeditor/qlonglongvalidator.h new file mode 100644 index 0000000..4a70908 --- /dev/null +++ b/src/designer/src/components/propertyeditor/qlonglongvalidator.h @@ -0,0 +1,72 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QLONGLONGVALIDATOR_H +#define QLONGLONGVALIDATOR_H + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QLongLongValidator : public QValidator +{ + Q_OBJECT + Q_PROPERTY(qlonglong bottom READ bottom WRITE setBottom) + Q_PROPERTY(qlonglong top READ top WRITE setTop) + +public: + explicit QLongLongValidator(QObject * parent); + QLongLongValidator(qlonglong bottom, qlonglong top, QObject * parent); + ~QLongLongValidator(); + + QValidator::State validate(QString &, int &) const override; + + void setBottom(qlonglong); + void setTop(qlonglong); + void setRange(qlonglong bottom, qlonglong top); + + qlonglong bottom() const { return b; } + qlonglong top() const { return t; } + +private: + Q_DISABLE_COPY_MOVE(QLongLongValidator) + + qlonglong b; + qlonglong t; +}; + +// ---------------------------------------------------------------------------- +class QULongLongValidator : public QValidator +{ + Q_OBJECT + Q_PROPERTY(qulonglong bottom READ bottom WRITE setBottom) + Q_PROPERTY(qulonglong top READ top WRITE setTop) + +public: + explicit QULongLongValidator(QObject * parent); + QULongLongValidator(qulonglong bottom, qulonglong top, QObject * parent); + ~QULongLongValidator(); + + QValidator::State validate(QString &, int &) const override; + + void setBottom(qulonglong); + void setTop(qulonglong); + void setRange(qulonglong bottom, qulonglong top); + + qulonglong bottom() const { return b; } + qulonglong top() const { return t; } + +private: + Q_DISABLE_COPY_MOVE(QULongLongValidator) + + qulonglong b; + qulonglong t; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QLONGLONGVALIDATOR_H diff --git a/src/designer/src/components/propertyeditor/resetdecorator.cpp b/src/designer/src/components/propertyeditor/resetdecorator.cpp new file mode 100644 index 0000000..1ee5d2b --- /dev/null +++ b/src/designer/src/components/propertyeditor/resetdecorator.cpp @@ -0,0 +1,212 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "resetdecorator.h" +#include "qtpropertybrowser_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +using namespace Qt::StringLiterals; + +ResetWidget::ResetWidget(QtProperty *property, QWidget *parent) : + QWidget(parent), + m_property(property), + m_textLabel(new QLabel(this)), + m_iconLabel(new QLabel(this)), + m_button(new QToolButton(this)) +{ + m_textLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed)); + m_iconLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + m_button->setToolButtonStyle(Qt::ToolButtonIconOnly); + m_button->setIcon(createIconSet("resetproperty.png"_L1)); + m_button->setIconSize(QSize(8,8)); + m_button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding)); + connect(m_button, &QAbstractButton::clicked, this, &ResetWidget::slotClicked); + QLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(m_spacing); + layout->addWidget(m_iconLabel); + layout->addWidget(m_textLabel); + layout->addWidget(m_button); + setFocusProxy(m_textLabel); + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); +} + +void ResetWidget::setSpacing(int spacing) +{ + m_spacing = spacing; + layout()->setSpacing(m_spacing); +} + +void ResetWidget::setWidget(QWidget *widget) +{ + if (m_textLabel) { + delete m_textLabel; + m_textLabel = nullptr; + } + if (m_iconLabel) { + delete m_iconLabel; + m_iconLabel = nullptr; + } + delete layout(); + QLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(m_spacing); + layout->addWidget(widget); + layout->addWidget(m_button); + setFocusProxy(widget); +} + +void ResetWidget::setResetEnabled(bool enabled) +{ + m_button->setEnabled(enabled); +} + +void ResetWidget::setValueText(const QString &text) +{ + if (m_textLabel) + m_textLabel->setText(text); +} + +void ResetWidget::setValueIcon(const QIcon &icon) +{ + QPixmap pix = icon.pixmap(QSize(16, 16)); + if (m_iconLabel) { + m_iconLabel->setVisible(!pix.isNull()); + m_iconLabel->setPixmap(pix); + } +} + +void ResetWidget::slotClicked() +{ + emit resetProperty(m_property); +} + +ResetDecorator::ResetDecorator(const QDesignerFormEditorInterface *core, QObject *parent) + : QObject(parent) + , m_core(core) +{ +} + +ResetDecorator::~ResetDecorator() +{ + const auto editors = m_resetWidgetToProperty.keys(); + qDeleteAll(editors); +} + +void ResetDecorator::connectPropertyManager(QtAbstractPropertyManager *manager) +{ + connect(manager, &QtAbstractPropertyManager::propertyChanged, + this, &ResetDecorator::slotPropertyChanged); +} + +void ResetDecorator::disconnectPropertyManager(QtAbstractPropertyManager *manager) +{ + disconnect(manager, &QtAbstractPropertyManager::propertyChanged, + this, &ResetDecorator::slotPropertyChanged); +} + +void ResetDecorator::setSpacing(int spacing) +{ + m_spacing = spacing; +} + +static inline bool isModifiedInMultiSelection(const QDesignerFormEditorInterface *core, + const QString &propertyName) +{ + const QDesignerFormWindowInterface *form = core->formWindowManager()->activeFormWindow(); + if (!form) + return false; + const QDesignerFormWindowCursorInterface *cursor = form->cursor(); + const int selectionSize = cursor->selectedWidgetCount(); + if (selectionSize < 2) + return false; + for (int i = 0; i < selectionSize; ++i) { + const QDesignerPropertySheetExtension *sheet = + qt_extension(core->extensionManager(), + cursor->selectedWidget(i)); + const int index = sheet->indexOf(propertyName); + if (index >= 0 && sheet->isChanged(index)) + return true; + } + return false; +} + +QWidget *ResetDecorator::editor(QWidget *subEditor, bool resettable, QtAbstractPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + Q_UNUSED(manager); + + ResetWidget *resetWidget = nullptr; + if (resettable) { + resetWidget = new ResetWidget(property, parent); + resetWidget->setSpacing(m_spacing); + resetWidget->setResetEnabled(property->isModified() || isModifiedInMultiSelection(m_core, property->propertyName())); + resetWidget->setValueText(property->valueText()); + resetWidget->setValueIcon(property->valueIcon()); + resetWidget->setAutoFillBackground(true); + connect(resetWidget, &QObject::destroyed, this, &ResetDecorator::slotEditorDestroyed); + connect(resetWidget, &ResetWidget::resetProperty, this, &ResetDecorator::resetProperty); + m_createdResetWidgets[property].append(resetWidget); + m_resetWidgetToProperty[resetWidget] = property; + } + if (subEditor) { + if (resetWidget) { + subEditor->setParent(resetWidget); + resetWidget->setWidget(subEditor); + } + } + if (resetWidget) + return resetWidget; + return subEditor; +} + +void ResetDecorator::slotPropertyChanged(QtProperty *property) +{ + const auto prIt = m_createdResetWidgets.constFind(property); + if (prIt == m_createdResetWidgets.constEnd()) + return; + + for (ResetWidget *widget : prIt.value()) { + widget->setResetEnabled(property->isModified() + || isModifiedInMultiSelection(m_core, property->propertyName())); + widget->setValueText(property->valueText()); + widget->setValueIcon(property->valueIcon()); + } +} + +void ResetDecorator::slotEditorDestroyed(QObject *object) +{ + for (auto itEditor = m_resetWidgetToProperty.cbegin(), cend = m_resetWidgetToProperty.cend(); itEditor != cend; ++itEditor) { + if (itEditor.key() == object) { + ResetWidget *editor = itEditor.key(); + QtProperty *property = itEditor.value(); + m_resetWidgetToProperty.remove(editor); + m_createdResetWidgets[property].removeAll(editor); + if (m_createdResetWidgets[property].isEmpty()) + m_createdResetWidgets.remove(property); + return; + } + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/resetdecorator.h b/src/designer/src/components/propertyeditor/resetdecorator.h new file mode 100644 index 0000000..f3734c9 --- /dev/null +++ b/src/designer/src/components/propertyeditor/resetdecorator.h @@ -0,0 +1,80 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef RESETDECORATOR_H +#define RESETDECORATOR_H + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QLabel) +QT_FORWARD_DECLARE_CLASS(QToolButton) +QT_FORWARD_DECLARE_CLASS(QDesignerFormEditorInterface) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QtProperty; +class QtAbstractPropertyManager; + +namespace qdesigner_internal +{ + +class ResetWidget : public QWidget +{ + Q_OBJECT +public: + explicit ResetWidget(QtProperty *property, QWidget *parent = nullptr); + + void setWidget(QWidget *widget); + void setResetEnabled(bool enabled); + void setValueText(const QString &text); + void setValueIcon(const QIcon &icon); + void setSpacing(int spacing); + +signals: + void resetProperty(QtProperty *property); + +private slots: + void slotClicked(); + +private: + QtProperty *m_property; + QLabel *m_textLabel; + QLabel *m_iconLabel; + QToolButton *m_button; + int m_spacing = -1; +}; + +class ResetDecorator : public QObject +{ + Q_OBJECT +public: + explicit ResetDecorator(const QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~ResetDecorator(); + + void connectPropertyManager(QtAbstractPropertyManager *manager); + QWidget *editor(QWidget *subEditor, bool resettable, QtAbstractPropertyManager *manager, QtProperty *property, + QWidget *parent); + void disconnectPropertyManager(QtAbstractPropertyManager *manager); + void setSpacing(int spacing); + +signals: + void resetProperty(QtProperty *property); + +private slots: + void slotPropertyChanged(QtProperty *property); + void slotEditorDestroyed(QObject *object); + +private: + QHash> m_createdResetWidgets; + QHash m_resetWidgetToProperty; + int m_spacing = -1; + const QDesignerFormEditorInterface *m_core; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // RESETDECORATOR_H diff --git a/src/designer/src/components/propertyeditor/stringlisteditor.cpp b/src/designer/src/components/propertyeditor/stringlisteditor.cpp new file mode 100644 index 0000000..3e170ee --- /dev/null +++ b/src/designer/src/components/propertyeditor/stringlisteditor.cpp @@ -0,0 +1,181 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "stringlisteditor.h" +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +StringListEditor::StringListEditor(QWidget *parent) + : QDialog(parent), m_model(new QStringListModel(this)) +{ + setupUi(this); + listView->setModel(m_model); + + connect(listView->selectionModel(), + &QItemSelectionModel::currentChanged, + this, &StringListEditor::currentIndexChanged); + connect(listView->itemDelegate(), + &QAbstractItemDelegate::closeEditor, + this, &StringListEditor::currentValueChanged); + + connect(upButton, &QAbstractButton::clicked, this, &StringListEditor::upButtonClicked); + connect(downButton, &QAbstractButton::clicked, this, &StringListEditor::downButtonClicked); + connect(newButton, &QAbstractButton::clicked, this, &StringListEditor::newButtonClicked); + connect(deleteButton, &QAbstractButton::clicked, this, &StringListEditor::deleteButtonClicked); + connect(valueEdit, &QLineEdit::textEdited, this, &StringListEditor::valueEdited); + + QIcon upIcon = createIconSet("up.png"_L1); + QIcon downIcon = createIconSet("down.png"_L1); + QIcon minusIcon = createIconSet("minus.png"_L1); + QIcon plusIcon = createIconSet("plus.png"_L1); + upButton->setIcon(upIcon); + downButton->setIcon(downIcon); + newButton->setIcon(plusIcon); + deleteButton->setIcon(minusIcon); + + updateUi(); +} + +StringListEditor::~StringListEditor() = default; + +QStringList StringListEditor::getStringList(QWidget *parent, const QStringList &init, int *result) +{ + StringListEditor dlg(parent); + dlg.setStringList(init); + int res = dlg.exec(); + if (result) + *result = res; + return (res == QDialog::Accepted) ? dlg.stringList() : init; +} + +void StringListEditor::setStringList(const QStringList &stringList) +{ + m_model->setStringList(stringList); + updateUi(); +} + +QStringList StringListEditor::stringList() const +{ + return m_model->stringList(); +} + +void StringListEditor::currentIndexChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_UNUSED(previous); + setCurrentIndex(current.row()); + updateUi(); +} + +void StringListEditor::currentValueChanged() +{ + setCurrentIndex(currentIndex()); + updateUi(); +} + +void StringListEditor::upButtonClicked() +{ + int from = currentIndex(); + int to = currentIndex() - 1; + QString value = stringAt(from); + removeString(from); + insertString(to, value); + setCurrentIndex(to); + updateUi(); +} + +void StringListEditor::downButtonClicked() +{ + int from = currentIndex(); + int to = currentIndex() + 1; + QString value = stringAt(from); + removeString(from); + insertString(to, value); + setCurrentIndex(to); + updateUi(); +} + +void StringListEditor::newButtonClicked() +{ + int to = currentIndex(); + if (to == -1) + to = count() - 1; + ++to; + insertString(to, QString()); + setCurrentIndex(to); + updateUi(); + editString(to); +} + +void StringListEditor::deleteButtonClicked() +{ + removeString(currentIndex()); + setCurrentIndex(currentIndex()); + updateUi(); +} + +void StringListEditor::valueEdited(const QString &text) +{ + setStringAt(currentIndex(), text); +} + +void StringListEditor::updateUi() +{ + upButton->setEnabled((count() > 1) && (currentIndex() > 0)); + downButton->setEnabled((count() > 1) && (currentIndex() >= 0) && (currentIndex() < (count() - 1))); + deleteButton->setEnabled(currentIndex() != -1); + valueEdit->setEnabled(currentIndex() != -1); +} + +int StringListEditor::currentIndex() const +{ + return listView->currentIndex().row(); +} + +void StringListEditor::setCurrentIndex(int index) +{ + QModelIndex modelIndex = m_model->index(index, 0); + if (listView->currentIndex() != modelIndex) + listView->setCurrentIndex(modelIndex); + valueEdit->setText(stringAt(index)); +} + +int StringListEditor::count() const +{ + return m_model->rowCount(); +} + +QString StringListEditor::stringAt(int index) const +{ + return qvariant_cast(m_model->data(m_model->index(index, 0), Qt::DisplayRole)); +} + +void StringListEditor::setStringAt(int index, const QString &value) +{ + m_model->setData(m_model->index(index, 0), value); +} + +void StringListEditor::removeString(int index) +{ + m_model->removeRows(index, 1); +} + +void StringListEditor::insertString(int index, const QString &value) +{ + m_model->insertRows(index, 1); + m_model->setData(m_model->index(index, 0), value); +} + +void StringListEditor::editString(int index) +{ + listView->edit(m_model->index(index, 0)); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/stringlisteditor.h b/src/designer/src/components/propertyeditor/stringlisteditor.h new file mode 100644 index 0000000..a64250f --- /dev/null +++ b/src/designer/src/components/propertyeditor/stringlisteditor.h @@ -0,0 +1,54 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef STRINGLISTEDITOR_H +#define STRINGLISTEDITOR_H + +#include "ui_stringlisteditor.h" +#include + +QT_BEGIN_NAMESPACE +class QStringListModel; + +namespace qdesigner_internal { + +class StringListEditor : public QDialog, private Ui::Dialog +{ + Q_OBJECT +public: + ~StringListEditor(); + void setStringList(const QStringList &stringList); + QStringList stringList() const; + + static QStringList getStringList( + QWidget *parent, const QStringList &init = QStringList(), int *result = nullptr); + +private slots: + void upButtonClicked(); + void downButtonClicked(); + void newButtonClicked(); + void deleteButtonClicked(); + void valueEdited(const QString &text); + void currentIndexChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentValueChanged(); + +private: + StringListEditor(QWidget *parent = nullptr); + void updateUi(); + int currentIndex() const; + void setCurrentIndex(int index); + int count() const; + QString stringAt(int index) const; + void setStringAt(int index, const QString &value); + void removeString(int index); + void insertString(int index, const QString &value); + void editString(int index); + + QStringListModel *m_model; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // STRINGLISTEDITOR_H diff --git a/src/designer/src/components/propertyeditor/stringlisteditor.ui b/src/designer/src/components/propertyeditor/stringlisteditor.ui new file mode 100644 index 0000000..c7a718c --- /dev/null +++ b/src/designer/src/components/propertyeditor/stringlisteditor.ui @@ -0,0 +1,229 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::Dialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + 9 + + + 6 + + + + + StringList + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + 0 + + + 6 + + + + + New String + + + &New + + + Qt::ToolButtonTextBesideIcon + + + + + + + Delete String + + + &Delete + + + Qt::ToolButtonTextBesideIcon + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 6 + + + + + &Value: + + + valueEdit + + + + + + + + + + + + + + 0 + + + 6 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Move String Up + + + Up + + + + + + + Move String Down + + + Down + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + qdesigner_internal::Dialog + accept() + + + 258 + 283 + + + 138 + 294 + + + + + buttonBox + rejected() + qdesigner_internal::Dialog + reject() + + + 350 + 284 + + + 369 + 295 + + + + + diff --git a/src/designer/src/components/propertyeditor/stringlisteditorbutton.cpp b/src/designer/src/components/propertyeditor/stringlisteditorbutton.cpp new file mode 100644 index 0000000..5932368 --- /dev/null +++ b/src/designer/src/components/propertyeditor/stringlisteditorbutton.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "stringlisteditorbutton.h" +#include "stringlisteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +StringListEditorButton::StringListEditorButton( + const QStringList &stringList, QWidget *parent) + : QToolButton(parent), m_stringList(stringList) +{ + setFocusPolicy(Qt::NoFocus); + setText(tr("Change String List")); + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + + connect(this, &QAbstractButton::clicked, this, &StringListEditorButton::showStringListEditor); +} + +StringListEditorButton::~StringListEditorButton() = default; + +void StringListEditorButton::setStringList(const QStringList &stringList) +{ + m_stringList = stringList; +} + +void StringListEditorButton::showStringListEditor() +{ + int result; + QStringList lst = StringListEditor::getStringList(nullptr, m_stringList, &result); + if (result == QDialog::Accepted) { + m_stringList = lst; + emit stringListChanged(m_stringList); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/stringlisteditorbutton.h b/src/designer/src/components/propertyeditor/stringlisteditorbutton.h new file mode 100644 index 0000000..8a41c06 --- /dev/null +++ b/src/designer/src/components/propertyeditor/stringlisteditorbutton.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef STRINGLISTEDITORBUTTON_H +#define STRINGLISTEDITORBUTTON_H + +#include "propertyeditor_global.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QT_PROPERTYEDITOR_EXPORT StringListEditorButton: public QToolButton +{ + Q_OBJECT +public: + explicit StringListEditorButton(const QStringList &stringList, QWidget *parent = nullptr); + ~StringListEditorButton() override; + + inline QStringList stringList() const + { return m_stringList; } + +signals: + void stringListChanged(const QStringList &stringList); + +public slots: + void setStringList(const QStringList &stringList); + +private slots: + void showStringListEditor(); + +private: + QStringList m_stringList; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // STRINGLISTEDITORBUTTON_H diff --git a/src/designer/src/components/propertyeditor/texteditor.cpp b/src/designer/src/components/propertyeditor/texteditor.cpp new file mode 100644 index 0000000..e8acd8f --- /dev/null +++ b/src/designer/src/components/propertyeditor/texteditor.cpp @@ -0,0 +1,193 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "texteditor.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal +{ + +TextEditor::TextEditor(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_editor(new TextPropertyEditor(this)), + m_themeEditor(new IconThemeEditor(this, false)), + m_iconThemeModeEnabled(false), + m_richTextDefaultFont(QApplication::font()), + m_button(new QToolButton(this)), + m_menu(new QMenu(this)), + m_resourceAction(new QAction(tr("Choose Resource..."), this)), + m_fileAction(new QAction(tr("Choose File..."), this)), + m_layout(new QHBoxLayout(this)), + m_core(core) +{ + m_themeEditor->setVisible(false); + m_button->setVisible(false); + + m_layout->addWidget(m_editor); + m_layout->addWidget(m_themeEditor); + m_button->setText(tr("...")); + m_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + m_button->setFixedWidth(20); + m_layout->addWidget(m_button); + m_layout->setContentsMargins(QMargins()); + m_layout->setSpacing(0); + + connect(m_resourceAction, &QAction::triggered, this, &TextEditor::resourceActionActivated); + connect(m_fileAction, &QAction::triggered, this, &TextEditor::fileActionActivated); + connect(m_editor, &TextPropertyEditor::textChanged, this, &TextEditor::textChanged); + connect(m_themeEditor, &IconThemeEditor::edited, this, &TextEditor::textChanged); + connect(m_button, &QAbstractButton::clicked, this, &TextEditor::buttonClicked); + + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + setFocusProxy(m_editor); + + m_menu->addAction(m_resourceAction); + m_menu->addAction(m_fileAction); +} + +void TextEditor::setSpacing(int spacing) +{ + m_layout->setSpacing(spacing); +} + +void TextEditor::setIconThemeModeEnabled(bool enable) +{ + if (m_iconThemeModeEnabled == enable) + return; // nothing changes + m_iconThemeModeEnabled = enable; + m_editor->setVisible(!enable); + m_themeEditor->setVisible(enable); + if (enable) { + m_themeEditor->setTheme(m_editor->text()); + setFocusProxy(m_themeEditor); + } else { + m_editor->setText(m_themeEditor->theme()); + setFocusProxy(m_editor); + } +} + +TextPropertyValidationMode TextEditor::textPropertyValidationMode() const +{ + return m_editor->textPropertyValidationMode(); +} + +void TextEditor::setTextPropertyValidationMode(TextPropertyValidationMode vm) +{ + m_editor->setTextPropertyValidationMode(vm); + if (vm == ValidationURL) { + m_button->setMenu(m_menu); + m_button->setFixedWidth(30); + m_button->setPopupMode(QToolButton::MenuButtonPopup); + } else { + m_button->setMenu(nullptr); + m_button->setFixedWidth(20); + m_button->setPopupMode(QToolButton::DelayedPopup); + } + m_button->setVisible(vm == ValidationStyleSheet || vm == ValidationRichText || vm == ValidationMultiLine || vm == ValidationURL); +} + +void TextEditor::setText(const QString &text) +{ + if (m_iconThemeModeEnabled) + m_themeEditor->setTheme(text); + else + m_editor->setText(text); +} + +void TextEditor::buttonClicked() +{ + const QString oldText = m_editor->text(); + QString newText; + switch (textPropertyValidationMode()) { + case ValidationStyleSheet: { + StyleSheetEditorDialog dlg(m_core, this); + dlg.setText(oldText); + if (dlg.exec() != QDialog::Accepted) + return; + newText = dlg.text(); + } + break; + case ValidationRichText: { + RichTextEditorDialog dlg(m_core, this); + dlg.setDefaultFont(m_richTextDefaultFont); + dlg.setText(oldText); + if (dlg.showDialog() != QDialog::Accepted) + return; + newText = dlg.text(Qt::AutoText); + } + break; + case ValidationMultiLine: { + PlainTextEditorDialog dlg(m_core, this); + dlg.setDefaultFont(m_richTextDefaultFont); + dlg.setText(oldText); + if (dlg.showDialog() != QDialog::Accepted) + return; + newText = dlg.text(); + } + break; + case ValidationURL: + if (oldText.isEmpty() || oldText.startsWith("qrc:"_L1)) + resourceActionActivated(); + else + fileActionActivated(); + return; + default: + return; + } + if (newText != oldText) { + m_editor->setText(newText); + emit textChanged(newText); + } +} + +void TextEditor::resourceActionActivated() +{ + QString oldPath = m_editor->text(); + if (oldPath.startsWith("qrc:"_L1)) + oldPath.remove(0, 4); + // returns ':/file' + QString newPath = IconSelector::choosePixmapResource(m_core, m_core->resourceModel(), oldPath, this); + if (newPath.startsWith(u':')) + newPath.remove(0, 1); + if (newPath.isEmpty() || newPath == oldPath) + return; + const QString newText = "qrc:"_L1 + newPath; + m_editor->setText(newText); + emit textChanged(newText); +} + +void TextEditor::fileActionActivated() +{ + QString oldPath = m_editor->text(); + if (oldPath.startsWith("file:"_L1)) + oldPath = oldPath.mid(5); + const QString newPath = m_core->dialogGui()->getOpenFileName(this, tr("Choose a File"), oldPath); + if (newPath.isEmpty() || newPath == oldPath) + return; + const QString newText = QUrl::fromLocalFile(newPath).toString(); + m_editor->setText(newText); + emit textChanged(newText); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/propertyeditor/texteditor.h b/src/designer/src/components/propertyeditor/texteditor.h new file mode 100644 index 0000000..61c579a --- /dev/null +++ b/src/designer/src/components/propertyeditor/texteditor.h @@ -0,0 +1,76 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TEXTEDITOR_H +#define TEXTEDITOR_H + +#include +#include + +#include + +#include + +QT_FORWARD_DECLARE_CLASS(QAction) +QT_FORWARD_DECLARE_CLASS(QHBoxLayout) +QT_FORWARD_DECLARE_CLASS(QLabel) +QT_FORWARD_DECLARE_CLASS(QMenu) +QT_FORWARD_DECLARE_CLASS(QToolButton) + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal +{ + +class IconThemeEditor; + +class TextEditor : public QWidget +{ + Q_OBJECT +public: + TextEditor(QDesignerFormEditorInterface *core, QWidget *parent); + + TextPropertyValidationMode textPropertyValidationMode() const; + void setTextPropertyValidationMode(TextPropertyValidationMode vm); + + void setRichTextDefaultFont(const QFont &font) { m_richTextDefaultFont = font; } + QFont richTextDefaultFont() const { return m_richTextDefaultFont; } + + void setSpacing(int spacing); + + TextPropertyEditor::UpdateMode updateMode() const { return m_editor->updateMode(); } + void setUpdateMode(TextPropertyEditor::UpdateMode um) { m_editor->setUpdateMode(um); } + + void setIconThemeModeEnabled(bool enable); + +public slots: + void setText(const QString &text); + +signals: + void textChanged(const QString &text); + +private slots: + void buttonClicked(); + void resourceActionActivated(); + void fileActionActivated(); + +private: + TextPropertyEditor *m_editor; + IconThemeEditor *m_themeEditor; + bool m_iconThemeModeEnabled; + QFont m_richTextDefaultFont; + QToolButton *m_button; + QMenu *m_menu; + QAction *m_resourceAction; + QAction *m_fileAction; + QHBoxLayout *m_layout; + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif //TEXTEDITOR_H diff --git a/src/designer/src/components/signalsloteditor/connectdialog.cpp b/src/designer/src/components/signalsloteditor/connectdialog.cpp new file mode 100644 index 0000000..6f122b1 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/connectdialog.cpp @@ -0,0 +1,296 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "connectdialog_p.h" +#include "signalslot_utils_p.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QString realClassName(QDesignerFormEditorInterface *core, QWidget *widget) +{ + QString class_name = QLatin1StringView(widget->metaObject()->className()); + const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); + const int idx = wdb->indexOfObject(widget); + if (idx != -1) + class_name = wdb->item(idx)->name(); + return class_name; +} + +static QString widgetLabel(QDesignerFormEditorInterface *core, QWidget *widget) +{ + return "%1 (%2)"_L1 + .arg(qdesigner_internal::realObjectName(core, widget), + realClassName(core, widget)); +} + +namespace qdesigner_internal { + +ConnectDialog::ConnectDialog(QDesignerFormWindowInterface *formWindow, + QWidget *source, QWidget *destination, + QWidget *parent) : + QDialog(parent), + m_source(source), + m_destination(destination), + m_sourceMode(widgetMode(m_source, formWindow)), + m_destinationMode(widgetMode(m_destination, formWindow)), + m_formWindow(formWindow) +{ + m_ui.setupUi(this); + + connect(m_ui.signalList, &QListWidget::itemClicked, + this, &ConnectDialog::selectSignal); + connect(m_ui.slotList, &QListWidget::itemClicked, + this, &ConnectDialog::selectSlot); + m_ui.slotList->setEnabled(false); + + QPushButton *ok_button = okButton(); + ok_button->setDefault(true); + ok_button->setEnabled(false); + + connect(m_ui.showAllCheckBox, &QCheckBox::toggled, this, &ConnectDialog::populateLists); + + QDesignerFormEditorInterface *core = m_formWindow->core(); + m_ui.signalGroupBox->setTitle(widgetLabel(core, source)); + m_ui.slotGroupBox->setTitle(widgetLabel(core, destination)); + + m_ui.editSignalsButton->setEnabled(m_sourceMode != NormalWidget); + connect(m_ui.editSignalsButton, &QAbstractButton::clicked, + this, &ConnectDialog::editSignals); + + m_ui.editSlotsButton->setEnabled(m_destinationMode != NormalWidget); + connect(m_ui.editSlotsButton, &QAbstractButton::clicked, + this, &ConnectDialog::editSlots); + + populateLists(); +} + +ConnectDialog::WidgetMode ConnectDialog::widgetMode(QWidget *w, QDesignerFormWindowInterface *formWindow) +{ + QDesignerFormEditorInterface *core = formWindow->core(); + if (qt_extension(core->extensionManager(), core)) + return NormalWidget; + + if (w == formWindow || formWindow->mainContainer() == w) + return MainContainer; + + if (isPromoted(formWindow->core(), w)) + return PromotedWidget; + + return NormalWidget; +} + +QPushButton *ConnectDialog::okButton() +{ + return m_ui.buttonBox->button(QDialogButtonBox::Ok); +} + +void ConnectDialog::setOkButtonEnabled(bool e) +{ + okButton()->setEnabled(e); +} + +void ConnectDialog::populateLists() +{ + populateSignalList(); +} + +void ConnectDialog::setSignalSlot(const QString &signal, const QString &slot) +{ + auto sigItems = m_ui.signalList->findItems(signal, Qt::MatchExactly); + + if (sigItems.isEmpty()) { + m_ui.showAllCheckBox->setChecked(true); + sigItems = m_ui.signalList->findItems(signal, Qt::MatchExactly); + } + + if (!sigItems.isEmpty()) { + selectSignal(sigItems.constFirst()); + auto slotItems = m_ui.slotList->findItems(slot, Qt::MatchExactly); + if (slotItems.isEmpty()) { + m_ui.showAllCheckBox->setChecked(true); + slotItems = m_ui.slotList->findItems(slot, Qt::MatchExactly); + } + if (!slotItems.isEmpty()) + selectSlot(slotItems.constFirst()); + } +} + +bool ConnectDialog::showAllSignalsSlots() const +{ + return m_ui.showAllCheckBox->isChecked(); +} + +void ConnectDialog::setShowAllSignalsSlots(bool showIt) +{ + m_ui.showAllCheckBox->setChecked(showIt); +} + +void ConnectDialog::selectSignal(QListWidgetItem *item) +{ + if (item) { + m_ui.signalList->setCurrentItem(item); + populateSlotList(item->text()); + m_ui.slotList->setEnabled(true); + setOkButtonEnabled(!m_ui.slotList->selectedItems().isEmpty()); + } else { + m_ui.signalList->clearSelection(); + populateSlotList(); + m_ui.slotList->setEnabled(false); + setOkButtonEnabled(false); + } +} + +void ConnectDialog::selectSlot(QListWidgetItem *item) +{ + if (item) { + m_ui.slotList->setCurrentItem(item); + } else { + m_ui.slotList->clearSelection(); + } + setOkButtonEnabled(true); +} + +QString ConnectDialog::signal() const +{ + const auto item_list = m_ui.signalList->selectedItems(); + if (item_list.size() != 1) + return QString(); + return item_list.at(0)->text(); +} + +QString ConnectDialog::slot() const +{ + const auto item_list = m_ui.slotList->selectedItems(); + if (item_list.size() != 1) + return QString(); + return item_list.at(0)->text(); +} + +void ConnectDialog::populateSlotList(const QString &signal) +{ + enum { deprecatedSlot = 0 }; + QString selectedName; + if (const QListWidgetItem * item = m_ui.slotList->currentItem()) + selectedName = item->text(); + + m_ui.slotList->clear(); + + QMap memberToClassName = getMatchingSlots(m_formWindow->core(), m_destination, signal, showAllSignalsSlots()); + + QFont font = QApplication::font(); + font.setItalic(true); + QVariant variantFont = QVariant::fromValue(font); + + QListWidgetItem *curr = nullptr; + for (auto itMember = memberToClassName.cbegin(), itMemberEnd = memberToClassName.cend(); itMember != itMemberEnd; ++itMember) { + const QString member = itMember.key(); + QListWidgetItem *item = new QListWidgetItem(m_ui.slotList); + item->setText(member); + if (member == selectedName) + curr = item; + + // Mark deprecated slots red. Not currently in use (historically for Qt 3 slots in Qt 4), + // but may be used again in the future. + if (deprecatedSlot) { + item->setData(Qt::FontRole, variantFont); + item->setData(Qt::ForegroundRole, QColor(Qt::red)); + } + } + + if (curr) + m_ui.slotList->setCurrentItem(curr); + + if (m_ui.slotList->selectedItems().isEmpty()) + setOkButtonEnabled(false); +} + +void ConnectDialog::populateSignalList() +{ + enum { deprecatedSignal = 0 }; + + QString selectedName; + if (const QListWidgetItem *item = m_ui.signalList->currentItem()) + selectedName = item->text(); + + m_ui.signalList->clear(); + + QMap memberToClassName = getSignals(m_formWindow->core(), m_source, showAllSignalsSlots()); + + QFont font = QApplication::font(); + font.setItalic(true); + QVariant variantFont = QVariant::fromValue(font); + + QListWidgetItem *curr = nullptr; + for (auto itMember = memberToClassName.cbegin(), itMemberEnd = memberToClassName.cend(); itMember != itMemberEnd; ++itMember) { + const QString member = itMember.key(); + + QListWidgetItem *item = new QListWidgetItem(m_ui.signalList); + item->setText(member); + if (!selectedName.isEmpty() && member == selectedName) + curr = item; + + // Mark deprecated signals red. Not currently in use (historically for Qt 3 slots in Qt 4), + // but may be used again in the future. + if (deprecatedSignal) { + item->setData(Qt::FontRole, variantFont); + item->setData(Qt::ForegroundRole, QColor(Qt::red)); + } + } + + if (curr) { + m_ui.signalList->setCurrentItem(curr); + } else { + selectedName.clear(); + } + + populateSlotList(selectedName); + if (!curr) + m_ui.slotList->setEnabled(false); +} + +void ConnectDialog::editSignals() +{ + editSignalsSlots(m_source, m_sourceMode, SignalSlotDialog::FocusSignals); +} + +void ConnectDialog::editSlots() +{ + editSignalsSlots(m_destination, m_destinationMode, SignalSlotDialog::FocusSlots); +} + +void ConnectDialog::editSignalsSlots(QWidget *w, WidgetMode mode, int signalSlotDialogModeInt) +{ + const SignalSlotDialog::FocusMode signalSlotDialogMode = static_cast(signalSlotDialogModeInt); + switch (mode) { + case NormalWidget: + break; + case MainContainer: + if (SignalSlotDialog::editMetaDataBase(m_formWindow, w, this, signalSlotDialogMode)) + populateLists(); + break; + case PromotedWidget: + if (SignalSlotDialog::editPromotedClass(m_formWindow->core(), w, this, signalSlotDialogMode)) + populateLists(); + break; + } +} + +} + +QT_END_NAMESPACE + +#include "moc_connectdialog_p.cpp" + diff --git a/src/designer/src/components/signalsloteditor/connectdialog.ui b/src/designer/src/components/signalsloteditor/connectdialog.ui new file mode 100644 index 0000000..568516a --- /dev/null +++ b/src/designer/src/components/signalsloteditor/connectdialog.ui @@ -0,0 +1,150 @@ + + ConnectDialog + + + + 0 + 0 + 585 + 361 + + + + Configure Connection + + + + + + GroupBox + + + + + + Qt::ElideMiddle + + + + + + + + + Edit... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + GroupBox + + + + + + Qt::ElideMiddle + + + + + + + + + Edit... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Show signals and slots inherited from QWidget + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ConnectDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ConnectDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/designer/src/components/signalsloteditor/connectdialog_p.h b/src/designer/src/components/signalsloteditor/connectdialog_p.h new file mode 100644 index 0000000..fe9fae3 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/connectdialog_p.h @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONNECTDIALOG_H +#define CONNECTDIALOG_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "ui_connectdialog.h" +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QPushButton; + +namespace qdesigner_internal { + +class ConnectDialog : public QDialog +{ + Q_OBJECT +public: + ConnectDialog(QDesignerFormWindowInterface *formWindow, QWidget *sender, QWidget *receiver, QWidget *parent = nullptr); + + QString signal() const; + QString slot() const; + + void setSignalSlot(const QString &signal, const QString &slot); + + bool showAllSignalsSlots() const; + void setShowAllSignalsSlots(bool showIt); + +private slots: + void populateLists(); + void selectSignal(QListWidgetItem *item); + void selectSlot(QListWidgetItem *item); + void populateSignalList(); + void populateSlotList(const QString &signal = QString()); + void editSignals(); + void editSlots(); + +private: + enum WidgetMode { NormalWidget, MainContainer, PromotedWidget }; + + static WidgetMode widgetMode(QWidget *w, QDesignerFormWindowInterface *formWindow); + QPushButton *okButton(); + void setOkButtonEnabled(bool); + void editSignalsSlots(QWidget *w, WidgetMode mode, int signalSlotDialogMode); + + QWidget *m_source; + QWidget *m_destination; + const WidgetMode m_sourceMode; + const WidgetMode m_destinationMode; + QDesignerFormWindowInterface *m_formWindow; + QT_PREPEND_NAMESPACE(Ui)::ConnectDialog m_ui; +}; + +} + +QT_END_NAMESPACE + +#endif // CONNECTDIALOG_H diff --git a/src/designer/src/components/signalsloteditor/signalslot_utils.cpp b/src/designer/src/components/signalsloteditor/signalslot_utils.cpp new file mode 100644 index 0000000..f8db32e --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalslot_utils.cpp @@ -0,0 +1,261 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalslot_utils_p.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using ClassNameSignaturePair = std::pair; + +// Find all member functions that match a predicate on the signature string +// using the member sheet and the fake methods stored in the widget +// database and the meta data base. +// Assign a pair of to OutputIterator. + +template +static void memberList(QDesignerFormEditorInterface *core, + QObject *object, + qdesigner_internal::MemberType member_type, + bool showAll, + SignaturePredicate predicate, + OutputIterator it) +{ + if (!object) + return; + // 1) member sheet + const QDesignerMemberSheetExtension *members = qt_extension(core->extensionManager(), object); + Q_ASSERT(members != nullptr); + const int count = members->count(); + for (int i = 0; i < count; ++i) { + if (!members->isVisible(i)) + continue; + + if (member_type == qdesigner_internal::SignalMember && !members->isSignal(i)) + continue; + + if (member_type == qdesigner_internal::SlotMember && !members->isSlot(i)) + continue; + + if (!showAll && members->inheritedFromWidget(i)) + continue; + + const QString signature = members->signature(i); + if (predicate(signature)) { + *it = ClassNameSignaturePair(members->declaredInClass(i), signature); + ++it; + } + } + // 2) fake slots from widget DB + const qdesigner_internal::WidgetDataBase *wdb = qobject_cast(core->widgetDataBase()); + if (!wdb) + return; + const int idx = wdb->indexOfObject(object); + Q_ASSERT(idx != -1); + // get the promoted class name + const qdesigner_internal::WidgetDataBaseItem *wdbItem = static_cast(wdb->item(idx)); + const QString className = wdbItem->name(); + + const QStringList wdbFakeMethods = member_type == qdesigner_internal::SlotMember ? wdbItem->fakeSlots() : wdbItem->fakeSignals(); + if (!wdbFakeMethods.isEmpty()) + for (const QString &fakeMethod : wdbFakeMethods) + if (predicate(fakeMethod)) { + *it = ClassNameSignaturePair(className, fakeMethod); + ++it; + } + // 3) fake slots from meta DB + qdesigner_internal::MetaDataBase *metaDataBase = qobject_cast(core->metaDataBase()); + if (!metaDataBase) + return; + + if (const qdesigner_internal::MetaDataBaseItem *mdbItem = metaDataBase->metaDataBaseItem(object)) { + const QStringList mdbFakeMethods = member_type == qdesigner_internal::SlotMember ? mdbItem->fakeSlots() : mdbItem->fakeSignals(); + if (!mdbFakeMethods.isEmpty()) + for (const QString &fakeMethod : mdbFakeMethods) + if (predicate(fakeMethod)) { + *it = ClassNameSignaturePair(className, fakeMethod); + ++it; + } + } +} + +namespace { + // Predicate that matches the exact signature string + class EqualsPredicate { + public: + EqualsPredicate(const QString &pattern) : m_pattern(pattern) {} + bool operator()(const QString &s) const { return s == m_pattern; } + private: + const QString m_pattern; + }; + // Predicate for a QString member signature that matches signals up with slots and vice versa + class SignalMatchesSlotPredicate { + public: + SignalMatchesSlotPredicate(QDesignerFormEditorInterface *core, const QString &peer, qdesigner_internal::MemberType memberType); + bool operator()(const QString &s) const; + + private: + bool signalMatchesSlot(const QString &signal, const QString &slot) const; + + const QString m_peer; + qdesigner_internal::MemberType m_memberType; + const QDesignerLanguageExtension *m_lang; + }; + + SignalMatchesSlotPredicate::SignalMatchesSlotPredicate(QDesignerFormEditorInterface *core, const QString &peer, qdesigner_internal::MemberType memberType) : + m_peer(peer), + m_memberType(memberType), + m_lang(qt_extension(core->extensionManager(), core)) + { + } + + bool SignalMatchesSlotPredicate::operator()(const QString &s) const + { + return m_memberType == qdesigner_internal::SlotMember ? signalMatchesSlot(m_peer, s) : signalMatchesSlot(s, m_peer); + } + + bool SignalMatchesSlotPredicate::signalMatchesSlot(const QString &signal, const QString &slot) const + { + if (m_lang) + return m_lang->signalMatchesSlot(signal, slot); + + return QDesignerMemberSheet::signalMatchesSlot(signal, slot); + } + + // Output iterator for a pair of pair of + // that builds the reverse class list for reverseClassesMemberFunctions() + // (for the combos of the ToolWindow) + class ReverseClassesMemberIterator { + public: + ReverseClassesMemberIterator(qdesigner_internal::ClassesMemberFunctions *result); + + ReverseClassesMemberIterator &operator*() { return *this; } + ReverseClassesMemberIterator &operator++() { return *this; } + ReverseClassesMemberIterator &operator=(const ClassNameSignaturePair &classNameSignature); + + private: + qdesigner_internal::ClassesMemberFunctions *m_result; + QString m_lastClassName; + QStringList *m_memberList; + }; + + ReverseClassesMemberIterator::ReverseClassesMemberIterator(qdesigner_internal::ClassesMemberFunctions *result) : + m_result(result), + m_memberList(nullptr) + { + } + + ReverseClassesMemberIterator &ReverseClassesMemberIterator::operator=(const ClassNameSignaturePair &classNameSignature) + { + // prepend a new entry if class changes + if (!m_memberList || classNameSignature.first != m_lastClassName) { + m_lastClassName = classNameSignature.first; + m_result->push_front(qdesigner_internal::ClassMemberFunctions(m_lastClassName)); + m_memberList = &(m_result->front().m_memberList); + } + m_memberList->push_back(classNameSignature.second); + return *this; + } + + // Output iterator for a pair of pair of + // that adds the signatures to a string list + class SignatureIterator { + public: + SignatureIterator(QMap *result) : m_result(result) {} + + SignatureIterator &operator*() { return *this; } + SignatureIterator &operator++() { return *this; } + SignatureIterator &operator=(const ClassNameSignaturePair &classNameSignature) + { + m_result->insert(classNameSignature.second, classNameSignature.first); + return *this; + } + + private: + QMap *m_result; + }; +} + +static inline bool truePredicate(const QString &) { return true; } + +namespace qdesigner_internal { + + ClassMemberFunctions::ClassMemberFunctions(const QString &class_name) : + m_className(class_name) + { + } + + bool signalMatchesSlot(QDesignerFormEditorInterface *core, const QString &signal, const QString &slot) + { + const SignalMatchesSlotPredicate predicate(core, signal, qdesigner_internal::SlotMember); + return predicate(slot); + } + + // return classes and members in reverse class order to + // populate of the combo of the ToolWindow + ClassesMemberFunctions reverseClassesMemberFunctions(const QString &obj_name, MemberType member_type, + const QString &peer, QDesignerFormWindowInterface *form) + { + QObject *object = nullptr; + if (obj_name == form->mainContainer()->objectName()) { + object = form->mainContainer(); + } else { + object = form->mainContainer()->findChild(obj_name); + } + if (!object) + return ClassesMemberFunctions(); + QDesignerFormEditorInterface *core = form->core(); + + ClassesMemberFunctions rc; + memberList(form->core(), object, member_type, true, SignalMatchesSlotPredicate(core, peer, member_type), + ReverseClassesMemberIterator(&rc)); + return rc; + } + + QMap getSignals(QDesignerFormEditorInterface *core, QObject *object, bool showAll) + { + QMap rc; + memberList(core, object, SignalMember, showAll, truePredicate, SignatureIterator(&rc)); + return rc; + } + + QMap getMatchingSlots(QDesignerFormEditorInterface *core, QObject *object, const QString &signalSignature, bool showAll) + { + QMap rc; + memberList(core, object, SlotMember, showAll, SignalMatchesSlotPredicate(core, signalSignature, qdesigner_internal::SlotMember), SignatureIterator(&rc)); + return rc; + } + + bool memberFunctionListContains(QDesignerFormEditorInterface *core, QObject *object, MemberType type, const QString &signature) + { + QMap rc; + memberList(core, object, type, true, EqualsPredicate(signature), SignatureIterator(&rc)); + return !rc.isEmpty(); + } + + // ### deprecated + QString realObjectName(QDesignerFormEditorInterface *core, QObject *object) + { + if (!object) + return QString(); + + const QDesignerMetaDataBaseInterface *mdb = core->metaDataBase(); + if (const QDesignerMetaDataBaseItemInterface *item = mdb->item(object)) + return item->name(); + + return object->objectName(); + } +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/signalsloteditor/signalslot_utils_p.h b/src/designer/src/components/signalsloteditor/signalslot_utils_p.h new file mode 100644 index 0000000..e7441f8 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalslot_utils_p.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTUTILS_P_H +#define SIGNALSLOTUTILS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +enum MemberType { SignalMember, SlotMember }; + +// member to class name +QMap getSignals(QDesignerFormEditorInterface *core, QObject *object, bool showAll); +QMap getMatchingSlots(QDesignerFormEditorInterface *core, QObject *object, + const QString &signalSignature, bool showAll); + +bool memberFunctionListContains(QDesignerFormEditorInterface *core, QObject *object, MemberType type, const QString &signature); + +// Members functions listed by class they were inherited from +struct ClassMemberFunctions +{ + ClassMemberFunctions() = default; + ClassMemberFunctions(const QString &_class_name); + + QString m_className; + QStringList m_memberList; +}; + +using ClassesMemberFunctions = QList; + +// Return classes and members in reverse class order to +// populate of the combo of the ToolWindow. + +ClassesMemberFunctions reverseClassesMemberFunctions(const QString &obj_name, MemberType member_type, + const QString &peer, QDesignerFormWindowInterface *form); + +bool signalMatchesSlot(QDesignerFormEditorInterface *core, const QString &signal, const QString &slot); + +QString realObjectName(QDesignerFormEditorInterface *core, QObject *object); + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTUTILS_P_H diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor.cpp b/src/designer/src/components/signalsloteditor/signalsloteditor.cpp new file mode 100644 index 0000000..76239f0 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor.cpp @@ -0,0 +1,525 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalsloteditor.h" +#include "signalsloteditor_p.h" +#include "connectdialog_p.h" +#include "signalslot_utils_p.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +/******************************************************************************* +** SignalSlotConnection +*/ + +SignalSlotConnection::SignalSlotConnection(ConnectionEdit *edit, QWidget *source, QWidget *target) + : Connection(edit, source, target) +{ +} + +DomConnection *SignalSlotConnection::toUi() const +{ + DomConnection *result = new DomConnection; + + result->setElementSender(sender()); + result->setElementSignal(signal()); + result->setElementReceiver(receiver()); + result->setElementSlot(slot()); + + DomConnectionHints *hints = new DomConnectionHints; + QList list; + + QPoint sp = endPointPos(EndPoint::Source); + QPoint tp = endPointPos(EndPoint::Target); + + DomConnectionHint *hint = new DomConnectionHint; + hint->setAttributeType(u"sourcelabel"_s); + hint->setElementX(sp.x()); + hint->setElementY(sp.y()); + list.append(hint); + + hint = new DomConnectionHint; + hint->setAttributeType(u"destinationlabel"_s); + hint->setElementX(tp.x()); + hint->setElementY(tp.y()); + list.append(hint); + + hints->setElementHint(list); + result->setElementHints(hints); + + return result; +} + +void SignalSlotConnection::setSignal(const QString &signal) +{ + m_signal = signal; + setLabel(EndPoint::Source, m_signal); +} + +void SignalSlotConnection::setSlot(const QString &slot) +{ + m_slot = slot; + setLabel(EndPoint::Target, m_slot); +} + +QString SignalSlotConnection::sender() const +{ + QObject *source = object(EndPoint::Source); + if (!source) + return QString(); + + SignalSlotEditor *edit = qobject_cast(this->edit()); + Q_ASSERT(edit != nullptr); + + return realObjectName(edit->formWindow()->core(), source); +} + +QString SignalSlotConnection::receiver() const +{ + QObject *sink = object(EndPoint::Target); + if (!sink) + return QString(); + + SignalSlotEditor *edit = qobject_cast(this->edit()); + Q_ASSERT(edit != nullptr); + return realObjectName(edit->formWindow()->core(), sink); +} + +void SignalSlotConnection::updateVisibility() +{ + Connection::updateVisibility(); + if (isVisible() && (signal().isEmpty() || slot().isEmpty())) + setVisible(false); +} + +QString SignalSlotConnection::toString() const +{ + return QCoreApplication::translate("SignalSlotConnection", "SENDER(%1), SIGNAL(%2), RECEIVER(%3), SLOT(%4)") + .arg(sender(), signal(), receiver(), slot()); +} + +SignalSlotConnection::State SignalSlotConnection::isValid(const QWidget *background) const +{ + const QObject *source = object(EndPoint::Source); + if (!source) + return ObjectDeleted; + + const QObject *target = object(EndPoint::Target); + if (!target) + return ObjectDeleted; + + if (m_slot.isEmpty() || m_signal.isEmpty()) + return InvalidMethod; + + if (const QWidget *sourceWidget = qobject_cast(source)) + if (!background->isAncestorOf(sourceWidget)) + return NotAncestor; + + if (const QWidget *targetWidget = qobject_cast(target)) + if (!background->isAncestorOf(targetWidget)) + return NotAncestor; + + return Valid; +} + +/******************************************************************************* +** Commands +*/ + +class SetMemberCommand : public QUndoCommand, public CETypes +{ +public: + SetMemberCommand(SignalSlotConnection *con, EndPoint::Type type, + const QString &member, SignalSlotEditor *editor); + void redo() override; + void undo() override; +private: + const QString m_old_member; + const QString m_new_member; + const EndPoint::Type m_type; + SignalSlotConnection *m_con; + SignalSlotEditor *m_editor; +}; + +SetMemberCommand::SetMemberCommand(SignalSlotConnection *con, EndPoint::Type type, + const QString &member, SignalSlotEditor *editor) : + m_old_member(type == EndPoint::Source ? con->signal() : con->slot()), + m_new_member(member), + m_type(type), + m_con(con), + m_editor(editor) +{ + if (type == EndPoint::Source) + setText(QApplication::translate("Command", "Change signal")); + else + setText(QApplication::translate("Command", "Change slot")); +} + +void SetMemberCommand::redo() +{ + m_con->update(); + if (m_type == EndPoint::Source) + m_con->setSignal(m_new_member); + else + m_con->setSlot(m_new_member); + m_con->update(); + emit m_editor->connectionChanged(m_con); +} + +void SetMemberCommand::undo() +{ + m_con->update(); + if (m_type == EndPoint::Source) + m_con->setSignal(m_old_member); + else + m_con->setSlot(m_old_member); + m_con->update(); + emit m_editor->connectionChanged(m_con); +} + +// Command to modify a connection +class ModifyConnectionCommand : public QDesignerFormWindowCommand +{ +public: + explicit ModifyConnectionCommand(QDesignerFormWindowInterface *form, + SignalSlotConnection *conn, + const QString &newSignal, + const QString &newSlot); + void redo() override; + void undo() override; + +private: + SignalSlotConnection *m_conn; + const QString m_oldSignal; + const QString m_oldSlot; + const QString m_newSignal; + const QString m_newSlot; +}; + +ModifyConnectionCommand::ModifyConnectionCommand(QDesignerFormWindowInterface *form, + SignalSlotConnection *conn, + const QString &newSignal, + const QString &newSlot) : + QDesignerFormWindowCommand(QCoreApplication::translate("Command", "Change signal-slot connection"), form), + m_conn(conn), + m_oldSignal(conn->signal()), + m_oldSlot(conn->slot()), + m_newSignal(newSignal), + m_newSlot(newSlot) +{ +} + +void ModifyConnectionCommand::redo() +{ + m_conn->setSignal(m_newSignal); + m_conn->setSlot(m_newSlot); +} + +void ModifyConnectionCommand::undo() +{ + m_conn->setSignal(m_oldSignal); + m_conn->setSlot(m_oldSlot); +} + +/******************************************************************************* +** SignalSlotEditor +*/ + +SignalSlotEditor::SignalSlotEditor(QDesignerFormWindowInterface *form_window, QWidget *parent) : + ConnectionEdit(parent, form_window), + m_form_window(form_window), + m_showAllSignalsSlots(false) +{ +} + +void SignalSlotEditor::modifyConnection(Connection *con) +{ + SignalSlotConnection *sigslot_con = static_cast(con); + ConnectDialog dialog(m_form_window, + sigslot_con->widget(EndPoint::Source), + sigslot_con->widget(EndPoint::Target), + m_form_window->core()->topLevel()); + + dialog.setSignalSlot(sigslot_con->signal(), sigslot_con->slot()); + dialog.setShowAllSignalsSlots(m_showAllSignalsSlots); + + if (dialog.exec() == QDialog::Accepted) { + const QString newSignal = dialog.signal(); + const QString newSlot = dialog.slot(); + if (sigslot_con->signal() != newSignal || sigslot_con->slot() != newSlot) { + ModifyConnectionCommand *cmd = new ModifyConnectionCommand(m_form_window, sigslot_con, newSignal, newSlot); + m_form_window->commandHistory()->push(cmd); + } + } + + m_showAllSignalsSlots = dialog.showAllSignalsSlots(); +} + +Connection *SignalSlotEditor::createConnection(QWidget *source, QWidget *destination) +{ + SignalSlotConnection *con = nullptr; + + Q_ASSERT(source != nullptr); + Q_ASSERT(destination != nullptr); + + ConnectDialog dialog(m_form_window, source, destination, m_form_window->core()->topLevel()); + dialog.setShowAllSignalsSlots(m_showAllSignalsSlots); + + if (dialog.exec() == QDialog::Accepted) { + con = new SignalSlotConnection(this, source, destination); + con->setSignal(dialog.signal()); + con->setSlot(dialog.slot()); + } + + m_showAllSignalsSlots = dialog.showAllSignalsSlots(); + + return con; +} + +DomConnections *SignalSlotEditor::toUi() const +{ + DomConnections *result = new DomConnections; + QList list; + + const int count = connectionCount(); + list.reserve(count); + for (int i = 0; i < count; ++i) { + const SignalSlotConnection *con = static_cast(connection(i)); + Q_ASSERT(con != nullptr); + + // If a widget's parent has been removed or moved to a different form, + // and the parent was not a managed widget + // (a page in a tab widget), we never get a widgetRemoved(). So we filter out + // these child widgets here (check QPointer and verify ancestor). + // Also, the user might demote a promoted widget or remove a fake + // slot in the editor, which causes the connection to become invalid + // once he doubleclicks on the method combo. + switch (con->isValid(background())) { + case SignalSlotConnection::Valid: + list.append(con->toUi()); + break; + case SignalSlotConnection::ObjectDeleted: + case SignalSlotConnection::InvalidMethod: + case SignalSlotConnection::NotAncestor: + break; + } + } + result->setElementConnection(list); + return result; +} + +QObject *SignalSlotEditor::objectByName(QWidget *topLevel, const QString &name) const +{ + if (name.isEmpty()) + return nullptr; + + Q_ASSERT(topLevel); + QObject *object = nullptr; + if (topLevel->objectName() == name) + object = topLevel; + else + object = topLevel->findChild(name); + const QDesignerMetaDataBaseInterface *mdb = formWindow()->core()->metaDataBase(); + if (mdb->item(object)) + return object; + return nullptr; +} + +void SignalSlotEditor::fromUi(const DomConnections *connections, QWidget *parent) +{ + if (connections == nullptr) + return; + + // For old forms, that were saved before Qt 4 times, there was no + // section inside ui file. Currently, when we specify custom signals or slots + // for the form, we add them into the section. For all signals / slots + // inside section uic creates string-based connections. + // In order to fix old forms, we detect if a signal or slot used inside connection + // is a custom (fake) one, like it's being done inside SignalSlotDialog. + // In case of a fake signal / slot we register it inside meta data base, so that + // the next save will add a missing section. + QStringList existingSlots, existingSignals; + SignalSlotDialog::existingMethodsFromMemberSheet(m_form_window->core(), parent, + existingSlots, existingSignals); + QStringList fakeSlots, fakeSignals; + SignalSlotDialog::fakeMethodsFromMetaDataBase(m_form_window->core(), parent, + fakeSlots, fakeSignals); + + setBackground(parent); + clear(); + const auto &list = connections->elementConnection(); + for (const DomConnection *dom_con : list) { + QObject *source = objectByName(parent, dom_con->elementSender()); + if (source == nullptr) { + qDebug("SignalSlotEditor::fromUi(): no source widget called \"%s\"", + dom_con->elementSender().toUtf8().constData()); + continue; + } + QObject *destination = objectByName(parent, dom_con->elementReceiver()); + if (destination == nullptr) { + qDebug("SignalSlotEditor::fromUi(): no destination widget called \"%s\"", + dom_con->elementReceiver().toUtf8().constData()); + continue; + } + + QPoint sp = QPoint(20, 20), tp = QPoint(20, 20); + const DomConnectionHints *dom_hints = dom_con->elementHints(); + if (dom_hints != nullptr) { + const auto &hints = dom_hints->elementHint(); + for (DomConnectionHint *hint : hints) { + QString attr_type = hint->attributeType(); + QPoint p = QPoint(hint->elementX(), hint->elementY()); + if (attr_type == "sourcelabel"_L1) + sp = p; + else if (attr_type == "destinationlabel"_L1) + tp = p; + } + } + + const QString sourceSignal = dom_con->elementSignal(); + if (source == parent && !existingSignals.contains(sourceSignal) + && !fakeSignals.contains(sourceSignal)) { + fakeSignals.append(sourceSignal); + } + + const QString destSlot = dom_con->elementSlot(); + if (destination == parent && !existingSlots.contains(destSlot) + && !fakeSlots.contains(destSlot)) { + fakeSlots.append(destSlot); + } + + + SignalSlotConnection *con = new SignalSlotConnection(this); + + con->setEndPoint(EndPoint::Source, source, sp); + con->setEndPoint(EndPoint::Target, destination, tp); + con->setSignal(sourceSignal); + con->setSlot(destSlot); + addConnection(con); + } + SignalSlotDialog::fakeMethodsToMetaDataBase(m_form_window->core(), parent, + fakeSlots, fakeSignals); +} + +static bool skipWidget(const QWidget *w) +{ + const QString name = QLatin1StringView(w->metaObject()->className()); + if (name == "QDesignerWidget"_L1) + return true; + if (name == "QLayoutWidget"_L1) + return true; + if (name == "qdesigner_internal::FormWindow"_L1) + return true; + if (name == "Spacer"_L1) + return true; + return false; +} + +QWidget *SignalSlotEditor::widgetAt(const QPoint &pos) const +{ + QWidget *widget = ConnectionEdit::widgetAt(pos); + + if (widget == m_form_window->mainContainer()) + return widget; + + for (; widget != nullptr; widget = widget->parentWidget()) { + QDesignerMetaDataBaseItemInterface *item = m_form_window->core()->metaDataBase()->item(widget); + if (item == nullptr) + continue; + if (skipWidget(widget)) + continue; + break; + } + + return widget; +} + +void SignalSlotEditor::setSignal(SignalSlotConnection *con, const QString &member) +{ + if (member == con->signal()) + return; + + m_form_window->beginCommand(QApplication::translate("Command", "Change signal")); + undoStack()->push(new SetMemberCommand(con, EndPoint::Source, member, this)); + if (!signalMatchesSlot(m_form_window->core(), member, con->slot())) + undoStack()->push(new SetMemberCommand(con, EndPoint::Target, QString(), this)); + m_form_window->endCommand(); +} + +void SignalSlotEditor::setSlot(SignalSlotConnection *con, const QString &member) +{ + if (member == con->slot()) + return; + + m_form_window->beginCommand(QApplication::translate("Command", "Change slot")); + undoStack()->push(new SetMemberCommand(con, EndPoint::Target, member, this)); + if (!signalMatchesSlot(m_form_window->core(), con->signal(), member)) + undoStack()->push(new SetMemberCommand(con, EndPoint::Source, QString(), this)); + m_form_window->endCommand(); +} + +void SignalSlotEditor::setSource(Connection *_con, const QString &obj_name) +{ + SignalSlotConnection *con = static_cast(_con); + + if (con->sender() == obj_name) + return; + + m_form_window->beginCommand(QApplication::translate("Command", "Change sender")); + ConnectionEdit::setSource(con, obj_name); + + QObject *sourceObject = con->object(EndPoint::Source); + + if (!memberFunctionListContains(m_form_window->core(), sourceObject, SignalMember, con->signal())) + undoStack()->push(new SetMemberCommand(con, EndPoint::Source, QString(), this)); + + m_form_window->endCommand(); +} + +void SignalSlotEditor::setTarget(Connection *_con, const QString &obj_name) +{ + SignalSlotConnection *con = static_cast(_con); + + if (con->receiver() == obj_name) + return; + + m_form_window->beginCommand(QApplication::translate("Command", "Change receiver")); + ConnectionEdit::setTarget(con, obj_name); + + QObject *targetObject = con->object(EndPoint::Target); + if (!memberFunctionListContains(m_form_window->core(), targetObject, SlotMember, con->slot())) + undoStack()->push(new SetMemberCommand(con, EndPoint::Target, QString(), this)); + + m_form_window->endCommand(); +} + +void SignalSlotEditor::addEmptyConnection() +{ + SignalSlotConnection *con = new SignalSlotConnection(this); + undoStack()->push(new AddConnectionCommand(this, con)); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor.h b/src/designer/src/components/signalsloteditor/signalsloteditor.h new file mode 100644 index 0000000..6ce4989 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_H +#define SIGNALSLOTEDITOR_H + +#include "signalsloteditor_global.h" + +#include + +QT_BEGIN_NAMESPACE + +class DomConnections; + +namespace qdesigner_internal { + +class SignalSlotConnection; + +class QT_SIGNALSLOTEDITOR_EXPORT SignalSlotEditor : public ConnectionEdit +{ + Q_OBJECT + +public: + SignalSlotEditor(QDesignerFormWindowInterface *form_window, QWidget *parent); + + virtual void setSignal(SignalSlotConnection *con, const QString &member); + virtual void setSlot(SignalSlotConnection *con, const QString &member); + void setSource(Connection *con, const QString &obj_name) override; + void setTarget(Connection *con, const QString &obj_name) override; + + DomConnections *toUi() const; + void fromUi(const DomConnections *connections, QWidget *parent); + + QDesignerFormWindowInterface *formWindow() const { return m_form_window; } + + QObject *objectByName(QWidget *topLevel, const QString &name) const; + + void addEmptyConnection(); + +protected: + QWidget *widgetAt(const QPoint &pos) const override; + +private: + Connection *createConnection(QWidget *source, QWidget *destination) override; + void modifyConnection(Connection *con) override; + + QDesignerFormWindowInterface *m_form_window; + bool m_showAllSignalsSlots; + + friend class SetMemberCommand; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITOR_H diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor.json b/src/designer/src/components/signalsloteditor/signalsloteditor.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor.json @@ -0,0 +1 @@ +{} diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor_global.h b/src/designer/src/components/signalsloteditor/signalsloteditor_global.h new file mode 100644 index 0000000..fbe0253 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_GLOBAL_H +#define SIGNALSLOTEDITOR_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_SIGNALSLOTEDITOR_LIBRARY +# define QT_SIGNALSLOTEDITOR_EXPORT +#else +# define QT_SIGNALSLOTEDITOR_EXPORT +#endif +#else +#define QT_SIGNALSLOTEDITOR_EXPORT +#endif + +#endif // SIGNALSLOTEDITOR_GLOBAL_H diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor_p.h b/src/designer/src/components/signalsloteditor/signalsloteditor_p.h new file mode 100644 index 0000000..9875b7b --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor_p.h @@ -0,0 +1,101 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_P_H +#define SIGNALSLOTEDITOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class DomConnection; + +namespace qdesigner_internal { + +class SignalSlotEditor; + +class SignalSlotConnection : public Connection +{ +public: + explicit SignalSlotConnection(ConnectionEdit *edit, QWidget *source = nullptr, QWidget *target = nullptr); + + void setSignal(const QString &signal); + void setSlot(const QString &slot); + + QString sender() const; + QString receiver() const; + inline QString signal() const { return m_signal; } + inline QString slot() const { return m_slot; } + + DomConnection *toUi() const; + + void updateVisibility() override; + + enum State { Valid, ObjectDeleted, InvalidMethod, NotAncestor }; + State isValid(const QWidget *background) const; + + // format for messages, etc. + QString toString() const; + +private: + QString m_signal, m_slot; +}; + +class ConnectionModel : public QAbstractItemModel +{ + Q_OBJECT +public: + explicit ConnectionModel(QObject *parent = nullptr); + void setEditor(SignalSlotEditor *editor = nullptr); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &data, int role = Qt::DisplayRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + QModelIndex connectionToIndex(Connection *con) const; + Connection *indexToConnection(const QModelIndex &index) const; + void updateAll(); + + const SignalSlotConnection *connectionAt(const QModelIndex &index) const; + static QString columnText(const SignalSlotConnection *con, int column); + +private slots: + void connectionAdded(qdesigner_internal::Connection *con); + void connectionRemoved(int idx); + void aboutToRemoveConnection(qdesigner_internal::Connection *con); + void aboutToAddConnection(int idx); + void connectionChanged(qdesigner_internal::Connection *con); + +private: + QPointer m_editor; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITOR_P_H diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor_plugin.cpp b/src/designer/src/components/signalsloteditor/signalsloteditor_plugin.cpp new file mode 100644 index 0000000..32d5f93 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor_plugin.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalsloteditor_plugin.h" +#include "signalsloteditor_tool.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +SignalSlotEditorPlugin::SignalSlotEditorPlugin() = default; + +SignalSlotEditorPlugin::~SignalSlotEditorPlugin() = default; + +bool SignalSlotEditorPlugin::isInitialized() const +{ + return m_initialized; +} + +void SignalSlotEditorPlugin::initialize(QDesignerFormEditorInterface *core) +{ + Q_ASSERT(!isInitialized()); + + m_action = new QAction(tr("Edit Signals/Slots"), this); + m_action->setObjectName(u"__qt_edit_signals_slots_action"_s); + m_action->setShortcut(tr("F4")); + m_action->setIcon(createIconSet("signalslottool.png"_L1)); + m_action->setEnabled(false); + + setParent(core); + m_core = core; + m_initialized = true; + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowAdded, + this, &SignalSlotEditorPlugin::addFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowRemoved, + this, &SignalSlotEditorPlugin::removeFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &SignalSlotEditorPlugin::activeFormWindowChanged); +} + +QDesignerFormEditorInterface *SignalSlotEditorPlugin::core() const +{ + return m_core; +} + +void SignalSlotEditorPlugin::addFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == false); + + SignalSlotEditorTool *tool = new SignalSlotEditorTool(formWindow, this); + connect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + m_tools[formWindow] = tool; + formWindow->registerTool(tool); +} + +void SignalSlotEditorPlugin::removeFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == true); + + SignalSlotEditorTool *tool = m_tools.value(formWindow); + m_tools.remove(formWindow); + disconnect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + // ### FIXME disable the tool + + delete tool; +} + +QAction *SignalSlotEditorPlugin::action() const +{ + return m_action; +} + +void SignalSlotEditorPlugin::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) +{ + m_action->setEnabled(formWindow != nullptr); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "moc_signalsloteditor_plugin.cpp" diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor_plugin.h b/src/designer/src/components/signalsloteditor/signalsloteditor_plugin.h new file mode 100644 index 0000000..dc70c67 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor_plugin.h @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_PLUGIN_H +#define SIGNALSLOTEDITOR_PLUGIN_H + +#include "signalsloteditor_global.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class SignalSlotEditorTool; + +class QT_SIGNALSLOTEDITOR_EXPORT SignalSlotEditorPlugin: public QObject, public QDesignerFormEditorPluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerFormEditorPluginInterface" FILE "signalsloteditor.json") + Q_INTERFACES(QDesignerFormEditorPluginInterface) +public: + SignalSlotEditorPlugin(); + ~SignalSlotEditorPlugin() override; + + bool isInitialized() const override; + void initialize(QDesignerFormEditorInterface *core) override; + QAction *action() const override; + + QDesignerFormEditorInterface *core() const override; + +public slots: + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + +private slots: + void addFormWindow(QDesignerFormWindowInterface *formWindow); + void removeFormWindow(QDesignerFormWindowInterface *formWindow); + +private: + QPointer m_core; + QHash m_tools; + bool m_initialized = false; + QAction *m_action = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITOR_PLUGIN_H diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor_tool.cpp b/src/designer/src/components/signalsloteditor/signalsloteditor_tool.cpp new file mode 100644 index 0000000..9b5e72f --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor_tool.cpp @@ -0,0 +1,87 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalsloteditor_tool.h" +#include "signalsloteditor.h" + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +SignalSlotEditorTool::SignalSlotEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent) + : QDesignerFormWindowToolInterface(parent), + m_formWindow(formWindow), + m_action(new QAction(tr("Edit Signals/Slots"), this)) +{ +} + +SignalSlotEditorTool::~SignalSlotEditorTool() = default; + +QDesignerFormEditorInterface *SignalSlotEditorTool::core() const +{ + return m_formWindow->core(); +} + +QDesignerFormWindowInterface *SignalSlotEditorTool::formWindow() const +{ + return m_formWindow; +} + +bool SignalSlotEditorTool::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + Q_UNUSED(widget); + Q_UNUSED(managedWidget); + Q_UNUSED(event); + + return false; +} + +QWidget *SignalSlotEditorTool::editor() const +{ + if (!m_editor) { + Q_ASSERT(formWindow() != nullptr); + m_editor = new qdesigner_internal::SignalSlotEditor(formWindow(), nullptr); + connect(formWindow(), &QDesignerFormWindowInterface::mainContainerChanged, + m_editor.data(), &SignalSlotEditor::setBackground); + connect(formWindow(), &QDesignerFormWindowInterface::changed, + m_editor.data(), &SignalSlotEditor::updateBackground); + } + + return m_editor; +} + +QAction *SignalSlotEditorTool::action() const +{ + return m_action; +} + +void SignalSlotEditorTool::activated() +{ + m_editor->enableUpdateBackground(true); +} + +void SignalSlotEditorTool::deactivated() +{ + m_editor->enableUpdateBackground(false); +} + +void SignalSlotEditorTool::saveToDom(DomUI *ui, QWidget*) +{ + ui->setElementConnections(m_editor->toUi()); +} + +void SignalSlotEditorTool::loadFromDom(DomUI *ui, QWidget *mainContainer) +{ + m_editor->fromUi(ui->elementConnections(), mainContainer); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/signalsloteditor/signalsloteditor_tool.h b/src/designer/src/components/signalsloteditor/signalsloteditor_tool.h new file mode 100644 index 0000000..7627389 --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditor_tool.h @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_TOOL_H +#define SIGNALSLOTEDITOR_TOOL_H + +#include "signalsloteditor_global.h" +#include "signalsloteditor.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class SignalSlotEditor; + +class QT_SIGNALSLOTEDITOR_EXPORT SignalSlotEditorTool: public QDesignerFormWindowToolInterface +{ + Q_OBJECT +public: + explicit SignalSlotEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent = nullptr); + ~SignalSlotEditorTool() override; + + QDesignerFormEditorInterface *core() const override; + QDesignerFormWindowInterface *formWindow() const override; + + QWidget *editor() const override; + + QAction *action() const override; + + void activated() override; + void deactivated() override; + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) override; + + void saveToDom(DomUI *ui, QWidget *mainContainer) override; + void loadFromDom(DomUI *ui, QWidget *mainContainer) override; + +private: + QDesignerFormWindowInterface *m_formWindow; + mutable QPointer m_editor; + QAction *m_action; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITOR_TOOL_H diff --git a/src/designer/src/components/signalsloteditor/signalsloteditorwindow.cpp b/src/designer/src/components/signalsloteditor/signalsloteditorwindow.cpp new file mode 100644 index 0000000..7ab81eb --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditorwindow.cpp @@ -0,0 +1,820 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalsloteditorwindow.h" +#include "signalsloteditor_p.h" +#include "signalsloteditor.h" +#include "signalslot_utils_p.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Add suitable form widgets to a list of objects for the signal slot +// editor. Prevent special widgets from showing up there. +static void addWidgetToObjectList(const QWidget *w, QStringList &r) +{ + const QMetaObject *mo = w->metaObject(); + if (mo != &QLayoutWidget::staticMetaObject && mo != &Spacer::staticMetaObject) { + const QString name = w->objectName().trimmed(); + if (!name.isEmpty()) + r.push_back(name); + } +} + +static QStringList objectNameList(QDesignerFormWindowInterface *form) +{ + QStringList result; + + QWidget *mainContainer = form->mainContainer(); + if (!mainContainer) + return result; + + // Add main container container pages (QStatusBar, QWizardPages) etc. + // to the list. Pages of containers on the form are not added, however. + if (const QDesignerContainerExtension *c = qt_extension(form->core()->extensionManager(), mainContainer)) { + const int count = c->count(); + for (int i = 0 ; i < count; i++) + addWidgetToObjectList(c->widget(i), result); + } + + const QDesignerFormWindowCursorInterface *cursor = form->cursor(); + const int widgetCount = cursor->widgetCount(); + for (int i = 0; i < widgetCount; ++i) + addWidgetToObjectList(cursor->widget(i), result); + + const QDesignerMetaDataBaseInterface *mdb = form->core()->metaDataBase(); + + // Add managed actions and actions with managed menus + const auto actions = mainContainer->findChildren(); + for (QAction *a : actions) { + if (!a->isSeparator()) { + if (QMenu *menu = a->menu()) { + if (mdb->item(menu)) + result.push_back(menu->objectName()); + } else { + if (mdb->item(a)) + result.push_back(a->objectName()); + } + } + } + + // Add managed buttons groups + const auto buttonGroups = mainContainer->findChildren(); + for (QButtonGroup * b : buttonGroups) { + if (mdb->item(b)) + result.append(b->objectName()); + } + + result.sort(); + return result; +} + +namespace qdesigner_internal { + +// ------------ ConnectionModel + +ConnectionModel::ConnectionModel(QObject *parent) : + QAbstractItemModel(parent) +{ +} + +void ConnectionModel::setEditor(SignalSlotEditor *editor) +{ + if (m_editor == editor) + return; + beginResetModel(); + + if (m_editor) { + disconnect(m_editor.data(), &SignalSlotEditor::connectionAdded, + this, &ConnectionModel::connectionAdded); + disconnect(m_editor.data(), &SignalSlotEditor::connectionRemoved, + this, &ConnectionModel::connectionRemoved); + disconnect(m_editor.data(), &SignalSlotEditor::aboutToRemoveConnection, + this, &ConnectionModel::aboutToRemoveConnection); + disconnect(m_editor.data(), &SignalSlotEditor::aboutToAddConnection, + this, &ConnectionModel::aboutToAddConnection); + disconnect(m_editor.data(), &SignalSlotEditor::connectionChanged, + this, &ConnectionModel::connectionChanged); + } + m_editor = editor; + if (m_editor) { + connect(m_editor.data(), &SignalSlotEditor::connectionAdded, + this, &ConnectionModel::connectionAdded); + connect(m_editor.data(), &SignalSlotEditor::connectionRemoved, + this, &ConnectionModel::connectionRemoved); + connect(m_editor.data(), &SignalSlotEditor::aboutToRemoveConnection, + this, &ConnectionModel::aboutToRemoveConnection); + connect(m_editor.data(), &SignalSlotEditor::aboutToAddConnection, + this, &ConnectionModel::aboutToAddConnection); + connect(m_editor.data(), &SignalSlotEditor::connectionChanged, + this, &ConnectionModel::connectionChanged); + } + endResetModel(); +} + +QVariant ConnectionModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Vertical || role != Qt::DisplayRole) + return QVariant(); + + static const QVariant senderTitle = tr("Sender"); + static const QVariant signalTitle = tr("Signal"); + static const QVariant receiverTitle = tr("Receiver"); + static const QVariant slotTitle = tr("Slot"); + + switch (section) { + case 0: + return senderTitle; + case 1: + return signalTitle; + case 2: + return receiverTitle; + case 3: + return slotTitle; + } + return QVariant(); +} + +QModelIndex ConnectionModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (parent.isValid() || !m_editor) + return QModelIndex(); + if (row < 0 || row >= m_editor->connectionCount()) + return QModelIndex(); + return createIndex(row, column); +} + +Connection *ConnectionModel::indexToConnection(const QModelIndex &index) const +{ + if (!index.isValid() || !m_editor) + return nullptr; + if (index.row() < 0 || index.row() >= m_editor->connectionCount()) + return nullptr; + return m_editor->connection(index.row()); +} + +QModelIndex ConnectionModel::connectionToIndex(Connection *con) const +{ + Q_ASSERT(m_editor); + return createIndex(m_editor->indexOfConnection(con), 0); +} + +QModelIndex ConnectionModel::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +int ConnectionModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid() || !m_editor) + return 0; + return m_editor->connectionCount(); +} + +int ConnectionModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return 4; +} + +const SignalSlotConnection *ConnectionModel::connectionAt(const QModelIndex &index) const +{ + const int row = index.row(); + return m_editor != nullptr && row >= 0 && row < m_editor->connectionCount() + ? static_cast(m_editor->connection(row)) + : nullptr; +} + +QVariant ConnectionModel::data(const QModelIndex &index, int role) const +{ + enum { deprecatedMember = 0 }; + + const SignalSlotConnection *con = connectionAt(index); + if (con == nullptr) + return QVariant(); + + // Mark deprecated slots red/italic. Not currently in use (historically for Qt 3 slots in Qt 4), + // but may be used again in the future. + switch (role) { + case Qt::ForegroundRole: + return deprecatedMember ? QColor(Qt::red) : QVariant(); + case Qt::FontRole: + if (deprecatedMember) { + QFont font = QApplication::font(); + font.setItalic(true); + return font; + } + return QVariant(); + case Qt::DisplayRole: + case Qt::EditRole: + return ConnectionModel::columnText(con, index.column()); + default: + break; + } + + return QVariant(); +} + +QString ConnectionModel::columnText(const SignalSlotConnection *con, int column) +{ + static const QString senderDefault = tr(""); + static const QString signalDefault = tr(""); + static const QString receiverDefault = tr(""); + static const QString slotDefault = tr(""); + + switch (column) { + case 0: { + const QString sender = con->sender(); + return sender.isEmpty() ? senderDefault : sender; + } + case 1: { + const QString signalName = con->signal(); + return signalName.isEmpty() ? signalDefault : signalName; + } + case 2: { + const QString receiver = con->receiver(); + return receiver.isEmpty() ? receiverDefault : receiver; + } + case 3: { + const QString slotName = con->slot(); + return slotName.isEmpty() ? slotDefault : slotName; + } + } + return QString(); +} + +bool ConnectionModel::setData(const QModelIndex &index, const QVariant &data, int) +{ + if (!index.isValid() || !m_editor) + return false; + if (data.metaType().id() != QMetaType::QString) + return false; + + SignalSlotConnection *con = static_cast(m_editor->connection(index.row())); + QDesignerFormWindowInterface *form = m_editor->formWindow(); + + QString s = data.toString(); + switch (index.column()) { + case 0: + if (!s.isEmpty() && !objectNameList(form).contains(s)) + s.clear(); + m_editor->setSource(con, s); + break; + case 1: + if (!memberFunctionListContains(form->core(), con->object(CETypes::EndPoint::Source), SignalMember, s)) + s.clear(); + m_editor->setSignal(con, s); + break; + case 2: + if (!s.isEmpty() && !objectNameList(form).contains(s)) + s.clear(); + m_editor->setTarget(con, s); + break; + case 3: + if (!memberFunctionListContains(form->core(), con->object(CETypes::EndPoint::Target), SlotMember, s)) + s.clear(); + m_editor->setSlot(con, s); + break; + } + + return true; +} + +void ConnectionModel::connectionAdded(Connection*) +{ + endInsertRows(); +} + +void ConnectionModel::connectionRemoved(int) +{ + endRemoveRows(); +} + +void ConnectionModel::aboutToRemoveConnection(Connection *con) +{ + Q_ASSERT(m_editor); + int idx = m_editor->indexOfConnection(con); + beginRemoveRows(QModelIndex(), idx, idx); +} + +void ConnectionModel::aboutToAddConnection(int idx) +{ + Q_ASSERT(m_editor); + beginInsertRows(QModelIndex(), idx, idx); +} + +Qt::ItemFlags ConnectionModel::flags(const QModelIndex&) const +{ + return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; +} + +void ConnectionModel::connectionChanged(Connection *con) +{ + Q_ASSERT(m_editor); + const int idx = m_editor->indexOfConnection(con); + SignalSlotConnection *changedCon = static_cast(m_editor->connection(idx)); + SignalSlotConnection *c = nullptr; + for (int i=0; iconnectionCount(); ++i) { + if (i == idx) + continue; + c = static_cast(m_editor->connection(i)); + if (c->sender() == changedCon->sender() && c->signal() == changedCon->signal() + && c->receiver() == changedCon->receiver() && c->slot() == changedCon->slot()) { + const QString message = tr("The connection already exists!
%1").arg(changedCon->toString()); + m_editor->formWindow()->core()->dialogGui()->message(m_editor->parentWidget(), QDesignerDialogGuiInterface::SignalSlotEditorMessage, + QMessageBox::Warning, tr("Signal and Slot Editor"), message, QMessageBox::Ok); + break; + } + } + emit dataChanged(createIndex(idx, 0), createIndex(idx, 3)); +} + +void ConnectionModel::updateAll() +{ + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} +} + +namespace { +// ---------------------- InlineEditorModel + +class InlineEditorModel : public QStandardItemModel +{ + Q_OBJECT +public: + enum { TitleItem = 1 }; + + InlineEditorModel(int rows, int cols, QObject *parent = nullptr); + + void addTitle(const QString &title); + void addTextList(const QMap &text_list); + void addText(const QString &text); + bool isTitle(int idx) const; + + int findText(const QString &text) const; + + Qt::ItemFlags flags(const QModelIndex &index) const override; +}; + +InlineEditorModel::InlineEditorModel(int rows, int cols, QObject *parent) + : QStandardItemModel(rows, cols, parent) +{ +} + +void InlineEditorModel::addTitle(const QString &title) +{ + const int cnt = rowCount(); + insertRows(cnt, 1); + QModelIndex cat_idx = index(cnt, 0); + setData(cat_idx, QString(title + u':'), Qt::DisplayRole); + setData(cat_idx, TitleItem, Qt::UserRole); + QFont font = QApplication::font(); + font.setBold(true); + setData(cat_idx, font, Qt::FontRole); +} + +bool InlineEditorModel::isTitle(int idx) const +{ + if (idx == -1) + return false; + + return data(index(idx, 0), Qt::UserRole).toInt() == TitleItem; +} + +void InlineEditorModel::addText(const QString &text) +{ + const int cnt = rowCount(); + insertRows(cnt, 1); + setData(index(cnt, 0), text, Qt::DisplayRole); +} + +void InlineEditorModel::addTextList(const QMap &text_list) +{ + int cnt = rowCount(); + insertRows(cnt, text_list.size()); + QFont font = QApplication::font(); + font.setItalic(true); + QVariant fontVariant = QVariant::fromValue(font); + for (auto it = text_list.cbegin(), itEnd = text_list.cend(); it != itEnd; ++it) { + const QModelIndex text_idx = index(cnt++, 0); + setData(text_idx, it.key(), Qt::DisplayRole); + if (it.value()) { + setData(text_idx, fontVariant, Qt::FontRole); + setData(text_idx, QColor(Qt::red), Qt::ForegroundRole); + } + } +} + +Qt::ItemFlags InlineEditorModel::flags(const QModelIndex &index) const +{ + return isTitle(index.row()) + ? Qt::ItemFlags(Qt::ItemIsEnabled) + : Qt::ItemFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); +} + +int InlineEditorModel::findText(const QString &text) const +{ + const int cnt = rowCount(); + for (int i = 0; i < cnt; ++i) { + const QModelIndex idx = index(i, 0); + if (data(idx, Qt::UserRole).toInt() == TitleItem) + continue; + if (data(idx, Qt::DisplayRole).toString() == text) + return i; + } + return -1; +} + +// ------------ InlineEditor +class InlineEditor : public QComboBox +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText USER true) +public: + InlineEditor(QWidget *parent = nullptr); + + QString text() const; + void setText(const QString &text); + + void addTitle(const QString &title); + void addText(const QString &text); + void addTextList(const QMap &text_list); + +private slots: + void checkSelection(int idx); + +private: + InlineEditorModel *m_model; + int m_idx = -1; +}; + +InlineEditor::InlineEditor(QWidget *parent) : + QComboBox(parent) +{ + setModel(m_model = new InlineEditorModel(0, 4, this)); + setFrame(false); + m_idx = -1; + connect(this, &QComboBox::activated, + this, &InlineEditor::checkSelection); +} + +void InlineEditor::checkSelection(int idx) +{ + if (idx == m_idx) + return; + + if (m_model->isTitle(idx)) + setCurrentIndex(m_idx); + else + m_idx = idx; +} + +void InlineEditor::addTitle(const QString &title) +{ + m_model->addTitle(title); +} + +void InlineEditor::addTextList(const QMap &text_list) +{ + m_model->addTextList(text_list); +} + +void InlineEditor::addText(const QString &text) +{ + m_model->addText(text); +} + +QString InlineEditor::text() const +{ + return currentText(); +} + +void InlineEditor::setText(const QString &text) +{ + m_idx = m_model->findText(text); + if (m_idx == -1) + m_idx = 0; + setCurrentIndex(m_idx); +} + +// ------------------ ConnectionDelegate + +class ConnectionDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + ConnectionDelegate(QWidget *parent = nullptr); + + void setForm(QDesignerFormWindowInterface *form); + + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + +private slots: + void emitCommitData(); + +private: + QDesignerFormWindowInterface *m_form; +}; + +ConnectionDelegate::ConnectionDelegate(QWidget *parent) + : QStyledItemDelegate(parent) +{ + m_form = nullptr; + + static QItemEditorFactory *factory = nullptr; + if (factory == nullptr) { + factory = new QItemEditorFactory; + QItemEditorCreatorBase *creator + = new QItemEditorCreator("text"); + factory->registerEditor(QMetaType::QString, creator); + } + + setItemEditorFactory(factory); +} + +void ConnectionDelegate::setForm(QDesignerFormWindowInterface *form) +{ + m_form = form; +} + +QWidget *ConnectionDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (m_form == nullptr) + return nullptr; + + QWidget *w = QStyledItemDelegate::createEditor(parent, option, index); + InlineEditor *inline_editor = qobject_cast(w); + Q_ASSERT(inline_editor != nullptr); + const QAbstractItemModel *model = index.model(); + + const QModelIndex obj_name_idx = model->index(index.row(), index.column() <= 1 ? 0 : 2); + const QString obj_name = model->data(obj_name_idx, Qt::DisplayRole).toString(); + + switch (index.column()) { + case 0: + case 2: { // object names + const QStringList &obj_name_list = objectNameList(m_form); + QMap markedNameList; + markedNameList.insert(tr(""), false); + inline_editor->addTextList(markedNameList); + markedNameList.clear(); + for (const QString &name : obj_name_list) + markedNameList.insert(name, false); + inline_editor->addTextList(markedNameList); + } + break; + case 1: + case 3: { // signals, slots + const qdesigner_internal::MemberType type = index.column() == 1 ? qdesigner_internal::SignalMember : qdesigner_internal::SlotMember; + const QModelIndex peer_index = model->index(index.row(), type == qdesigner_internal::SignalMember ? 3 : 1); + const QString peer = model->data(peer_index, Qt::DisplayRole).toString(); + + const qdesigner_internal::ClassesMemberFunctions class_list = qdesigner_internal::reverseClassesMemberFunctions(obj_name, type, peer, m_form); + + inline_editor->addText(type == qdesigner_internal::SignalMember ? tr("") : tr("")); + for (const qdesigner_internal::ClassMemberFunctions &classInfo : class_list) { + if (classInfo.m_className.isEmpty() || classInfo.m_memberList.isEmpty()) + continue; + // Mark deprecated members by passing bool=true. + QMap markedMemberList; + for (const QString &member : std::as_const(classInfo.m_memberList)) + markedMemberList.insert(member, false); + inline_editor->addTitle(classInfo.m_className); + inline_editor->addTextList(markedMemberList); + } + } + break; + default: + break; + } + + connect(inline_editor, &QComboBox::activated, + this, &ConnectionDelegate::emitCommitData); + + return inline_editor; +} + +void ConnectionDelegate::emitCommitData() +{ + InlineEditor *editor = qobject_cast(sender()); + emit commitData(editor); +} + +} + +namespace qdesigner_internal { + +/******************************************************************************* +** SignalSlotEditorWindow +*/ + +SignalSlotEditorWindow::SignalSlotEditorWindow(QDesignerFormEditorInterface *core, + QWidget *parent) : + QWidget(parent), + m_view(new QTreeView), + m_editor(nullptr), + m_add_button(new QToolButton), + m_remove_button(new QToolButton), + m_core(core), + m_model(new ConnectionModel(this)), + m_proxy_model(new QSortFilterProxyModel(this)), + m_handling_selection_change(false) +{ + m_proxy_model->setSourceModel(m_model); + m_view->setModel(m_proxy_model); + m_view->setSortingEnabled(true); + m_view->setItemDelegate(new ConnectionDelegate(this)); + m_view->setEditTriggers(QAbstractItemView::DoubleClicked + | QAbstractItemView::EditKeyPressed); + m_view->setRootIsDecorated(false); + m_view->setTextElideMode (Qt::ElideMiddle); + connect(m_view->selectionModel(), &QItemSelectionModel::currentChanged, + this, &SignalSlotEditorWindow::updateUi); + connect(m_view->header(), &QHeaderView::sectionDoubleClicked, + m_view, &QTreeView::resizeColumnToContents); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + + QToolBar *toolBar = new QToolBar; + toolBar->setIconSize(QSize(22, 22)); + m_add_button->setIcon(createIconSet("plus.png"_L1)); + connect(m_add_button, &QAbstractButton::clicked, this, &SignalSlotEditorWindow::addConnection); + toolBar->addWidget(m_add_button); + + m_remove_button->setIcon(createIconSet("minus.png"_L1)); + connect(m_remove_button, &QAbstractButton::clicked, this, &SignalSlotEditorWindow::removeConnection); + toolBar->addWidget(m_remove_button); + + layout->addWidget(toolBar); + layout->addWidget(m_view); + + connect(core->formWindowManager(), + &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &SignalSlotEditorWindow::setActiveFormWindow); + + updateUi(); +} + +void SignalSlotEditorWindow::setActiveFormWindow(QDesignerFormWindowInterface *form) +{ + QDesignerIntegrationInterface *integration = m_core->integration(); + + if (!m_editor.isNull()) { + disconnect(m_view->selectionModel(), + &QItemSelectionModel::currentChanged, + this, &SignalSlotEditorWindow::updateEditorSelection); + disconnect(m_editor.data(), &SignalSlotEditor::connectionSelected, + this, &SignalSlotEditorWindow::updateDialogSelection); + disconnect(m_editor.data(), &SignalSlotEditor::connectionAdded, + this, &SignalSlotEditorWindow::resizeColumns); + if (integration) { + disconnect(integration, &QDesignerIntegrationInterface::objectNameChanged, + this, &SignalSlotEditorWindow::objectNameChanged); + } + } + + m_editor = form ? form->findChild() : nullptr; + m_model->setEditor(m_editor); + if (!m_editor.isNull()) { + ConnectionDelegate *delegate + = qobject_cast(m_view->itemDelegate()); + if (delegate != nullptr) + delegate->setForm(form); + + connect(m_view->selectionModel(), + &QItemSelectionModel::currentChanged, + this, &SignalSlotEditorWindow::updateEditorSelection); + connect(m_editor.data(), &SignalSlotEditor::connectionSelected, + this, &SignalSlotEditorWindow::updateDialogSelection); + connect(m_editor.data(), &SignalSlotEditor::connectionAdded, + this, &SignalSlotEditorWindow::resizeColumns); + if (integration) { + connect(integration, &QDesignerIntegrationInterface::objectNameChanged, + this, &SignalSlotEditorWindow::objectNameChanged); + } + } + + resizeColumns(); + updateUi(); +} + +void SignalSlotEditorWindow::updateDialogSelection(Connection *con) +{ + if (m_handling_selection_change || m_editor == nullptr) + return; + + QModelIndex index = m_proxy_model->mapFromSource(m_model->connectionToIndex(con)); + if (!index.isValid() || index == m_view->currentIndex()) + return; + m_handling_selection_change = true; + m_view->scrollTo(index, QTreeView::EnsureVisible); + m_view->setCurrentIndex(index); + m_handling_selection_change = false; + + updateUi(); +} + +void SignalSlotEditorWindow::updateEditorSelection(const QModelIndex &index) +{ + if (m_handling_selection_change || m_editor == nullptr) + return; + + if (m_editor == nullptr) + return; + + Connection *con = m_model->indexToConnection(m_proxy_model->mapToSource(index)); + if (m_editor->selected(con)) + return; + m_handling_selection_change = true; + m_editor->selectNone(); + m_editor->setSelected(con, true); + m_handling_selection_change = false; + + updateUi(); +} + +void SignalSlotEditorWindow::objectNameChanged(QDesignerFormWindowInterface *, QObject *, const QString &, const QString &) +{ + if (m_editor) + m_model->updateAll(); +} + +void SignalSlotEditorWindow::addConnection() +{ + if (m_editor.isNull()) + return; + + m_editor->addEmptyConnection(); + updateUi(); +} + +void SignalSlotEditorWindow::removeConnection() +{ + if (m_editor.isNull()) + return; + + m_editor->deleteSelected(); + updateUi(); +} + +void SignalSlotEditorWindow::updateUi() +{ + m_add_button->setEnabled(!m_editor.isNull()); + m_remove_button->setEnabled(!m_editor.isNull() && m_view->currentIndex().isValid()); +} + +void SignalSlotEditorWindow::resizeColumns() +{ + for (int c = 0, count = m_model->columnCount(); c < count; ++c) + m_view->resizeColumnToContents(c); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "signalsloteditorwindow.moc" diff --git a/src/designer/src/components/signalsloteditor/signalsloteditorwindow.h b/src/designer/src/components/signalsloteditor/signalsloteditorwindow.h new file mode 100644 index 0000000..622e18b --- /dev/null +++ b/src/designer/src/components/signalsloteditor/signalsloteditorwindow.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITORWINDOW_H +#define SIGNALSLOTEDITORWINDOW_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QModelIndex; +class QSortFilterProxyModel; +class QTreeView; +class QToolButton; + +namespace qdesigner_internal { + +class SignalSlotEditor; +class ConnectionModel; +class Connection; + +class SignalSlotEditorWindow : public QWidget +{ + Q_OBJECT +public: + explicit SignalSlotEditorWindow(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + +public slots: + void setActiveFormWindow(QDesignerFormWindowInterface *form); + +private slots: + void updateDialogSelection(qdesigner_internal::Connection *con); + void updateEditorSelection(const QModelIndex &index); + + void objectNameChanged(QDesignerFormWindowInterface *formWindow, QObject *object, const QString &newName, const QString &oldName); + + void addConnection(); + void removeConnection(); + void updateUi(); + void resizeColumns(); + +private: + QTreeView *m_view; + QPointer m_editor; + QToolButton *m_add_button, *m_remove_button; + QDesignerFormEditorInterface *m_core; + ConnectionModel *m_model; + QSortFilterProxyModel *m_proxy_model; + bool m_handling_selection_change; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITORWINDOW_H diff --git a/src/designer/src/components/tabordereditor/tabordereditor.cpp b/src/designer/src/components/tabordereditor/tabordereditor.cpp new file mode 100644 index 0000000..76fe3b1 --- /dev/null +++ b/src/designer/src/components/tabordereditor/tabordereditor.cpp @@ -0,0 +1,398 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tabordereditor.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QWidgetList) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { VBOX_MARGIN = 1, HBOX_MARGIN = 4, BG_ALPHA = 32 }; +} + +static QRect fixRect(QRect r) +{ + return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1); +} + +namespace qdesigner_internal { + +TabOrderEditor::TabOrderEditor(QDesignerFormWindowInterface *form, QWidget *parent) : + QWidget(parent), + m_form_window(form), + m_bg_widget(nullptr), + m_undo_stack(form->commandHistory()), + m_font_metrics(font()), + m_current_index(0), + m_beginning(true) +{ + connect(form, &QDesignerFormWindowInterface::widgetRemoved, this, &TabOrderEditor::widgetRemoved); + + QFont tabFont = font(); + tabFont.setPointSize(tabFont.pointSize()*2); + tabFont.setBold(true); + setFont(tabFont); + m_font_metrics = QFontMetrics(tabFont); + setAttribute(Qt::WA_MouseTracking, true); +} + +QDesignerFormWindowInterface *TabOrderEditor::formWindow() const +{ + return m_form_window; +} + +void TabOrderEditor::setBackground(QWidget *background) +{ + if (background == m_bg_widget) { + return; + } + + m_bg_widget = background; + updateBackground(); +} + +void TabOrderEditor::updateBackground() +{ + if (m_bg_widget == nullptr) { + // nothing to do + return; + } + + initTabOrder(); + update(); +} + +void TabOrderEditor::widgetRemoved(QWidget*) +{ + initTabOrder(); +} + +void TabOrderEditor::showEvent(QShowEvent *e) +{ + QWidget::showEvent(e); + updateBackground(); +} + +QRect TabOrderEditor::indicatorRect(int index) const +{ + if (index < 0 || index >= m_tab_order_list.size()) + return QRect(); + + const QWidget *w = m_tab_order_list.at(index); + const QString text = QString::number(index + 1); + + const QPoint tl = mapFromGlobal(w->mapToGlobal(w->rect().topLeft())); + const QSize size = m_font_metrics.size(Qt::TextSingleLine, text); + QRect r(tl - QPoint(size.width(), size.height())/2, size); + r = QRect(r.left() - HBOX_MARGIN, r.top() - VBOX_MARGIN, + r.width() + HBOX_MARGIN*2, r.height() + VBOX_MARGIN*2); + + return r; +} + +static bool isWidgetVisible(QWidget *widget) +{ + while (widget && widget->parentWidget()) { + if (!widget->isVisibleTo(widget->parentWidget())) + return false; + + widget = widget->parentWidget(); + } + + return true; +} + +void TabOrderEditor::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + p.setClipRegion(e->region()); + + int cur = m_current_index - 1; + if (!m_beginning && cur < 0) + cur = m_tab_order_list.size() - 1; + + for (qsizetype i = 0; i < m_tab_order_list.size(); ++i) { + QWidget *widget = m_tab_order_list.at(i); + if (!isWidgetVisible(widget)) + continue; + + const QRect r = indicatorRect(i); + + QColor c = Qt::darkGreen; + if (i == cur) + c = Qt::red; + else if (i > cur) + c = Qt::blue; + p.setPen(c); + c.setAlpha(BG_ALPHA); + p.setBrush(c); + p.drawRect(fixRect(r)); + + p.setPen(Qt::white); + p.drawText(r, QString::number(i + 1), QTextOption(Qt::AlignCenter)); + } +} + +bool TabOrderEditor::skipWidget(QWidget *w) const +{ + if (qobject_cast(w) + || w == formWindow()->mainContainer() + || w->isHidden()) + return true; + + if (!formWindow()->isManaged(w)) { + return true; + } + + QExtensionManager *ext = formWindow()->core()->extensionManager(); + if (const QDesignerPropertySheetExtension *sheet = qt_extension(ext, w)) { + const int index = sheet->indexOf(u"focusPolicy"_s); + if (index != -1) { + bool ok = false; + Qt::FocusPolicy q = (Qt::FocusPolicy) Utils::valueOf(sheet->property(index), &ok); + return !ok || !(q & Qt::TabFocus); + } + } + + return true; +} + +void TabOrderEditor::initTabOrder() +{ + m_tab_order_list.clear(); + + QDesignerFormEditorInterface *core = formWindow()->core(); + + if (const QDesignerMetaDataBaseItemInterface *item = core->metaDataBase()->item(formWindow())) { + m_tab_order_list = item->tabOrder(); + } + + // Remove any widgets that have been removed form the form + for (qsizetype i = 0; i < m_tab_order_list.size(); ) { + QWidget *w = m_tab_order_list.at(i); + if (!formWindow()->mainContainer()->isAncestorOf(w) || skipWidget(w)) + m_tab_order_list.removeAt(i); + else + ++i; + } + + // Append any widgets that are in the form but are not in the tab order + QWidgetList childQueue; + childQueue.append(formWindow()->mainContainer()); + while (!childQueue.isEmpty()) { + QWidget *child = childQueue.takeFirst(); + childQueue += qvariant_cast(child->property("_q_widgetOrder")); + + if (skipWidget(child)) + continue; + + if (!m_tab_order_list.contains(child)) + m_tab_order_list.append(child); + } + + // Just in case we missed some widgets + QDesignerFormWindowCursorInterface *cursor = formWindow()->cursor(); + for (int i = 0; i < cursor->widgetCount(); ++i) { + + QWidget *widget = cursor->widget(i); + if (skipWidget(widget)) + continue; + + if (!m_tab_order_list.contains(widget)) + m_tab_order_list.append(widget); + } + + m_indicator_region = QRegion(); + for (qsizetype i = 0; i < m_tab_order_list.size(); ++i) { + if (m_tab_order_list.at(i)->isVisible()) + m_indicator_region |= indicatorRect(i); + } + + if (m_current_index >= m_tab_order_list.size()) + m_current_index = m_tab_order_list.size() - 1; + if (m_current_index < 0) + m_current_index = 0; +} + +void TabOrderEditor::mouseMoveEvent(QMouseEvent *e) +{ + e->accept(); +#if QT_CONFIG(cursor) + if (m_indicator_region.contains(e->position().toPoint())) + setCursor(Qt::PointingHandCursor); + else + setCursor(QCursor()); +#endif +} + +int TabOrderEditor::widgetIndexAt(QPoint pos) const +{ + int target_index = -1; + for (qsizetype i = 0; i < m_tab_order_list.size(); ++i) { + if (!m_tab_order_list.at(i)->isVisible()) + continue; + if (indicatorRect(i).contains(pos)) { + target_index = i; + break; + } + } + + return target_index; +} + +void TabOrderEditor::mousePressEvent(QMouseEvent *e) +{ + e->accept(); + + if (!m_indicator_region.contains(e->position().toPoint())) { + if (QWidget *child = m_bg_widget->childAt(e->position().toPoint())) { + QDesignerFormEditorInterface *core = m_form_window->core(); + if (core->widgetFactory()->isPassiveInteractor(child)) { + + QMouseEvent event(QEvent::MouseButtonPress, + child->mapFromGlobal(e->globalPosition().toPoint()), + e->globalPosition().toPoint(), e->button(), e->buttons(), + e->modifiers()); + + qApp->sendEvent(child, &event); + + QMouseEvent event2(QEvent::MouseButtonRelease, + child->mapFromGlobal(e->globalPosition().toPoint()), + e->globalPosition().toPoint(), e->button(), e->buttons(), + e->modifiers()); + + qApp->sendEvent(child, &event2); + + updateBackground(); + } + } + return; + } + + if (e->button() != Qt::LeftButton) + return; + + const int target_index = widgetIndexAt(e->position().toPoint()); + if (target_index == -1) + return; + + m_beginning = false; + + if (e->modifiers() & Qt::ControlModifier) { + m_current_index = target_index + 1; + if (m_current_index >= m_tab_order_list.size()) + m_current_index = 0; + update(); + return; + } + + if (m_current_index == -1) + return; + + m_tab_order_list.swapItemsAt(target_index, m_current_index); + + ++m_current_index; + if (m_current_index == m_tab_order_list.size()) + m_current_index = 0; + + TabOrderCommand *cmd = new TabOrderCommand(formWindow()); + cmd->init(m_tab_order_list); + formWindow()->commandHistory()->push(cmd); +} + +void TabOrderEditor::contextMenuEvent(QContextMenuEvent *e) +{ + QMenu menu(this); + const int target_index = widgetIndexAt(e->pos()); + QAction *setIndex = menu.addAction(tr("Start from Here")); + setIndex->setEnabled(target_index >= 0); + + QAction *resetIndex = menu.addAction(tr("Restart")); + menu.addSeparator(); + QAction *showDialog = menu.addAction(tr("Tab Order List...")); + showDialog->setEnabled(m_tab_order_list.size() > 1); + + QAction *result = menu.exec(e->globalPos()); + if (result == resetIndex) { + m_current_index = 0; + m_beginning = true; + update(); + } else if (result == setIndex) { + m_beginning = false; + m_current_index = target_index + 1; + if (m_current_index >= m_tab_order_list.size()) + m_current_index = 0; + update(); + } else if (result == showDialog) { + showTabOrderDialog(); + } +} + +void TabOrderEditor::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) + return; + + const int target_index = widgetIndexAt(e->position().toPoint()); + if (target_index >= 0) + return; + + m_beginning = true; + m_current_index = 0; + update(); +} + +void TabOrderEditor::resizeEvent(QResizeEvent *e) +{ + updateBackground(); + QWidget::resizeEvent(e); +} + +void TabOrderEditor::showTabOrderDialog() +{ + if (m_tab_order_list.size() < 2) + return; + OrderDialog dlg(this); + dlg.setWindowTitle(tr("Tab Order List")); + dlg.setDescription(tr("Tab Order")); + dlg.setFormat(OrderDialog::TabOrderFormat); + dlg.setPageList(m_tab_order_list); + + if (dlg.exec() == QDialog::Rejected) + return; + + const QWidgetList newOrder = dlg.pageList(); + if (newOrder == m_tab_order_list) + return; + + m_tab_order_list = newOrder; + TabOrderCommand *cmd = new TabOrderCommand(formWindow()); + cmd->init(m_tab_order_list); + formWindow()->commandHistory()->push(cmd); + update(); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/tabordereditor/tabordereditor.h b/src/designer/src/components/tabordereditor/tabordereditor.h new file mode 100644 index 0000000..d3406e7 --- /dev/null +++ b/src/designer/src/components/tabordereditor/tabordereditor.h @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABORDEREDITOR_H +#define TABORDEREDITOR_H + +#include "tabordereditor_global.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QUndoStack; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class QT_TABORDEREDITOR_EXPORT TabOrderEditor : public QWidget +{ + Q_OBJECT + +public: + TabOrderEditor(QDesignerFormWindowInterface *form, QWidget *parent); + + QDesignerFormWindowInterface *formWindow() const; + +public slots: + void setBackground(QWidget *background); + void updateBackground(); + void widgetRemoved(QWidget*); + void initTabOrder(); + +private slots: + void showTabOrderDialog(); + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void showEvent(QShowEvent *e) override; + +private: + QRect indicatorRect(int index) const; + int widgetIndexAt(QPoint pos) const; + bool skipWidget(QWidget *w) const; + + QPointer m_form_window; + + QWidgetList m_tab_order_list; + + QWidget *m_bg_widget; + QUndoStack *m_undo_stack; + QRegion m_indicator_region; + + QFontMetrics m_font_metrics; + int m_current_index; + bool m_beginning; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/components/tabordereditor/tabordereditor.json b/src/designer/src/components/tabordereditor/tabordereditor.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/designer/src/components/tabordereditor/tabordereditor.json @@ -0,0 +1 @@ +{} diff --git a/src/designer/src/components/tabordereditor/tabordereditor_global.h b/src/designer/src/components/tabordereditor/tabordereditor_global.h new file mode 100644 index 0000000..68d5c86 --- /dev/null +++ b/src/designer/src/components/tabordereditor/tabordereditor_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABORDEREDITOR_GLOBAL_H +#define TABORDEREDITOR_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_TABORDEREDITOR_LIBRARY +# define QT_TABORDEREDITOR_EXPORT +#else +# define QT_TABORDEREDITOR_EXPORT +#endif +#else +#define QT_TABORDEREDITOR_EXPORT +#endif + +#endif // TABORDEREDITOR_GLOBAL_H diff --git a/src/designer/src/components/tabordereditor/tabordereditor_plugin.cpp b/src/designer/src/components/tabordereditor/tabordereditor_plugin.cpp new file mode 100644 index 0000000..15f80ae --- /dev/null +++ b/src/designer/src/components/tabordereditor/tabordereditor_plugin.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include "tabordereditor_plugin.h" +#include "tabordereditor_tool.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TabOrderEditorPlugin::TabOrderEditorPlugin() = default; + +TabOrderEditorPlugin::~TabOrderEditorPlugin() = default; + +bool TabOrderEditorPlugin::isInitialized() const +{ + return m_initialized; +} + +void TabOrderEditorPlugin::initialize(QDesignerFormEditorInterface *core) +{ + Q_ASSERT(!isInitialized()); + + m_action = new QAction(tr("Edit Tab Order"), this); + m_action->setObjectName(u"_qt_edit_tab_order_action"_s); + m_action->setIcon(createIconSet("tabordertool.png"_L1)); + m_action->setEnabled(false); + + setParent(core); + m_core = core; + m_initialized = true; + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowAdded, + this, &TabOrderEditorPlugin::addFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowRemoved, + this, &TabOrderEditorPlugin::removeFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &TabOrderEditorPlugin::activeFormWindowChanged); +} + +void TabOrderEditorPlugin::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) +{ + m_action->setEnabled(formWindow != nullptr); +} + +QDesignerFormEditorInterface *TabOrderEditorPlugin::core() const +{ + return m_core; +} + +void TabOrderEditorPlugin::addFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == false); + + TabOrderEditorTool *tool = new TabOrderEditorTool(formWindow, this); + m_tools[formWindow] = tool; + connect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + formWindow->registerTool(tool); +} + +void TabOrderEditorPlugin::removeFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == true); + + TabOrderEditorTool *tool = m_tools.value(formWindow); + m_tools.remove(formWindow); + disconnect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + // ### FIXME disable the tool + + delete tool; +} + +QAction *TabOrderEditorPlugin::action() const +{ + return m_action; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "moc_tabordereditor_plugin.cpp" diff --git a/src/designer/src/components/tabordereditor/tabordereditor_plugin.h b/src/designer/src/components/tabordereditor/tabordereditor_plugin.h new file mode 100644 index 0000000..9916809 --- /dev/null +++ b/src/designer/src/components/tabordereditor/tabordereditor_plugin.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABORDEREDITOR_PLUGIN_H +#define TABORDEREDITOR_PLUGIN_H + +#include "tabordereditor_global.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class TabOrderEditorTool; + +class QT_TABORDEREDITOR_EXPORT TabOrderEditorPlugin: public QObject, public QDesignerFormEditorPluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerFormEditorPluginInterface" FILE "tabordereditor.json") + Q_INTERFACES(QDesignerFormEditorPluginInterface) +public: + TabOrderEditorPlugin(); + ~TabOrderEditorPlugin() override; + + bool isInitialized() const override; + void initialize(QDesignerFormEditorInterface *core) override; + QAction *action() const override; + + QDesignerFormEditorInterface *core() const override; + +public slots: + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + +private slots: + void addFormWindow(QDesignerFormWindowInterface *formWindow); + void removeFormWindow(QDesignerFormWindowInterface *formWindow); + +private: + QPointer m_core; + QHash m_tools; + bool m_initialized = false; + QAction *m_action = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TABORDEREDITOR_PLUGIN_H diff --git a/src/designer/src/components/tabordereditor/tabordereditor_tool.cpp b/src/designer/src/components/tabordereditor/tabordereditor_tool.cpp new file mode 100644 index 0000000..46270b0 --- /dev/null +++ b/src/designer/src/components/tabordereditor/tabordereditor_tool.cpp @@ -0,0 +1,75 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tabordereditor_tool.h" +#include "tabordereditor.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +TabOrderEditorTool::TabOrderEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent) + : QDesignerFormWindowToolInterface(parent), + m_formWindow(formWindow), + m_action(new QAction(tr("Edit Tab Order"), this)) +{ +} + +TabOrderEditorTool::~TabOrderEditorTool() = default; + +QDesignerFormEditorInterface *TabOrderEditorTool::core() const +{ + return m_formWindow->core(); +} + +QDesignerFormWindowInterface *TabOrderEditorTool::formWindow() const +{ + return m_formWindow; +} + +bool TabOrderEditorTool::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + Q_UNUSED(widget); + Q_UNUSED(managedWidget); + + return event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease; +} + +QWidget *TabOrderEditorTool::editor() const +{ + if (!m_editor) { + Q_ASSERT(formWindow() != nullptr); + m_editor = new TabOrderEditor(formWindow(), nullptr); + connect(formWindow(), &QDesignerFormWindowInterface::mainContainerChanged, + m_editor.data(), &TabOrderEditor::setBackground); + } + + return m_editor; +} + +void TabOrderEditorTool::activated() +{ + connect(formWindow(), &QDesignerFormWindowInterface::changed, + m_editor.data(), &TabOrderEditor::updateBackground); +} + +void TabOrderEditorTool::deactivated() +{ + disconnect(formWindow(), &QDesignerFormWindowInterface::changed, + m_editor.data(), &TabOrderEditor::updateBackground); +} + +QAction *TabOrderEditorTool::action() const +{ + return m_action; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/tabordereditor/tabordereditor_tool.h b/src/designer/src/components/tabordereditor/tabordereditor_tool.h new file mode 100644 index 0000000..bbd4f5c --- /dev/null +++ b/src/designer/src/components/tabordereditor/tabordereditor_tool.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABORDEREDITOR_TOOL_H +#define TABORDEREDITOR_TOOL_H + +#include "tabordereditor_global.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class TabOrderEditor; + +class QT_TABORDEREDITOR_EXPORT TabOrderEditorTool: public QDesignerFormWindowToolInterface +{ + Q_OBJECT +public: + explicit TabOrderEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent = nullptr); + ~TabOrderEditorTool() override; + + QDesignerFormEditorInterface *core() const override; + QDesignerFormWindowInterface *formWindow() const override; + + QWidget *editor() const override; + QAction *action() const override; + + void activated() override; + void deactivated() override; + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) override; + +private: + QDesignerFormWindowInterface *m_formWindow; + mutable QPointer m_editor; + QAction *m_action; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TABORDEREDITOR_TOOL_H diff --git a/src/designer/src/components/taskmenu/button_taskmenu.cpp b/src/designer/src/components/taskmenu/button_taskmenu.cpp new file mode 100644 index 0000000..7edf6c0 --- /dev/null +++ b/src/designer/src/components/taskmenu/button_taskmenu.cpp @@ -0,0 +1,665 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "button_taskmenu.h" +#include "inplace_editor.h" +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +Q_DECLARE_METATYPE(QButtonGroup*) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +enum { debugButtonMenu = 0 }; + +using ButtonList = QList; +using ButtonGroupList = QList; + +// ButtonGroupCommand: Base for commands handling button groups and button lists +// addButtonsToGroup() and removeButtonsFromGroup() are low-level helpers for +// adding/removing members to/from existing groups. +// +// createButtonGroup()/breakButtonGroup() create and remove the groups from scratch. +// When using them in a command, the command must be executed within +// a macro since it makes the form emit objectRemoved() which might cause other components +// to add commands (for example, removal of signals and slots) +class ButtonGroupCommand : public QDesignerFormWindowCommand { + +protected: + ButtonGroupCommand(const QString &description, QDesignerFormWindowInterface *formWindow); + + void initialize(const ButtonList &bl, QButtonGroup *buttonGroup); + + // Helper: Add the buttons to the group + void addButtonsToGroup(); + // Helper; Remove the buttons + void removeButtonsFromGroup(); + + // Create the button group in Designer + void createButtonGroup(); + // Remove the button group from Designer + void breakButtonGroup(); + +public: + static QString nameList(const ButtonList& bl); + static ButtonGroupList managedButtonGroups(const QDesignerFormWindowInterface *formWindow); + +private: + ButtonList m_buttonList; + QButtonGroup *m_buttonGroup; +}; + +ButtonGroupCommand::ButtonGroupCommand(const QString &description, QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(description, formWindow), + m_buttonGroup(nullptr) +{ +} + +void ButtonGroupCommand::initialize(const ButtonList &bl, QButtonGroup *buttonGroup) +{ + m_buttonList = bl; + m_buttonGroup = buttonGroup; +} + +void ButtonGroupCommand::addButtonsToGroup() +{ + if (debugButtonMenu) + qDebug() << "Adding " << m_buttonList << " to " << m_buttonGroup; + for (auto *b : std::as_const(m_buttonList)) + m_buttonGroup->addButton(b); +} + +void ButtonGroupCommand::removeButtonsFromGroup() +{ + if (debugButtonMenu) + qDebug() << "Removing " << m_buttonList << " from " << m_buttonGroup; + for (auto *b : std::as_const(m_buttonList)) + m_buttonGroup->removeButton(b); +} + +void ButtonGroupCommand::createButtonGroup() +{ + if (debugButtonMenu) + qDebug() << "Creating " << m_buttonGroup << " from " << m_buttonList; + + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + core->metaDataBase()->add(m_buttonGroup); + addButtonsToGroup(); + // Make button group visible + core->objectInspector()->setFormWindow(fw); +} + +void ButtonGroupCommand::breakButtonGroup() +{ + if (debugButtonMenu) + qDebug() << "Removing " << m_buttonGroup << " consisting of " << m_buttonList; + + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + // Button group was selected, that is, break was invoked via its context menu. Remove it from property editor, select the buttons + if (core->propertyEditor()->object() == m_buttonGroup) { + fw->clearSelection(false); + for (auto *b : std::as_const(m_buttonList)) + fw->selectWidget(b, true); + } + // Now remove and refresh object inspector + removeButtonsFromGroup(); + // Notify components (for example, signal slot editor) + if (qdesigner_internal::FormWindowBase *fwb = qobject_cast(fw)) + fwb->emitObjectRemoved(m_buttonGroup); + core->metaDataBase()->remove(m_buttonGroup); + core->objectInspector()->setFormWindow(fw); +} + +QString ButtonGroupCommand::nameList(const ButtonList& bl) +{ + QString rc; + const QChar quote = QLatin1Char('\''); + const auto separator = ", "_L1; + for (qsizetype i = 0, size = bl.size(); i < size; ++i) { + if (i) + rc += separator; + rc += quote; + rc += bl.at(i)->objectName(); + rc += quote; + } + return rc; + +} + +ButtonGroupList ButtonGroupCommand::managedButtonGroups(const QDesignerFormWindowInterface *formWindow) +{ + const QDesignerMetaDataBaseInterface *mdb = formWindow->core()->metaDataBase(); + ButtonGroupList bl; + // Check 1st order children for managed button groups + for (auto *o : formWindow->mainContainer()->children()) { + if (!o->isWidgetType()) { + if (QButtonGroup *bg = qobject_cast(o)) { + if (mdb->item(bg)) + bl.push_back(bg); + } + } + } + return bl; +} + +// --------------- CreateButtonGroupCommand +// This command might be executed in a macro with a remove +// command to move buttons from one group to a new one. +class CreateButtonGroupCommand : public ButtonGroupCommand { +public: + CreateButtonGroupCommand(QDesignerFormWindowInterface *formWindow); + bool init(const ButtonList &bl); + + void undo() override { breakButtonGroup(); } + void redo() override { createButtonGroup(); } +}; + +CreateButtonGroupCommand::CreateButtonGroupCommand(QDesignerFormWindowInterface *formWindow) : + ButtonGroupCommand(QApplication::translate("Command", "Create button group"), formWindow) +{ +} + +bool CreateButtonGroupCommand::init(const ButtonList &bl) +{ + if (bl.isEmpty()) + return false; + QDesignerFormWindowInterface *fw = formWindow(); + QButtonGroup *buttonGroup = new QButtonGroup(fw->mainContainer()); + buttonGroup->setObjectName(u"buttonGroup"_s); + fw->ensureUniqueObjectName(buttonGroup); + initialize(bl, buttonGroup); + return true; +} + +// --------------- BreakButtonGroupCommand +class BreakButtonGroupCommand : public ButtonGroupCommand { +public: + BreakButtonGroupCommand(QDesignerFormWindowInterface *formWindow); + bool init(QButtonGroup *group); + + void undo() override { createButtonGroup(); } + void redo() override { breakButtonGroup(); } +}; + +BreakButtonGroupCommand::BreakButtonGroupCommand(QDesignerFormWindowInterface *formWindow) : + ButtonGroupCommand(QApplication::translate("Command", "Break button group"), formWindow) +{ +} + +bool BreakButtonGroupCommand::init(QButtonGroup *group) +{ + if (!group) + return false; + initialize(group->buttons(), group); + setText(QApplication::translate("Command", "Break button group '%1'").arg(group->objectName())); + return true; +} + +// --------------- AddButtonsToGroupCommand +// This command might be executed in a macro with a remove +// command to move buttons from one group to a new one. +class AddButtonsToGroupCommand : public ButtonGroupCommand { +public: + AddButtonsToGroupCommand(QDesignerFormWindowInterface *formWindow); + void init(const ButtonList &bl, QButtonGroup *group); + + void undo() override { removeButtonsFromGroup(); } + void redo() override { addButtonsToGroup(); } +}; + +AddButtonsToGroupCommand::AddButtonsToGroupCommand(QDesignerFormWindowInterface *formWindow) : + ButtonGroupCommand(QApplication::translate("Command", "Add buttons to group"), formWindow) +{ +} + +void AddButtonsToGroupCommand::init(const ButtonList &bl, QButtonGroup *group) +{ + initialize(bl, group); + //: Command description for adding buttons to a QButtonGroup + setText(QApplication::translate("Command", "Add '%1' to '%2'").arg(nameList(bl), group->objectName())); +} + +//-------------------- RemoveButtonsFromGroupCommand +class RemoveButtonsFromGroupCommand : public ButtonGroupCommand { +public: + RemoveButtonsFromGroupCommand(QDesignerFormWindowInterface *formWindow); + bool init(const ButtonList &bl); + + void undo() override { addButtonsToGroup(); } + void redo() override { removeButtonsFromGroup(); } +}; + +RemoveButtonsFromGroupCommand::RemoveButtonsFromGroupCommand(QDesignerFormWindowInterface *formWindow) : + ButtonGroupCommand(QApplication::translate("Command", "Remove buttons from group"), formWindow) +{ +} + +bool RemoveButtonsFromGroupCommand::init(const ButtonList &bl) +{ + if (bl.isEmpty()) + return false; + QButtonGroup *group = bl.constFirst()->group(); + if (!group) + return false; + if (bl.size() >= group->buttons().size()) + return false; + initialize(bl, group); + //: Command description for removing buttons from a QButtonGroup + setText(QApplication::translate("Command", "Remove '%1' from '%2'").arg(nameList(bl), group->objectName())); + return true; +} + +// -------- ButtonGroupMenu +ButtonGroupMenu::ButtonGroupMenu(QObject *parent) : + QObject(parent), + m_selectGroupAction(new QAction(tr("Select members"), this)), + m_breakGroupAction(new QAction(tr("Break"), this)) +{ + connect(m_breakGroupAction, &QAction::triggered, this, &ButtonGroupMenu::breakGroup); + connect(m_selectGroupAction, &QAction::triggered, this, &ButtonGroupMenu::selectGroup); +} + +void ButtonGroupMenu::initialize(QDesignerFormWindowInterface *formWindow, QButtonGroup *buttonGroup, QAbstractButton *currentButton) +{ + m_buttonGroup = buttonGroup; + m_currentButton = currentButton; + m_formWindow = formWindow; + Q_ASSERT(m_formWindow); + + const bool canBreak = buttonGroup != nullptr; + m_breakGroupAction->setEnabled(canBreak); + m_selectGroupAction->setEnabled(canBreak); +} + +void ButtonGroupMenu::selectGroup() +{ + // Select and make current button "current" again by selecting it last (if there is any) + const ButtonList buttons = m_buttonGroup->buttons(); + m_formWindow->clearSelection(false); + for (auto *b : buttons) { + if (b != m_currentButton) + m_formWindow->selectWidget(b, true); + } + if (m_currentButton) + m_formWindow->selectWidget(m_currentButton, true); +} + +void ButtonGroupMenu::breakGroup() +{ + BreakButtonGroupCommand *cmd = new BreakButtonGroupCommand(m_formWindow); + if (cmd->init(m_buttonGroup)) { + // Need a macro since the command might trigger additional commands + QUndoStack *history = m_formWindow->commandHistory(); + history->beginMacro(cmd->text()); + history->push(cmd); + history->endMacro(); + } else { + qWarning("** WARNING Failed to initialize BreakButtonGroupCommand!"); + delete cmd; + } +} + +// ButtonGroupTaskMenu +ButtonGroupTaskMenu::ButtonGroupTaskMenu(QButtonGroup *buttonGroup, QObject *parent) : + QObject(parent), + m_buttonGroup(buttonGroup) +{ + m_taskActions.push_back(m_menu.breakGroupAction()); + m_taskActions.push_back(m_menu.selectGroupAction()); +} + +QAction *ButtonGroupTaskMenu::preferredEditAction() const +{ + return m_menu.selectGroupAction(); +} + +QList ButtonGroupTaskMenu::taskActions() const +{ + m_menu.initialize(QDesignerFormWindowInterface::findFormWindow(m_buttonGroup), m_buttonGroup); + return m_taskActions; +} + +// -------- Text area editor +class ButtonTextTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + ButtonTextTaskMenuInlineEditor(QAbstractButton *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +ButtonTextTaskMenuInlineEditor::ButtonTextTaskMenuInlineEditor(QAbstractButton *button, QObject *parent) : + TaskMenuInlineEditor(button, ValidationMultiLine, u"text"_s, parent) +{ +} + +QRect ButtonTextTaskMenuInlineEditor::editRectangle() const +{ + QWidget *w = widget(); + QStyleOptionButton opt; + opt.initFrom(w); + return w->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, w); +} + +// -------- Command link button description editor +class LinkDescriptionTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + LinkDescriptionTaskMenuInlineEditor(QAbstractButton *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +LinkDescriptionTaskMenuInlineEditor::LinkDescriptionTaskMenuInlineEditor(QAbstractButton *button, QObject *parent) : + TaskMenuInlineEditor(button, ValidationMultiLine, u"description"_s, parent) +{ +} + +QRect LinkDescriptionTaskMenuInlineEditor::editRectangle() const +{ + QWidget *w = widget(); // TODO: What is the exact description area? + QStyleOptionButton opt; + opt.initFrom(w); + return w->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, w); +} + +// ----------- ButtonTaskMenu: + +ButtonTaskMenu::ButtonTaskMenu(QAbstractButton *button, QObject *parent) : + QDesignerTaskMenu(button, parent), + m_assignGroupSubMenu(new QMenu), + m_assignActionGroup(nullptr), + m_assignToGroupSubMenuAction(new QAction(tr("Assign to button group"), this)), + m_currentGroupSubMenu(new QMenu), + m_currentGroupSubMenuAction(new QAction(tr("Button group"), this)), + m_createGroupAction(new QAction(tr("New button group"), this)), + m_preferredEditAction(new QAction(tr("Change text..."), this)), + m_removeFromGroupAction(new QAction(tr("None"), this)) +{ + connect(m_createGroupAction, &QAction::triggered, this, &ButtonTaskMenu::createGroup); + TaskMenuInlineEditor *textEditor = new ButtonTextTaskMenuInlineEditor(button, this); + connect(m_preferredEditAction, &QAction::triggered, textEditor, &TaskMenuInlineEditor::editText); + connect(m_removeFromGroupAction, &QAction::triggered, this, &ButtonTaskMenu::removeFromGroup); + + m_assignToGroupSubMenuAction->setMenu(m_assignGroupSubMenu); + + m_currentGroupSubMenu->addAction(m_groupMenu.breakGroupAction()); + m_currentGroupSubMenu->addAction(m_groupMenu.selectGroupAction()); + m_currentGroupSubMenuAction->setMenu(m_currentGroupSubMenu); + + + m_taskActions.append(m_preferredEditAction); + m_taskActions.append(m_assignToGroupSubMenuAction); + m_taskActions.append(m_currentGroupSubMenuAction); + m_taskActions.append(createSeparator()); +} + +ButtonTaskMenu::~ButtonTaskMenu() +{ + delete m_assignGroupSubMenu; + delete m_currentGroupSubMenu; +} + +QAction *ButtonTaskMenu::preferredEditAction() const +{ + return m_preferredEditAction; +} + +bool ButtonTaskMenu::refreshAssignMenu(const QDesignerFormWindowInterface *fw, int buttonCount, SelectionType st, QButtonGroup *currentGroup) +{ + // clear + if (m_assignActionGroup) { + delete m_assignActionGroup; + m_assignActionGroup = nullptr; + } + m_assignGroupSubMenu->clear(); + if (st == OtherSelection) + return false; + + + // Assign to new: Need several + const bool canAssignToNewGroup = buttonCount > 1; + m_createGroupAction->setEnabled(canAssignToNewGroup); + if (canAssignToNewGroup) + m_assignGroupSubMenu->addAction(m_createGroupAction); + + // Assign to other + const ButtonGroupList bl = ButtonGroupCommand::managedButtonGroups(fw); + // Groups: Any groups to add to except the current? + const auto groupCount = bl.size(); + const bool hasAddGroups = groupCount > 1 || (groupCount == 1 && !bl.contains(currentGroup)); + if (hasAddGroups) { + if (!m_assignGroupSubMenu->isEmpty()) + m_assignGroupSubMenu->addSeparator(); + // Create a new action group + m_assignActionGroup = new QActionGroup(this); + connect(m_assignActionGroup, &QActionGroup::triggered, this, &ButtonTaskMenu::addToGroup); + for (auto *bg : bl) { + if (bg != currentGroup) { + QAction *a = new QAction(bg->objectName(), m_assignGroupSubMenu); + a->setData(QVariant::fromValue(bg)); + m_assignActionGroup->addAction(a); + m_assignGroupSubMenu->addAction(a); + } + } + } + // Can remove: A homogenous selection of another group that does not completely break it. + const bool canRemoveFromGroup = st == GroupedButtonSelection; + m_removeFromGroupAction->setEnabled(canRemoveFromGroup); + if (canRemoveFromGroup) { + if (!m_assignGroupSubMenu->isEmpty()) + m_assignGroupSubMenu->addSeparator(); + m_assignGroupSubMenu->addAction(m_removeFromGroupAction); + } + return !m_assignGroupSubMenu->isEmpty(); +} + +QList ButtonTaskMenu::taskActions() const +{ + ButtonTaskMenu *ncThis = const_cast(this); + QButtonGroup *buttonGroup = nullptr; + + QDesignerFormWindowInterface *fw = formWindow(); + const SelectionType st = selectionType(fw->cursor(), &buttonGroup); + + m_groupMenu.initialize(fw, buttonGroup, button()); + const bool hasAssignOptions = ncThis->refreshAssignMenu(fw, fw->cursor()->selectedWidgetCount(), st, buttonGroup); + m_assignToGroupSubMenuAction->setVisible(hasAssignOptions); + // add/remove + switch (st) { + case UngroupedButtonSelection: + case OtherSelection: + m_currentGroupSubMenuAction->setVisible(false); + break; + case GroupedButtonSelection: + m_currentGroupSubMenuAction->setText(tr("Button group '%1'").arg(buttonGroup->objectName())); + m_currentGroupSubMenuAction->setVisible(true); + break; + } + + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + + +void ButtonTaskMenu::insertAction(int index, QAction *a) +{ + m_taskActions.insert(index, a); +} + +/* Create a button list from the cursor selection */ +static ButtonList buttonList(const QDesignerFormWindowCursorInterface *cursor) +{ + ButtonList rc; + const int selectionCount = cursor->selectedWidgetCount(); + for (int i = 0; i < selectionCount; i++) { + QAbstractButton *ab = qobject_cast(cursor->selectedWidget(i)); + Q_ASSERT(ab); + rc += ab; + } + return rc; +} + +// Create a command to remove the buttons from their group +// If it would leave an empty or 1-member group behind, create a break command instead + +static QUndoCommand *createRemoveButtonsCommand(QDesignerFormWindowInterface *fw, const ButtonList &bl) +{ + + QButtonGroup *bg = bl.constFirst()->group(); + // Complete group or 1-member group? + if (bl.size() >= bg->buttons().size() - 1) { + BreakButtonGroupCommand *breakCmd = new BreakButtonGroupCommand(fw); + if (!breakCmd->init(bg)) { + qWarning("** WARNING Failed to initialize BreakButtonGroupCommand!"); + delete breakCmd; + return nullptr; + } + return breakCmd; + } + // Just remove the buttons + + RemoveButtonsFromGroupCommand *removeCmd = new RemoveButtonsFromGroupCommand(fw); + if (!removeCmd->init(bl)) { + qWarning("** WARNING Failed to initialize RemoveButtonsFromGroupCommand!"); + delete removeCmd; + return nullptr; + } + return removeCmd; +} + +void ButtonTaskMenu::createGroup() +{ + QDesignerFormWindowInterface *fw = formWindow(); + const ButtonList bl = buttonList(fw->cursor()); + // Do we need to remove the buttons from an existing group? + QUndoCommand *removeCmd = nullptr; + if (bl.constFirst()->group()) { + removeCmd = createRemoveButtonsCommand(fw, bl); + if (!removeCmd) + return; + } + // Add cmd + CreateButtonGroupCommand *addCmd = new CreateButtonGroupCommand(fw); + if (!addCmd->init(bl)) { + qWarning("** WARNING Failed to initialize CreateButtonGroupCommand!"); + delete addCmd; + return; + } + // Need a macro [even if we only have the add command] since the command might trigger additional commands + QUndoStack *history = fw->commandHistory(); + history->beginMacro(addCmd->text()); + if (removeCmd) + history->push(removeCmd); + history->push(addCmd); + history->endMacro(); +} + +QAbstractButton *ButtonTaskMenu::button() const +{ + return qobject_cast(widget()); +} + +// Figure out if we have a homogenous selections (buttons of the same group or no group) +ButtonTaskMenu::SelectionType ButtonTaskMenu::selectionType(const QDesignerFormWindowCursorInterface *cursor, QButtonGroup **ptrToGroup) const +{ + const int selectionCount = cursor->selectedWidgetCount(); + if (!selectionCount) + return OtherSelection; + + QButtonGroup *commonGroup = nullptr; + for (int i = 0; i < selectionCount; i++) { + if (const QAbstractButton *ab = qobject_cast(cursor->selectedWidget(i))) { + QButtonGroup *buttonGroup = ab->group(); + if (i) { + if (buttonGroup != commonGroup) + return OtherSelection; + } else { + commonGroup = buttonGroup; + } + } else { + return OtherSelection; + } + } + + if (ptrToGroup) + *ptrToGroup = commonGroup; + + return commonGroup ? GroupedButtonSelection : UngroupedButtonSelection; +} + +void ButtonTaskMenu::addToGroup(QAction *a) +{ + QButtonGroup *bg = qvariant_cast(a->data()); + Q_ASSERT(bg); + + QDesignerFormWindowInterface *fw = formWindow(); + const ButtonList bl = buttonList(fw->cursor()); + // Do we need to remove the buttons from an existing group? + QUndoCommand *removeCmd = nullptr; + if (bl.constFirst()->group()) { + removeCmd = createRemoveButtonsCommand(fw, bl); + if (!removeCmd) + return; + } + AddButtonsToGroupCommand *addCmd = new AddButtonsToGroupCommand(fw); + addCmd->init(bl, bg); + + QUndoStack *history = fw->commandHistory(); + if (removeCmd) { + history->beginMacro(addCmd->text()); + history->push(removeCmd); + history->push(addCmd); + history->endMacro(); + } else { + history->push(addCmd); + } +} + +void ButtonTaskMenu::removeFromGroup() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (QUndoCommand *cmd = createRemoveButtonsCommand(fw, buttonList(fw->cursor()))) + fw->commandHistory()->push(cmd); +} + +// -------------- CommandLinkButtonTaskMenu + +CommandLinkButtonTaskMenu::CommandLinkButtonTaskMenu(QCommandLinkButton *button, QObject *parent) : + ButtonTaskMenu(button, parent) +{ + TaskMenuInlineEditor *descriptonEditor = new LinkDescriptionTaskMenuInlineEditor(button, this); + QAction *descriptionAction = new QAction(tr("Change description..."), this); + connect(descriptionAction, &QAction::triggered, descriptonEditor, &TaskMenuInlineEditor::editText); + insertAction(1, descriptionAction); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/button_taskmenu.h b/src/designer/src/components/taskmenu/button_taskmenu.h new file mode 100644 index 0000000..9623bea --- /dev/null +++ b/src/designer/src/components/taskmenu/button_taskmenu.h @@ -0,0 +1,132 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUTTON_TASKMENU_H +#define BUTTON_TASKMENU_H + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QMenu; +class QActionGroup; +class QDesignerFormWindowCursorInterface; + +namespace qdesigner_internal { + +// ButtonGroupMenu: Mixin menu for the 'select members'/'break group' options of +// the task menu of buttons and button group +class ButtonGroupMenu : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ButtonGroupMenu) +public: + ButtonGroupMenu(QObject *parent = nullptr); + + void initialize(QDesignerFormWindowInterface *formWindow, + QButtonGroup *buttonGroup = nullptr, + /* Current button for selection in ButtonMode */ + QAbstractButton *currentButton = nullptr); + + QAction *selectGroupAction() const { return m_selectGroupAction; } + QAction *breakGroupAction() const { return m_breakGroupAction; } + +private slots: + void selectGroup(); + void breakGroup(); + +private: + QAction *m_selectGroupAction; + QAction *m_breakGroupAction; + + QDesignerFormWindowInterface *m_formWindow = nullptr; + QButtonGroup *m_buttonGroup = nullptr; + QAbstractButton *m_currentButton = nullptr; +}; + +// Task menu extension of a QButtonGroup +class ButtonGroupTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ButtonGroupTaskMenu) + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit ButtonGroupTaskMenu(QButtonGroup *buttonGroup, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QButtonGroup *m_buttonGroup; + QList m_taskActions; + mutable ButtonGroupMenu m_menu; +}; + +// Task menu extension of a QAbstractButton +class ButtonTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ButtonTaskMenu) +public: + explicit ButtonTaskMenu(QAbstractButton *button, QObject *parent = nullptr); + ~ButtonTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + + QAbstractButton *button() const; + +protected: + void insertAction(int index, QAction *a); + +private slots: + void createGroup(); + void addToGroup(QAction *a); + void removeFromGroup(); + +private: + enum SelectionType { + OtherSelection, + UngroupedButtonSelection, + GroupedButtonSelection + }; + + SelectionType selectionType(const QDesignerFormWindowCursorInterface *cursor, QButtonGroup ** ptrToGroup = nullptr) const; + bool refreshAssignMenu(const QDesignerFormWindowInterface *fw, int buttonCount, SelectionType st, QButtonGroup *currentGroup); + QMenu *createGroupSelectionMenu(const QDesignerFormWindowInterface *fw); + + QList m_taskActions; + mutable ButtonGroupMenu m_groupMenu; + QMenu *m_assignGroupSubMenu; + QActionGroup *m_assignActionGroup; + QAction *m_assignToGroupSubMenuAction; + QMenu *m_currentGroupSubMenu; + QAction *m_currentGroupSubMenuAction; + + QAction *m_createGroupAction; + QAction *m_preferredEditAction; + QAction *m_removeFromGroupAction; +}; + +// Task menu extension of a QCommandLinkButton +class CommandLinkButtonTaskMenu: public ButtonTaskMenu +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(CommandLinkButtonTaskMenu) +public: + explicit CommandLinkButtonTaskMenu(QCommandLinkButton *button, QObject *parent = nullptr); +}; + +using ButtonGroupTaskMenuFactory = ExtensionFactory; +using CommandLinkButtonTaskMenuFactory = ExtensionFactory; +using ButtonTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // BUTTON_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/combobox_taskmenu.cpp b/src/designer/src/components/taskmenu/combobox_taskmenu.cpp new file mode 100644 index 0000000..75c1959 --- /dev/null +++ b/src/designer/src/components/taskmenu/combobox_taskmenu.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "combobox_taskmenu.h" +#include "listwidgeteditor.h" +#include "qdesigner_utils_p.h" +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +ComboBoxTaskMenu::ComboBoxTaskMenu(QComboBox *button, QObject *parent) + : QDesignerTaskMenu(button, parent), + m_comboBox(button) +{ + m_editItemsAction = new QAction(this); + m_editItemsAction->setText(tr("Edit Items...")); + connect(m_editItemsAction, &QAction::triggered, this, &ComboBoxTaskMenu::editItems); + m_taskActions.append(m_editItemsAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +ComboBoxTaskMenu::~ComboBoxTaskMenu() = default; + +QAction *ComboBoxTaskMenu::preferredEditAction() const +{ + return m_editItemsAction; +} + +QList ComboBoxTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void ComboBoxTaskMenu::editItems() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_comboBox); + if (m_formWindow.isNull()) + return; + + Q_ASSERT(m_comboBox != nullptr); + + ListWidgetEditor dlg(m_formWindow, m_comboBox->window()); + ListContents oldItems = dlg.fillContentsFromComboBox(m_comboBox); + if (dlg.exec() == QDialog::Accepted) { + ListContents items = dlg.contents(); + if (items != oldItems) { + ChangeListContentsCommand *cmd = new ChangeListContentsCommand(m_formWindow); + cmd->init(m_comboBox, oldItems, items); + cmd->setText(tr("Change Combobox Contents")); + m_formWindow->commandHistory()->push(cmd); + } + } +} + +ComboBoxTaskMenuFactory::ComboBoxTaskMenuFactory(const QString &iid, QExtensionManager *extensionManager) : + ExtensionFactory(iid, extensionManager) +{ +} + +QComboBox *ComboBoxTaskMenuFactory::checkObject(QObject *qObject) const +{ + QComboBox *combo = qobject_cast(qObject); + if (!combo) + return nullptr; + if (qobject_cast(combo)) + return nullptr; + return combo; +} + +void ComboBoxTaskMenu::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/combobox_taskmenu.h b/src/designer/src/components/taskmenu/combobox_taskmenu.h new file mode 100644 index 0000000..47dad22 --- /dev/null +++ b/src/designer/src/components/taskmenu/combobox_taskmenu.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef COMBOBOX_TASKMENU_H +#define COMBOBOX_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLineEdit; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class ComboBoxTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit ComboBoxTaskMenu(QComboBox *button, + QObject *parent = nullptr); + ~ComboBoxTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editItems(); + void updateSelection(); + +private: + QComboBox *m_comboBox; + QPointer m_formWindow; + QPointer m_editor; + mutable QList m_taskActions; + QAction *m_editItemsAction; +}; + +class ComboBoxTaskMenuFactory : public ExtensionFactory +{ +public: + explicit ComboBoxTaskMenuFactory(const QString &iid, QExtensionManager *extensionManager); + +private: + QComboBox *checkObject(QObject *qObject) const override; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // COMBOBOX_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/containerwidget_taskmenu.cpp b/src/designer/src/components/taskmenu/containerwidget_taskmenu.cpp new file mode 100644 index 0000000..a9cec6e --- /dev/null +++ b/src/designer/src/components/taskmenu/containerwidget_taskmenu.cpp @@ -0,0 +1,302 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "containerwidget_taskmenu.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +ContainerWidgetTaskMenu::ContainerWidgetTaskMenu(QWidget *widget, ContainerType type, QObject *parent) : + QDesignerTaskMenu(widget, parent), + m_type(type), + m_containerWidget(widget), + m_core(formWindow()->core()), + m_pagePromotionTaskMenu(new PromotionTaskMenu(nullptr, PromotionTaskMenu::ModeSingleWidget, this)), + m_pageMenuAction(new QAction(this)), + m_pageMenu(new QMenu), + m_actionInsertPageAfter(new QAction(this)), + m_actionInsertPage(nullptr), + m_actionDeletePage(new QAction(tr("Delete"), this)) +{ + Q_ASSERT(m_core); + m_taskActions.append(createSeparator()); + + connect(m_actionDeletePage, &QAction::triggered, this, &ContainerWidgetTaskMenu::removeCurrentPage); + + connect(m_actionInsertPageAfter, &QAction::triggered, this, &ContainerWidgetTaskMenu::addPageAfter); + // Empty Per-Page submenu, deletion and promotion. Updated on demand due to promotion state + switch (m_type) { + case WizardContainer: + case PageContainer: + m_taskActions.append(createSeparator()); // for the browse actions + break; + case MdiContainer: + break; + } + // submenu + m_pageMenuAction->setMenu(m_pageMenu); + m_taskActions.append(m_pageMenuAction); + // Insertion + switch (m_type) { + case WizardContainer: + case PageContainer: { // Before and after in a submenu + QAction *insertMenuAction = new QAction(tr("Insert"), this); + QMenu *insertMenu = new QMenu; + // before + m_actionInsertPage = new QAction(tr("Insert Page Before Current Page"), this); + connect(m_actionInsertPage, &QAction::triggered, this, &ContainerWidgetTaskMenu::addPage); + insertMenu->addAction(m_actionInsertPage); + // after + m_actionInsertPageAfter->setText(tr("Insert Page After Current Page")); + insertMenu->addAction(m_actionInsertPageAfter); + + insertMenuAction->setMenu(insertMenu); + m_taskActions.append(insertMenuAction); + } + break; + case MdiContainer: // No concept of order + m_actionInsertPageAfter->setText(tr("Add Subwindow")); + m_taskActions.append(m_actionInsertPageAfter); + break; + } +} + +ContainerWidgetTaskMenu::~ContainerWidgetTaskMenu() = default; + +QAction *ContainerWidgetTaskMenu::preferredEditAction() const +{ + return nullptr; +} + +bool ContainerWidgetTaskMenu::canDeletePage() const +{ + switch (pageCount()) { + case 0: + return false; + case 1: + return m_type != PageContainer; // Do not delete last page of page-type container + default: + break; + } + return true; +} + +int ContainerWidgetTaskMenu::pageCount() const +{ + if (const QDesignerContainerExtension *ce = containerExtension()) + return ce->count(); + return 0; +} + +QString ContainerWidgetTaskMenu::pageMenuText(ContainerType ct, int index, int count) +{ + if (ct == MdiContainer) + return tr("Subwindow"); // No concept of order, same text everywhere + if (index < 0) + return tr("Page"); + return tr("Page %1 of %2").arg(index + 1).arg(count); +} + +QList ContainerWidgetTaskMenu::taskActions() const +{ + const QDesignerContainerExtension *ce = containerExtension(); + const int index = ce->currentIndex(); + + auto actions = QDesignerTaskMenu::taskActions(); + actions += m_taskActions; + // Update the page submenu, deletion and promotion. Updated on demand due to promotion state. + m_pageMenu->clear(); + const bool canAddWidget = ce->canAddWidget(); + if (m_actionInsertPage) + m_actionInsertPage->setEnabled(canAddWidget); + m_actionInsertPageAfter->setEnabled(canAddWidget); + m_pageMenu->addAction(m_actionDeletePage); + m_actionDeletePage->setEnabled(index >= 0 && ce->canRemove(index) && canDeletePage()); + m_pageMenuAction->setText(pageMenuText(m_type, index, ce->count())); + if (index != -1) { // Has a page + m_pageMenuAction->setEnabled(true); + m_pagePromotionTaskMenu->setWidget(ce->widget(index)); + m_pagePromotionTaskMenu->addActions(PromotionTaskMenu::LeadingSeparator|PromotionTaskMenu::SuppressGlobalEdit, m_pageMenu); + } else { // No page + m_pageMenuAction->setEnabled(false); + } + + return actions; +} + +QDesignerFormWindowInterface *ContainerWidgetTaskMenu::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(m_containerWidget); +} + +QDesignerContainerExtension *ContainerWidgetTaskMenu::containerExtension() const +{ + QExtensionManager *mgr = m_core->extensionManager(); + return qt_extension(mgr, m_containerWidget); +} + +void ContainerWidgetTaskMenu::removeCurrentPage() +{ + if (QDesignerContainerExtension *c = containerExtension()) { + if (c->currentIndex() == -1) + return; + + QDesignerFormWindowInterface *fw = formWindow(); + DeleteContainerWidgetPageCommand *cmd = new DeleteContainerWidgetPageCommand(fw); + cmd->init(m_containerWidget, m_type); + fw->commandHistory()->push(cmd); + } +} + +void ContainerWidgetTaskMenu::addPage() +{ + if (containerExtension()) { + QDesignerFormWindowInterface *fw = formWindow(); + AddContainerWidgetPageCommand *cmd = new AddContainerWidgetPageCommand(fw); + cmd->init(m_containerWidget, m_type, AddContainerWidgetPageCommand::InsertBefore); + fw->commandHistory()->push(cmd); + } +} + +void ContainerWidgetTaskMenu::addPageAfter() +{ + if (containerExtension()) { + QDesignerFormWindowInterface *fw = formWindow(); + AddContainerWidgetPageCommand *cmd = new AddContainerWidgetPageCommand(fw); + cmd->init(m_containerWidget, m_type, AddContainerWidgetPageCommand::InsertAfter); + fw->commandHistory()->push(cmd); + } +} + +// -------------- WizardContainerWidgetTaskMenu +WizardContainerWidgetTaskMenu::WizardContainerWidgetTaskMenu(QWizard *w, QObject *parent) : + ContainerWidgetTaskMenu(w, WizardContainer, parent), + m_nextAction(new QAction(tr("Next"), this)), + m_previousAction(new QAction(tr("Back"), this)) +{ + connect(m_nextAction, &QAction::triggered, w, &QWizard::next); + connect(m_previousAction, &QAction::triggered, w, &QWizard::back); + auto &l = containerActions(); + l.push_front(createSeparator()); + l.push_front(m_nextAction); + l.push_front(m_previousAction); + l.push_front(createSeparator()); +} + +QList WizardContainerWidgetTaskMenu::taskActions() const +{ + // Enable + const QDesignerContainerExtension *ce = containerExtension(); + const int index = ce->currentIndex(); + m_previousAction->setEnabled(index > 0); + m_nextAction->setEnabled(index >= 0 && index < (ce->count() - 1)); + return ContainerWidgetTaskMenu::taskActions(); +} + +// -------------- MdiContainerWidgetTaskMenu + +MdiContainerWidgetTaskMenu::MdiContainerWidgetTaskMenu(QMdiArea *m, QObject *parent) : + ContainerWidgetTaskMenu(m, MdiContainer, parent) +{ + initializeActions(); + connect(m_nextAction, &QAction::triggered, m, &QMdiArea::activateNextSubWindow); + connect(m_previousAction, &QAction::triggered, m , &QMdiArea::activatePreviousSubWindow); + connect(m_tileAction, &QAction::triggered, m, &QMdiArea::tileSubWindows); + connect(m_cascadeAction, &QAction::triggered, m, &QMdiArea::cascadeSubWindows); +} + +void MdiContainerWidgetTaskMenu::initializeActions() +{ + m_nextAction =new QAction(tr("Next Subwindow"), this); + m_previousAction = new QAction(tr("Previous Subwindow"), this); + m_tileAction = new QAction(tr("Tile"), this); + m_cascadeAction = new QAction(tr("Cascade"), this); + + auto &l = containerActions(); + l.push_front(createSeparator()); + l.push_front(m_tileAction); + l.push_front(m_cascadeAction); + l.push_front(m_previousAction); + l.push_front(m_nextAction); + l.push_front(createSeparator()); +} + +QList MdiContainerWidgetTaskMenu::taskActions() const +{ + const auto rc = ContainerWidgetTaskMenu::taskActions(); + // Enable + const int count = pageCount(); + m_nextAction->setEnabled(count > 1); + m_previousAction->setEnabled(count > 1); + m_tileAction->setEnabled(count); + m_cascadeAction->setEnabled(count); + return rc; +} + +// -------------- ContainerWidgetTaskMenuFactory + +ContainerWidgetTaskMenuFactory::ContainerWidgetTaskMenuFactory(QDesignerFormEditorInterface *core, QExtensionManager *extensionManager) : + QExtensionFactory(extensionManager), + m_core(core) +{ +} + +QObject *ContainerWidgetTaskMenuFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const +{ + if (iid != "QDesignerInternalTaskMenuExtension"_L1 || !object->isWidgetType()) + return nullptr; + + QWidget *widget = qobject_cast(object); + + if (qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget)) { + // Are we using Designer's own container extensions and task menus or did + // someone provide an extra one with an addpage method, for example for a QScrollArea? + if (const WidgetDataBase *wb = qobject_cast(m_core->widgetDataBase())) { + const int idx = wb->indexOfObject(widget); + const WidgetDataBaseItem *item = static_cast(wb->item(idx)); + if (item->addPageMethod().isEmpty()) + return nullptr; + } + } + + if (qt_extension(extensionManager(), object) == nullptr) + return nullptr; + + if (QMdiArea* ma = qobject_cast(widget)) + return new MdiContainerWidgetTaskMenu(ma, parent); + if (QWizard *wz = qobject_cast(widget)) + return new WizardContainerWidgetTaskMenu(wz, parent); + return new ContainerWidgetTaskMenu(widget, PageContainer, parent); +} + +} +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/containerwidget_taskmenu.h b/src/designer/src/components/taskmenu/containerwidget_taskmenu.h new file mode 100644 index 0000000..e27cb29 --- /dev/null +++ b/src/designer/src/components/taskmenu/containerwidget_taskmenu.h @@ -0,0 +1,119 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONTAINERWIDGER_TASKMENU_H +#define CONTAINERWIDGER_TASKMENU_H + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QDesignerContainerExtension; +class QAction; +class QMdiArea; +class QMenu; +class QWizard; + +namespace qdesigner_internal { + +class PromotionTaskMenu; + +// ContainerWidgetTaskMenu: Task menu for containers with extension + +class ContainerWidgetTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit ContainerWidgetTaskMenu(QWidget *widget, ContainerType type, QObject *parent = nullptr); + ~ContainerWidgetTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void removeCurrentPage(); + void addPage(); + void addPageAfter(); + +protected: + QDesignerContainerExtension *containerExtension() const; + QList &containerActions() { return m_taskActions; } + int pageCount() const; + +private: + QDesignerFormWindowInterface *formWindow() const; + +private: + static QString pageMenuText(ContainerType ct, int index, int count); + bool canDeletePage() const; + + const ContainerType m_type; + QWidget *m_containerWidget; + QDesignerFormEditorInterface *m_core; + PromotionTaskMenu *m_pagePromotionTaskMenu; + QAction *m_pageMenuAction; + QMenu *m_pageMenu; + QList m_taskActions; + QAction *m_actionInsertPageAfter; + QAction *m_actionInsertPage; + QAction *m_actionDeletePage; +}; + +// WizardContainerWidgetTaskMenu: Provide next/back since QWizard +// has modes in which the "Back" button is not visible. + +class WizardContainerWidgetTaskMenu : public ContainerWidgetTaskMenu { + Q_OBJECT +public: + explicit WizardContainerWidgetTaskMenu(QWizard *w, QObject *parent = nullptr); + + QList taskActions() const override; + +private: + QAction *m_nextAction; + QAction *m_previousAction; +}; + + +// MdiContainerWidgetTaskMenu: Provide tile/cascade for MDI containers in addition + +class MdiContainerWidgetTaskMenu : public ContainerWidgetTaskMenu { + Q_OBJECT +public: + explicit MdiContainerWidgetTaskMenu(QMdiArea *m, QObject *parent = nullptr); + + QList taskActions() const override; +private: + void initializeActions(); + + QAction *m_nextAction = nullptr; + QAction *m_previousAction = nullptr; + QAction *m_tileAction = nullptr; + QAction *m_cascadeAction = nullptr; +}; + +class ContainerWidgetTaskMenuFactory: public QExtensionFactory +{ + Q_OBJECT +public: + explicit ContainerWidgetTaskMenuFactory(QDesignerFormEditorInterface *core, QExtensionManager *extensionManager = nullptr); + +protected: + QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; + +private: + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // CONTAINERWIDGER_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/groupbox_taskmenu.cpp b/src/designer/src/components/taskmenu/groupbox_taskmenu.cpp new file mode 100644 index 0000000..f8818bd --- /dev/null +++ b/src/designer/src/components/taskmenu/groupbox_taskmenu.cpp @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "groupbox_taskmenu.h" +#include "inplace_editor.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// -------- GroupBoxTaskMenuInlineEditor +class GroupBoxTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + GroupBoxTaskMenuInlineEditor(QGroupBox *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +GroupBoxTaskMenuInlineEditor::GroupBoxTaskMenuInlineEditor(QGroupBox *w, QObject *parent) : + TaskMenuInlineEditor(w, ValidationSingleLine, u"title"_s, parent) +{ +} + +QRect GroupBoxTaskMenuInlineEditor::editRectangle() const +{ + QWidget *w = widget(); + QStyleOption opt; // ## QStyleOptionGroupBox + opt.initFrom(w); + return QRect(QPoint(), QSize(w->width(),20)); +} + +// --------------- GroupBoxTaskMenu + +GroupBoxTaskMenu::GroupBoxTaskMenu(QGroupBox *groupbox, QObject *parent) + : QDesignerTaskMenu(groupbox, parent), + m_editTitleAction(new QAction(tr("Change title..."), this)) + +{ + TaskMenuInlineEditor *editor = new GroupBoxTaskMenuInlineEditor(groupbox, this); + connect(m_editTitleAction, &QAction::triggered, editor, &TaskMenuInlineEditor::editText); + m_taskActions.append(m_editTitleAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +QList GroupBoxTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +QAction *GroupBoxTaskMenu::preferredEditAction() const +{ + return m_editTitleAction; +} + +} +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/groupbox_taskmenu.h b/src/designer/src/components/taskmenu/groupbox_taskmenu.h new file mode 100644 index 0000000..6217273 --- /dev/null +++ b/src/designer/src/components/taskmenu/groupbox_taskmenu.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef GROUPBOX_TASKMENU_H +#define GROUPBOX_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { +class InPlaceEditor; + +class GroupBoxTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit GroupBoxTaskMenu(QGroupBox *groupbox, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QAction *m_editTitleAction; + QList m_taskActions; +}; + +using GroupBoxTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // GROUPBOX_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/inplace_editor.cpp b/src/designer/src/components/taskmenu/inplace_editor.cpp new file mode 100644 index 0000000..5afbea3 --- /dev/null +++ b/src/designer/src/components/taskmenu/inplace_editor.cpp @@ -0,0 +1,101 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindow.h" +#include "inplace_editor.h" + +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// ----------------- InPlaceEditor + +InPlaceEditor::InPlaceEditor(QWidget *widget, + TextPropertyValidationMode validationMode, + QDesignerFormWindowInterface *fw, + const QString& text, + const QRect& r) : + TextPropertyEditor(widget, EmbeddingInPlace, validationMode), + m_InPlaceWidgetHelper(this, widget, fw) +{ + setAlignment(m_InPlaceWidgetHelper.alignment()); + setObjectName(u"__qt__passive_m_editor"_s); + + setText(text); + selectAll(); + + setGeometry(QRect(widget->mapTo(widget->window(), r.topLeft()), r.size())); + setFocus(); + show(); + + connect(this, &TextPropertyEditor::editingFinished,this, &QWidget::close); +} + + +// -------------- TaskMenuInlineEditor + +TaskMenuInlineEditor::TaskMenuInlineEditor(QWidget *w, TextPropertyValidationMode vm, + const QString &property, QObject *parent) : + QObject(parent), + m_vm(vm), + m_property(property), + m_widget(w), + m_managed(true) +{ +} + +void TaskMenuInlineEditor::editText() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_widget); + if (m_formWindow.isNull()) + return; + m_managed = m_formWindow->isManaged(m_widget); + // Close as soon as a different widget is selected + connect(m_formWindow.data(), &QDesignerFormWindowInterface::selectionChanged, + this, &TaskMenuInlineEditor::updateSelection); + + // get old value + QDesignerFormEditorInterface *core = m_formWindow->core(); + const QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), m_widget); + const int index = sheet->indexOf(m_property); + if (index == -1) + return; + m_value = qvariant_cast(sheet->property(index)); + const QString oldValue = m_value.value(); + + m_editor = new InPlaceEditor(m_widget, m_vm, m_formWindow, oldValue, editRectangle()); + connect(m_editor.data(), &InPlaceEditor::textChanged, this, &TaskMenuInlineEditor::updateText); +} + +void TaskMenuInlineEditor::updateText(const QString &text) +{ + // In the [rare] event we are invoked on an unmanaged widget, + // do not use the cursor selection + m_value.setValue(text); + if (m_managed) { + m_formWindow->cursor()->setProperty(m_property, QVariant::fromValue(m_value)); + } else { + m_formWindow->cursor()->setWidgetProperty(m_widget, m_property, QVariant::fromValue(m_value)); + } +} + +void TaskMenuInlineEditor::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/inplace_editor.h b/src/designer/src/components/taskmenu/inplace_editor.h new file mode 100644 index 0000000..c8b28f0 --- /dev/null +++ b/src/designer/src/components/taskmenu/inplace_editor.h @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef INPLACE_EDITOR_H +#define INPLACE_EDITOR_H + +#include +#include + +#include "inplace_widget_helper.h" +#include + +#include + +QT_BEGIN_NAMESPACE + + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class InPlaceEditor: public TextPropertyEditor +{ + Q_OBJECT +public: + InPlaceEditor(QWidget *widget, + TextPropertyValidationMode validationMode, + QDesignerFormWindowInterface *fw, + const QString& text, + const QRect& r); +private: + InPlaceWidgetHelper m_InPlaceWidgetHelper; +}; + +// Base class for inline editor helpers to be embedded into a task menu. +// Inline-edits a property on a multi-selection. +// To use it for a particular widget/property, overwrite the method +// returning the edit area. + +class TaskMenuInlineEditor : public QObject +{ + Q_OBJECT + +public slots: + void editText(); + +private slots: + void updateText(const QString &text); + void updateSelection(); + +protected: + TaskMenuInlineEditor(QWidget *w, TextPropertyValidationMode vm, const QString &property, QObject *parent); + // Overwrite to return the area for the inline editor. + virtual QRect editRectangle() const = 0; + QWidget *widget() const { return m_widget; } + +private: + const TextPropertyValidationMode m_vm; + const QString m_property; + QWidget *m_widget; + QPointer m_formWindow; + QPointer m_editor; + bool m_managed; + qdesigner_internal::PropertySheetStringValue m_value; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // INPLACE_EDITOR_H diff --git a/src/designer/src/components/taskmenu/inplace_widget_helper.cpp b/src/designer/src/components/taskmenu/inplace_widget_helper.cpp new file mode 100644 index 0000000..0183076 --- /dev/null +++ b/src/designer/src/components/taskmenu/inplace_widget_helper.cpp @@ -0,0 +1,86 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindow.h" +#include "inplace_widget_helper.h" + + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + InPlaceWidgetHelper::InPlaceWidgetHelper(QWidget *editorWidget, QWidget *parentWidget, QDesignerFormWindowInterface *fw) + : QObject(nullptr), + m_editorWidget(editorWidget), + m_parentWidget(parentWidget), + m_noChildEvent(m_parentWidget->testAttribute(Qt::WA_NoChildEventsForParent)) + { + m_editorWidget->setAttribute(Qt::WA_DeleteOnClose); + m_editorWidget->setParent(m_parentWidget->window()); + m_parentWidget->installEventFilter(this); + m_editorWidget->installEventFilter(this); + connect(m_editorWidget, &QObject::destroyed, + fw->mainContainer(), QOverload<>::of(&QWidget::setFocus)); + } + + InPlaceWidgetHelper::~InPlaceWidgetHelper() + { + if (m_parentWidget) + m_parentWidget->setAttribute(Qt::WA_NoChildEventsForParent, m_noChildEvent); + } + + Qt::Alignment InPlaceWidgetHelper::alignment() const { + if (m_parentWidget->metaObject()->indexOfProperty("alignment") != -1) + return Qt::Alignment(m_parentWidget->property("alignment").toInt()); + + if (qobject_cast(m_parentWidget) + || qobject_cast(m_parentWidget) /* tool needs to be more complex */) + return Qt::AlignHCenter; + + return Qt::AlignJustify; + } + + + bool InPlaceWidgetHelper::eventFilter(QObject *object, QEvent *e) + { + if (object == m_parentWidget) { + if (e->type() == QEvent::Resize) { + const QResizeEvent *event = static_cast(e); + const QPoint localPos = m_parentWidget->geometry().topLeft(); + const QPoint globalPos = m_parentWidget->parentWidget() ? m_parentWidget->parentWidget()->mapToGlobal(localPos) : localPos; + const QPoint newPos = (m_editorWidget->parentWidget() ? m_editorWidget->parentWidget()->mapFromGlobal(globalPos) : globalPos) + + m_posOffset; + const QSize newSize = event->size() + m_sizeOffset; + m_editorWidget->setGeometry(QRect(newPos, newSize)); + } + } else if (object == m_editorWidget) { + if (e->type() == QEvent::ShortcutOverride) { + if (static_cast(e)->key() == Qt::Key_Escape) { + e->accept(); + return false; + } + } else if (e->type() == QEvent::KeyPress) { + if (static_cast(e)->key() == Qt::Key_Escape) { + e->accept(); + m_editorWidget->close(); + return true; + } + } else if (e->type() == QEvent::Show) { + const QPoint localPos = m_parentWidget->geometry().topLeft(); + const QPoint globalPos = m_parentWidget->parentWidget() ? m_parentWidget->parentWidget()->mapToGlobal(localPos) : localPos; + const QPoint newPos = m_editorWidget->parentWidget() ? m_editorWidget->parentWidget()->mapFromGlobal(globalPos) : globalPos; + m_posOffset = m_editorWidget->geometry().topLeft() - newPos; + m_sizeOffset = m_editorWidget->size() - m_parentWidget->size(); + } + } + + return QObject::eventFilter(object, e); + } +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/inplace_widget_helper.h b/src/designer/src/components/taskmenu/inplace_widget_helper.h new file mode 100644 index 0000000..587f3a4 --- /dev/null +++ b/src/designer/src/components/taskmenu/inplace_widget_helper.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef INPLACE_WIDGETHELPER_H +#define INPLACE_WIDGETHELPER_H + + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + + // A helper class to make an editor widget suitable for form inline + // editing. Derive from the editor widget class and make InPlaceWidgetHelper a member. + // + // Sets "destructive close" on the editor widget and + // wires "ESC" to it. + // Installs an event filter on the parent to listen for + // resize events and passes them on to the child. + // You might want to connect editingFinished() to close() of the editor widget. + class InPlaceWidgetHelper: public QObject + { + Q_OBJECT + public: + InPlaceWidgetHelper(QWidget *editorWidget, QWidget *parentWidget, QDesignerFormWindowInterface *fw); + ~InPlaceWidgetHelper() override; + + bool eventFilter(QObject *object, QEvent *event) override; + + // returns a recommended alignment for the editor widget determined from the parent. + Qt::Alignment alignment() const; + private: + QWidget *m_editorWidget; + QPointer m_parentWidget; + const bool m_noChildEvent; + QPoint m_posOffset; + QSize m_sizeOffset; + }; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // INPLACE_WIDGETHELPER_H diff --git a/src/designer/src/components/taskmenu/itemlisteditor.cpp b/src/designer/src/components/taskmenu/itemlisteditor.cpp new file mode 100644 index 0000000..6969276 --- /dev/null +++ b/src/designer/src/components/taskmenu/itemlisteditor.cpp @@ -0,0 +1,490 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "itemlisteditor.h" +#include +#include +#include +#include + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +class ItemPropertyBrowser : public QtTreePropertyBrowser +{ +public: + ItemPropertyBrowser() + { + setResizeMode(Interactive); + //: Sample string to determinate the width for the first column of the list item property browser + const QString widthSampleString = QCoreApplication::translate("ItemPropertyBrowser", "XX Icon Selected off"); + m_width = fontMetrics().horizontalAdvance(widthSampleString); + setSplitterPosition(m_width); + m_width += fontMetrics().horizontalAdvance(u"/this/is/some/random/path"_s); + } + + QSize sizeHint() const override + { + return QSize(m_width, 1); + } + +private: + int m_width; +}; + +////////////////// Item editor /////////////// +AbstractItemEditor::AbstractItemEditor(QDesignerFormWindowInterface *form, QWidget *parent) + : QWidget(parent), + m_iconCache(qobject_cast(form)->iconCache()) +{ + m_propertyManager = new DesignerPropertyManager(form->core(), this); + m_editorFactory = new DesignerEditorFactory(form->core(), this); + m_editorFactory->setSpacing(0); + m_propertyBrowser = new ItemPropertyBrowser; + m_propertyBrowser->setFactoryForManager(static_cast(m_propertyManager), + m_editorFactory); + + connect(m_editorFactory, &DesignerEditorFactory::resetProperty, + this, &AbstractItemEditor::resetProperty); + connect(m_propertyManager, &DesignerPropertyManager::valueChanged, + this, &AbstractItemEditor::propertyChanged); + connect(iconCache(), &DesignerIconCache::reloaded, this, &AbstractItemEditor::cacheReloaded); +} + +AbstractItemEditor::~AbstractItemEditor() +{ + m_propertyBrowser->unsetFactoryForManager(m_propertyManager); +} + +static const char * const itemFlagNames[] = { + QT_TRANSLATE_NOOP("AbstractItemEditor", "Selectable"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "Editable"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "DragEnabled"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "DropEnabled"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "UserCheckable"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "Enabled"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "Tristate"), + nullptr +}; + +static const char * const checkStateNames[] = { + QT_TRANSLATE_NOOP("AbstractItemEditor", "Unchecked"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "PartiallyChecked"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "Checked"), + nullptr +}; + +static QStringList c2qStringList(const char * const in[]) +{ + QStringList out; + for (int i = 0; in[i]; i++) + out << AbstractItemEditor::tr(in[i]); + return out; +} + +void AbstractItemEditor::setupProperties(const PropertyDefinition *propList, + Qt::Alignment alignDefault) +{ + for (int i = 0; propList[i].name; i++) { + int type = propList[i].typeFunc ? propList[i].typeFunc() : propList[i].type; + int role = propList[i].role; + QtVariantProperty *prop = m_propertyManager->addProperty(type, QLatin1StringView(propList[i].name)); + if (role == Qt::TextAlignmentRole) { + prop->setAttribute(DesignerPropertyManager::alignDefaultAttribute(), + QVariant(uint(alignDefault))); + } + Q_ASSERT(prop); + if (role == Qt::ToolTipPropertyRole || role == Qt::WhatsThisPropertyRole) + prop->setAttribute(u"validationMode"_s, ValidationRichText); + else if (role == Qt::DisplayPropertyRole) + prop->setAttribute(u"validationMode"_s, ValidationMultiLine); + else if (role == Qt::StatusTipPropertyRole) + prop->setAttribute(u"validationMode"_s, ValidationSingleLine); + else if (role == ItemFlagsShadowRole) + prop->setAttribute(u"flagNames"_s, c2qStringList(itemFlagNames)); + else if (role == Qt::CheckStateRole) + prop->setAttribute(u"enumNames"_s, c2qStringList(checkStateNames)); + prop->setAttribute(u"resettable"_s, true); + m_properties.append(prop); + m_rootProperties.append(prop); + m_propertyToRole.insert(prop, role); + } +} + +void AbstractItemEditor::setupObject(QWidget *object) +{ + m_propertyManager->setObject(object); + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(object); + FormWindowBase *fwb = qobject_cast(formWindow); + m_editorFactory->setFormWindowBase(fwb); +} + +void AbstractItemEditor::setupEditor(QWidget *object, + const PropertyDefinition *propList, + Qt::Alignment alignDefault) +{ + setupProperties(propList, alignDefault); + setupObject(object); +} + +void AbstractItemEditor::propertyChanged(QtProperty *property) +{ + if (m_updatingBrowser) + return; + + + BoolBlocker block(m_updatingBrowser); + QtVariantProperty *prop = m_propertyManager->variantProperty(property); + int role; + if ((role = m_propertyToRole.value(prop, -1)) == -1) + // Subproperty + return; + + if ((role == ItemFlagsShadowRole && prop->value().toInt() == defaultItemFlags()) + || (role == Qt::DecorationPropertyRole && !qvariant_cast(prop->value()).mask()) + || (role == Qt::FontRole && !qvariant_cast(prop->value()).resolveMask())) { + prop->setModified(false); + setItemData(role, QVariant()); + } else { + prop->setModified(true); + setItemData(role, prop->value()); + } + + switch (role) { + case Qt::DecorationPropertyRole: + setItemData(Qt::DecorationRole, QVariant::fromValue(iconCache()->icon(qvariant_cast(prop->value())))); + break; + case Qt::DisplayPropertyRole: + setItemData(Qt::EditRole, QVariant::fromValue(qvariant_cast(prop->value()).value())); + break; + case Qt::ToolTipPropertyRole: + setItemData(Qt::ToolTipRole, QVariant::fromValue(qvariant_cast(prop->value()).value())); + break; + case Qt::StatusTipPropertyRole: + setItemData(Qt::StatusTipRole, QVariant::fromValue(qvariant_cast(prop->value()).value())); + break; + case Qt::WhatsThisPropertyRole: + setItemData(Qt::WhatsThisRole, QVariant::fromValue(qvariant_cast(prop->value()).value())); + break; + default: + break; + } + + prop->setValue(getItemData(role)); +} + +void AbstractItemEditor::resetProperty(QtProperty *property) +{ + if (m_propertyManager->resetFontSubProperty(property)) + return; + + if (m_propertyManager->resetIconSubProperty(property)) + return; + + if (m_propertyManager->resetTextAlignmentProperty(property)) + return; + + BoolBlocker block(m_updatingBrowser); + + QtVariantProperty *prop = m_propertyManager->variantProperty(property); + int role = m_propertyToRole.value(prop); + if (role == ItemFlagsShadowRole) + prop->setValue(QVariant::fromValue(defaultItemFlags())); + else + prop->setValue(QVariant(QMetaType(prop->valueType()), nullptr)); + prop->setModified(false); + + setItemData(role, QVariant()); + if (role == Qt::DecorationPropertyRole) + setItemData(Qt::DecorationRole, QVariant::fromValue(QIcon())); + if (role == Qt::DisplayPropertyRole) + setItemData(Qt::EditRole, QVariant::fromValue(QString())); + if (role == Qt::ToolTipPropertyRole) + setItemData(Qt::ToolTipRole, QVariant::fromValue(QString())); + if (role == Qt::StatusTipPropertyRole) + setItemData(Qt::StatusTipRole, QVariant::fromValue(QString())); + if (role == Qt::WhatsThisPropertyRole) + setItemData(Qt::WhatsThisRole, QVariant::fromValue(QString())); +} + +void AbstractItemEditor::cacheReloaded() +{ + BoolBlocker block(m_updatingBrowser); + m_propertyManager->reloadResourceProperties(); +} + +void AbstractItemEditor::updateBrowser() +{ + BoolBlocker block(m_updatingBrowser); + for (QtVariantProperty *prop : std::as_const(m_properties)) { + int role = m_propertyToRole.value(prop); + QVariant val = getItemData(role); + + bool modified = false; + if (!val.isValid()) { + if (role == ItemFlagsShadowRole) + val = QVariant::fromValue(defaultItemFlags()); + else + val = QVariant(QMetaType(prop->value().userType()), nullptr); + } else { + modified = role != Qt::TextAlignmentRole + || val.toUInt() != DesignerPropertyManager::alignDefault(prop); + } + prop->setModified(modified); + prop->setValue(val); + } + + if (m_propertyBrowser->topLevelItems().isEmpty()) { + for (QtVariantProperty *prop : std::as_const(m_rootProperties)) + m_propertyBrowser->addProperty(prop); + } +} + +void AbstractItemEditor::injectPropertyBrowser(QWidget *parent, QWidget *widget) +{ + // It is impossible to design a splitter with just one widget, so we do it by hand. + m_propertySplitter = new QSplitter; + m_propertySplitter->addWidget(widget); + m_propertySplitter->addWidget(m_propertyBrowser); + m_propertySplitter->setStretchFactor(0, 1); + m_propertySplitter->setStretchFactor(1, 0); + parent->layout()->addWidget(m_propertySplitter); +} + +////////////////// List editor /////////////// +ItemListEditor::ItemListEditor(QDesignerFormWindowInterface *form, QWidget *parent) + : AbstractItemEditor(form, parent), + m_updating(false) +{ + ui.setupUi(this); + + injectPropertyBrowser(this, ui.widget); + connect(ui.showPropertiesButton, &QAbstractButton::clicked, + this, &ItemListEditor::togglePropertyBrowser); + + connect(ui.newListItemButton, &QAbstractButton::clicked, + this, &ItemListEditor::newListItemButtonClicked); + connect(ui.deleteListItemButton, &QAbstractButton::clicked, + this, &ItemListEditor::deleteListItemButtonClicked); + connect(ui.moveListItemUpButton, &QAbstractButton::clicked, + this, &ItemListEditor::moveListItemUpButtonClicked); + connect(ui.moveListItemDownButton, &QAbstractButton::clicked, + this, &ItemListEditor::moveListItemDownButtonClicked); + connect(ui.listWidget, &QListWidget::currentRowChanged, + this, &ItemListEditor::listWidgetCurrentRowChanged); + connect(ui.listWidget, &QListWidget::itemChanged, + this, &ItemListEditor::listWidgetItemChanged); + + setPropertyBrowserVisible(false); + + QIcon upIcon = createIconSet("up.png"_L1); + QIcon downIcon = createIconSet("down.png"_L1); + QIcon minusIcon = createIconSet("minus.png"_L1); + QIcon plusIcon = createIconSet("plus.png"_L1); + ui.moveListItemUpButton->setIcon(upIcon); + ui.moveListItemDownButton->setIcon(downIcon); + ui.newListItemButton->setIcon(plusIcon); + ui.deleteListItemButton->setIcon(minusIcon); + + connect(iconCache(), &DesignerIconCache::reloaded, this, &AbstractItemEditor::cacheReloaded); +} + +void ItemListEditor::setupEditor(QWidget *object, + const PropertyDefinition *propList, + Qt::Alignment alignDefault) +{ + AbstractItemEditor::setupEditor(object, propList, alignDefault); + + if (ui.listWidget->count() > 0) + ui.listWidget->setCurrentRow(0); + else + updateEditor(); +} + +void ItemListEditor::setCurrentIndex(int idx) +{ + m_updating = true; + ui.listWidget->setCurrentRow(idx); + m_updating = false; +} + +void ItemListEditor::newListItemButtonClicked() +{ + int row = ui.listWidget->currentRow() + 1; + + QListWidgetItem *item = new QListWidgetItem(m_newItemText); + item->setData(Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(m_newItemText))); + if (m_alignDefault != 0) + item->setTextAlignment(Qt::Alignment(m_alignDefault)); + item->setFlags(item->flags() | Qt::ItemIsEditable); + if (row < ui.listWidget->count()) + ui.listWidget->insertItem(row, item); + else + ui.listWidget->addItem(item); + emit itemInserted(row); + + ui.listWidget->setCurrentItem(item); + ui.listWidget->editItem(item); +} + +void ItemListEditor::deleteListItemButtonClicked() +{ + int row = ui.listWidget->currentRow(); + + if (row != -1) { + delete ui.listWidget->takeItem(row); + emit itemDeleted(row); + } + + if (row == ui.listWidget->count()) + row--; + if (row < 0) + updateEditor(); + else + ui.listWidget->setCurrentRow(row); +} + +void ItemListEditor::moveListItemUpButtonClicked() +{ + int row = ui.listWidget->currentRow(); + if (row <= 0) + return; // nothing to do + + ui.listWidget->insertItem(row - 1, ui.listWidget->takeItem(row)); + ui.listWidget->setCurrentRow(row - 1); + emit itemMovedUp(row); +} + +void ItemListEditor::moveListItemDownButtonClicked() +{ + int row = ui.listWidget->currentRow(); + if (row == -1 || row == ui.listWidget->count() - 1) + return; // nothing to do + + ui.listWidget->insertItem(row + 1, ui.listWidget->takeItem(row)); + ui.listWidget->setCurrentRow(row + 1); + emit itemMovedDown(row); +} + +void ItemListEditor::listWidgetCurrentRowChanged() +{ + updateEditor(); + if (!m_updating) + emit indexChanged(ui.listWidget->currentRow()); +} + +void ItemListEditor::listWidgetItemChanged(QListWidgetItem *item) +{ + if (m_updatingBrowser) + return; + + PropertySheetStringValue val = qvariant_cast(item->data(Qt::DisplayPropertyRole)); + val.setValue(item->text()); + BoolBlocker block(m_updatingBrowser); + item->setData(Qt::DisplayPropertyRole, QVariant::fromValue(val)); + + // The checkState could change, too, but if this signal is connected, + // checkState is not in the list anyway, as we are editing a header item. + emit itemChanged(ui.listWidget->currentRow(), Qt::DisplayPropertyRole, + QVariant::fromValue(val)); + updateBrowser(); +} + +void ItemListEditor::togglePropertyBrowser() +{ + setPropertyBrowserVisible(!m_propertyBrowser->isVisible()); +} + +void ItemListEditor::setPropertyBrowserVisible(bool v) +{ + ui.showPropertiesButton->setText(v ? tr("Properties &>>") : tr("Properties &<<")); + m_propertyBrowser->setVisible(v); +} + +void ItemListEditor::setItemData(int role, const QVariant &v) +{ + QListWidgetItem *item = ui.listWidget->currentItem(); + bool reLayout = false; + if ((role == Qt::EditRole + && (v.toString().count(u'\n') != item->data(role).toString().count(u'\n'))) + || role == Qt::FontRole) { + reLayout = true; + } + QVariant newValue = v; + if (role == Qt::FontRole && newValue.metaType().id() == QMetaType::QFont) { + QFont oldFont = ui.listWidget->font(); + QFont newFont = qvariant_cast(newValue).resolve(oldFont); + newValue = QVariant::fromValue(newFont); + item->setData(role, QVariant()); // force the right font with the current resolve mask is set (item view bug) + } + item->setData(role, newValue); + if (reLayout) + ui.listWidget->doItemsLayout(); + emit itemChanged(ui.listWidget->currentRow(), role, newValue); +} + +QVariant ItemListEditor::getItemData(int role) const +{ + return ui.listWidget->currentItem()->data(role); +} + +int ItemListEditor::defaultItemFlags() const +{ + static const int flags = QListWidgetItem().flags(); + return flags; +} + +void ItemListEditor::cacheReloaded() +{ + reloadIconResources(iconCache(), ui.listWidget); +} + +void ItemListEditor::updateEditor() +{ + bool currentItemEnabled = false; + + bool moveRowUpEnabled = false; + bool moveRowDownEnabled = false; + + QListWidgetItem *item = ui.listWidget->currentItem(); + if (item) { + currentItemEnabled = true; + int currentRow = ui.listWidget->currentRow(); + if (currentRow > 0) + moveRowUpEnabled = true; + if (currentRow < ui.listWidget->count() - 1) + moveRowDownEnabled = true; + } + + ui.moveListItemUpButton->setEnabled(moveRowUpEnabled); + ui.moveListItemDownButton->setEnabled(moveRowDownEnabled); + ui.deleteListItemButton->setEnabled(currentItemEnabled); + + if (item) + updateBrowser(); + else + m_propertyBrowser->clear(); +} + +uint ItemListEditor::alignDefault() const +{ + return m_alignDefault; +} + +void ItemListEditor::setAlignDefault(uint newAlignDefault) +{ + m_alignDefault = newAlignDefault; +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/itemlisteditor.h b/src/designer/src/components/taskmenu/itemlisteditor.h new file mode 100644 index 0000000..084a329 --- /dev/null +++ b/src/designer/src/components/taskmenu/itemlisteditor.h @@ -0,0 +1,140 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ITEMLISTEDITOR_H +#define ITEMLISTEDITOR_H + +#include "ui_itemlisteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QtProperty; +class QtVariantProperty; +class QtTreePropertyBrowser; +class QSplitter; +class QVBoxLayout; + +namespace qdesigner_internal { + +class DesignerIconCache; +class DesignerPropertyManager; +class DesignerEditorFactory; + +// Utility class that ensures a bool is true while in scope. +// Courtesy of QBoolBlocker in qobject_p.h +class BoolBlocker +{ +public: + Q_DISABLE_COPY_MOVE(BoolBlocker); + + inline explicit BoolBlocker(bool &b) noexcept : block(b), reset(b) { block = true; } + inline ~BoolBlocker() noexcept { block = reset; } +private: + bool █ + bool reset; +}; + +class AbstractItemEditor: public QWidget +{ + Q_OBJECT + +public: + explicit AbstractItemEditor(QDesignerFormWindowInterface *form, QWidget *parent); + ~AbstractItemEditor(); + + DesignerIconCache *iconCache() const { return m_iconCache; } + + struct PropertyDefinition { + int role; + int type; + int (*typeFunc)(); + const char *name; + }; + +public slots: + void cacheReloaded(); + +private slots: + void propertyChanged(QtProperty *property); + void resetProperty(QtProperty *property); + +protected: + virtual int defaultItemFlags() const = 0; + void setupProperties(const PropertyDefinition *propList, + Qt::Alignment alignDefault = Qt::AlignLeading | Qt::AlignVCenter); + void setupObject(QWidget *object); + void setupEditor(QWidget *object, const PropertyDefinition *propDefs, + Qt::Alignment alignDefault = Qt::AlignLeading | Qt::AlignVCenter); + void injectPropertyBrowser(QWidget *parent, QWidget *widget); + void updateBrowser(); + virtual void setItemData(int role, const QVariant &v) = 0; + virtual QVariant getItemData(int role) const = 0; + + DesignerIconCache *m_iconCache; + DesignerPropertyManager *m_propertyManager; + DesignerEditorFactory *m_editorFactory; + QSplitter *m_propertySplitter = nullptr; + QtTreePropertyBrowser *m_propertyBrowser; + QList m_properties; + QList m_rootProperties; + QHash m_propertyToRole; + bool m_updatingBrowser = false; +}; + +class ItemListEditor: public AbstractItemEditor +{ + Q_OBJECT + +public: + explicit ItemListEditor(QDesignerFormWindowInterface *form, QWidget *parent); + + void setupEditor(QWidget *object, const PropertyDefinition *propDefs, + Qt::Alignment alignDefault = Qt::AlignLeading | Qt::AlignVCenter); + QListWidget *listWidget() const { return ui.listWidget; } + void setNewItemText(const QString &tpl) { m_newItemText = tpl; } + QString newItemText() const { return m_newItemText; } + void setCurrentIndex(int idx); + + uint alignDefault() const; + void setAlignDefault(uint newAlignDefault); + +signals: + void indexChanged(int idx); + void itemChanged(int idx, int role, const QVariant &v); + void itemInserted(int idx); + void itemDeleted(int idx); + void itemMovedUp(int idx); + void itemMovedDown(int idx); + +private slots: + void newListItemButtonClicked(); + void deleteListItemButtonClicked(); + void moveListItemUpButtonClicked(); + void moveListItemDownButtonClicked(); + void listWidgetCurrentRowChanged(); + void listWidgetItemChanged(QListWidgetItem * item); + void togglePropertyBrowser(); + void cacheReloaded(); + +protected: + void setItemData(int role, const QVariant &v) override; + QVariant getItemData(int role) const override; + int defaultItemFlags() const override; + +private: + void setPropertyBrowserVisible(bool v); + void updateEditor(); + Ui::ItemListEditor ui; + uint m_alignDefault = 0; + bool m_updating; + QString m_newItemText; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ITEMLISTEDITOR_H diff --git a/src/designer/src/components/taskmenu/itemlisteditor.ui b/src/designer/src/components/taskmenu/itemlisteditor.ui new file mode 100644 index 0000000..75394ac --- /dev/null +++ b/src/designer/src/components/taskmenu/itemlisteditor.ui @@ -0,0 +1,120 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::ItemListEditor + + + + 0 + 0 + 550 + 360 + + + + + + + + + + + 0 + + + + + true + + + Items List + + + + + + + + + New Item + + + &New + + + + + + + Delete Item + + + &Delete + + + + + + + Qt::Horizontal + + + + 16 + 10 + + + + + + + + Move Item Up + + + U + + + + + + + Move Item Down + + + D + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Properties &>> + + + + + + + + + + + + + diff --git a/src/designer/src/components/taskmenu/label_taskmenu.cpp b/src/designer/src/components/taskmenu/label_taskmenu.cpp new file mode 100644 index 0000000..14a58d8 --- /dev/null +++ b/src/designer/src/components/taskmenu/label_taskmenu.cpp @@ -0,0 +1,82 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "label_taskmenu.h" +#include "inplace_editor.h" + +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto textPropertyC = "text"_L1; + +namespace qdesigner_internal { + +// -------- LabelTaskMenuInlineEditor +class LabelTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + LabelTaskMenuInlineEditor(QLabel *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +LabelTaskMenuInlineEditor::LabelTaskMenuInlineEditor(QLabel *w, QObject *parent) : + TaskMenuInlineEditor(w, ValidationRichText, textPropertyC, parent) +{ +} + +QRect LabelTaskMenuInlineEditor::editRectangle() const +{ + QStyleOptionButton opt; + opt.initFrom(widget()); + return opt.rect; +} + +// --------------- LabelTaskMenu + +LabelTaskMenu::LabelTaskMenu(QLabel *label, QObject *parent) + : QDesignerTaskMenu(label, parent), + m_label(label), + m_editRichTextAction(new QAction(tr("Change rich text..."), this)), + m_editPlainTextAction(new QAction(tr("Change plain text..."), this)) +{ + LabelTaskMenuInlineEditor *editor = new LabelTaskMenuInlineEditor(label, this); + connect(m_editPlainTextAction, &QAction::triggered, editor, &LabelTaskMenuInlineEditor::editText); + m_taskActions.append(m_editPlainTextAction); + + connect(m_editRichTextAction, &QAction::triggered, this, &LabelTaskMenu::editRichText); + m_taskActions.append(m_editRichTextAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +QAction *LabelTaskMenu::preferredEditAction() const +{ + if (m_label->textFormat () == Qt::PlainText) return m_editPlainTextAction; + return Qt::mightBeRichText(m_label->text()) ? m_editRichTextAction : m_editPlainTextAction; +} + +QList LabelTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void LabelTaskMenu::editRichText() +{ + changeTextProperty(textPropertyC, QString(), MultiSelectionMode, m_label->textFormat()); +} + +} +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/label_taskmenu.h b/src/designer/src/components/taskmenu/label_taskmenu.h new file mode 100644 index 0000000..b7aed92 --- /dev/null +++ b/src/designer/src/components/taskmenu/label_taskmenu.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LABEL_TASKMENU_H +#define LABEL_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class LabelTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit LabelTaskMenu(QLabel *button, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editRichText(); + +private: + QLabel *m_label; + QList m_taskActions; + QAction *m_editRichTextAction; + QAction *m_editPlainTextAction; +}; + +using LabelTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LABEL_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/layouttaskmenu.cpp b/src/designer/src/components/taskmenu/layouttaskmenu.cpp new file mode 100644 index 0000000..256fd29 --- /dev/null +++ b/src/designer/src/components/taskmenu/layouttaskmenu.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "layouttaskmenu.h" +#include +#include + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +// ------------ LayoutWidgetTaskMenu +LayoutWidgetTaskMenu::LayoutWidgetTaskMenu(QLayoutWidget *lw, QObject *parent) : + QObject(parent), + m_widget(lw), + m_morphMenu(new qdesigner_internal::MorphMenu(this)), + m_formLayoutMenu(new qdesigner_internal::FormLayoutMenu(this)) +{ +} + +QAction *LayoutWidgetTaskMenu::preferredEditAction() const +{ + return m_formLayoutMenu->preferredEditAction(m_widget, m_widget->formWindow()); +} + +QList LayoutWidgetTaskMenu::taskActions() const +{ + QList rc; + QDesignerFormWindowInterface *fw = m_widget->formWindow(); + m_morphMenu->populate(m_widget, fw, rc); + m_formLayoutMenu->populate(m_widget, fw, rc); + return rc; +} + +// ------------- SpacerTaskMenu +SpacerTaskMenu::SpacerTaskMenu(Spacer *, QObject *parent) : + QObject(parent) +{ +} + +QAction *SpacerTaskMenu::preferredEditAction() const +{ + return nullptr; +} + +QList SpacerTaskMenu::taskActions() const +{ + return {}; +} + +QT_END_NAMESPACE + diff --git a/src/designer/src/components/taskmenu/layouttaskmenu.h b/src/designer/src/components/taskmenu/layouttaskmenu.h new file mode 100644 index 0000000..d0b3740 --- /dev/null +++ b/src/designer/src/components/taskmenu/layouttaskmenu.h @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LAYOUTTASKMENU_H +#define LAYOUTTASKMENU_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + class FormLayoutMenu; + class MorphMenu; +} + +// Morph menu for QLayoutWidget. +class LayoutWidgetTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit LayoutWidgetTaskMenu(QLayoutWidget *w, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QLayoutWidget *m_widget; + qdesigner_internal::MorphMenu *m_morphMenu; + qdesigner_internal::FormLayoutMenu *m_formLayoutMenu; +}; + +// Empty task menu for spacers. +class SpacerTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit SpacerTaskMenu(Spacer *bar, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +}; + +using LayoutWidgetTaskMenuFactory = qdesigner_internal::ExtensionFactory; +using SpacerTaskMenuFactory = qdesigner_internal::ExtensionFactory; + +QT_END_NAMESPACE + +#endif // LAYOUTTASKMENU_H diff --git a/src/designer/src/components/taskmenu/lineedit_taskmenu.cpp b/src/designer/src/components/taskmenu/lineedit_taskmenu.cpp new file mode 100644 index 0000000..88dcf45 --- /dev/null +++ b/src/designer/src/components/taskmenu/lineedit_taskmenu.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "lineedit_taskmenu.h" +#include "inplace_editor.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// -------- LineEditTaskMenuInlineEditor +class LineEditTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + LineEditTaskMenuInlineEditor(QLineEdit *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +LineEditTaskMenuInlineEditor::LineEditTaskMenuInlineEditor(QLineEdit *w, QObject *parent) : + TaskMenuInlineEditor(w, ValidationSingleLine, u"text"_s, parent) +{ +} + +QRect LineEditTaskMenuInlineEditor::editRectangle() const +{ + QStyleOption opt; + opt.initFrom(widget()); + return opt.rect; +} + +// --------------- LineEditTaskMenu +LineEditTaskMenu::LineEditTaskMenu(QLineEdit *lineEdit, QObject *parent) : + QDesignerTaskMenu(lineEdit, parent), + m_editTextAction(new QAction(tr("Change text..."), this)) +{ + TaskMenuInlineEditor *editor = new LineEditTaskMenuInlineEditor(lineEdit, this); + connect(m_editTextAction, &QAction::triggered, editor, &LineEditTaskMenuInlineEditor::editText); + m_taskActions.append(m_editTextAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +QAction *LineEditTaskMenu::preferredEditAction() const +{ + return m_editTextAction; +} + +QList LineEditTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/lineedit_taskmenu.h b/src/designer/src/components/taskmenu/lineedit_taskmenu.h new file mode 100644 index 0000000..efc0002 --- /dev/null +++ b/src/designer/src/components/taskmenu/lineedit_taskmenu.h @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LINEEDIT_TASKMENU_H +#define LINEEDIT_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class LineEditTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit LineEditTaskMenu(QLineEdit *button, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QList m_taskActions; + QAction *m_editTextAction; +}; + +using LineEditTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LINEEDIT_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/listwidget_taskmenu.cpp b/src/designer/src/components/taskmenu/listwidget_taskmenu.cpp new file mode 100644 index 0000000..4bb08c4 --- /dev/null +++ b/src/designer/src/components/taskmenu/listwidget_taskmenu.cpp @@ -0,0 +1,80 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "listwidget_taskmenu.h" +#include "listwidgeteditor.h" +#include "qdesigner_utils_p.h" +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +ListWidgetTaskMenu::ListWidgetTaskMenu(QListWidget *button, QObject *parent) + : QDesignerTaskMenu(button, parent), + m_listWidget(button) +{ + m_editItemsAction = new QAction(this); + m_editItemsAction->setText(tr("Edit Items...")); + connect(m_editItemsAction, &QAction::triggered, this, &ListWidgetTaskMenu::editItems); + m_taskActions.append(m_editItemsAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +ListWidgetTaskMenu::~ListWidgetTaskMenu() = default; + +QAction *ListWidgetTaskMenu::preferredEditAction() const +{ + return m_editItemsAction; +} + +QList ListWidgetTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void ListWidgetTaskMenu::editItems() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_listWidget); + if (m_formWindow.isNull()) + return; + + Q_ASSERT(m_listWidget != nullptr); + + ListWidgetEditor dlg(m_formWindow, m_listWidget->window()); + ListContents oldItems = dlg.fillContentsFromListWidget(m_listWidget); + if (dlg.exec() == QDialog::Accepted) { + ListContents items = dlg.contents(); + if (items != oldItems) { + ChangeListContentsCommand *cmd = new ChangeListContentsCommand(m_formWindow); + cmd->init(m_listWidget, oldItems, items); + cmd->setText(tr("Change List Contents")); + m_formWindow->commandHistory()->push(cmd); + } + } +} + +void ListWidgetTaskMenu::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/listwidget_taskmenu.h b/src/designer/src/components/taskmenu/listwidget_taskmenu.h new file mode 100644 index 0000000..a08d4b6 --- /dev/null +++ b/src/designer/src/components/taskmenu/listwidget_taskmenu.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LISTWIDGET_TASKMENU_H +#define LISTWIDGET_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLineEdit; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class ListWidgetTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit ListWidgetTaskMenu(QListWidget *button, QObject *parent = nullptr); + ~ListWidgetTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editItems(); + void updateSelection(); + +private: + QListWidget *m_listWidget; + QPointer m_formWindow; + QPointer m_editor; + mutable QList m_taskActions; + QAction *m_editItemsAction; +}; + +using ListWidgetTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LISTWIDGET_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/listwidgeteditor.cpp b/src/designer/src/components/taskmenu/listwidgeteditor.cpp new file mode 100644 index 0000000..84e9b0a --- /dev/null +++ b/src/designer/src/components/taskmenu/listwidgeteditor.cpp @@ -0,0 +1,101 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "listwidgeteditor.h" +#include +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +ListWidgetEditor::ListWidgetEditor(QDesignerFormWindowInterface *form, + QWidget *parent) + : QDialog(parent) +{ + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + m_itemsEditor = new ItemListEditor(form, nullptr); + m_itemsEditor->layout()->setContentsMargins(QMargins()); + m_itemsEditor->setNewItemText(tr("New Item")); + + QFrame *sep = new QFrame; + sep->setFrameStyle(QFrame::HLine | QFrame::Sunken); + + QBoxLayout *box = new QVBoxLayout(this); + box->addWidget(m_itemsEditor); + box->addWidget(sep); + box->addWidget(buttonBox); + + // Numbers copied from itemlisteditor.ui + // (Automatic resizing doesn't work because ui has parent). + resize(550, 360); +} + +static AbstractItemEditor::PropertyDefinition listBoxPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, + { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QBrush, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { ItemFlagsShadowRole, 0, QtVariantPropertyManager::flagTypeId, "flags" }, + { Qt::CheckStateRole, 0, QtVariantPropertyManager::enumTypeId, "checkState" }, + { 0, 0, nullptr, nullptr } +}; + +ListContents ListWidgetEditor::fillContentsFromListWidget(QListWidget *listWidget) +{ + setWindowTitle(tr("Edit List Widget")); + + ListContents retVal; + retVal.createFromListWidget(listWidget, false); + retVal.applyToListWidget(m_itemsEditor->listWidget(), m_itemsEditor->iconCache(), true); + + m_itemsEditor->setupEditor(listWidget, listBoxPropList); + + return retVal; +} + +static AbstractItemEditor::PropertyDefinition comboBoxPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { 0, 0, nullptr, nullptr } +}; + +ListContents ListWidgetEditor::fillContentsFromComboBox(QComboBox *comboBox) +{ + setWindowTitle(tr("Edit Combobox")); + + ListContents retVal; + retVal.createFromComboBox(comboBox); + retVal.applyToListWidget(m_itemsEditor->listWidget(), m_itemsEditor->iconCache(), true); + + m_itemsEditor->setupEditor(comboBox, comboBoxPropList); + + return retVal; +} + +ListContents ListWidgetEditor::contents() const +{ + ListContents retVal; + retVal.createFromListWidget(m_itemsEditor->listWidget(), true); + return retVal; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/listwidgeteditor.h b/src/designer/src/components/taskmenu/listwidgeteditor.h new file mode 100644 index 0000000..b16f63b --- /dev/null +++ b/src/designer/src/components/taskmenu/listwidgeteditor.h @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LISTWIDGETEDITOR_H +#define LISTWIDGETEDITOR_H + +#include "itemlisteditor.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +class QListWidget; +class QComboBox; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class ListWidgetEditor: public QDialog +{ + Q_OBJECT + +public: + ListWidgetEditor(QDesignerFormWindowInterface *form, + QWidget *parent); + + ListContents fillContentsFromListWidget(QListWidget *listWidget); + ListContents fillContentsFromComboBox(QComboBox *comboBox); + ListContents contents() const; + +private: + ItemListEditor *m_itemsEditor; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LISTWIDGETEDITOR_H diff --git a/src/designer/src/components/taskmenu/menutaskmenu.cpp b/src/designer/src/components/taskmenu/menutaskmenu.cpp new file mode 100644 index 0000000..6b95a1b --- /dev/null +++ b/src/designer/src/components/taskmenu/menutaskmenu.cpp @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "menutaskmenu.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + // ------------ MenuTaskMenu + MenuTaskMenu::MenuTaskMenu(QDesignerMenu *menu, QObject *parent) : + QObject(parent), + m_menu(menu), + m_removeAction(new QAction(tr("Remove"), this)), + m_promotionTaskMenu(new PromotionTaskMenu(menu, PromotionTaskMenu::ModeSingleWidget, this)) + { + connect(m_removeAction, &QAction::triggered, this, &MenuTaskMenu::removeMenu); + } + + QAction *MenuTaskMenu::preferredEditAction() const + { + return nullptr; + } + + QList MenuTaskMenu::taskActions() const + { + QList rc; + rc.push_back(m_removeAction); + m_promotionTaskMenu->addActions(PromotionTaskMenu::LeadingSeparator, rc); + return rc; + } + + void MenuTaskMenu::removeMenu() + { + // Are we on a menu bar or on a menu? + QWidget *pw = m_menu->parentWidget(); + if (QDesignerMenuBar *mb = qobject_cast(pw)) { + mb->deleteMenuAction(m_menu->menuAction()); + return; + } + if (QDesignerMenu *m = qobject_cast(pw)) { + m->deleteAction(m_menu->menuAction()); + } + } + + // ------------- MenuBarTaskMenu + MenuBarTaskMenu::MenuBarTaskMenu(QDesignerMenuBar *bar, QObject *parent) : + QObject(parent), + m_bar(bar) + { + } + + QAction *MenuBarTaskMenu::preferredEditAction() const + { + return nullptr; + } + + QList MenuBarTaskMenu::taskActions() const + { + return m_bar->contextMenuActions(); + } +} + +QT_END_NAMESPACE + diff --git a/src/designer/src/components/taskmenu/menutaskmenu.h b/src/designer/src/components/taskmenu/menutaskmenu.h new file mode 100644 index 0000000..1d290d4 --- /dev/null +++ b/src/designer/src/components/taskmenu/menutaskmenu.h @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MENUTASKMENU_H +#define MENUTASKMENU_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + + class PromotionTaskMenu; + +// The QMenu task menu provides promotion and a remove option. The actual +// menu context options are not forwarded since they make only sense +// when a menu is being edited/visible. + +class MenuTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit MenuTaskMenu(QDesignerMenu *menu, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void removeMenu(); + +private: + QDesignerMenu *m_menu; + QAction *m_removeAction; + PromotionTaskMenu *m_promotionTaskMenu; +}; + +// The QMenuBar task menu forwards the actions of QDesignerMenuBar, +// making them available in the object inspector. + +class MenuBarTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit MenuBarTaskMenu(QDesignerMenuBar *bar, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QDesignerMenuBar *m_bar; +}; + +using MenuTaskMenuFactory = ExtensionFactory; +using MenuBarTaskMenuFactory = ExtensionFactory; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // MENUTASKMENU_H diff --git a/src/designer/src/components/taskmenu/tablewidget_taskmenu.cpp b/src/designer/src/components/taskmenu/tablewidget_taskmenu.cpp new file mode 100644 index 0000000..9ace1ea --- /dev/null +++ b/src/designer/src/components/taskmenu/tablewidget_taskmenu.cpp @@ -0,0 +1,78 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tablewidget_taskmenu.h" +#include "tablewidgeteditor.h" + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +TableWidgetTaskMenu::TableWidgetTaskMenu(QTableWidget *button, QObject *parent) + : QDesignerTaskMenu(button, parent), + m_tableWidget(button), + m_editItemsAction(new QAction(tr("Edit Items..."), this)) +{ + connect(m_editItemsAction, &QAction::triggered, this, &TableWidgetTaskMenu::editItems); + m_taskActions.append(m_editItemsAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + + +TableWidgetTaskMenu::~TableWidgetTaskMenu() = default; + +QAction *TableWidgetTaskMenu::preferredEditAction() const +{ + return m_editItemsAction; +} + +QList TableWidgetTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void TableWidgetTaskMenu::editItems() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_tableWidget); + if (m_formWindow.isNull()) + return; + + Q_ASSERT(m_tableWidget != nullptr); + + TableWidgetEditorDialog dlg(m_formWindow, m_tableWidget->window()); + TableWidgetContents oldCont = dlg.fillContentsFromTableWidget(m_tableWidget); + if (dlg.exec() == QDialog::Accepted) { + TableWidgetContents newCont = dlg.contents(); + if (newCont != oldCont) { + ChangeTableContentsCommand *cmd = new ChangeTableContentsCommand(m_formWindow); + cmd->init(m_tableWidget, oldCont, newCont); + m_formWindow->commandHistory()->push(cmd); + } + } +} + +void TableWidgetTaskMenu::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/tablewidget_taskmenu.h b/src/designer/src/components/taskmenu/tablewidget_taskmenu.h new file mode 100644 index 0000000..6c3c79c --- /dev/null +++ b/src/designer/src/components/taskmenu/tablewidget_taskmenu.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABLEWIDGET_TASKMENU_H +#define TABLEWIDGET_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLineEdit; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class TableWidgetTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit TableWidgetTaskMenu(QTableWidget *button, QObject *parent = nullptr); + ~TableWidgetTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editItems(); + void updateSelection(); + +private: + QTableWidget *m_tableWidget; + QPointer m_formWindow; + QPointer m_editor; + mutable QList m_taskActions; + QAction *m_editItemsAction; +}; + +using TableWidgetTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TABLEWIDGET_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/tablewidgeteditor.cpp b/src/designer/src/components/taskmenu/tablewidgeteditor.cpp new file mode 100644 index 0000000..8664fd5 --- /dev/null +++ b/src/designer/src/components/taskmenu/tablewidgeteditor.cpp @@ -0,0 +1,427 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tablewidgeteditor.h" +#include +#include +#include +#include "formwindowbase_p.h" +#include "qdesigner_utils_p.h" +#include +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TableWidgetEditor::TableWidgetEditor(QDesignerFormWindowInterface *form, QDialog *dialog) + : AbstractItemEditor(form, nullptr), m_updatingBrowser(false) +{ + m_columnEditor = new ItemListEditor(form, this); + m_columnEditor->setObjectName(u"columnEditor"_s); + m_columnEditor->setAlignDefault(Qt::AlignCenter); + m_columnEditor->setNewItemText(tr("New Column")); + m_rowEditor = new ItemListEditor(form, this); + m_rowEditor->setObjectName(u"rowEditor"_s); + m_rowEditor->setNewItemText(tr("New Row")); + ui.setupUi(dialog); + + injectPropertyBrowser(ui.itemsTab, ui.widget); + connect(ui.showPropertiesButton, &QAbstractButton::clicked, + this, &TableWidgetEditor::togglePropertyBrowser); + setPropertyBrowserVisible(false); + + ui.tabWidget->insertTab(0, m_columnEditor, tr("&Columns")); + ui.tabWidget->insertTab(1, m_rowEditor, tr("&Rows")); + ui.tabWidget->setCurrentIndex(0); + + ui.tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(iconCache(), &DesignerIconCache::reloaded, this, &TableWidgetEditor::cacheReloaded); + + connect(ui.tableWidget, &QTableWidget::currentCellChanged, + this, &TableWidgetEditor::tableWidgetCurrentCellChanged); + connect(ui.tableWidget, &QTableWidget::itemChanged, + this, &TableWidgetEditor::tableWidgetItemChanged); + connect(m_columnEditor, &ItemListEditor::indexChanged, + this, &TableWidgetEditor::columnEditorIndexChanged); + connect(m_columnEditor, &ItemListEditor::itemChanged, + this, &TableWidgetEditor::columnEditorItemChanged); + connect(m_columnEditor, &ItemListEditor::itemInserted, + this, &TableWidgetEditor::columnEditorItemInserted); + connect(m_columnEditor, &ItemListEditor::itemDeleted, + this, &TableWidgetEditor::columnEditorItemDeleted); + connect(m_columnEditor, &ItemListEditor::itemMovedUp, + this, &TableWidgetEditor::columnEditorItemMovedUp); + connect(m_columnEditor, &ItemListEditor::itemMovedDown, + this, &TableWidgetEditor::columnEditorItemMovedDown); + + connect(m_rowEditor, &ItemListEditor::indexChanged, + this, &TableWidgetEditor::rowEditorIndexChanged); + connect(m_rowEditor, &ItemListEditor::itemChanged, + this, &TableWidgetEditor::rowEditorItemChanged); + connect(m_rowEditor, &ItemListEditor::itemInserted, + this, &TableWidgetEditor::rowEditorItemInserted); + connect(m_rowEditor, &ItemListEditor::itemDeleted, + this, &TableWidgetEditor::rowEditorItemDeleted); + connect(m_rowEditor, &ItemListEditor::itemMovedUp, + this, &TableWidgetEditor::rowEditorItemMovedUp); + connect(m_rowEditor, &ItemListEditor::itemMovedDown, + this, &TableWidgetEditor::rowEditorItemMovedDown); +} + +static AbstractItemEditor::PropertyDefinition tableHeaderPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, +// { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QColor, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { 0, 0, nullptr, nullptr } +}; + +static AbstractItemEditor::PropertyDefinition tableItemPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, +// { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QBrush, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { ItemFlagsShadowRole, 0, QtVariantPropertyManager::flagTypeId, "flags" }, + { Qt::CheckStateRole, 0, QtVariantPropertyManager::enumTypeId, "checkState" }, + { 0, 0, nullptr, nullptr } +}; + +TableWidgetContents TableWidgetEditor::fillContentsFromTableWidget(QTableWidget *tableWidget) +{ + TableWidgetContents tblCont; + tblCont.fromTableWidget(tableWidget, false); + tblCont.applyToTableWidget(ui.tableWidget, iconCache(), true); + + auto *header = tableWidget->verticalHeader(); + auto headerAlignment = header != nullptr + ? header->defaultAlignment() : Qt::Alignment(Qt::AlignLeading | Qt::AlignVCenter); + tblCont.m_verticalHeader.applyToListWidget(m_rowEditor->listWidget(), iconCache(), + true, headerAlignment); + m_rowEditor->setupEditor(tableWidget, tableHeaderPropList, headerAlignment); + + header = tableWidget->horizontalHeader(); + headerAlignment = header != nullptr + ? header->defaultAlignment() : Qt::Alignment(Qt::AlignCenter); + tblCont.m_horizontalHeader.applyToListWidget(m_columnEditor->listWidget(), iconCache(), + true, headerAlignment); + m_columnEditor->setupEditor(tableWidget, tableHeaderPropList, headerAlignment); + + setupEditor(tableWidget, tableItemPropList); + if (ui.tableWidget->columnCount() > 0 && ui.tableWidget->rowCount() > 0) + ui.tableWidget->setCurrentCell(0, 0); + + updateEditor(); + + return tblCont; +} + +TableWidgetContents TableWidgetEditor::contents() const +{ + TableWidgetContents retVal; + retVal.fromTableWidget(ui.tableWidget, true); + return retVal; +} + +void TableWidgetEditor::setItemData(int role, const QVariant &v) +{ + QTableWidgetItem *item = ui.tableWidget->currentItem(); + BoolBlocker block(m_updatingBrowser); + if (!item) { + item = new QTableWidgetItem; + ui.tableWidget->setItem(ui.tableWidget->currentRow(), ui.tableWidget->currentColumn(), item); + } + QVariant newValue = v; + if (role == Qt::FontRole && newValue.metaType().id() == QMetaType::QFont) { + QFont oldFont = ui.tableWidget->font(); + QFont newFont = qvariant_cast(newValue).resolve(oldFont); + newValue = QVariant::fromValue(newFont); + item->setData(role, QVariant()); // force the right font with the current resolve mask is set (item view bug) + } + item->setData(role, newValue); +} + +QVariant TableWidgetEditor::getItemData(int role) const +{ + QTableWidgetItem *item = ui.tableWidget->currentItem(); + if (!item) + return QVariant(); + return item->data(role); +} + +int TableWidgetEditor::defaultItemFlags() const +{ + static const int flags = QTableWidgetItem().flags(); + return flags; +} + +void TableWidgetEditor::tableWidgetCurrentCellChanged(int currentRow, int currentCol) +{ + m_rowEditor->setCurrentIndex(currentRow); + m_columnEditor->setCurrentIndex(currentCol); + updateBrowser(); +} + +void TableWidgetEditor::tableWidgetItemChanged(QTableWidgetItem *item) +{ + if (m_updatingBrowser) + return; + + PropertySheetStringValue val = qvariant_cast(item->data(Qt::DisplayPropertyRole)); + val.setValue(item->text()); + BoolBlocker block(m_updatingBrowser); + item->setData(Qt::DisplayPropertyRole, QVariant::fromValue(val)); + + updateBrowser(); +} + +void TableWidgetEditor::columnEditorIndexChanged(int col) +{ + ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow(), col); +} + +void TableWidgetEditor::columnEditorItemChanged(int idx, int role, const QVariant &v) +{ + ui.tableWidget->horizontalHeaderItem(idx)->setData(role, v); +} + +void TableWidgetEditor::rowEditorIndexChanged(int col) +{ + ui.tableWidget->setCurrentCell(col, ui.tableWidget->currentColumn()); +} + +void TableWidgetEditor::rowEditorItemChanged(int idx, int role, const QVariant &v) +{ + ui.tableWidget->verticalHeaderItem(idx)->setData(role, v); +} + +void TableWidgetEditor::setPropertyBrowserVisible(bool v) +{ + ui.showPropertiesButton->setText(v ? tr("Properties &>>") : tr("Properties &<<")); + m_propertyBrowser->setVisible(v); +} + +void TableWidgetEditor::togglePropertyBrowser() +{ + setPropertyBrowserVisible(!m_propertyBrowser->isVisible()); +} + +void TableWidgetEditor::updateEditor() +{ + const bool wasEnabled = ui.tabWidget->isTabEnabled(2); + const bool isEnabled = ui.tableWidget->columnCount() && ui.tableWidget->rowCount(); + ui.tabWidget->setTabEnabled(2, isEnabled); + if (!wasEnabled && isEnabled) + ui.tableWidget->setCurrentCell(0, 0); + + QMetaObject::invokeMethod(ui.tableWidget, "updateGeometries"); + ui.tableWidget->viewport()->update(); +} + +void TableWidgetEditor::moveColumnsLeft(int fromColumn, int toColumn) +{ + if (fromColumn >= toColumn) + return; + + QTableWidgetItem *lastItem = ui.tableWidget->takeHorizontalHeaderItem(toColumn); + for (int i = toColumn; i > fromColumn; i--) { + ui.tableWidget->setHorizontalHeaderItem(i, + ui.tableWidget->takeHorizontalHeaderItem(i - 1)); + } + ui.tableWidget->setHorizontalHeaderItem(fromColumn, lastItem); + + for (int i = 0; i < ui.tableWidget->rowCount(); i++) { + QTableWidgetItem *lastItem = ui.tableWidget->takeItem(i, toColumn); + for (int j = toColumn; j > fromColumn; j--) + ui.tableWidget->setItem(i, j, ui.tableWidget->takeItem(i, j - 1)); + ui.tableWidget->setItem(i, fromColumn, lastItem); + } +} + +void TableWidgetEditor::moveColumnsRight(int fromColumn, int toColumn) +{ + if (fromColumn >= toColumn) + return; + + QTableWidgetItem *lastItem = ui.tableWidget->takeHorizontalHeaderItem(fromColumn); + for (int i = fromColumn; i < toColumn; i++) { + ui.tableWidget->setHorizontalHeaderItem(i, + ui.tableWidget->takeHorizontalHeaderItem(i + 1)); + } + ui.tableWidget->setHorizontalHeaderItem(toColumn, lastItem); + + for (int i = 0; i < ui.tableWidget->rowCount(); i++) { + QTableWidgetItem *lastItem = ui.tableWidget->takeItem(i, fromColumn); + for (int j = fromColumn; j < toColumn; j++) + ui.tableWidget->setItem(i, j, ui.tableWidget->takeItem(i, j + 1)); + ui.tableWidget->setItem(i, toColumn, lastItem); + } +} + +void TableWidgetEditor::moveRowsDown(int fromRow, int toRow) +{ + if (fromRow >= toRow) + return; + + QTableWidgetItem *lastItem = ui.tableWidget->takeVerticalHeaderItem(toRow); + for (int i = toRow; i > fromRow; i--) { + ui.tableWidget->setVerticalHeaderItem(i, + ui.tableWidget->takeVerticalHeaderItem(i - 1)); + } + ui.tableWidget->setVerticalHeaderItem(fromRow, lastItem); + + for (int i = 0; i < ui.tableWidget->columnCount(); i++) { + QTableWidgetItem *lastItem = ui.tableWidget->takeItem(toRow, i); + for (int j = toRow; j > fromRow; j--) + ui.tableWidget->setItem(j, i, ui.tableWidget->takeItem(j - 1, i)); + ui.tableWidget->setItem(fromRow, i, lastItem); + } +} + +void TableWidgetEditor::moveRowsUp(int fromRow, int toRow) +{ + if (fromRow >= toRow) + return; + + QTableWidgetItem *lastItem = ui.tableWidget->takeVerticalHeaderItem(fromRow); + for (int i = fromRow; i < toRow; i++) { + ui.tableWidget->setVerticalHeaderItem(i, + ui.tableWidget->takeVerticalHeaderItem(i + 1)); + } + ui.tableWidget->setVerticalHeaderItem(toRow, lastItem); + + for (int i = 0; i < ui.tableWidget->columnCount(); i++) { + QTableWidgetItem *lastItem = ui.tableWidget->takeItem(fromRow, i); + for (int j = fromRow; j < toRow; j++) + ui.tableWidget->setItem(j, i, ui.tableWidget->takeItem(j + 1, i)); + ui.tableWidget->setItem(toRow, i, lastItem); + } +} + +void TableWidgetEditor::columnEditorItemInserted(int idx) +{ + const int columnCount = ui.tableWidget->columnCount(); + ui.tableWidget->setColumnCount(columnCount + 1); + + QTableWidgetItem *newItem = new QTableWidgetItem(m_columnEditor->newItemText()); + newItem->setData(Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(m_columnEditor->newItemText()))); + ui.tableWidget->setHorizontalHeaderItem(columnCount, newItem); + + moveColumnsLeft(idx, columnCount); + + int row = ui.tableWidget->currentRow(); + if (row >= 0) + ui.tableWidget->setCurrentCell(row, idx); + + updateEditor(); +} + +void TableWidgetEditor::columnEditorItemDeleted(int idx) +{ + const int columnCount = ui.tableWidget->columnCount(); + + moveColumnsRight(idx, columnCount - 1); + ui.tableWidget->setColumnCount(columnCount - 1); + + updateEditor(); +} + +void TableWidgetEditor::columnEditorItemMovedUp(int idx) +{ + moveColumnsRight(idx - 1, idx); + + ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow(), idx - 1); +} + +void TableWidgetEditor::columnEditorItemMovedDown(int idx) +{ + moveColumnsLeft(idx, idx + 1); + + ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow(), idx + 1); +} + +void TableWidgetEditor::rowEditorItemInserted(int idx) +{ + const int rowCount = ui.tableWidget->rowCount(); + ui.tableWidget->setRowCount(rowCount + 1); + + QTableWidgetItem *newItem = new QTableWidgetItem(m_rowEditor->newItemText()); + newItem->setData(Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(m_rowEditor->newItemText()))); + ui.tableWidget->setVerticalHeaderItem(rowCount, newItem); + + moveRowsDown(idx, rowCount); + + int col = ui.tableWidget->currentColumn(); + if (col >= 0) + ui.tableWidget->setCurrentCell(idx, col); + + updateEditor(); +} + +void TableWidgetEditor::rowEditorItemDeleted(int idx) +{ + const int rowCount = ui.tableWidget->rowCount(); + + moveRowsUp(idx, rowCount - 1); + ui.tableWidget->setRowCount(rowCount - 1); + + updateEditor(); +} + +void TableWidgetEditor::rowEditorItemMovedUp(int idx) +{ + moveRowsUp(idx - 1, idx); + + ui.tableWidget->setCurrentCell(idx - 1, ui.tableWidget->currentColumn()); +} + +void TableWidgetEditor::rowEditorItemMovedDown(int idx) +{ + moveRowsDown(idx, idx + 1); + + ui.tableWidget->setCurrentCell(idx + 1, ui.tableWidget->currentColumn()); +} + +void TableWidgetEditor::cacheReloaded() +{ + reloadIconResources(iconCache(), ui.tableWidget); +} + +TableWidgetEditorDialog::TableWidgetEditorDialog(QDesignerFormWindowInterface *form, QWidget *parent) : + QDialog(parent), m_editor(form, this) +{ +} + +TableWidgetContents TableWidgetEditorDialog::fillContentsFromTableWidget(QTableWidget *tableWidget) +{ + return m_editor.fillContentsFromTableWidget(tableWidget); +} + +TableWidgetContents TableWidgetEditorDialog::contents() const +{ + return m_editor.contents(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/tablewidgeteditor.h b/src/designer/src/components/taskmenu/tablewidgeteditor.h new file mode 100644 index 0000000..defc05f --- /dev/null +++ b/src/designer/src/components/taskmenu/tablewidgeteditor.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABLEWIDGETEDITOR_H +#define TABLEWIDGETEDITOR_H + +#include "ui_tablewidgeteditor.h" + +#include "listwidgeteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +class QTableWidget; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class FormWindowBase; +class PropertySheetIconValue; + +class TableWidgetEditor: public AbstractItemEditor +{ + Q_OBJECT +public: + explicit TableWidgetEditor(QDesignerFormWindowInterface *form, QDialog *dialog); + + TableWidgetContents fillContentsFromTableWidget(QTableWidget *tableWidget); + TableWidgetContents contents() const; + +private slots: + + void tableWidgetCurrentCellChanged(int currentRow, int currentCol); + void tableWidgetItemChanged(QTableWidgetItem *item); + + void columnEditorIndexChanged(int idx); + void columnEditorItemChanged(int idx, int role, const QVariant &v); + + void columnEditorItemInserted(int idx); + void columnEditorItemDeleted(int idx); + void columnEditorItemMovedUp(int idx); + void columnEditorItemMovedDown(int idx); + + void rowEditorIndexChanged(int idx); + void rowEditorItemChanged(int idx, int role, const QVariant &v); + + void rowEditorItemInserted(int idx); + void rowEditorItemDeleted(int idx); + void rowEditorItemMovedUp(int idx); + void rowEditorItemMovedDown(int idx); + + void togglePropertyBrowser(); + + void cacheReloaded(); + +protected: + void setItemData(int role, const QVariant &v) override; + QVariant getItemData(int role) const override; + int defaultItemFlags() const override; + +private: + void setPropertyBrowserVisible(bool v); + void updateEditor(); + void moveColumnsLeft(int fromColumn, int toColumn); + void moveColumnsRight(int fromColumn, int toColumn); + void moveRowsUp(int fromRow, int toRow); + void moveRowsDown(int fromRow, int toRow); + + Ui::TableWidgetEditor ui; + ItemListEditor *m_rowEditor; + ItemListEditor *m_columnEditor; + bool m_updatingBrowser; +}; + +class TableWidgetEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit TableWidgetEditorDialog(QDesignerFormWindowInterface *form, QWidget *parent); + + TableWidgetContents fillContentsFromTableWidget(QTableWidget *tableWidget); + TableWidgetContents contents() const; + +private: + TableWidgetEditor m_editor; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TABLEWIDGETEDITOR_H diff --git a/src/designer/src/components/taskmenu/tablewidgeteditor.ui b/src/designer/src/components/taskmenu/tablewidgeteditor.ui new file mode 100644 index 0000000..ad067ba --- /dev/null +++ b/src/designer/src/components/taskmenu/tablewidgeteditor.ui @@ -0,0 +1,121 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::TableWidgetEditor + + + + 0 + 0 + 550 + 360 + + + + Edit Table Widget + + + + + + 0 + + + + &Items + + + + + + + 0 + + + + + Table Items + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Properties &>> + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + qdesigner_internal::TableWidgetEditor + accept() + + + 431 + 351 + + + 373 + 362 + + + + + buttonBox + rejected() + qdesigner_internal::TableWidgetEditor + reject() + + + 547 + 354 + + + 562 + 362 + + + + + diff --git a/src/designer/src/components/taskmenu/taskmenu_component.cpp b/src/designer/src/components/taskmenu/taskmenu_component.cpp new file mode 100644 index 0000000..8da00ee --- /dev/null +++ b/src/designer/src/components/taskmenu/taskmenu_component.cpp @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "taskmenu_component.h" +#include "button_taskmenu.h" +#include "groupbox_taskmenu.h" +#include "label_taskmenu.h" +#include "lineedit_taskmenu.h" +#include "listwidget_taskmenu.h" +#include "treewidget_taskmenu.h" +#include "tablewidget_taskmenu.h" +#include "containerwidget_taskmenu.h" +#include "combobox_taskmenu.h" +#include "textedit_taskmenu.h" +#include "menutaskmenu.h" +#include "toolbar_taskmenu.h" +#include "layouttaskmenu.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TaskMenuComponent::TaskMenuComponent(QDesignerFormEditorInterface *core, QObject *parent) + : QObject(parent), + m_core(core) +{ + Q_ASSERT(m_core != nullptr); + + QExtensionManager *mgr = core->extensionManager(); + const QString taskMenuId = u"QDesignerInternalTaskMenuExtension"_s; + + ButtonTaskMenuFactory::registerExtension(mgr, taskMenuId); + CommandLinkButtonTaskMenuFactory::registerExtension(mgr, taskMenuId); // Order! + ButtonGroupTaskMenuFactory::registerExtension(mgr, taskMenuId); + + GroupBoxTaskMenuFactory::registerExtension(mgr, taskMenuId); + LabelTaskMenuFactory::registerExtension(mgr, taskMenuId); + LineEditTaskMenuFactory::registerExtension(mgr, taskMenuId); + ListWidgetTaskMenuFactory::registerExtension(mgr, taskMenuId); + TreeWidgetTaskMenuFactory::registerExtension(mgr, taskMenuId); + TableWidgetTaskMenuFactory::registerExtension(mgr, taskMenuId); + TextEditTaskMenuFactory::registerExtension(mgr, taskMenuId); + PlainTextEditTaskMenuFactory::registerExtension(mgr, taskMenuId); + MenuTaskMenuFactory::registerExtension(mgr, taskMenuId); + MenuBarTaskMenuFactory::registerExtension(mgr, taskMenuId); + ToolBarTaskMenuFactory::registerExtension(mgr, taskMenuId); + StatusBarTaskMenuFactory::registerExtension(mgr, taskMenuId); + LayoutWidgetTaskMenuFactory::registerExtension(mgr, taskMenuId); + SpacerTaskMenuFactory::registerExtension(mgr, taskMenuId); + + mgr->registerExtensions(new ContainerWidgetTaskMenuFactory(core, mgr), taskMenuId); + mgr->registerExtensions(new ComboBoxTaskMenuFactory(taskMenuId, mgr), taskMenuId); +} + +TaskMenuComponent::~TaskMenuComponent() = default; + +QDesignerFormEditorInterface *TaskMenuComponent::core() const +{ + return m_core; + +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + diff --git a/src/designer/src/components/taskmenu/taskmenu_component.h b/src/designer/src/components/taskmenu/taskmenu_component.h new file mode 100644 index 0000000..c97eb3e --- /dev/null +++ b/src/designer/src/components/taskmenu/taskmenu_component.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TASKMENU_COMPONENT_H +#define TASKMENU_COMPONENT_H + +#include "taskmenu_global.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class QT_TASKMENU_EXPORT TaskMenuComponent: public QObject +{ + Q_OBJECT +public: + explicit TaskMenuComponent(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~TaskMenuComponent() override; + + QDesignerFormEditorInterface *core() const; + +private: + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TASKMENU_COMPONENT_H diff --git a/src/designer/src/components/taskmenu/taskmenu_global.h b/src/designer/src/components/taskmenu/taskmenu_global.h new file mode 100644 index 0000000..a8e6a3d --- /dev/null +++ b/src/designer/src/components/taskmenu/taskmenu_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TASKMENU_GLOBAL_H +#define TASKMENU_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_TASKMENU_LIBRARY +# define QT_TASKMENU_EXPORT +#else +# define QT_TASKMENU_EXPORT +#endif +#else +#define QT_TASKMENU_EXPORT +#endif + +#endif // TASKMENU_GLOBAL_H diff --git a/src/designer/src/components/taskmenu/textedit_taskmenu.cpp b/src/designer/src/components/taskmenu/textedit_taskmenu.cpp new file mode 100644 index 0000000..ed85cf7 --- /dev/null +++ b/src/designer/src/components/taskmenu/textedit_taskmenu.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "textedit_taskmenu.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TextEditTaskMenu::TextEditTaskMenu(QTextEdit *textEdit, QObject *parent) : + QDesignerTaskMenu(textEdit, parent), + m_format(Qt::RichText), + m_property(u"html"_s), + m_windowTitle(tr("Edit HTML")), + m_editTextAction(new QAction(tr("Change HTML..."), this)) +{ + initialize(); +} + +TextEditTaskMenu::TextEditTaskMenu(QPlainTextEdit *textEdit, QObject *parent) : + QDesignerTaskMenu(textEdit, parent), + m_format(Qt::PlainText), + m_property(u"plainText"_s), + m_windowTitle(tr("Edit Text")), + m_editTextAction(new QAction(tr("Change Plain Text..."), this)) +{ + initialize(); +} + + +void TextEditTaskMenu::initialize() +{ + connect(m_editTextAction, &QAction::triggered, this, &TextEditTaskMenu::editText); + m_taskActions.append(m_editTextAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +TextEditTaskMenu::~TextEditTaskMenu() = default; + +QAction *TextEditTaskMenu::preferredEditAction() const +{ + return m_editTextAction; +} + +QList TextEditTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void TextEditTaskMenu::editText() +{ + changeTextProperty(m_property, m_windowTitle, MultiSelectionMode, m_format); +} + +} +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/textedit_taskmenu.h b/src/designer/src/components/taskmenu/textedit_taskmenu.h new file mode 100644 index 0000000..38814f4 --- /dev/null +++ b/src/designer/src/components/taskmenu/textedit_taskmenu.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TEXTEDIT_TASKMENU_H +#define TEXTEDIT_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class TextEditTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit TextEditTaskMenu(QTextEdit *button, QObject *parent = nullptr); + explicit TextEditTaskMenu(QPlainTextEdit *button, QObject *parent = nullptr); + + ~TextEditTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editText(); + +private: + void initialize(); + + const Qt::TextFormat m_format; + const QString m_property; + const QString m_windowTitle; + + mutable QList m_taskActions; + QAction *m_editTextAction; +}; + +using TextEditTaskMenuFactory = ExtensionFactory; +using PlainTextEditTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TEXTEDIT_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/toolbar_taskmenu.cpp b/src/designer/src/components/taskmenu/toolbar_taskmenu.cpp new file mode 100644 index 0000000..3b23070 --- /dev/null +++ b/src/designer/src/components/taskmenu/toolbar_taskmenu.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "toolbar_taskmenu.h" +#include "qdesigner_toolbar_p.h" + +#include + +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + // ------------ ToolBarTaskMenu + ToolBarTaskMenu::ToolBarTaskMenu(QToolBar *tb, QObject *parent) : + QObject(parent), + m_toolBar(tb) + { + } + + QAction *ToolBarTaskMenu::preferredEditAction() const + { + return nullptr; + } + + QList ToolBarTaskMenu::taskActions() const + { + if (ToolBarEventFilter *ef = ToolBarEventFilter::eventFilterOf(m_toolBar)) + return ef->contextMenuActions(); + return {}; + } + + // ------------ StatusBarTaskMenu + StatusBarTaskMenu::StatusBarTaskMenu(QStatusBar *sb, QObject *parent) : + QObject(parent), + m_statusBar(sb), + m_removeAction(new QAction(tr("Remove"), this)), + m_promotionTaskMenu(new PromotionTaskMenu(sb, PromotionTaskMenu::ModeSingleWidget, this)) + { + connect(m_removeAction, &QAction::triggered, this, &StatusBarTaskMenu::removeStatusBar); + } + + QAction *StatusBarTaskMenu::preferredEditAction() const + { + return nullptr; + } + + QList StatusBarTaskMenu::taskActions() const + { + QList rc; + rc.push_back(m_removeAction); + m_promotionTaskMenu->addActions(PromotionTaskMenu::LeadingSeparator, rc); + return rc; + } + + void StatusBarTaskMenu::removeStatusBar() + { + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_statusBar)) { + DeleteStatusBarCommand *cmd = new DeleteStatusBarCommand(fw); + cmd->init(m_statusBar); + fw->commandHistory()->push(cmd); + } + } +} + +QT_END_NAMESPACE + diff --git a/src/designer/src/components/taskmenu/toolbar_taskmenu.h b/src/designer/src/components/taskmenu/toolbar_taskmenu.h new file mode 100644 index 0000000..ea6e1cf --- /dev/null +++ b/src/designer/src/components/taskmenu/toolbar_taskmenu.h @@ -0,0 +1,61 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TOOLBAR_TASKMENU_H +#define TOOLBAR_TASKMENU_H + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + class PromotionTaskMenu; + +// ToolBarTaskMenu forwards the actions of ToolBarEventFilter +class ToolBarTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit ToolBarTaskMenu(QToolBar *tb, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QToolBar *m_toolBar; +}; + +// StatusBarTaskMenu provides promotion and deletion +class StatusBarTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit StatusBarTaskMenu(QStatusBar *tb, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void removeStatusBar(); + +private: + QStatusBar *m_statusBar; + QAction *m_removeAction; + PromotionTaskMenu *m_promotionTaskMenu; +}; + +using ToolBarTaskMenuFactory = ExtensionFactory; +using StatusBarTaskMenuFactory = ExtensionFactory; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TOOLBAR_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/treewidget_taskmenu.cpp b/src/designer/src/components/taskmenu/treewidget_taskmenu.cpp new file mode 100644 index 0000000..6ce0b39 --- /dev/null +++ b/src/designer/src/components/taskmenu/treewidget_taskmenu.cpp @@ -0,0 +1,77 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "treewidget_taskmenu.h" +#include "treewidgeteditor.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +TreeWidgetTaskMenu::TreeWidgetTaskMenu(QTreeWidget *button, QObject *parent) + : QDesignerTaskMenu(button, parent), + m_treeWidget(button), + m_editItemsAction(new QAction(tr("Edit Items..."), this)) +{ + connect(m_editItemsAction, &QAction::triggered, this, &TreeWidgetTaskMenu::editItems); + m_taskActions.append(m_editItemsAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + + +TreeWidgetTaskMenu::~TreeWidgetTaskMenu() = default; + +QAction *TreeWidgetTaskMenu::preferredEditAction() const +{ + return m_editItemsAction; +} + +QList TreeWidgetTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void TreeWidgetTaskMenu::editItems() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_treeWidget); + if (m_formWindow.isNull()) + return; + + Q_ASSERT(m_treeWidget != nullptr); + + TreeWidgetEditorDialog dlg(m_formWindow, m_treeWidget->window()); + TreeWidgetContents oldCont = dlg.fillContentsFromTreeWidget(m_treeWidget); + if (dlg.exec() == QDialog::Accepted) { + TreeWidgetContents newCont = dlg.contents(); + if (newCont != oldCont) { + ChangeTreeContentsCommand *cmd = new ChangeTreeContentsCommand(m_formWindow); + cmd->init(m_treeWidget, oldCont, newCont); + m_formWindow->commandHistory()->push(cmd); + } + } +} + +void TreeWidgetTaskMenu::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/treewidget_taskmenu.h b/src/designer/src/components/taskmenu/treewidget_taskmenu.h new file mode 100644 index 0000000..5ad6c34 --- /dev/null +++ b/src/designer/src/components/taskmenu/treewidget_taskmenu.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TREEWIDGET_TASKMENU_H +#define TREEWIDGET_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLineEdit; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class TreeWidgetTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit TreeWidgetTaskMenu(QTreeWidget *button, QObject *parent = nullptr); + ~TreeWidgetTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editItems(); + void updateSelection(); + +private: + QTreeWidget *m_treeWidget; + QPointer m_formWindow; + QPointer m_editor; + mutable QList m_taskActions; + QAction *m_editItemsAction; +}; + +using TreeWidgetTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TREEWIDGET_TASKMENU_H diff --git a/src/designer/src/components/taskmenu/treewidgeteditor.cpp b/src/designer/src/components/taskmenu/treewidgeteditor.cpp new file mode 100644 index 0000000..db887d1 --- /dev/null +++ b/src/designer/src/components/taskmenu/treewidgeteditor.cpp @@ -0,0 +1,618 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "treewidgeteditor.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TreeWidgetEditor::TreeWidgetEditor(QDesignerFormWindowInterface *form, QDialog *dialog) + : AbstractItemEditor(form, nullptr), m_updatingBrowser(false) +{ + m_columnEditor = new ItemListEditor(form, this); + m_columnEditor->setObjectName(u"columnEditor"_s); + m_columnEditor->setNewItemText(tr("New Column")); + ui.setupUi(dialog); + + injectPropertyBrowser(ui.itemsTab, ui.widget); + connect(ui.showPropertiesButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::togglePropertyBrowser); + setPropertyBrowserVisible(false); + + ui.tabWidget->insertTab(0, m_columnEditor, tr("&Columns")); + ui.tabWidget->setCurrentIndex(0); + + ui.newItemButton->setIcon(createIconSet("plus.png"_L1)); + ui.newSubItemButton->setIcon(createIconSet("downplus.png"_L1)); + ui.deleteItemButton->setIcon(createIconSet("minus.png"_L1)); + ui.moveItemUpButton->setIcon(createIconSet("up.png"_L1)); + ui.moveItemDownButton->setIcon(createIconSet("down.png"_L1)); + ui.moveItemRightButton->setIcon(createIconSet("leveldown.png"_L1)); + ui.moveItemLeftButton->setIcon(createIconSet("levelup.png"_L1)); + + ui.treeWidget->header()->setSectionsMovable(false); + + connect(ui.newItemButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::newItemButtonClicked); + connect(ui.newSubItemButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::newSubItemButtonClicked); + connect(ui.moveItemUpButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::moveItemUpButtonClicked); + connect(ui.moveItemDownButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::moveItemDownButtonClicked); + connect(ui.moveItemRightButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::moveItemRightButtonClicked); + connect(ui.moveItemLeftButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::moveItemLeftButtonClicked); + connect(ui.deleteItemButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::deleteItemButtonClicked); + connect(ui.treeWidget, &QTreeWidget::currentItemChanged, + this, &TreeWidgetEditor::treeWidgetCurrentItemChanged); + connect(ui.treeWidget, &QTreeWidget::itemChanged, + this, &TreeWidgetEditor::treeWidgetItemChanged); + + connect(m_columnEditor, &ItemListEditor::indexChanged, + this, &TreeWidgetEditor::columnEditorIndexChanged); + connect(m_columnEditor, &ItemListEditor::itemChanged, + this, &TreeWidgetEditor::columnEditorItemChanged); + connect(m_columnEditor, &ItemListEditor::itemInserted, + this, &TreeWidgetEditor::columnEditorItemInserted); + connect(m_columnEditor, &ItemListEditor::itemDeleted, + this, &TreeWidgetEditor::columnEditorItemDeleted); + connect(m_columnEditor, &ItemListEditor::itemMovedUp, + this, &TreeWidgetEditor::columnEditorItemMovedUp); + connect(m_columnEditor, &ItemListEditor::itemMovedDown, + this, &TreeWidgetEditor::columnEditorItemMovedDown); + + connect(iconCache(), &DesignerIconCache::reloaded, this, &TreeWidgetEditor::cacheReloaded); +} + +static AbstractItemEditor::PropertyDefinition treeHeaderPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, + { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QColor, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { 0, 0, nullptr, nullptr } +}; + +static AbstractItemEditor::PropertyDefinition treeItemColumnPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, + { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QBrush, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { Qt::CheckStateRole, 0, QtVariantPropertyManager::enumTypeId, "checkState" }, + { 0, 0, nullptr, nullptr } +}; + +static AbstractItemEditor::PropertyDefinition treeItemCommonPropList[] = { + { ItemFlagsShadowRole, 0, QtVariantPropertyManager::flagTypeId, "flags" }, + { 0, 0, nullptr, nullptr } +}; + +QtVariantProperty *TreeWidgetEditor::setupPropertyGroup(const QString &title, PropertyDefinition *propDefs) +{ + setupProperties(propDefs); + QtVariantProperty *groupProp = m_propertyManager->addProperty(QtVariantPropertyManager::groupTypeId(), title); + for (QtVariantProperty *prop : std::as_const(m_rootProperties)) + groupProp->addSubProperty(prop); + m_rootProperties.clear(); + return groupProp; +} + +TreeWidgetContents TreeWidgetEditor::fillContentsFromTreeWidget(QTreeWidget *treeWidget) +{ + TreeWidgetContents treeCont; + treeCont.fromTreeWidget(treeWidget, false); + treeCont.applyToTreeWidget(ui.treeWidget, iconCache(), true); + + treeCont.m_headerItem.applyToListWidget(m_columnEditor->listWidget(), iconCache(), true); + m_columnEditor->setupEditor(treeWidget, treeHeaderPropList); + + QList rootProperties; + rootProperties.append(setupPropertyGroup(tr("Per column properties"), treeItemColumnPropList)); + rootProperties.append(setupPropertyGroup(tr("Common properties"), treeItemCommonPropList)); + m_rootProperties = rootProperties; + m_propertyBrowser->setPropertiesWithoutValueMarked(true); + m_propertyBrowser->setRootIsDecorated(false); + setupObject(treeWidget); + + if (ui.treeWidget->topLevelItemCount() > 0) + ui.treeWidget->setCurrentItem(ui.treeWidget->topLevelItem(0)); + + updateEditor(); + + return treeCont; +} + +TreeWidgetContents TreeWidgetEditor::contents() const +{ + TreeWidgetContents retVal; + retVal.fromTreeWidget(ui.treeWidget, true); + return retVal; +} + +void TreeWidgetEditor::setItemData(int role, const QVariant &v) +{ + const int col = (role == ItemFlagsShadowRole) ? 0 : ui.treeWidget->currentColumn(); + QVariant newValue = v; + BoolBlocker block(m_updatingBrowser); + if (role == Qt::FontRole && newValue.metaType().id() == QMetaType::QFont) { + QFont oldFont = ui.treeWidget->font(); + QFont newFont = qvariant_cast(newValue).resolve(oldFont); + newValue = QVariant::fromValue(newFont); + ui.treeWidget->currentItem()->setData(col, role, QVariant()); // force the right font with the current resolve mask is set (item view bug) + } + ui.treeWidget->currentItem()->setData(col, role, newValue); +} + +QVariant TreeWidgetEditor::getItemData(int role) const +{ + const int col = (role == ItemFlagsShadowRole) ? 0 : ui.treeWidget->currentColumn(); + return ui.treeWidget->currentItem()->data(col, role); +} + +int TreeWidgetEditor::defaultItemFlags() const +{ + static const int flags = QTreeWidgetItem().flags(); + return flags; +} + +void TreeWidgetEditor::newItemButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + QTreeWidgetItem *newItem = nullptr; + ui.treeWidget->blockSignals(true); + if (curItem) { + if (curItem->parent()) + newItem = new QTreeWidgetItem(curItem->parent(), curItem); + else + newItem = new QTreeWidgetItem(ui.treeWidget, curItem); + } else + newItem = new QTreeWidgetItem(ui.treeWidget); + const QString newItemText = tr("New Item"); + newItem->setText(0, newItemText); + newItem->setData(0, Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(newItemText))); + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(newItem, qMax(ui.treeWidget->currentColumn(), 0)); + updateEditor(); + ui.treeWidget->editItem(newItem, ui.treeWidget->currentColumn()); +} + +void TreeWidgetEditor::newSubItemButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + ui.treeWidget->blockSignals(true); + QTreeWidgetItem *newItem = new QTreeWidgetItem(curItem); + const QString newItemText = tr("New Subitem"); + newItem->setText(0, newItemText); + newItem->setData(0, Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(newItemText))); + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(newItem, ui.treeWidget->currentColumn()); + updateEditor(); + ui.treeWidget->editItem(newItem, ui.treeWidget->currentColumn()); +} + +void TreeWidgetEditor::deleteItemButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + QTreeWidgetItem *nextCurrent = nullptr; + if (curItem->parent()) { + int idx = curItem->parent()->indexOfChild(curItem); + if (idx == curItem->parent()->childCount() - 1) + idx--; + else + idx++; + if (idx < 0) + nextCurrent = curItem->parent(); + else + nextCurrent = curItem->parent()->child(idx); + } else { + int idx = ui.treeWidget->indexOfTopLevelItem(curItem); + if (idx == ui.treeWidget->topLevelItemCount() - 1) + idx--; + else + idx++; + if (idx >= 0) + nextCurrent = ui.treeWidget->topLevelItem(idx); + } + closeEditors(); + ui.treeWidget->blockSignals(true); + delete curItem; + ui.treeWidget->blockSignals(false); + + if (nextCurrent) + ui.treeWidget->setCurrentItem(nextCurrent, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::moveItemUpButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + int idx; + if (curItem->parent()) + idx = curItem->parent()->indexOfChild(curItem); + else + idx = ui.treeWidget->indexOfTopLevelItem(curItem); + if (idx == 0) + return; + + QTreeWidgetItem *takenItem; + ui.treeWidget->blockSignals(true); + if (curItem->parent()) { + QTreeWidgetItem *parentItem = curItem->parent(); + takenItem = parentItem->takeChild(idx); + parentItem->insertChild(idx - 1, takenItem); + } else { + takenItem = ui.treeWidget->takeTopLevelItem(idx); + ui.treeWidget->insertTopLevelItem(idx - 1, takenItem); + } + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(takenItem, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::moveItemDownButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + int idx, idxCount; + if (curItem->parent()) { + idx = curItem->parent()->indexOfChild(curItem); + idxCount = curItem->parent()->childCount(); + } else { + idx = ui.treeWidget->indexOfTopLevelItem(curItem); + idxCount = ui.treeWidget->topLevelItemCount(); + } + if (idx == idxCount - 1) + return; + + QTreeWidgetItem *takenItem; + ui.treeWidget->blockSignals(true); + if (curItem->parent()) { + QTreeWidgetItem *parentItem = curItem->parent(); + takenItem = parentItem->takeChild(idx); + parentItem->insertChild(idx + 1, takenItem); + } else { + takenItem = ui.treeWidget->takeTopLevelItem(idx); + ui.treeWidget->insertTopLevelItem(idx + 1, takenItem); + } + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(takenItem, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::moveItemLeftButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + QTreeWidgetItem *parentItem = curItem->parent(); + if (!parentItem) + return; + + ui.treeWidget->blockSignals(true); + QTreeWidgetItem *takenItem = parentItem->takeChild(parentItem->indexOfChild(curItem)); + if (parentItem->parent()) { + int idx = parentItem->parent()->indexOfChild(parentItem); + parentItem->parent()->insertChild(idx, takenItem); + } else { + int idx = ui.treeWidget->indexOfTopLevelItem(parentItem); + ui.treeWidget->insertTopLevelItem(idx, takenItem); + } + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(takenItem, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::moveItemRightButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + int idx, idxCount; + if (curItem->parent()) { + idx = curItem->parent()->indexOfChild(curItem); + idxCount = curItem->parent()->childCount(); + } else { + idx = ui.treeWidget->indexOfTopLevelItem(curItem); + idxCount = ui.treeWidget->topLevelItemCount(); + } + if (idx == idxCount - 1) + return; + + QTreeWidgetItem *takenItem; + ui.treeWidget->blockSignals(true); + if (curItem->parent()) { + QTreeWidgetItem *parentItem = curItem->parent()->child(idx + 1); + takenItem = curItem->parent()->takeChild(idx); + parentItem->insertChild(0, takenItem); + } else { + QTreeWidgetItem *parentItem = ui.treeWidget->topLevelItem(idx + 1); + takenItem = ui.treeWidget->takeTopLevelItem(idx); + parentItem->insertChild(0, takenItem); + } + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(takenItem, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::togglePropertyBrowser() +{ + setPropertyBrowserVisible(!m_propertyBrowser->isVisible()); +} + +void TreeWidgetEditor::setPropertyBrowserVisible(bool v) +{ + ui.showPropertiesButton->setText(v ? tr("Properties &>>") : tr("Properties &<<")); + m_propertyBrowser->setVisible(v); +} + +void TreeWidgetEditor::treeWidgetCurrentItemChanged() +{ + m_columnEditor->setCurrentIndex(ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::treeWidgetItemChanged(QTreeWidgetItem *item, int column) +{ + if (m_updatingBrowser) + return; + + PropertySheetStringValue val = qvariant_cast(item->data(column, Qt::DisplayPropertyRole)); + val.setValue(item->text(column)); + BoolBlocker block(m_updatingBrowser); + item->setData(column, Qt::DisplayPropertyRole, QVariant::fromValue(val)); + + updateBrowser(); +} + +void TreeWidgetEditor::columnEditorIndexChanged(int idx) +{ + if (QTreeWidgetItem *item = ui.treeWidget->currentItem()) + ui.treeWidget->setCurrentItem(item, idx); +} + +void TreeWidgetEditor::columnEditorItemChanged(int idx, int role, const QVariant &v) +{ + if (role == Qt::DisplayPropertyRole) + ui.treeWidget->headerItem()->setData(idx, Qt::EditRole, qvariant_cast(v).value()); + ui.treeWidget->headerItem()->setData(idx, role, v); +} + +void TreeWidgetEditor::updateEditor() +{ + QTreeWidgetItem *current = ui.treeWidget->currentItem(); + + bool itemsEnabled = false; + bool currentItemEnabled = false; + bool moveItemUpEnabled = false; + bool moveItemDownEnabled = false; + bool moveItemRightEnabled = false; + bool moveItemLeftEnabled = false; + + if (ui.treeWidget->columnCount() > 0) { + itemsEnabled = true; + if (current) { + int idx; + int idxCount; + currentItemEnabled = true; + if (current->parent()) { + moveItemLeftEnabled = true; + idx = current->parent()->indexOfChild(current); + idxCount = current->parent()->childCount(); + } else { + idx = ui.treeWidget->indexOfTopLevelItem(current); + idxCount = ui.treeWidget->topLevelItemCount(); + } + if (idx > 0) + moveItemUpEnabled = true; + if (idx < idxCount - 1) { + moveItemDownEnabled = true; + moveItemRightEnabled = true; + } + } + } + ui.tabWidget->setTabEnabled(1, itemsEnabled); + ui.newSubItemButton->setEnabled(currentItemEnabled); + ui.deleteItemButton->setEnabled(currentItemEnabled); + + ui.moveItemUpButton->setEnabled(moveItemUpEnabled); + ui.moveItemDownButton->setEnabled(moveItemDownEnabled); + ui.moveItemRightButton->setEnabled(moveItemRightEnabled); + ui.moveItemLeftButton->setEnabled(moveItemLeftEnabled); + + if (current) + updateBrowser(); + else + m_propertyBrowser->clear(); +} + +void TreeWidgetEditor::moveColumnItems(const PropertyDefinition *propList, + QTreeWidgetItem *item, int fromColumn, int toColumn, int step) +{ + BoolBlocker block(m_updatingBrowser); + + QList saveCol; + for (int j = 0; propList[j].name; j++) + saveCol.append(item->data(toColumn, propList[j].role)); + QVariant editVariant = item->data(toColumn, Qt::EditRole); + QVariant toolTipVariant = item->data(toColumn, Qt::ToolTipRole); + QVariant statusTipVariant = item->data(toColumn, Qt::StatusTipRole); + QVariant whatsThisVariant = item->data(toColumn, Qt::WhatsThisRole); + QVariant decorationVariant = item->data(toColumn, Qt::DecorationRole); + for (int i = toColumn; i != fromColumn; i += step) { + for (int j = 0; propList[j].name; j++) + item->setData(i, propList[j].role, item->data(i + step, propList[j].role)); + item->setData(i, Qt::EditRole, item->data(i + step, Qt::EditRole)); + item->setData(i, Qt::ToolTipRole, item->data(i + step, Qt::ToolTipRole)); + item->setData(i, Qt::StatusTipRole, item->data(i + step, Qt::StatusTipRole)); + item->setData(i, Qt::WhatsThisRole, item->data(i + step, Qt::WhatsThisRole)); + item->setData(i, Qt::DecorationRole, item->data(i + step, Qt::DecorationRole)); + } + for (int j = 0; propList[j].name; j++) + item->setData(fromColumn, propList[j].role, saveCol[j]); + item->setData(fromColumn, Qt::EditRole, editVariant); + item->setData(fromColumn, Qt::ToolTipRole, toolTipVariant); + item->setData(fromColumn, Qt::StatusTipRole, statusTipVariant); + item->setData(fromColumn, Qt::WhatsThisRole, whatsThisVariant); + item->setData(fromColumn, Qt::DecorationRole, decorationVariant); +} + +void TreeWidgetEditor::moveColumns(int fromColumn, int toColumn, int step) +{ + ui.treeWidget->blockSignals(true); + + moveColumnItems(treeHeaderPropList, ui.treeWidget->headerItem(), fromColumn, toColumn, step); + + QQueue pendingQueue; + for (int i = 0; i < ui.treeWidget->topLevelItemCount(); i++) + pendingQueue.enqueue(ui.treeWidget->topLevelItem(i)); + + while (!pendingQueue.isEmpty()) { + QTreeWidgetItem *item = pendingQueue.dequeue(); + for (int i = 0; i < item->childCount(); i++) + pendingQueue.enqueue(item->child(i)); + + moveColumnItems(treeItemColumnPropList, item, fromColumn, toColumn, step); + } + + ui.treeWidget->blockSignals(false); +} + +void TreeWidgetEditor::moveColumnsLeft(int fromColumn, int toColumn) +{ + if (fromColumn >= toColumn) + return; + + moveColumns(fromColumn, toColumn, -1); +} + +void TreeWidgetEditor::moveColumnsRight(int fromColumn, int toColumn) +{ + if (fromColumn >= toColumn) + return; + + moveColumns(toColumn, fromColumn, 1); +} + +void TreeWidgetEditor::columnEditorItemInserted(int idx) +{ + int columnCount = ui.treeWidget->columnCount(); + ui.treeWidget->setColumnCount(columnCount + 1); + ui.treeWidget->headerItem()->setText(columnCount, m_columnEditor->newItemText()); + moveColumnsLeft(idx, columnCount); + + updateEditor(); +} + +void TreeWidgetEditor::columnEditorItemDeleted(int idx) +{ + closeEditors(); + + int columnCount = ui.treeWidget->columnCount() - 1; + if (!columnCount) + ui.treeWidget->clear(); + else + moveColumnsRight(idx, columnCount); + ui.treeWidget->setColumnCount(columnCount); + + updateEditor(); +} + +void TreeWidgetEditor::columnEditorItemMovedUp(int idx) +{ + moveColumnsRight(idx - 1, idx); + + ui.treeWidget->setCurrentItem(ui.treeWidget->currentItem(), idx - 1); + updateEditor(); +} + +void TreeWidgetEditor::columnEditorItemMovedDown(int idx) +{ + moveColumnsLeft(idx, idx + 1); + + ui.treeWidget->setCurrentItem(ui.treeWidget->currentItem(), idx + 1); + updateEditor(); +} + +void TreeWidgetEditor::closeEditors() +{ + if (QTreeWidgetItem *cur = ui.treeWidget->currentItem() ) { + const int numCols = cur->columnCount (); + for (int i = 0; i < numCols; i++) + ui.treeWidget->closePersistentEditor (cur, i); + } +} + +void TreeWidgetEditor::cacheReloaded() +{ + reloadIconResources(iconCache(), ui.treeWidget); +} + +TreeWidgetEditorDialog::TreeWidgetEditorDialog(QDesignerFormWindowInterface *form, QWidget *parent) : + QDialog(parent), m_editor(form, this) +{ +} + +TreeWidgetContents TreeWidgetEditorDialog::fillContentsFromTreeWidget(QTreeWidget *treeWidget) +{ + return m_editor.fillContentsFromTreeWidget(treeWidget); +} + +TreeWidgetContents TreeWidgetEditorDialog::contents() const +{ + return m_editor.contents(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/taskmenu/treewidgeteditor.h b/src/designer/src/components/taskmenu/treewidgeteditor.h new file mode 100644 index 0000000..b2d306f --- /dev/null +++ b/src/designer/src/components/taskmenu/treewidgeteditor.h @@ -0,0 +1,92 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TREEWIDGETEDITOR_H +#define TREEWIDGETEDITOR_H + +#include "ui_treewidgeteditor.h" + +#include "listwidgeteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +class QTreeWidget; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class FormWindowBase; +class PropertySheetIconValue; + +class TreeWidgetEditor: public AbstractItemEditor +{ + Q_OBJECT +public: + explicit TreeWidgetEditor(QDesignerFormWindowInterface *form, QDialog *dialog); + + TreeWidgetContents fillContentsFromTreeWidget(QTreeWidget *treeWidget); + TreeWidgetContents contents() const; + +private slots: + void newItemButtonClicked(); + void newSubItemButtonClicked(); + void deleteItemButtonClicked(); + void moveItemUpButtonClicked(); + void moveItemDownButtonClicked(); + void moveItemRightButtonClicked(); + void moveItemLeftButtonClicked(); + + void treeWidgetCurrentItemChanged(); + void treeWidgetItemChanged(QTreeWidgetItem *item, int column); + + void columnEditorIndexChanged(int idx); + void columnEditorItemChanged(int idx, int role, const QVariant &v); + + void columnEditorItemInserted(int idx); + void columnEditorItemDeleted(int idx); + void columnEditorItemMovedUp(int idx); + void columnEditorItemMovedDown(int idx); + + void togglePropertyBrowser(); + void cacheReloaded(); + +protected: + void setItemData(int role, const QVariant &v) override; + QVariant getItemData(int role) const override; + int defaultItemFlags() const override; + +private: + void setPropertyBrowserVisible(bool v); + QtVariantProperty *setupPropertyGroup(const QString &title, PropertyDefinition *propDefs); + void updateEditor(); + void moveColumnItems(const PropertyDefinition *propList, QTreeWidgetItem *item, int fromColumn, int toColumn, int step); + void moveColumns(int fromColumn, int toColumn, int step); + void moveColumnsLeft(int fromColumn, int toColumn); + void moveColumnsRight(int fromColumn, int toColumn); + void closeEditors(); + + Ui::TreeWidgetEditor ui; + ItemListEditor *m_columnEditor; + bool m_updatingBrowser; +}; + +class TreeWidgetEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit TreeWidgetEditorDialog(QDesignerFormWindowInterface *form, QWidget *parent); + + TreeWidgetContents fillContentsFromTreeWidget(QTreeWidget *treeWidget); + TreeWidgetContents contents() const; + +private: + TreeWidgetEditor m_editor; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TREEWIDGETEDITOR_H diff --git a/src/designer/src/components/taskmenu/treewidgeteditor.ui b/src/designer/src/components/taskmenu/treewidgeteditor.ui new file mode 100644 index 0000000..688b2f4 --- /dev/null +++ b/src/designer/src/components/taskmenu/treewidgeteditor.ui @@ -0,0 +1,221 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::TreeWidgetEditor + + + + 0 + 0 + 700 + 360 + + + + Edit Tree Widget + + + + + + 0 + + + + &Items + + + + 9 + + + 9 + + + 9 + + + + + + 0 + + + + + Qt::WheelFocus + + + Tree Items + + + + 1 + + + + + + + + + + New Item + + + &New + + + + + + + New Subitem + + + New &Subitem + + + + + + + Delete Item + + + &Delete + + + + + + + Qt::Horizontal + + + + 28 + 23 + + + + + + + + Move Item Left (before Parent Item) + + + L + + + + + + + Move Item Right (as a First Subitem of the Next Sibling Item) + + + R + + + + + + + Move Item Up + + + U + + + + + + + Move Item Down + + + D + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Properties &>> + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + qdesigner_internal::TreeWidgetEditor + accept() + + + 440 + 335 + + + 373 + 362 + + + + + buttonBox + rejected() + qdesigner_internal::TreeWidgetEditor + reject() + + + 556 + 335 + + + 562 + 362 + + + + + diff --git a/src/designer/src/components/widgetbox/widgetbox.cpp b/src/designer/src/components/widgetbox/widgetbox.cpp new file mode 100644 index 0000000..7294799 --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetbox.cpp @@ -0,0 +1,238 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetbox.h" +#include "widgetboxtreewidget.h" +#include "widgetbox_dnditem.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +/* WidgetBoxFilterLineEdit: This widget should never have initial focus + * (ie, be the first widget of a dialog, else, the hint cannot be displayed. + * As it is the only focusable control in the widget box, it clears the focus + * policy and focusses explicitly on click only (note that setting Qt::ClickFocus + * is not sufficient for that as an ActivationFocus will occur). */ + +class WidgetBoxFilterLineEdit : public QLineEdit { +public: + explicit WidgetBoxFilterLineEdit(QWidget *parent = nullptr) : QLineEdit(parent), m_defaultFocusPolicy(focusPolicy()) + { setFocusPolicy(Qt::NoFocus); } + +protected: + void mousePressEvent(QMouseEvent *event) override; + void focusInEvent(QFocusEvent *e) override; + +private: + const Qt::FocusPolicy m_defaultFocusPolicy; +}; + +void WidgetBoxFilterLineEdit::mousePressEvent(QMouseEvent *e) +{ + if (!hasFocus()) // Explicitly focus on click. + setFocus(Qt::OtherFocusReason); + QLineEdit::mousePressEvent(e); +} + +void WidgetBoxFilterLineEdit::focusInEvent(QFocusEvent *e) +{ + // Refuse the focus if the mouse it outside. In addition to the mouse + // press logic, this prevents a re-focussing which occurs once + // we actually had focus + const Qt::FocusReason reason = e->reason(); + if (reason == Qt::ActiveWindowFocusReason || reason == Qt::PopupFocusReason) { + const QPoint mousePos = mapFromGlobal(QCursor::pos()); + const bool refuse = !geometry().contains(mousePos); + if (refuse) { + e->ignore(); + return; + } + } + QLineEdit::focusInEvent(e); +} + +WidgetBox::WidgetBox(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) + : QDesignerWidgetBox(parent, flags), + m_core(core), + m_view(new WidgetBoxTreeWidget(m_core)) +{ + + QVBoxLayout *l = new QVBoxLayout(this); + l->setContentsMargins(QMargins()); + l->setSpacing(0); + + // Prevent the filter from grabbing focus since Our view has Qt::NoFocus + QToolBar *toolBar = new QToolBar(this); + QLineEdit *filterWidget = new WidgetBoxFilterLineEdit(toolBar); + filterWidget->setPlaceholderText(tr("Filter")); + filterWidget->setClearButtonEnabled(true); + connect(filterWidget, &QLineEdit::textChanged, m_view, &WidgetBoxTreeWidget::filter); + toolBar->addWidget(filterWidget); + l->addWidget(toolBar); + + // View + connect(m_view, &WidgetBoxTreeWidget::widgetBoxPressed, + this, &WidgetBox::handleMousePress); + l->addWidget(m_view); + + setAcceptDrops (true); +} + +WidgetBox::~WidgetBox() = default; + +QDesignerFormEditorInterface *WidgetBox::core() const +{ + return m_core; +} + +void WidgetBox::handleMousePress(const QString &name, const QString &xml, const QPoint &global_mouse_pos) +{ + if (QApplication::mouseButtons() != Qt::LeftButton) + return; + + DomUI *ui = xmlToUi(name, xml, true); + if (ui == nullptr) + return; + QList item_list; + item_list.append(new WidgetBoxDnDItem(core(), ui, global_mouse_pos)); + m_core->formWindowManager()->dragItems(item_list); +} + +int WidgetBox::categoryCount() const +{ + return m_view->categoryCount(); +} + +QDesignerWidgetBoxInterface::Category WidgetBox::category(int cat_idx) const +{ + return m_view->category(cat_idx); +} + +void WidgetBox::addCategory(const Category &cat) +{ + m_view->addCategory(cat); +} + +void WidgetBox::removeCategory(int cat_idx) +{ + m_view->removeCategory(cat_idx); +} + +int WidgetBox::widgetCount(int cat_idx) const +{ + return m_view->widgetCount(cat_idx); +} + +QDesignerWidgetBoxInterface::Widget WidgetBox::widget(int cat_idx, int wgt_idx) const +{ + return m_view->widget(cat_idx, wgt_idx); +} + +void WidgetBox::addWidget(int cat_idx, const Widget &wgt) +{ + m_view->addWidget(cat_idx, wgt); +} + +void WidgetBox::removeWidget(int cat_idx, int wgt_idx) +{ + m_view->removeWidget(cat_idx, wgt_idx); +} + +void WidgetBox::dropWidgets(const QList &item_list, const QPoint&) +{ + m_view->dropWidgets(item_list); +} + +void WidgetBox::setFileName(const QString &file_name) +{ + m_view->setFileName(file_name); +} + +QString WidgetBox::fileName() const +{ + return m_view->fileName(); +} + +bool WidgetBox::load() +{ + return m_view->load(loadMode()); +} + +bool WidgetBox::loadContents(const QString &contents) +{ + return m_view->loadContents(contents); +} + +bool WidgetBox::save() +{ + return m_view->save(); +} + +static const QDesignerMimeData *checkDragEvent(QDropEvent * event, + bool acceptEventsFromWidgetBox) +{ + const QDesignerMimeData *mimeData = qobject_cast(event->mimeData()); + if (!mimeData) { + event->ignore(); + return nullptr; + } + // If desired, ignore a widget box drag and drop, where widget==0. + if (!acceptEventsFromWidgetBox) { + const bool fromWidgetBox = !mimeData->items().first()->widget(); + if (fromWidgetBox) { + event->ignore(); + return nullptr; + } + } + + mimeData->acceptEvent(event); + return mimeData; +} + +void WidgetBox::dragEnterEvent (QDragEnterEvent * event) +{ + // We accept event originating from the widget box also here, + // because otherwise Windows will not show the DnD pixmap. + checkDragEvent(event, true); +} + +void WidgetBox::dragMoveEvent(QDragMoveEvent * event) +{ + checkDragEvent(event, true); +} + +void WidgetBox::dropEvent(QDropEvent * event) +{ + const QDesignerMimeData *mimeData = checkDragEvent(event, false); + if (!mimeData) + return; + + dropWidgets(mimeData->items(), event->position().toPoint()); + QDesignerMimeData::removeMovedWidgetsFromSourceForm(mimeData->items()); +} + +QIcon WidgetBox::iconForWidget(const QString &className, const QString &category) const +{ + Widget widgetData; + if (!findWidget(this, className, category, &widgetData)) + return QIcon(); + return m_view->iconForWidget(widgetData.iconName()); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/widgetbox/widgetbox.h b/src/designer/src/components/widgetbox/widgetbox.h new file mode 100644 index 0000000..cf43b63 --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetbox.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOX_H +#define WIDGETBOX_H + +#include "widgetbox_global.h" +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class WidgetBoxTreeWidget; + +class QT_WIDGETBOX_EXPORT WidgetBox : public QDesignerWidgetBox +{ + Q_OBJECT +public: + explicit WidgetBox(QDesignerFormEditorInterface *core, QWidget *parent = nullptr, + Qt::WindowFlags flags = {}); + ~WidgetBox() override; + + QDesignerFormEditorInterface *core() const; + + int categoryCount() const override; + Category category(int cat_idx) const override; + void addCategory(const Category &cat) override; + void removeCategory(int cat_idx) override; + + int widgetCount(int cat_idx) const override; + Widget widget(int cat_idx, int wgt_idx) const override; + void addWidget(int cat_idx, const Widget &wgt) override; + void removeWidget(int cat_idx, int wgt_idx) override; + + void dropWidgets(const QList &item_list, const QPoint &global_mouse_pos) override; + + void setFileName(const QString &file_name) override; + QString fileName() const override; + bool load() override; + bool save() override; + + bool loadContents(const QString &contents) override; + QIcon iconForWidget(const QString &className, const QString &category = QString()) const override; + +protected: + void dragEnterEvent (QDragEnterEvent * event) override; + void dragMoveEvent(QDragMoveEvent * event) override; + void dropEvent (QDropEvent * event) override; + +private slots: + void handleMousePress(const QString &name, const QString &xml, const QPoint &global_mouse_pos); + +private: + QDesignerFormEditorInterface *m_core; + WidgetBoxTreeWidget *m_view; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETBOX_H diff --git a/src/designer/src/components/widgetbox/widgetbox.xml b/src/designer/src/components/widgetbox/widgetbox.xml new file mode 100644 index 0000000..4cb29cd --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetbox.xml @@ -0,0 +1,939 @@ + + + + + + + + + verticalLayoutWidget + + + + 0 + 0 + 160 + 80 + + + + + + + + + + + + + horizontalLayoutWidget + + + + 0 + 0 + 160 + 80 + + + + + + + + + + + + + gridLayoutWidget + + + + 0 + 0 + 160 + 80 + + + + + + + + + + + + + formLayoutWidget + + + + 0 + 0 + 160 + 80 + + + + + + + + + + + + + + + + + Qt::Horizontal + + + horizontalSpacer + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + verticalSpacer + + + + 20 + 40 + + + + + + + + + + + + + + + PushButton + + + pushButton + + + + + + + + + + toolButton + + + ... + + + + + + + + + + RadioButton + + + radioButton + + + + + + + + + + CheckBox + + + checkBox + + + + + + + + + + CommandLinkButton + + + commandLinkButton + + + + + + + + + + QDialogButtonBox::Ok|QDialogButtonBox::Cancel + + + buttonBox + + + + + + + + + + + + + + listView + + + + + + + + + + treeView + + + + + + + + + + tableView + + + + + + + + + + columnView + + + + + + + + + + undoView + + + + + + + + + + + + + listWidget + + + + + + + + + + treeWidget + + + + + + + + + + tableWidget + + + + + + + + + + + + + + GroupBox + + + + 0 + 0 + 120 + 80 + + + + groupBox + + + + + + + + + + scrollArea + + + true + + + + 0 + 0 + 120 + 80 + + + + + + + + + + + + + 0 + + + toolBox + + + + Page 1 + + + + + Page 2 + + + + + + + + + + + + 0 + 0 + 120 + 80 + + + + tabWidget + + + + Tab 1 + + + + + Tab 2 + + + + + + + + + + + + 0 + 0 + 120 + 80 + + + + stackedWidget + + + + + + + + + + + + QFrame::Raised + + + + 0 + 0 + 120 + 80 + + + + QFrame::StyledPanel + + + frame + + + + + + + + + + + 0 + 0 + 120 + 80 + + + + widget + + + + + + + + + + + 0 + 0 + 200 + 160 + + + + mdiArea + + + + + + + + + + + 0 + 0 + 120 + 80 + + + + dockWidget + + + + + + + + + + + + + + + + 119 + 28 + 41 + 22 + + + + comboBox + + + + + + + + + + + 119 + 28 + 41 + 22 + + + + fontComboBox + + + + + + + + + + + 0 + 1 + 113 + 20 + + + + lineEdit + + + + + + + + + + textEdit + + + + 0 + 0 + 104 + 64 + + + + + + + + + + + plainTextEdit + + + + 0 + 0 + 104 + 64 + + + + + + + + + + + + 119 + 0 + 42 + 22 + + + + spinBox + + + + + + + + + + + 119 + 0 + 62 + 22 + + + + doubleSpinBox + + + + + + + + + + + 0 + 28 + 118 + 22 + + + + timeEdit + + + + + + + + + + + 0 + 28 + 110 + 22 + + + + dateEdit + + + + + + + + + + + 0 + 28 + 194 + 22 + + + + dateTimeEdit + + + + + + + + + + + 110 + 0 + 50 + 64 + + + + dial + + + + + + + + + + Qt::Horizontal + + + + 0 + 126 + 160 + 16 + + + + horizontalScrollBar + + + + + + + + + + Qt::Vertical + + + + 0 + 126 + 16 + 160 + + + + verticalScrollBar + + + + + + + + + + Qt::Horizontal + + + + 0 + 126 + 160 + 16 + + + + horizontalSlider + + + + + + + + + + Qt::Vertical + + + + 0 + 126 + 16 + 160 + + + + verticalSlider + + + + + + + + + + 0 + 1 + 113 + 20 + + + + keySequenceEdit + + + + + + + + + + + + + + TextLabel + + + label + + + + + + + + + + textBrowser + + + + + + + + + + graphicsView + + + + + + + + + + calendarWidget + + + + + + + + + + lcdNumber + + + + + + + + + + 24 + + + + 9 + 38 + 118 + 23 + + + + progressBar + + + + + + + + + + Qt::Horizontal + + + line + + + + 9 + 67 + 118 + 3 + + + + + + + + + + + Qt::Vertical + + + line + + + + 133 + 9 + 3 + 61 + + + + + + + + + + + + 0 + 0 + 300 + 200 + + + + openGLWidget + + + + + + + diff --git a/src/designer/src/components/widgetbox/widgetbox_dnditem.cpp b/src/designer/src/components/widgetbox/widgetbox_dnditem.cpp new file mode 100644 index 0000000..afbfc05 --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetbox_dnditem.cpp @@ -0,0 +1,191 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetbox_dnditem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +/******************************************************************************* +** WidgetBoxResource +*/ + +static inline DeviceProfile currentDeviceProfile(const QDesignerFormEditorInterface *core) +{ + if (QDesignerFormWindowInterface *cfw = core->formWindowManager()->activeFormWindow()) + if (const FormWindowBase *fwb = qobject_cast(cfw)) + return fwb->deviceProfile(); + return DeviceProfile(); +} + +class WidgetBoxResource : public QDesignerFormBuilder +{ +public: + WidgetBoxResource(QDesignerFormEditorInterface *core); + + // protected->public + QWidget *createUI(DomUI *ui, QWidget *parents) { return QDesignerFormBuilder::create(ui, parents); } + +protected: + + QWidget *create(DomWidget *ui_widget, QWidget *parents) override; + QWidget *createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) override; + void createCustomWidgets(DomCustomWidgets *) override; +}; + +WidgetBoxResource::WidgetBoxResource(QDesignerFormEditorInterface *core) : + QDesignerFormBuilder(core, currentDeviceProfile(core)) +{ +} + + +QWidget *WidgetBoxResource::createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) +{ + if (widgetName == "Spacer"_L1) { + Spacer *spacer = new Spacer(parentWidget); + spacer->setObjectName(name); + return spacer; + } + + return QDesignerFormBuilder::createWidget(widgetName, parentWidget, name); +} + +QWidget *WidgetBoxResource::create(DomWidget *ui_widget, QWidget *parent) +{ + QWidget *result = QDesignerFormBuilder::create(ui_widget, parent); + // It is possible to have a syntax error or something in custom + // widget XML, so, try to recover here by creating an artificial + // top level + widget. + if (!result) { + const QString msg = QApplication::translate("qdesigner_internal::WidgetBox", "Warning: Widget creation failed in the widget box. This could be caused by invalid custom widget XML."); + qdesigner_internal::designerWarning(msg); + result = new QWidget(parent); + new QWidget(result); + } + result->setFocusPolicy(Qt::NoFocus); + result->setObjectName(ui_widget->attributeName()); + return result; +} + +void WidgetBoxResource::createCustomWidgets(DomCustomWidgets *dc) +{ + // Make a promotion entry in case someone has a promoted widget + // in the scratchpad. + QSimpleResource::handleDomCustomWidgets(core(), dc); + +} + +/******************************************************************************* +** WidgetBoxResource +*/ + +static QSize geometryProp(const DomWidget *dw) +{ + const auto &prop_list = dw->elementProperty(); + for (DomProperty *prop : prop_list) { + if (prop->attributeName() != "geometry"_L1) + continue; + DomRect *dr = prop->elementRect(); + if (dr == nullptr) + continue; + return QSize(dr->elementWidth(), dr->elementHeight()); + } + return QSize(); +} + +static QSize domWidgetSize(const DomWidget *dw) +{ + QSize size = geometryProp(dw); + if (size.isValid()) + return size; + + const auto &elementWidgets = dw->elementWidget(); + for (const DomWidget *child : elementWidgets) { + size = geometryProp(child); + if (size.isValid()) + return size; + } + + const auto &elementLayouts = dw->elementLayout(); + for (const DomLayout *dl : elementLayouts) { + const auto &elementItems = dl->elementItem(); + for (DomLayoutItem *item : elementItems) { + const DomWidget *child = item->elementWidget(); + if (child == nullptr) + continue; + size = geometryProp(child); + if (size.isValid()) + return size; + } + } + + return QSize(); +} + +static QWidget *decorationFromDomWidget(DomUI *dom_ui, QDesignerFormEditorInterface *core) +{ + WidgetBoxResource builder(core); + // We have the builder create the articial QWidget fake top level as a tooltip + // because the size algorithm works better at weird DPI settings + // if the actual widget is created as a child of a container + QWidget *fakeTopLevel = builder.createUI(dom_ui, nullptr); + fakeTopLevel->setParent(nullptr, Qt::ToolTip); // Container + // Actual widget + const DomWidget *domW = dom_ui->elementWidget()->elementWidget().constFirst(); + QWidget *w = fakeTopLevel->findChildren().constFirst(); + Q_ASSERT(w); + // hack begin; + // We set _q_dockDrag dynamic property which will be detected in drag enter event of form window. + // Dock drop is handled in special way (highlight goes to central widget of main window) + if (qobject_cast(w)) + fakeTopLevel->setProperty("_q_dockDrag", QVariant(true)); + // hack end; + w->setAutoFillBackground(true); // Different style for embedded + QSize size = domWidgetSize(domW); + const QSize minimumSize = w->minimumSizeHint(); + if (!size.isValid()) + size = w->sizeHint(); + if (size.width() < minimumSize.width()) + size.setWidth(minimumSize.width()); + if (size.height() < minimumSize.height()) + size.setHeight(minimumSize.height()); + // A QWidget might have size -1,-1 if no geometry property is specified in the widget box. + if (size.isEmpty()) + size = size.expandedTo(QSize(16, 16)); + w->setGeometry(QRect(QPoint(0, 0), size)); + fakeTopLevel->resize(size); + return fakeTopLevel; +} + +WidgetBoxDnDItem::WidgetBoxDnDItem(QDesignerFormEditorInterface *core, + DomUI *dom_ui, + const QPoint &global_mouse_pos) : + QDesignerDnDItem(CopyDrop) +{ + QWidget *decoration = decorationFromDomWidget(dom_ui, core); + decoration->move(global_mouse_pos - QPoint(5, 5)); + + init(dom_ui, nullptr, decoration, global_mouse_pos); +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/components/widgetbox/widgetbox_dnditem.h b/src/designer/src/components/widgetbox/widgetbox_dnditem.h new file mode 100644 index 0000000..f8f25d5 --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetbox_dnditem.h @@ -0,0 +1,29 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOX_DNDITEM_H +#define WIDGETBOX_DNDITEM_H + +#include +#include "widgetbox_global.h" + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class DomUI; + +namespace qdesigner_internal { + +class QT_WIDGETBOX_EXPORT WidgetBoxDnDItem : public QDesignerDnDItem +{ +public: + WidgetBoxDnDItem(QDesignerFormEditorInterface *core, + DomUI *dom_ui, + const QPoint &global_mouse_pos); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETBOX_DNDITEM_H diff --git a/src/designer/src/components/widgetbox/widgetbox_global.h b/src/designer/src/components/widgetbox/widgetbox_global.h new file mode 100644 index 0000000..68ebc35 --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetbox_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOX_GLOBAL_H +#define WIDGETBOX_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_WIDGETBOX_LIBRARY +# define QT_WIDGETBOX_EXPORT +#else +# define QT_WIDGETBOX_EXPORT +#endif +#else +#define QT_WIDGETBOX_EXPORT +#endif + +#endif // WIDGETBOX_GLOBAL_H diff --git a/src/designer/src/components/widgetbox/widgetboxcategorylistview.cpp b/src/designer/src/components/widgetbox/widgetboxcategorylistview.cpp new file mode 100644 index 0000000..1eb2af5 --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetboxcategorylistview.cpp @@ -0,0 +1,471 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetboxcategorylistview.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto widgetElementC = "widget"_L1; +static constexpr auto nameAttributeC = "name"_L1; +static constexpr auto uiOpeningTagC = ""_L1; +static constexpr auto uiClosingTagC = ""_L1; + +enum { FilterRole = Qt::UserRole + 11 }; + +static QString domToString(const QDomElement &elt) +{ + QString result; + QTextStream stream(&result, QIODevice::WriteOnly); + elt.save(stream, 2); + stream.flush(); + return result; +} + +static QDomDocument stringToDom(const QString &xml) +{ + QDomDocument result; + result.setContent(xml); + return result; +} + +namespace qdesigner_internal { + +// Entry of the model list + +struct WidgetBoxCategoryEntry { + WidgetBoxCategoryEntry() = default; + explicit WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &widget, + const QString &filter, + const QIcon &icon, + bool editable); + + QDesignerWidgetBoxInterface::Widget widget; + QString toolTip; + QString whatsThis; + QString filter; + QIcon icon; + bool editable{false}; +}; + +WidgetBoxCategoryEntry::WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &w, + const QString &filterIn, + const QIcon &i, bool e) : + widget(w), + filter(filterIn), + icon(i), + editable(e) +{ +} + +/* WidgetBoxCategoryModel, representing a list of category entries. Uses a + * QAbstractListModel since the behaviour depends on the view mode of the list + * view, it does not return text in the case of IconMode. */ + +class WidgetBoxCategoryModel : public QAbstractListModel { +public: + explicit WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + + // QAbstractListModel + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; + Qt::ItemFlags flags (const QModelIndex & index ) const override; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + + // The model returns no text in icon mode, so, it also needs to know it + QListView::ViewMode viewMode() const; + void setViewMode(QListView::ViewMode vm); + + void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable); + + QDesignerWidgetBoxInterface::Widget widgetAt(const QModelIndex & index) const; + QDesignerWidgetBoxInterface::Widget widgetAt(int row) const; + + int indexOfWidget(const QString &name); + + QDesignerWidgetBoxInterface::Category category() const; + bool removeCustomWidgets(); + +private: + QDesignerFormEditorInterface *m_core; + QList m_items; + QListView::ViewMode m_viewMode; +}; + +WidgetBoxCategoryModel::WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent) : + QAbstractListModel(parent), + m_core(core), + m_viewMode(QListView::ListMode) +{ +} + +QListView::ViewMode WidgetBoxCategoryModel::viewMode() const +{ + return m_viewMode; +} + +void WidgetBoxCategoryModel::setViewMode(QListView::ViewMode vm) +{ + if (m_viewMode == vm) + return; + const bool empty = m_items.isEmpty(); + if (!empty) + beginResetModel(); + m_viewMode = vm; + if (!empty) + endResetModel(); +} + +int WidgetBoxCategoryModel::indexOfWidget(const QString &name) +{ + for (qsizetype i = 0, count = m_items.size(); i < count; ++i) + if (m_items.at(i).widget.name() == name) + return i; + return -1; +} + +QDesignerWidgetBoxInterface::Category WidgetBoxCategoryModel::category() const +{ + QDesignerWidgetBoxInterface::Category rc; + for (const auto &c : m_items) + rc.addWidget(c.widget); + return rc; +} + +bool WidgetBoxCategoryModel::removeCustomWidgets() +{ + // Typically, we are a whole category of custom widgets, so, remove all + // and do reset. + bool changed = false; + for (auto it = m_items.begin(); it != m_items.end(); ) + if (it->widget.type() == QDesignerWidgetBoxInterface::Widget::Custom) { + if (!changed) + beginResetModel(); + it = m_items.erase(it); + changed = true; + } else { + ++it; + } + if (changed) + endResetModel(); + return changed; +} + +void WidgetBoxCategoryModel::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon,bool editable) +{ + static const QRegularExpression classNameRegExp(QStringLiteral("widgetDataBase(); + int dbIndex = className.isEmpty() ? -1 : db->indexOfClassName(className); + if (dbIndex == -1) + dbIndex = db->indexOfClassName(widget.name()); + if (dbIndex != -1) { + const QDesignerWidgetDataBaseItemInterface *dbItem = db->item(dbIndex); + const QString toolTip = dbItem->toolTip(); + if (!toolTip.isEmpty()) + item.toolTip = toolTip; + const QString whatsThis = dbItem->whatsThis(); + if (!whatsThis.isEmpty()) + item.whatsThis = whatsThis; + } + // insert + const int row = m_items.size(); + beginInsertRows(QModelIndex(), row, row); + m_items.push_back(item); + endInsertRows(); +} + +QVariant WidgetBoxCategoryModel::data(const QModelIndex &index, int role) const +{ + const int row = index.row(); + if (row < 0 || row >= m_items.size()) + return QVariant(); + + const WidgetBoxCategoryEntry &item = m_items.at(row); + switch (role) { + case Qt::DisplayRole: + // No text in icon mode + return QVariant(m_viewMode == QListView::ListMode ? item.widget.name() : QString()); + case Qt::DecorationRole: + return QVariant(item.icon); + case Qt::EditRole: + return QVariant(item.widget.name()); + case Qt::ToolTipRole: { + if (m_viewMode == QListView::ListMode) + return QVariant(item.toolTip); + // Icon mode tooltip should contain the class name + QString tt = item.widget.name(); + if (!item.toolTip.isEmpty()) + tt += u'\n' + item.toolTip; + return QVariant(tt); + + } + case Qt::WhatsThisRole: + return QVariant(item.whatsThis); + case FilterRole: + return item.filter; + } + return QVariant(); +} + +bool WidgetBoxCategoryModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + const int row = index.row(); + if (role != Qt::EditRole || row < 0 || row >= m_items.size() + || value.metaType().id() != QMetaType::QString) { + return false; + } + // Set name and adapt Xml + WidgetBoxCategoryEntry &item = m_items[row]; + const QString newName = value.toString(); + item.widget.setName(newName); + + const QDomDocument doc = stringToDom(WidgetBoxCategoryListView::widgetDomXml(item.widget)); + QDomElement widget_elt = doc.firstChildElement(widgetElementC); + if (!widget_elt.isNull()) { + widget_elt.setAttribute(nameAttributeC, newName); + item.widget.setDomXml(domToString(widget_elt)); + } + emit dataChanged(index, index); + return true; +} + +Qt::ItemFlags WidgetBoxCategoryModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags rc = Qt::ItemIsEnabled; + const int row = index.row(); + if (row >= 0 && row < m_items.size()) + if (m_items.at(row).editable) { + rc |= Qt::ItemIsSelectable; + // Can change name in list mode only + if (m_viewMode == QListView::ListMode) + rc |= Qt::ItemIsEditable; + } + return rc; +} + +int WidgetBoxCategoryModel::rowCount(const QModelIndex & /*parent*/) const +{ + return m_items.size(); +} + +bool WidgetBoxCategoryModel::removeRows(int row, int count, const QModelIndex & parent) +{ + if (row < 0 || count < 1) + return false; + const int size = m_items.size(); + const int last = row + count - 1; + if (row >= size || last >= size) + return false; + beginRemoveRows(parent, row, last); + for (int r = last; r >= row; r--) + m_items.removeAt(r); + endRemoveRows(); + return true; +} + +QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryModel::widgetAt(const QModelIndex & index) const +{ + return widgetAt(index.row()); +} + +QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryModel::widgetAt(int row) const +{ + if (row < 0 || row >= m_items.size()) + return QDesignerWidgetBoxInterface::Widget(); + return m_items.at(row).widget; +} + +/* WidgetSubBoxItemDelegate, ensures a valid name using a regexp validator */ + +class WidgetBoxCategoryEntryDelegate : public QStyledItemDelegate +{ +public: + explicit WidgetBoxCategoryEntryDelegate(QWidget *parent = nullptr) : QStyledItemDelegate(parent) {} + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +QWidget *WidgetBoxCategoryEntryDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QWidget *result = QStyledItemDelegate::createEditor(parent, option, index); + if (QLineEdit *line_edit = qobject_cast(result)) { + static const QRegularExpression re(u"^[_a-zA-Z][_a-zA-Z0-9]*$"_s); + Q_ASSERT(re.isValid()); + line_edit->setValidator(new QRegularExpressionValidator(re, line_edit)); + } + return result; +} + +// ---------------------- WidgetBoxCategoryListView + +WidgetBoxCategoryListView::WidgetBoxCategoryListView(QDesignerFormEditorInterface *core, QWidget *parent) : + QListView(parent), + m_proxyModel(new QSortFilterProxyModel(this)), + m_model(new WidgetBoxCategoryModel(core, this)) +{ + setFocusPolicy(Qt::NoFocus); + setFrameShape(QFrame::NoFrame); + setIconSize(QSize(22, 22)); + setSpacing(1); + setTextElideMode(Qt::ElideMiddle); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setResizeMode(QListView::Adjust); + setUniformItemSizes(true); + + setItemDelegate(new WidgetBoxCategoryEntryDelegate(this)); + + connect(this, &QListView::pressed, this, + &WidgetBoxCategoryListView::slotPressed); + setEditTriggers(QAbstractItemView::AnyKeyPressed); + + m_proxyModel->setSourceModel(m_model); + m_proxyModel->setFilterRole(FilterRole); + setModel(m_proxyModel); + connect(m_model, &QAbstractItemModel::dataChanged, + this, &WidgetBoxCategoryListView::scratchPadChanged); +} + +void WidgetBoxCategoryListView::setViewMode(ViewMode vm) +{ + QListView::setViewMode(vm); + m_model->setViewMode(vm); +} + +void WidgetBoxCategoryListView::setCurrentItem(AccessMode am, int row) +{ + const QModelIndex index = am == FilteredAccess ? + m_proxyModel->index(row, 0) : + m_proxyModel->mapFromSource(m_model->index(row, 0)); + + if (index.isValid()) + setCurrentIndex(index); +} + +void WidgetBoxCategoryListView::slotPressed(const QModelIndex &index) +{ + const QDesignerWidgetBoxInterface::Widget wgt = m_model->widgetAt(m_proxyModel->mapToSource(index)); + if (wgt.isNull()) + return; + emit widgetBoxPressed(wgt.name(), widgetDomXml(wgt), QCursor::pos()); +} + +void WidgetBoxCategoryListView::removeCurrentItem() +{ + const QModelIndex index = currentIndex(); + if (!index.isValid() || !m_proxyModel->removeRow(index.row())) + return; + + // We check the unfiltered item count here, we don't want to get removed if the + // filtered view is empty + if (m_model->rowCount()) { + emit itemRemoved(); + } else { + emit lastItemRemoved(); + } +} + +void WidgetBoxCategoryListView::editCurrentItem() +{ + const QModelIndex index = currentIndex(); + if (index.isValid()) + edit(index); +} + +int WidgetBoxCategoryListView::count(AccessMode am) const +{ + return am == FilteredAccess ? m_proxyModel->rowCount() : m_model->rowCount(); +} + +int WidgetBoxCategoryListView::mapRowToSource(int filterRow) const +{ + const QModelIndex filterIndex = m_proxyModel->index(filterRow, 0); + return m_proxyModel->mapToSource(filterIndex).row(); +} + +QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, const QModelIndex & index) const +{ + const QModelIndex unfilteredIndex = am == FilteredAccess ? m_proxyModel->mapToSource(index) : index; + return m_model->widgetAt(unfilteredIndex); +} + +QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, int row) const +{ + return m_model->widgetAt(am == UnfilteredAccess ? row : mapRowToSource(row)); +} + +void WidgetBoxCategoryListView::removeRow(AccessMode am, int row) +{ + m_model->removeRow(am == UnfilteredAccess ? row : mapRowToSource(row)); +} + +bool WidgetBoxCategoryListView::containsWidget(const QString &name) +{ + return m_model->indexOfWidget(name) != -1; +} + +void WidgetBoxCategoryListView::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable) +{ + m_model->addWidget(widget, icon, editable); +} + +QString WidgetBoxCategoryListView::widgetDomXml(const QDesignerWidgetBoxInterface::Widget &widget) +{ + QString domXml = widget.domXml(); + + if (domXml.isEmpty()) + domXml = uiOpeningTagC + ""_L1 + uiClosingTagC; + return domXml; +} + +void WidgetBoxCategoryListView::filter(const QString &needle, Qt::CaseSensitivity caseSensitivity) +{ + m_proxyModel->setFilterFixedString(needle); + m_proxyModel->setFilterCaseSensitivity(caseSensitivity); +} + +QDesignerWidgetBoxInterface::Category WidgetBoxCategoryListView::category() const +{ + return m_model->category(); +} + +bool WidgetBoxCategoryListView::removeCustomWidgets() +{ + return m_model->removeCustomWidgets(); +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/widgetbox/widgetboxcategorylistview.h b/src/designer/src/components/widgetbox/widgetboxcategorylistview.h new file mode 100644 index 0000000..9223a2f --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetboxcategorylistview.h @@ -0,0 +1,79 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOXCATEGORYLISTVIEW_H +#define WIDGETBOXCATEGORYLISTVIEW_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerDnDItemInterface; + +class QSortFilterProxyModel; + +namespace qdesigner_internal { + +class WidgetBoxCategoryModel; + +// List view of a category, switchable between icon and list mode. +// Provides a filtered view. +class WidgetBoxCategoryListView : public QListView +{ + Q_OBJECT +public: + // Whether to access the filtered or unfiltered view + enum AccessMode { FilteredAccess, UnfilteredAccess }; + + explicit WidgetBoxCategoryListView(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + void setViewMode(ViewMode vm); + + void dropWidgets(const QList &item_list); + + using QListView::contentsSize; + + // These methods operate on the filtered/unfiltered model according to accessmode + int count(AccessMode am) const; + QDesignerWidgetBoxInterface::Widget widgetAt(AccessMode am, const QModelIndex &index) const; + QDesignerWidgetBoxInterface::Widget widgetAt(AccessMode am, int row) const; + void removeRow(AccessMode am, int row); + void setCurrentItem(AccessMode am, int row); + + // These methods operate on the unfiltered model and are used for serialization + void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable); + bool containsWidget(const QString &name); + QDesignerWidgetBoxInterface::Category category() const; + bool removeCustomWidgets(); + + // Helper: Ensure a tag in the case of empty XML + static QString widgetDomXml(const QDesignerWidgetBoxInterface::Widget &widget); + +signals: + void scratchPadChanged(); + void widgetBoxPressed(const QString &name, const QString &xml, const QPoint &globalPos); + void itemRemoved(); + void lastItemRemoved(); + +public slots: + void filter(const QString &needle, Qt::CaseSensitivity caseSensitivity); + void removeCurrentItem(); + void editCurrentItem(); + +private slots: + void slotPressed(const QModelIndex &index); + +private: + int mapRowToSource(int filterRow) const; + QSortFilterProxyModel *m_proxyModel; + WidgetBoxCategoryModel *m_model; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETBOXCATEGORYLISTVIEW_H diff --git a/src/designer/src/components/widgetbox/widgetboxtreewidget.cpp b/src/designer/src/components/widgetbox/widgetboxtreewidget.cpp new file mode 100644 index 0000000..4dca4a1 --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetboxtreewidget.cpp @@ -0,0 +1,981 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetboxtreewidget.h" +#include "widgetboxcategorylistview.h" + +// shared +#include +#include +#include +#include +#include + +// sdk +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto widgetBoxRootElementC = "widgetbox"_L1; +static constexpr auto wbWidgetElementC = "widget"_L1; +static constexpr auto uiElementC = "ui"_L1; +static constexpr auto categoryElementC = "category"_L1; +static constexpr auto categoryEntryElementC = "categoryentry"_L1; +static constexpr auto wbNameAttributeC = "name"_L1; +static constexpr auto typeAttributeC = "type"_L1; +static constexpr auto iconAttributeC = "icon"_L1; +static constexpr auto defaultTypeValueC = "default"_L1; +static constexpr auto customValueC = "custom"_L1; +static constexpr auto iconPrefixC = "__qt_icon__"_L1; +static constexpr auto scratchPadValueC = "scratchpad"_L1; +static constexpr auto invisibleNameC = "[invisible]"_L1; + +enum TopLevelRole { NORMAL_ITEM, SCRATCHPAD_ITEM, CUSTOM_ITEM }; + +static void setTopLevelRole(TopLevelRole tlr, QTreeWidgetItem *item) +{ + item->setData(0, Qt::UserRole, QVariant(tlr)); +} + +static TopLevelRole topLevelRole(const QTreeWidgetItem *item) +{ + return static_cast(item->data(0, Qt::UserRole).toInt()); +} + +namespace qdesigner_internal { + +WidgetBoxTreeWidget::WidgetBoxTreeWidget(QDesignerFormEditorInterface *core, QWidget *parent) : + QTreeWidget(parent), + m_core(core), + m_iconMode(false), + m_scratchPadDeleteTimer(nullptr) +{ + setFocusPolicy(Qt::NoFocus); + setIndentation(0); + setRootIsDecorated(false); + setColumnCount(1); + header()->hide(); + header()->setSectionResizeMode(QHeaderView::Stretch); + setTextElideMode(Qt::ElideMiddle); + setVerticalScrollMode(ScrollPerPixel); + + setItemDelegate(new SheetDelegate(this, this)); + + connect(this, &QTreeWidget::itemPressed, + this, &WidgetBoxTreeWidget::handleMousePress); +} + +QIcon WidgetBoxTreeWidget::iconForWidget(const QString &iconName) const +{ + if (iconName.isEmpty()) + return qdesigner_internal::qtLogoIcon(); + + if (iconName.startsWith(iconPrefixC)) { + const auto it = m_pluginIcons.constFind(iconName); + if (it != m_pluginIcons.constEnd()) + return it.value(); + } + return createIconSet(iconName); +} + +WidgetBoxCategoryListView *WidgetBoxTreeWidget::categoryViewAt(int idx) const +{ + WidgetBoxCategoryListView *rc = nullptr; + if (QTreeWidgetItem *cat_item = topLevelItem(idx)) + if (QTreeWidgetItem *embedItem = cat_item->child(0)) + rc = qobject_cast(itemWidget(embedItem, 0)); + Q_ASSERT(rc); + return rc; +} + +static constexpr auto widgetBoxSettingsGroupC = "WidgetBox"_L1; +static constexpr auto widgetBoxExpandedKeyC = "Closed categories"_L1; +static constexpr auto widgetBoxViewModeKeyC = "View mode"_L1; + +void WidgetBoxTreeWidget::saveExpandedState() const +{ + QStringList closedCategories; + if (const int numCategories = categoryCount()) { + for (int i = 0; i < numCategories; ++i) { + const QTreeWidgetItem *cat_item = topLevelItem(i); + if (!cat_item->isExpanded()) + closedCategories.append(cat_item->text(0)); + } + } + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(widgetBoxSettingsGroupC); + settings->setValue(widgetBoxExpandedKeyC, closedCategories); + settings->setValue(widgetBoxViewModeKeyC, m_iconMode); + settings->endGroup(); +} + +void WidgetBoxTreeWidget::restoreExpandedState() +{ + using StringSet = QSet; + QDesignerSettingsInterface *settings = m_core->settingsManager(); + const QString groupKey = widgetBoxSettingsGroupC + u'/'; + m_iconMode = settings->value(groupKey + widgetBoxViewModeKeyC).toBool(); + updateViewMode(); + const auto &closedCategoryList = settings->value(groupKey + widgetBoxExpandedKeyC, QStringList()).toStringList(); + const StringSet closedCategories(closedCategoryList.cbegin(), closedCategoryList.cend()); + expandAll(); + if (closedCategories.isEmpty()) + return; + + if (const int numCategories = categoryCount()) { + for (int i = 0; i < numCategories; ++i) { + QTreeWidgetItem *item = topLevelItem(i); + if (closedCategories.contains(item->text(0))) + item->setExpanded(false); + } + } +} + +WidgetBoxTreeWidget::~WidgetBoxTreeWidget() +{ + saveExpandedState(); +} + +void WidgetBoxTreeWidget::setFileName(const QString &file_name) +{ + m_file_name = file_name; +} + +QString WidgetBoxTreeWidget::fileName() const +{ + return m_file_name; +} + +bool WidgetBoxTreeWidget::save() +{ + if (fileName().isEmpty()) + return false; + + QFile file(fileName()); + if (!file.open(QIODevice::WriteOnly)) + return false; + + CategoryList cat_list; + const int count = categoryCount(); + for (int i = 0; i < count; ++i) + cat_list.append(category(i)); + + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(1); + writer.writeStartDocument(); + writeCategories(writer, cat_list); + writer.writeEndDocument(); + + return true; +} + +void WidgetBoxTreeWidget::slotSave() +{ + save(); +} + +void WidgetBoxTreeWidget::handleMousePress(QTreeWidgetItem *item) +{ + if (item == nullptr) + return; + + if (QApplication::mouseButtons() != Qt::LeftButton) + return; + + if (item->parent() == nullptr) { + item->setExpanded(!item->isExpanded()); + return; + } +} + +int WidgetBoxTreeWidget::ensureScratchpad() +{ + const int existingIndex = indexOfScratchpad(); + if (existingIndex != -1) + return existingIndex; + + QTreeWidgetItem *scratch_item = new QTreeWidgetItem(this); + scratch_item->setText(0, tr("Scratchpad")); + setTopLevelRole(SCRATCHPAD_ITEM, scratch_item); + addCategoryView(scratch_item, false); // Scratchpad in list mode. + return categoryCount() - 1; +} + +WidgetBoxCategoryListView *WidgetBoxTreeWidget::addCategoryView(QTreeWidgetItem *parent, bool iconMode) +{ + QTreeWidgetItem *embed_item = new QTreeWidgetItem(parent); + embed_item->setFlags(Qt::ItemIsEnabled); + WidgetBoxCategoryListView *categoryView = new WidgetBoxCategoryListView(m_core, this); + categoryView->setViewMode(iconMode ? QListView::IconMode : QListView::ListMode); + connect(categoryView, &WidgetBoxCategoryListView::scratchPadChanged, + this, &WidgetBoxTreeWidget::slotSave); + connect(categoryView, &WidgetBoxCategoryListView::widgetBoxPressed, + this, &WidgetBoxTreeWidget::widgetBoxPressed); + connect(categoryView, &WidgetBoxCategoryListView::itemRemoved, + this, &WidgetBoxTreeWidget::slotScratchPadItemDeleted); + connect(categoryView, &WidgetBoxCategoryListView::lastItemRemoved, + this, &WidgetBoxTreeWidget::slotLastScratchPadItemDeleted); + setItemWidget(embed_item, 0, categoryView); + return categoryView; +} + +int WidgetBoxTreeWidget::indexOfScratchpad() const +{ + if (const int numTopLevels = topLevelItemCount()) { + for (int i = numTopLevels - 1; i >= 0; --i) { + if (topLevelRole(topLevelItem(i)) == SCRATCHPAD_ITEM) + return i; + } + } + return -1; +} + +int WidgetBoxTreeWidget::indexOfCategory(const QString &name) const +{ + const int topLevelCount = topLevelItemCount(); + for (int i = 0; i < topLevelCount; ++i) { + if (topLevelItem(i)->text(0) == name) + return i; + } + return -1; +} + +bool WidgetBoxTreeWidget::load(QDesignerWidgetBox::LoadMode loadMode) +{ + switch (loadMode) { + case QDesignerWidgetBox::LoadReplace: + clear(); + break; + case QDesignerWidgetBox::LoadCustomWidgetsOnly: + addCustomCategories(true); + updateGeometries(); + return true; + default: + break; + } + + const QString name = fileName(); + + QFile f(name); + if (!f.open(QIODevice::ReadOnly)) // Might not exist at first startup + return false; + + const QString contents = QString::fromUtf8(f.readAll()); + if (!loadContents(contents)) + return false; + if (topLevelItemCount() > 0) { + // QTBUG-93099: Set the single step to the item height to have some + // size-related value. + const auto itemHeight = visualItemRect(topLevelItem(0)).height(); + verticalScrollBar()->setSingleStep(itemHeight); + } + return true; +} + +bool WidgetBoxTreeWidget::loadContents(const QString &contents) +{ + QString errorMessage; + CategoryList cat_list; + if (!readCategories(m_file_name, contents, &cat_list, &errorMessage)) { + qdesigner_internal::designerWarning(errorMessage); + return false; + } + + for (const Category &cat : std::as_const(cat_list)) + addCategory(cat); + + addCustomCategories(false); + // Restore which items are expanded + restoreExpandedState(); + return true; +} + +void WidgetBoxTreeWidget::addCustomCategories(bool replace) +{ + if (replace) { + // clear out all existing custom widgets + if (const int numTopLevels = topLevelItemCount()) { + for (int t = 0; t < numTopLevels ; ++t) + categoryViewAt(t)->removeCustomWidgets(); + } + } + // re-add + const CategoryList customList = loadCustomCategoryList(); + for (const auto &c : customList) + addCategory(c); +} + +static inline QString msgXmlError(const QString &fileName, const QXmlStreamReader &r) +{ + return QDesignerWidgetBox::tr("An error has been encountered at line %1 of %2: %3") + .arg(r.lineNumber()).arg(fileName, r.errorString()); +} + +bool WidgetBoxTreeWidget::readCategories(const QString &fileName, const QString &contents, + CategoryList *cats, QString *errorMessage) +{ + // Read widget box XML: + // + // + // + // + // + // ... + + QXmlStreamReader reader(contents); + + + // Entries of category with name="invisible" should be ignored + bool ignoreEntries = false; + + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: { + const auto tag = reader.name(); + if (tag == widgetBoxRootElementC) { + // + continue; + } + if (tag == categoryElementC) { + // + const QXmlStreamAttributes attributes = reader.attributes(); + const QString categoryName = attributes.value(wbNameAttributeC).toString(); + if (categoryName == invisibleNameC) { + ignoreEntries = true; + } else { + Category category(categoryName); + if (attributes.value(typeAttributeC) == scratchPadValueC) + category.setType(Category::Scratchpad); + cats->push_back(category); + } + continue; + } + if (tag == categoryEntryElementC) { + // + if (!ignoreEntries) { + QXmlStreamAttributes attr = reader.attributes(); + const QString widgetName = attr.value(wbNameAttributeC).toString(); + const QString widgetIcon = attr.value(iconAttributeC).toString(); + const WidgetBoxTreeWidget::Widget::Type widgetType = + attr.value(typeAttributeC).toString() + == customValueC ? + WidgetBoxTreeWidget::Widget::Custom : + WidgetBoxTreeWidget::Widget::Default; + + Widget w; + w.setName(widgetName); + w.setIconName(widgetIcon); + w.setType(widgetType); + if (!readWidget(&w, contents, reader)) + continue; + + cats->back().addWidget(w); + } // ignoreEntries + continue; + } + break; + } + case QXmlStreamReader::EndElement: { + const auto tag = reader.name(); + if (tag == widgetBoxRootElementC) { + continue; + } + if (tag == categoryElementC) { + ignoreEntries = false; + continue; + } + if (tag == categoryEntryElementC) { + continue; + } + break; + } + default: break; + } + } + + if (reader.hasError()) { + *errorMessage = msgXmlError(fileName, reader); + return false; + } + + return true; +} + +/*! + * Read out a widget within a category. This can either be + * enclosed in a element or a (legacy) element which may + * contain nested elements. + * + * Examples: + * + * + * ... + * ... + * + * + * or + * + * + * ... + * ... + * + * + * Returns true on success, false if end was reached or an error has been encountered + * in which case the reader has its error flag set. If successful, the current item + * of the reader will be the closing element ( or ) + */ +bool WidgetBoxTreeWidget::readWidget(Widget *w, const QString &xml, QXmlStreamReader &r) +{ + qint64 startTagPosition =0, endTagPosition = 0; + + int nesting = 0; + bool endEncountered = false; + bool parsedWidgetTag = false; + while (!endEncountered) { + const qint64 currentPosition = r.characterOffset(); + switch(r.readNext()) { + case QXmlStreamReader::StartElement: + if (nesting++ == 0) { + // First element must be or (legacy) + const auto name = r.name(); + if (name == uiElementC) { + startTagPosition = currentPosition; + } else { + if (name == wbWidgetElementC) { + startTagPosition = currentPosition; + parsedWidgetTag = true; + } else { + r.raiseError(QDesignerWidgetBox::tr("Unexpected element <%1> encountered when parsing for or ").arg(name.toString())); + return false; + } + } + } else { + // We are within looking for the first tag + if (!parsedWidgetTag && r.name() == wbWidgetElementC) { + parsedWidgetTag = true; + } + } + break; + case QXmlStreamReader::EndElement: + // Reached end of widget? + if (--nesting == 0) { + endTagPosition = r.characterOffset(); + endEncountered = true; + } + break; + case QXmlStreamReader::EndDocument: + r.raiseError(QDesignerWidgetBox::tr("Unexpected end of file encountered when parsing widgets.")); + return false; + case QXmlStreamReader::Invalid: + return false; + default: + break; + } + } + if (!parsedWidgetTag) { + r.raiseError(QDesignerWidgetBox::tr("A widget element could not be found.")); + return false; + } + // Oddity: Startposition is 1 off + QString widgetXml = xml.mid(startTagPosition, endTagPosition - startTagPosition); + if (!widgetXml.startsWith(u'<')) + widgetXml.prepend(u'<'); + w->setDomXml(widgetXml); + return true; +} + +void WidgetBoxTreeWidget::writeCategories(QXmlStreamWriter &writer, const CategoryList &cat_list) const +{ + const QString widgetbox = widgetBoxRootElementC; + const QString name = wbNameAttributeC; + const QString type = typeAttributeC; + const QString icon = iconAttributeC; + const QString defaultType = defaultTypeValueC; + const QString category = categoryElementC; + const QString categoryEntry = categoryEntryElementC; + const QString iconPrefix = iconPrefixC; + + // + // + // + // + // + // ... + // + // + // ... + // + // ... + // + // + + writer.writeStartElement(widgetbox); + + for (const Category &cat : cat_list) { + writer.writeStartElement(category); + writer.writeAttribute(name, cat.name()); + if (cat.type() == Category::Scratchpad) + writer.writeAttribute(type, scratchPadValueC); + + const int widgetCount = cat.widgetCount(); + for (int i = 0; i < widgetCount; ++i) { + const Widget wgt = cat.widget(i); + if (wgt.type() == Widget::Custom) + continue; + + writer.writeStartElement(categoryEntry); + writer.writeAttribute(name, wgt.name()); + if (!wgt.iconName().startsWith(iconPrefix)) + writer.writeAttribute(icon, wgt.iconName()); + writer.writeAttribute(type, defaultType); + + const DomUI *domUI = QDesignerWidgetBox::xmlToUi(wgt.name(), WidgetBoxCategoryListView::widgetDomXml(wgt), false); + if (domUI) { + domUI->write(writer); + delete domUI; + } + + writer.writeEndElement(); // categoryEntry + } + writer.writeEndElement(); // categoryEntry + } + + writer.writeEndElement(); // widgetBox +} + +static int findCategory(const QString &name, const WidgetBoxTreeWidget::CategoryList &list) +{ + int idx = 0; + for (const WidgetBoxTreeWidget::Category &cat : list) { + if (cat.name() == name) + return idx; + ++idx; + } + return -1; +} + +static inline bool isValidIcon(const QIcon &icon) +{ + if (!icon.isNull()) { + const auto availableSizes = icon.availableSizes(); + return !availableSizes.isEmpty() && !availableSizes.constFirst().isEmpty(); + } + return false; +} + +WidgetBoxTreeWidget::CategoryList WidgetBoxTreeWidget::loadCustomCategoryList() const +{ + CategoryList result; + + const QDesignerPluginManager *pm = m_core->pluginManager(); + const QDesignerPluginManager::CustomWidgetList customWidgets = pm->registeredCustomWidgets(); + if (customWidgets.isEmpty()) + return result; + + static const QString customCatName = tr("Custom Widgets"); + + const QString invisible = invisibleNameC; + const QString iconPrefix = iconPrefixC; + + for (QDesignerCustomWidgetInterface *c : customWidgets) { + const QString dom_xml = c->domXml(); + if (dom_xml.isEmpty()) + continue; + + const QString pluginName = c->name(); + const QDesignerCustomWidgetData data = pm->customWidgetData(c); + QString displayName = data.xmlDisplayName(); + if (displayName.isEmpty()) + displayName = pluginName; + + QString cat_name = c->group(); + if (cat_name.isEmpty()) + cat_name = customCatName; + else if (cat_name == invisible) + continue; + + int idx = findCategory(cat_name, result); + if (idx == -1) { + result.append(Category(cat_name)); + idx = result.size() - 1; + } + Category &cat = result[idx]; + + const QIcon icon = c->icon(); + + QString icon_name; + if (isValidIcon(icon)) { + icon_name = iconPrefix; + icon_name += pluginName; + m_pluginIcons.insert(icon_name, icon); + } + + cat.addWidget(Widget(displayName, dom_xml, icon_name, Widget::Custom)); + } + + return result; +} + +void WidgetBoxTreeWidget::adjustSubListSize(QTreeWidgetItem *cat_item) +{ + QTreeWidgetItem *embedItem = cat_item->child(0); + if (embedItem == nullptr) + return; + + WidgetBoxCategoryListView *list_widget = static_cast(itemWidget(embedItem, 0)); + list_widget->setFixedWidth(header()->width()); + list_widget->doItemsLayout(); + const int height = qMax(list_widget->contentsSize().height() ,1); + list_widget->setFixedHeight(height); + embedItem->setSizeHint(0, QSize(-1, height - 1)); +} + +int WidgetBoxTreeWidget::categoryCount() const +{ + return topLevelItemCount(); +} + +WidgetBoxTreeWidget::Category WidgetBoxTreeWidget::category(int cat_idx) const +{ + if (cat_idx >= topLevelItemCount()) + return Category(); + + QTreeWidgetItem *cat_item = topLevelItem(cat_idx); + + QTreeWidgetItem *embedItem = cat_item->child(0); + WidgetBoxCategoryListView *categoryView = static_cast(itemWidget(embedItem, 0)); + + Category result = categoryView->category(); + result.setName(cat_item->text(0)); + + switch (topLevelRole(cat_item)) { + case SCRATCHPAD_ITEM: + result.setType(Category::Scratchpad); + break; + default: + result.setType(Category::Default); + break; + } + return result; +} + +void WidgetBoxTreeWidget::addCategory(const Category &cat) +{ + if (cat.widgetCount() == 0) + return; + + const bool isScratchPad = cat.type() == Category::Scratchpad; + WidgetBoxCategoryListView *categoryView; + QTreeWidgetItem *cat_item; + + if (isScratchPad) { + const int idx = ensureScratchpad(); + categoryView = categoryViewAt(idx); + cat_item = topLevelItem(idx); + } else { + const int existingIndex = indexOfCategory(cat.name()); + if (existingIndex == -1) { + cat_item = new QTreeWidgetItem(); + cat_item->setText(0, cat.name()); + setTopLevelRole(NORMAL_ITEM, cat_item); + // insert before scratchpad + const int scratchPadIndex = indexOfScratchpad(); + if (scratchPadIndex == -1) { + addTopLevelItem(cat_item); + } else { + insertTopLevelItem(scratchPadIndex, cat_item); + } + cat_item->setExpanded(true); + categoryView = addCategoryView(cat_item, m_iconMode); + } else { + categoryView = categoryViewAt(existingIndex); + cat_item = topLevelItem(existingIndex); + } + } + // The same categories are read from the file $HOME, avoid duplicates + const int widgetCount = cat.widgetCount(); + for (int i = 0; i < widgetCount; ++i) { + const Widget w = cat.widget(i); + if (!categoryView->containsWidget(w.name())) + categoryView->addWidget(w, iconForWidget(w.iconName()), isScratchPad); + } + adjustSubListSize(cat_item); +} + +void WidgetBoxTreeWidget::removeCategory(int cat_idx) +{ + if (cat_idx >= topLevelItemCount()) + return; + delete takeTopLevelItem(cat_idx); +} + +int WidgetBoxTreeWidget::widgetCount(int cat_idx) const +{ + if (cat_idx >= topLevelItemCount()) + return 0; + // SDK functions want unfiltered access + return categoryViewAt(cat_idx)->count(WidgetBoxCategoryListView::UnfilteredAccess); +} + +WidgetBoxTreeWidget::Widget WidgetBoxTreeWidget::widget(int cat_idx, int wgt_idx) const +{ + if (cat_idx >= topLevelItemCount()) + return Widget(); + // SDK functions want unfiltered access + WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); + return categoryView->widgetAt(WidgetBoxCategoryListView::UnfilteredAccess, wgt_idx); +} + +void WidgetBoxTreeWidget::addWidget(int cat_idx, const Widget &wgt) +{ + if (cat_idx >= topLevelItemCount()) + return; + + QTreeWidgetItem *cat_item = topLevelItem(cat_idx); + WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); + + const bool scratch = topLevelRole(cat_item) == SCRATCHPAD_ITEM; + categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), scratch); + adjustSubListSize(cat_item); +} + +void WidgetBoxTreeWidget::removeWidget(int cat_idx, int wgt_idx) +{ + if (cat_idx >= topLevelItemCount()) + return; + + WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); + + // SDK functions want unfiltered access + const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::UnfilteredAccess; + if (wgt_idx >= categoryView->count(am)) + return; + + categoryView->removeRow(am, wgt_idx); +} + +void WidgetBoxTreeWidget::slotScratchPadItemDeleted() +{ + const int scratch_idx = indexOfScratchpad(); + QTreeWidgetItem *scratch_item = topLevelItem(scratch_idx); + adjustSubListSize(scratch_item); + save(); +} + +void WidgetBoxTreeWidget::slotLastScratchPadItemDeleted() +{ + // Remove the scratchpad in the next idle loop + if (!m_scratchPadDeleteTimer) { + m_scratchPadDeleteTimer = new QTimer(this); + m_scratchPadDeleteTimer->setSingleShot(true); + m_scratchPadDeleteTimer->setInterval(0); + connect(m_scratchPadDeleteTimer, &QTimer::timeout, + this, &WidgetBoxTreeWidget::deleteScratchpad); + } + if (!m_scratchPadDeleteTimer->isActive()) + m_scratchPadDeleteTimer->start(); +} + +void WidgetBoxTreeWidget::deleteScratchpad() +{ + const int idx = indexOfScratchpad(); + if (idx == -1) + return; + delete takeTopLevelItem(idx); + save(); +} + + +void WidgetBoxTreeWidget::slotListMode() +{ + m_iconMode = false; + updateViewMode(); +} + +void WidgetBoxTreeWidget::slotIconMode() +{ + m_iconMode = true; + updateViewMode(); +} + +void WidgetBoxTreeWidget::updateViewMode() +{ + if (const int numTopLevels = topLevelItemCount()) { + for (int i = numTopLevels - 1; i >= 0; --i) { + QTreeWidgetItem *topLevel = topLevelItem(i); + // Scratch pad stays in list mode. + const QListView::ViewMode viewMode = m_iconMode && (topLevelRole(topLevel) != SCRATCHPAD_ITEM) ? QListView::IconMode : QListView::ListMode; + WidgetBoxCategoryListView *categoryView = categoryViewAt(i); + if (viewMode != categoryView->viewMode()) { + categoryView->setViewMode(viewMode); + adjustSubListSize(topLevelItem(i)); + } + } + } + + updateGeometries(); +} + +void WidgetBoxTreeWidget::resizeEvent(QResizeEvent *e) +{ + QTreeWidget::resizeEvent(e); + if (const int numTopLevels = topLevelItemCount()) { + for (int i = numTopLevels - 1; i >= 0; --i) + adjustSubListSize(topLevelItem(i)); + } +} + +void WidgetBoxTreeWidget::contextMenuEvent(QContextMenuEvent *e) +{ + QTreeWidgetItem *item = itemAt(e->pos()); + + const bool scratchpad_menu = item != nullptr + && item->parent() != nullptr + && topLevelRole(item->parent()) == SCRATCHPAD_ITEM; + + QMenu menu; + menu.addAction(tr("Expand all"), this, &WidgetBoxTreeWidget::expandAll); + menu.addAction(tr("Collapse all"), this, &WidgetBoxTreeWidget::collapseAll); + menu.addSeparator(); + + QAction *listModeAction = menu.addAction(tr("List View")); + QAction *iconModeAction = menu.addAction(tr("Icon View")); + listModeAction->setCheckable(true); + iconModeAction->setCheckable(true); + QActionGroup *viewModeGroup = new QActionGroup(&menu); + viewModeGroup->addAction(listModeAction); + viewModeGroup->addAction(iconModeAction); + if (m_iconMode) + iconModeAction->setChecked(true); + else + listModeAction->setChecked(true); + connect(listModeAction, &QAction::triggered, this, &WidgetBoxTreeWidget::slotListMode); + connect(iconModeAction, &QAction::triggered, this, &WidgetBoxTreeWidget::slotIconMode); + + if (scratchpad_menu) { + menu.addSeparator(); + WidgetBoxCategoryListView *listView = qobject_cast(itemWidget(item, 0)); + Q_ASSERT(listView); + menu.addAction(tr("Remove"), listView, &WidgetBoxCategoryListView::removeCurrentItem); + if (!m_iconMode) + menu.addAction(tr("Edit name"), listView, &WidgetBoxCategoryListView::editCurrentItem); + } + e->accept(); + menu.exec(mapToGlobal(e->pos())); +} + +void WidgetBoxTreeWidget::dropWidgets(const QList &item_list) +{ + QTreeWidgetItem *scratch_item = nullptr; + WidgetBoxCategoryListView *categoryView = nullptr; + bool added = false; + + for (QDesignerDnDItemInterface *item : item_list) { + QWidget *w = item->widget(); + if (w == nullptr) + continue; + + DomUI *dom_ui = item->domUi(); + if (dom_ui == nullptr) + continue; + + const int scratch_idx = ensureScratchpad(); + scratch_item = topLevelItem(scratch_idx); + categoryView = categoryViewAt(scratch_idx); + + // Temporarily remove the fake toplevel in-between + DomWidget *fakeTopLevel = dom_ui->takeElementWidget(); + DomWidget *firstWidget = nullptr; + if (fakeTopLevel && !fakeTopLevel->elementWidget().isEmpty()) { + firstWidget = fakeTopLevel->elementWidget().constFirst(); + dom_ui->setElementWidget(firstWidget); + } else { + dom_ui->setElementWidget(fakeTopLevel); + continue; + } + + // Serialize to XML + QString xml; + { + QXmlStreamWriter writer(&xml); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(1); + writer.writeStartDocument(); + dom_ui->write(writer); + writer.writeEndDocument(); + } + + // Insert fake toplevel again + dom_ui->takeElementWidget(); + dom_ui->setElementWidget(fakeTopLevel); + + const Widget wgt = Widget(w->objectName(), xml); + categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), true); + scratch_item->setExpanded(true); + added = true; + } + + if (added) { + save(); + activateWindow(); + // Is the new item visible in filtered mode? + const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::FilteredAccess; + if (const int count = categoryView->count(am)) + categoryView->setCurrentItem(am, count - 1); + categoryView->adjustSize(); // XXX + adjustSubListSize(scratch_item); + doItemsLayout(); + scrollToItem(scratch_item, PositionAtTop); + } +} + +void WidgetBoxTreeWidget::filter(const QString &f) +{ + const bool empty = f.isEmpty(); + const int numTopLevels = topLevelItemCount(); + bool changed = false; + for (int i = 0; i < numTopLevels; i++) { + QTreeWidgetItem *tl = topLevelItem(i); + WidgetBoxCategoryListView *categoryView = categoryViewAt(i); + // Anything changed? -> Enable the category + const int oldCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess); + categoryView->filter(f, Qt::CaseInsensitive); + const int newCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess); + if (oldCount != newCount) { + changed = true; + const bool categoryEnabled = newCount > 0 || empty; + if (categoryEnabled) { + categoryView->adjustSize(); + adjustSubListSize(tl); + } + setRowHidden (i, QModelIndex(), !categoryEnabled); + } + } + if (changed) + updateGeometries(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/components/widgetbox/widgetboxtreewidget.h b/src/designer/src/components/widgetbox/widgetboxtreewidget.h new file mode 100644 index 0000000..f557081 --- /dev/null +++ b/src/designer/src/components/widgetbox/widgetboxtreewidget.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOXTREEWIDGET_H +#define WIDGETBOXTREEWIDGET_H + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerDnDItemInterface; + +class QTimer; + +namespace qdesigner_internal { + +class WidgetBoxCategoryListView; + +// WidgetBoxTreeWidget: A tree of categories + +class WidgetBoxTreeWidget : public QTreeWidget +{ + Q_OBJECT + +public: + using Widget = QDesignerWidgetBoxInterface::Widget; + using Category = QDesignerWidgetBoxInterface::Category; + using CategoryList = QDesignerWidgetBoxInterface::CategoryList; + + explicit WidgetBoxTreeWidget(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~WidgetBoxTreeWidget(); + + int categoryCount() const; + Category category(int cat_idx) const; + void addCategory(const Category &cat); + void removeCategory(int cat_idx); + + int widgetCount(int cat_idx) const; + Widget widget(int cat_idx, int wgt_idx) const; + void addWidget(int cat_idx, const Widget &wgt); + void removeWidget(int cat_idx, int wgt_idx); + + void dropWidgets(const QList &item_list); + + void setFileName(const QString &file_name); + QString fileName() const; + bool load(QDesignerWidgetBox::LoadMode loadMode); + bool loadContents(const QString &contents); + bool save(); + QIcon iconForWidget(const QString &iconName) const; + +signals: + void widgetBoxPressed(const QString &name, const QString &dom_xml, + const QPoint &global_mouse_pos); + +public slots: + void filter(const QString &); + +protected: + void contextMenuEvent(QContextMenuEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private slots: + void slotSave(); + void slotScratchPadItemDeleted(); + void slotLastScratchPadItemDeleted(); + + void handleMousePress(QTreeWidgetItem *item); + void deleteScratchpad(); + void slotListMode(); + void slotIconMode(); + +private: + WidgetBoxCategoryListView *addCategoryView(QTreeWidgetItem *parent, bool iconMode); + WidgetBoxCategoryListView *categoryViewAt(int idx) const; + void adjustSubListSize(QTreeWidgetItem *cat_item); + + static bool readCategories(const QString &fileName, const QString &xml, CategoryList *cats, QString *errorMessage); + static bool readWidget(Widget *w, const QString &xml, QXmlStreamReader &r); + + CategoryList loadCustomCategoryList() const; + void writeCategories(QXmlStreamWriter &writer, const CategoryList &cat_list) const; + + int indexOfCategory(const QString &name) const; + int indexOfScratchpad() const; + int ensureScratchpad(); + void addCustomCategories(bool replace); + + void saveExpandedState() const; + void restoreExpandedState(); + void updateViewMode(); + + QDesignerFormEditorInterface *m_core; + QString m_file_name; + mutable QHash m_pluginIcons; + bool m_iconMode; + QTimer *m_scratchPadDeleteTimer; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETBOXTREEWIDGET_H diff --git a/src/designer/src/designer/CMakeLists.txt b/src/designer/src/designer/CMakeLists.txt new file mode 100644 index 0000000..811a874 --- /dev/null +++ b/src/designer/src/designer/CMakeLists.txt @@ -0,0 +1,175 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +##################################################################### +## designer App: +##################################################################### + +qt_internal_add_app(designer + SOURCES + ../../../shared/fontpanel/fontpanel.cpp ../../../shared/fontpanel/fontpanel_p.h + ../../../shared/qttoolbardialog/qttoolbardialog.cpp ../../../shared/qttoolbardialog/qttoolbardialog_p.h ../../../shared/qttoolbardialog/qttoolbardialog.ui + appfontdialog.cpp appfontdialog.h + assistantclient.cpp assistantclient.h + designer_enums.h + main.cpp + mainwindow.cpp mainwindow.h + newform.cpp newform.h + preferencesdialog.cpp preferencesdialog.h preferencesdialog.ui + qdesigner.cpp qdesigner.h + qdesigner_actions.cpp qdesigner_actions.h + qdesigner_appearanceoptions.cpp qdesigner_appearanceoptions.h qdesigner_appearanceoptions.ui + qdesigner_formwindow.cpp qdesigner_formwindow.h + qdesigner_server.cpp qdesigner_server.h + qdesigner_settings.cpp qdesigner_settings.h + qdesigner_toolwindow.cpp qdesigner_toolwindow.h + qdesigner_workbench.cpp qdesigner_workbench.h + saveformastemplate.cpp saveformastemplate.h saveformastemplate.ui + versiondialog.cpp versiondialog.h + INCLUDE_DIRECTORIES + ../../../shared/fontpanel + ../../../shared/qttoolbardialog + ../lib/extension + ../lib/sdk + ../lib/shared + extra + LIBRARIES + Qt::CorePrivate + Qt::DesignerComponentsPrivate + Qt::DesignerPrivate + Qt::Gui + Qt::Network + Qt::Widgets + Qt::Xml + ENABLE_AUTOGEN_TOOLS + uic + PRECOMPILED_HEADER + "qdesigner_pch.h" +) + +# Due to QTBUG-110369, don't add designer as dependency to External Project examples. +qt_internal_skip_dependency_for_examples(designer) + +# Resources: +set(designer_resource_files + "images/designer.png" +) + +qt_internal_add_resource(designer "designer" + PREFIX + "/qt-project.org/designer" + FILES + ${designer_resource_files} +) +set(qttoolbardialog_resource_files + "../../../shared/qttoolbardialog/images/back.png" + "../../../shared/qttoolbardialog/images/down.png" + "../../../shared/qttoolbardialog/images/forward.png" + "../../../shared/qttoolbardialog/images/minus.png" + "../../../shared/qttoolbardialog/images/plus.png" + "../../../shared/qttoolbardialog/images/up.png" +) + +qt_internal_add_resource(designer "qttoolbardialog" + PREFIX + "/qt-project.org/qttoolbardialog" + BASE + "../../../shared/qttoolbardialog" + FILES + ${qttoolbardialog_resource_files} +) + +set_target_properties(designer PROPERTIES + QT_TARGET_DESCRIPTION "Qt Widgets Designer" +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(designer CONDITION TARGET Qt::PrintSupport + PUBLIC_LIBRARIES + Qt::PrintSupport +) + +qt_internal_extend_target(designer CONDITION NOT QT_BUILD_SHARED_LIBS + DEFINES + QT_DESIGNER_STATIC +) + +if(WIN32) + set_target_properties(designer PROPERTIES + QT_TARGET_RC_ICONS "${CMAKE_CURRENT_SOURCE_DIR}/designer.ico" + ) +endif() + +if(WIN32) + set_target_properties(designer PROPERTIES + QT_TARGET_VERSION "${PROJECT_VERSION}.0" + ) +endif() + +if(UNIX) + set_target_properties(designer PROPERTIES + QT_TARGET_VERSION "${PROJECT_VERSION}" + ) +endif() + +if(QT_INSTALL_XDG_DESKTOP_ENTRIES) + if(UNIX AND NOT APPLE) + qt_path_join(xdg_install_dir ${QT_INSTALL_DIR} ${CMAKE_INSTALL_DATAROOTDIR}) + + qt_install(FILES designer.desktop + DESTINATION "${xdg_install_dir}/applications" + ) + qt_install(FILES designer.metainfo.xml + RENAME io.qt.Designer.metainfo.xml + DESTINATION "${xdg_install_dir}/metainfo" + ) + qt_install(FILES images/designer.png + DESTINATION "${xdg_install_dir}/icons/hicolor/128x128/apps" + ) + endif() +endif() + +if(APPLE) + set_target_properties(designer PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info_mac.plist" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_ICON_FILE "designer.icns" + OUTPUT_NAME "Designer" + ) + set_source_files_properties(designer.icns PROPERTIES + MACOSX_PACKAGE_LOCATION Resources + ) + target_sources(designer PRIVATE + designer.icns + ) + # special case end + # Set values to be replaced in the custom Info_mac.plist. + # Also package the uifile.icns. + set(ICON "designer.icns") + set(EXECUTABLE "Designer") + set_source_files_properties(uifile.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + target_sources(designer PRIVATE uifile.icns) + # special case end + + # Bundle the entitlements file in the installed bundle Resources subdirectory, so that the + # post-CI signing process uses it to ensure 3rd party widget plugins can also be loaded. + target_sources(designer PRIVATE Designer.entitlements) + set_source_files_properties(Designer.entitlements PROPERTIES + MACOSX_PACKAGE_LOCATION Resources + ) +endif() + +# FILETYPES.files = "uifile.icns" +# FILETYPES.path = "Contents/Resources" +# QMAKE_BUNDLE_DATA = "FILETYPES" + +qt_internal_extend_target(designer CONDITION UNIX AND NOT HAIKU AND NOT MACOS + PUBLIC_LIBRARIES + m +) +qt_internal_add_docs(designer + doc/qtdesigner.qdocconf +) diff --git a/src/designer/src/designer/Designer.entitlements b/src/designer/src/designer/Designer.entitlements new file mode 100644 index 0000000..79d2038 --- /dev/null +++ b/src/designer/src/designer/Designer.entitlements @@ -0,0 +1,9 @@ + + + + + com.apple.security.cs.disable-library-validation + + + diff --git a/src/designer/src/designer/Info_mac.plist b/src/designer/src/designer/Info_mac.plist new file mode 100644 index 0000000..fd61415 --- /dev/null +++ b/src/designer/src/designer/Info_mac.plist @@ -0,0 +1,35 @@ + + + + + NSPrincipalClass + NSApplication + CFBundleIconFile + @ICON@ + CFBundlePackageType + APPL + CFBundleIdentifier + org.qt-project.Designer + CFBundleSignature + ttxt + CFBundleExecutable + @EXECUTABLE@ + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + ui + + CFBundleTypeIconFile + uifile.icns + CFBundleTypeRole + Editor + LSIsAppleDefaultForType + + + + NSHumanReadableCopyright + (C) 2017 The Qt Company Ltd + + diff --git a/src/designer/src/designer/appfontdialog.cpp b/src/designer/src/designer/appfontdialog.cpp new file mode 100644 index 0000000..725e6d8 --- /dev/null +++ b/src/designer/src/designer/appfontdialog.cpp @@ -0,0 +1,384 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "appfontdialog.h" + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum {FileNameRole = Qt::UserRole + 1, IdRole = Qt::UserRole + 2 }; +enum { debugAppFontWidget = 0 }; + +static constexpr auto fontFileKeyC = "fontFiles"_L1; + +// AppFontManager: Singleton that maintains the mapping of loaded application font +// ids to the file names (which are not stored in QFontDatabase) +// and provides API for loading/unloading fonts as well for saving/restoring settings. + +class AppFontManager +{ + Q_DISABLE_COPY_MOVE(AppFontManager) + AppFontManager(); +public: + static AppFontManager &instance(); + + void save(QDesignerSettingsInterface *s, const QString &prefix) const; + void restore(const QDesignerSettingsInterface *s, const QString &prefix); + + // Return id or -1 + int add(const QString &fontFile, QString *errorMessage); + + bool remove(int id, QString *errorMessage); + bool remove(const QString &fontFile, QString *errorMessage); + bool removeAt(int index, QString *errorMessage); + + // Store loaded fonts as pair of file name and Id + using FileNameFontIdPair = std::pair; + using FileNameFontIdPairs = QList; + const FileNameFontIdPairs &fonts() const; + +private: + FileNameFontIdPairs m_fonts; +}; + +AppFontManager::AppFontManager() = default; + +AppFontManager &AppFontManager::instance() +{ + static AppFontManager rc; + return rc; +} + +void AppFontManager::save(QDesignerSettingsInterface *s, const QString &prefix) const +{ + // Store as list of file names + QStringList fontFiles; + for (const auto &fnp : m_fonts) + fontFiles.push_back(fnp.first); + + s->beginGroup(prefix); + s->setValue(fontFileKeyC, fontFiles); + s->endGroup(); + + if (debugAppFontWidget) + qDebug() << "AppFontManager::saved" << fontFiles.size() << "fonts under " << prefix; +} + +void AppFontManager::restore(const QDesignerSettingsInterface *s, const QString &prefix) +{ + const QString key = prefix + u'/' + fontFileKeyC; + const QStringList fontFiles = s->value(key, QStringList()).toStringList(); + + if (debugAppFontWidget) + qDebug() << "AppFontManager::restoring" << fontFiles.size() << "fonts from " << prefix; + if (!fontFiles.isEmpty()) { + QString errorMessage; + for (const auto &ff : fontFiles) { + if (add(ff, &errorMessage) == -1) + qWarning("%s", qPrintable(errorMessage)); + } + } +} + +int AppFontManager::add(const QString &fontFile, QString *errorMessage) +{ + const QFileInfo inf(fontFile); + if (!inf.isFile()) { + *errorMessage = QCoreApplication::translate("AppFontManager", "'%1' is not a file.").arg(fontFile); + return -1; + } + if (!inf.isReadable()) { + *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' does not have read permissions.").arg(fontFile); + return -1; + } + const QString fullPath = inf.absoluteFilePath(); + // Check if already loaded + for (const auto &fnp : std::as_const(m_fonts)) { + if (fnp.first == fullPath) { + *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' is already loaded.").arg(fontFile); + return -1; + } + } + + const int id = QFontDatabase::addApplicationFont(fullPath); + if (id == -1) { + *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' could not be loaded.").arg(fontFile); + return -1; + } + + if (debugAppFontWidget) + qDebug() << "AppFontManager::add" << fontFile << id; + m_fonts.push_back(FileNameFontIdPair(fullPath, id)); + return id; +} + +bool AppFontManager::remove(int id, QString *errorMessage) +{ + for (qsizetype i = 0, count = m_fonts.size(); i < count; ++i) + if (m_fonts.at(i).second == id) + return removeAt(i, errorMessage); + + *errorMessage = QCoreApplication::translate("AppFontManager", "'%1' is not a valid font id.").arg(id); + return false; +} + +bool AppFontManager::remove(const QString &fontFile, QString *errorMessage) +{ + for (qsizetype i = 0, count = m_fonts.size(); i < count; ++i) + if (m_fonts.at(i).first == fontFile) + return removeAt(i, errorMessage); + + *errorMessage = QCoreApplication::translate("AppFontManager", "There is no loaded font matching the id '%1'.").arg(fontFile); + return false; +} + +bool AppFontManager::removeAt(int index, QString *errorMessage) +{ + Q_ASSERT(index >= 0 && index < m_fonts.size()); + + const QString fontFile = m_fonts[index].first; + const int id = m_fonts[index].second; + + if (debugAppFontWidget) + qDebug() << "AppFontManager::removeAt" << index << '(' << fontFile << id << ')'; + + if (!QFontDatabase::removeApplicationFont(id)) { + *errorMessage = QCoreApplication::translate("AppFontManager", "The font '%1' (%2) could not be unloaded.").arg(fontFile).arg(id); + return false; + } + m_fonts.removeAt(index); + return true; +} + +const AppFontManager::FileNameFontIdPairs &AppFontManager::fonts() const +{ + return m_fonts; +} + +// ------------- AppFontModel +class AppFontModel : public QStandardItemModel { + Q_DISABLE_COPY_MOVE(AppFontModel) +public: + AppFontModel(QObject *parent = nullptr); + + void init(const AppFontManager &mgr); + void add(const QString &fontFile, int id); + int idAt(const QModelIndex &idx) const; +}; + +AppFontModel::AppFontModel(QObject * parent) : + QStandardItemModel(parent) +{ + setHorizontalHeaderLabels(QStringList(AppFontWidget::tr("Fonts"))); +} + +void AppFontModel::init(const AppFontManager &mgr) +{ + using FileNameFontIdPairs = AppFontManager::FileNameFontIdPairs; + + const FileNameFontIdPairs &fonts = mgr.fonts(); + for (const auto &fnp : fonts) + add(fnp.first, fnp.second); +} + +void AppFontModel::add(const QString &fontFile, int id) +{ + const QFileInfo inf(fontFile); + // Root item with base name + QStandardItem *fileItem = new QStandardItem(inf.completeBaseName()); + const QString fullPath = inf.absoluteFilePath(); + fileItem->setData(fullPath, FileNameRole); + fileItem->setToolTip(fullPath); + fileItem->setData(id, IdRole); + fileItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + + appendRow(fileItem); + const QStringList families = QFontDatabase::applicationFontFamilies(id); + for (const auto &fam : families) { + QStandardItem *familyItem = new QStandardItem(fam); + familyItem->setToolTip(fullPath); + familyItem->setFont(QFont(fam)); + familyItem->setFlags(Qt::ItemIsEnabled); + fileItem->appendRow(familyItem); + } +} + +int AppFontModel::idAt(const QModelIndex &idx) const +{ + if (const QStandardItem *item = itemFromIndex(idx)) + return item->data(IdRole).toInt(); + return -1; +} + +// ------------- AppFontWidget +AppFontWidget::AppFontWidget(QWidget *parent) : + QGroupBox(parent), + m_view(new QTreeView), + m_addButton(new QToolButton), + m_removeButton(new QToolButton), + m_removeAllButton(new QToolButton), + m_model(new AppFontModel(this)) +{ + m_model->init(AppFontManager::instance()); + m_view->setModel(m_model); + m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_view->expandAll(); + connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AppFontWidget::selectionChanged); + + m_addButton->setToolTip(tr("Add font files")); + m_addButton->setIcon(qdesigner_internal::createIconSet("plus.png"_L1)); + connect(m_addButton, &QAbstractButton::clicked, this, &AppFontWidget::addFiles); + + m_removeButton->setEnabled(false); + m_removeButton->setToolTip(tr("Remove current font file")); + m_removeButton->setIcon(qdesigner_internal::createIconSet("minus.png"_L1)); + connect(m_removeButton, &QAbstractButton::clicked, this, &AppFontWidget::slotRemoveFiles); + + m_removeAllButton->setToolTip(tr("Remove all font files")); + m_removeAllButton->setIcon(qdesigner_internal::createIconSet(QIcon::ThemeIcon::EditDelete, + "editdelete.png"_L1)); + connect(m_removeAllButton, &QAbstractButton::clicked, this, &AppFontWidget::slotRemoveAll); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->addWidget(m_addButton); + hLayout->addWidget(m_removeButton); + hLayout->addWidget(m_removeAllButton); + hLayout->addItem(new QSpacerItem(0, 0,QSizePolicy::MinimumExpanding)); + + QVBoxLayout *vLayout = new QVBoxLayout; + vLayout->addWidget(m_view); + vLayout->addLayout(hLayout); + setLayout(vLayout); +} + +void AppFontWidget::addFiles() +{ + const QStringList files = + QFileDialog::getOpenFileNames(this, tr("Add Font Files"), QString(), + tr("Font files (*.ttf)")); + if (files.isEmpty()) + return; + + QString errorMessage; + + AppFontManager &fmgr = AppFontManager::instance(); + for (const auto &f : files) { + const int id = fmgr.add(f, &errorMessage); + if (id != -1) { + m_model->add(f, id); + } else { + QMessageBox::critical(this, tr("Error Adding Fonts"), errorMessage); + } + } + m_view->expandAll(); +} + +static void removeFonts(const QModelIndexList &selectedIndexes, AppFontModel *model, QWidget *dialogParent) +{ + if (selectedIndexes.isEmpty()) + return; + + // Reverse sort top level rows and remove + AppFontManager &fmgr = AppFontManager::instance(); + QList rows; + rows.reserve(selectedIndexes.size()); + + QString errorMessage; + for (const auto &mi : selectedIndexes) { + const int id = model->idAt(mi); + if (id != -1) { + if (fmgr.remove(id, &errorMessage)) { + rows.append(mi.row()); + } else { + QMessageBox::critical(dialogParent, AppFontWidget::tr("Error Removing Fonts"), errorMessage); + } + } + } + + std::stable_sort(rows.begin(), rows.end()); + for (qsizetype i = rows.size() - 1; i >= 0; --i) + model->removeRow(rows.at(i)); +} + +void AppFontWidget::slotRemoveFiles() +{ + removeFonts(m_view->selectionModel()->selectedIndexes(), m_model, this); +} + +void AppFontWidget::slotRemoveAll() +{ + const int count = m_model->rowCount(); + if (!count) + return; + + const QMessageBox::StandardButton answer = + QMessageBox::question(this, tr("Remove Fonts"), tr("Would you like to remove all fonts?"), + QMessageBox::Yes|QMessageBox::No, QMessageBox::No); + if (answer == QMessageBox::No) + return; + + QModelIndexList topLevels; + for (int i = 0; i < count; i++) + topLevels.push_back(m_model->index(i, 0)); + removeFonts(topLevels, m_model, this); +} + +void AppFontWidget::selectionChanged(const QItemSelection &selected, const QItemSelection & /*deselected*/) +{ + m_removeButton->setEnabled(!selected.indexes().isEmpty()); +} + +void AppFontWidget::save(QDesignerSettingsInterface *s, const QString &prefix) +{ + AppFontManager::instance().save(s, prefix); +} + +void AppFontWidget::restore(const QDesignerSettingsInterface *s, const QString &prefix) +{ + AppFontManager::instance().restore(s, prefix); +} + +// ------------ AppFontDialog +AppFontDialog::AppFontDialog(QWidget *parent) : + QDialog(parent), + m_appFontWidget(new AppFontWidget) +{ + setAttribute(Qt::WA_DeleteOnClose, true); + setWindowTitle(tr("Additional Fonts")); + setModal(false); + QVBoxLayout *vl = new QVBoxLayout; + vl->addWidget(m_appFontWidget); + + QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close); + QDialog::connect(bb, &QDialogButtonBox::rejected, this, &AppFontDialog::reject); + vl->addWidget(bb); + setLayout(vl); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/appfontdialog.h b/src/designer/src/designer/appfontdialog.h new file mode 100644 index 0000000..5b33c4a --- /dev/null +++ b/src/designer/src/designer/appfontdialog.h @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_APPFONTWIDGET_H +#define QDESIGNER_APPFONTWIDGET_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class AppFontModel; + +class QTreeView; +class QToolButton; +class QItemSelection; +class QDesignerSettingsInterface; + +// AppFontWidget: Manages application fonts which the user can load and +// provides API for saving/restoring them. + +class AppFontWidget : public QGroupBox +{ + Q_DISABLE_COPY_MOVE(AppFontWidget) + Q_OBJECT +public: + explicit AppFontWidget(QWidget *parent = nullptr); + + QStringList fontFiles() const; + + static void save(QDesignerSettingsInterface *s, const QString &prefix); + static void restore(const QDesignerSettingsInterface *s, const QString &prefix); + +private slots: + void addFiles(); + void slotRemoveFiles(); + void slotRemoveAll(); + void selectionChanged(const QItemSelection & selected, const QItemSelection & deselected); + +private: + QTreeView *m_view; + QToolButton *m_addButton; + QToolButton *m_removeButton; + QToolButton *m_removeAllButton; + AppFontModel *m_model; +}; + +// AppFontDialog: Non modal dialog for AppFontWidget which has Qt::WA_DeleteOnClose set. + +class AppFontDialog : public QDialog +{ + Q_DISABLE_COPY_MOVE(AppFontDialog) + Q_OBJECT +public: + explicit AppFontDialog(QWidget *parent = nullptr); + +private: + AppFontWidget *m_appFontWidget; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_APPFONTWIDGET_H diff --git a/src/designer/src/designer/assistantclient.cpp b/src/designer/src/designer/assistantclient.cpp new file mode 100644 index 0000000..96be7dd --- /dev/null +++ b/src/designer/src/designer/assistantclient.cpp @@ -0,0 +1,157 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "assistantclient.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { debugAssistantClient = 0 }; + +AssistantClient::AssistantClient() = default; + +AssistantClient::~AssistantClient() +{ + if (isRunning()) { + QString errorMessage; + if (!sendCommand("quit"_L1, &errorMessage) || !m_process->waitForFinished(1000)) { + m_process->terminate(); + m_process->waitForFinished(); + } + } + delete m_process; +} + +bool AssistantClient::showPage(const QString &path, QString *errorMessage) +{ + const QString cmd = "SetSource "_L1 + path; + return sendCommand(cmd, errorMessage); +} + +bool AssistantClient::activateIdentifier(const QString &identifier, QString *errorMessage) +{ + const QString cmd = "ActivateIdentifier "_L1 + identifier; + return sendCommand(cmd, errorMessage); +} + +bool AssistantClient::activateKeyword(const QString &keyword, QString *errorMessage) +{ + const QString cmd = "ActivateKeyword "_L1 + keyword; + return sendCommand(cmd, errorMessage); +} + +bool AssistantClient::sendCommand(const QString &cmd, QString *errorMessage) +{ + if (debugAssistantClient) + qDebug() << "sendCommand " << cmd; + if (!ensureRunning(errorMessage)) + return false; + if (!m_process->isWritable() || m_process->bytesToWrite() > 0) { + *errorMessage = QCoreApplication::translate("AssistantClient", "Unable to send request: Assistant is not responding."); + return false; + } + QTextStream str(m_process); + str << cmd << "\n\n"; + return true; +} + +bool AssistantClient::isRunning() const +{ + return m_process && m_process->state() != QProcess::NotRunning; +} + +QString AssistantClient::binary() +{ + QString app = QLibraryInfo::path(QLibraryInfo::BinariesPath) + QDir::separator(); +#if !defined(Q_OS_MACOS) + app += "assistant"_L1; +#else + app += "Assistant.app/Contents/MacOS/Assistant"_L1; +#endif + +#if defined(Q_OS_WIN) + app += ".exe"_L1; +#endif + + return app; +} + +void AssistantClient::readyReadStandardError() +{ + qWarning("%s: %s", + qPrintable(QDir::toNativeSeparators(m_process->program())), + m_process->readAllStandardError().constData()); +} + +void AssistantClient::processTerminated(int exitCode, QProcess::ExitStatus exitStatus) +{ + const QString binary = QDir::toNativeSeparators(m_process->program()); + if (exitStatus != QProcess::NormalExit) + qWarning("%s: crashed.", qPrintable(binary)); + else if (exitCode != 0) + qWarning("%s: terminated with exit code %d.", qPrintable(binary), exitCode); +} + +bool AssistantClient::ensureRunning(QString *errorMessage) +{ + if (isRunning()) + return true; + + if (!m_process) { + m_process = new QProcess; + QObject::connect(m_process, QOverload::of(&QProcess::finished), + this, &AssistantClient::processTerminated); + QObject::connect(m_process, &QProcess::readyReadStandardError, + this, &AssistantClient::readyReadStandardError); + } + + const QString app = binary(); + if (!QFileInfo(app).isFile()) { + *errorMessage = QCoreApplication::translate("AssistantClient", "The binary '%1' does not exist.").arg(app); + return false; + } + if (debugAssistantClient) + qDebug() << "Running " << app; + // run + QStringList args{u"-enableRemoteControl"_s}; + m_process->start(app, args); + if (!m_process->waitForStarted()) { + *errorMessage = QCoreApplication::translate("AssistantClient", "Unable to launch assistant (%1).").arg(app); + return false; + } + return true; +} + +QString AssistantClient::documentUrl(const QString &module, int qtVersion) +{ + if (qtVersion == 0) + qtVersion = QT_VERSION; + QString rc; + QTextStream(&rc) << "qthelp://org.qt-project." << module << '.' + << (qtVersion >> 16) << ((qtVersion >> 8) & 0xFF) << (qtVersion & 0xFF) + << '/' << module << '/'; + return rc; +} + +QString AssistantClient::designerManualUrl(int qtVersion) +{ + return documentUrl(u"qtdesigner"_s, qtVersion); +} + +QString AssistantClient::qtReferenceManualUrl(int qtVersion) +{ + return documentUrl(u"qtdoc"_s, qtVersion); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/assistantclient.h b/src/designer/src/designer/assistantclient.h new file mode 100644 index 0000000..b1ee033 --- /dev/null +++ b/src/designer/src/designer/assistantclient.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ASSISTANTCLIENT_H +#define ASSISTANTCLIENT_H + +#include + +QT_BEGIN_NAMESPACE + +class QString; + +class AssistantClient : public QObject +{ + Q_OBJECT + +public: + AssistantClient(); + ~AssistantClient(); + + bool showPage(const QString &path, QString *errorMessage); + bool activateIdentifier(const QString &identifier, QString *errorMessage); + bool activateKeyword(const QString &keyword, QString *errorMessage); + + bool isRunning() const; + + static QString documentUrl(const QString &prefix, int qtVersion = 0); + // Root of the Qt Widgets Designer documentation + static QString designerManualUrl(int qtVersion = 0); + // Root of the Qt Reference documentation + static QString qtReferenceManualUrl(int qtVersion = 0); + +private slots: + void readyReadStandardError(); + void processTerminated(int exitCode, QProcess::ExitStatus exitStatus); + +private: + static QString binary(); + bool sendCommand(const QString &cmd, QString *errorMessage); + bool ensureRunning(QString *errorMessage); + + QProcess *m_process = nullptr; +}; + +QT_END_NAMESPACE + +#endif // ASSISTANTCLIENT_H diff --git a/src/designer/src/designer/designer.desktop b/src/designer/src/designer/designer.desktop new file mode 100644 index 0000000..c5af9c1 --- /dev/null +++ b/src/designer/src/designer/designer.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Qt Widgets Designer +GenericName=Interface Designer +Comment=Design GUIs for Qt applications +Exec=designer6 %F +Icon=designer +MimeType=application/x-designer; +Terminal=false +Type=Application +Categories=Qt;Development; diff --git a/src/designer/src/designer/designer.icns b/src/designer/src/designer/designer.icns new file mode 100644 index 0000000000000000000000000000000000000000..b0800b5fcb72f30644f67519268bc97d69ed6071 GIT binary patch literal 117397 zcmeEv2|SeD+y6ZbhQvsU6l2SlLYC|^$X3>}q-2nagzQqxAgQR7HW7-l?_08yHT#-9 zl6}jPeg5|hBYJv%@AG@#_y767@Bhs;5l@B5tV+&mU$*7g9T^2upU z1q1*9E*ADjg#mz+#{SsVD*yn2Sy&!>A?ou0mH$%YQCkbJy^2C^ z6@uS+B*BmP{{H?(2ZDr^6>N#mS5u9xJB4px1q)AHz8dOE+ZAG+HT=VIlzE3UzV3ks zu!d*x@|mnR5bLZ)pD+9*Wbq1RlnEb_UGI=JyuZJ<@H7ZLBuBvi{9ya}75@zZY^x)F z87o-*jR&f?6<}Mfy1MF@dU6TaFubs^Fxx?pL`5ZktR9}Ms~w)H$2UZQC1r53uDWfm zjaZNB8m_Evnd`*YJ@A02uEDs}`t=54J*sG?@h4$0F7exVQbxskhf!S%3qMBkKxjq< z0sr$O`sY{tw|Bs{`n!MR0iA6H*jCfg(f*|_bpmWi8yFa9!4M=uLSReUN5eBIO{Vw; zA+Sh;bsd)u7h+u~x$qVuF=tk)Zebs@`+Yd;BjQt~;UZ1pLpBqd&jmzX=1|>frVAYaw2BzqSJa2Di2RQpI_16@uS~7Jg~K;gTa18l2f{yJLXUFSE~#9zVxH(UOj4EW{Vg}ORRL*~T7DPd z4voibF#xY5yx)qIl~-{)0K}{tupwvV&1wHz1t6bQs-aF!e8Xwm4J7o>=F^{WkdB2eu_>Al5BH96@fEuQzY`Wc$~01YV4v16wFtzY^ql z%+|)A-#5j0$bY{4H(dpP48T7{e^ZE#`HhIs1j-2^_&a&^dIx_i-jF93ts{Dd!Sd~M z%}@L~KPoDr0N;jRLxANw;h|3eZ5V*ReZj&fh#hQ~cP*4T zGTZvM3cy}cr>C+uT=I*7g#M!&7W!hNVhagffSqqQV7Rk$V&Qu}-kRtK@-PW3?{4b;Di;zGSHid9SJGgq zzqkq5FaW=d6+Ny60{nDzF6IuTo+2XF6|66Fk17!4I@TR6Ccl#l3B?TD1qJ7-y6k>0 zj~;N9)zC5g*h^Tg<1t$dz$=L!Z~@o&r?vw?%(?*^av`arPQvPaql3T5)tZ|$RJGPk z*gAj>{?P{mY2rd6$^^t;4EPxFwtC>RIbNf%A_)1l1JHpT>F0#kEfSX3+Ab#=y7?2_ zG~>%={g*F{@Ly3x1VwxZVmkB*RoZPm;I{c zSNX4qzsk3F@f-OzLw+j($>~Fb?UUPuAaWW!I?9LXV0E9 zGCFT;j7FQ7nwnkM7T%NU>KhE5Q>Rbs>gnkl7@RRQ^f-I&oYDF7>y)hoHMJ8bPpWHZ zXliMJE2FIxycUpQj5gVVS5Z-2XArgg&aR($B_(BL6;)NW6DK@=8n%^UNU&>@ps0BK zILN_kIq|o4ojXUw%gM_tfRqi!_K|G$7amUt0gniBopKyB%yy1&H(2=cME6%06BV$t zu?a|_q@NWZ~>s#ovYVynHm}z9$Q^ZBye(a zaDWuH!&0DKSLblv_`$1}<3^3Q`-_M_$i;lDx~wVEz_g1r~@8YAy{dt0* zU97xP#3@O{gR5JR0y03mJowMdtlpH9m9?xI9rPrG9)VyJK@#jD5{@2Srx5HioBKp;&=XwDx{-YY0CDkk#)e(%Ls|FNX6V`3*opQ4630M>7+%MwU7H^E zAcmfUa}y6pN=O_#hNo-{Wy7v@7jK0A;7>RpB_%0|XKdI-7zxqE8`0{%tTSiaBgSl1$VN=Vd5`w!n8F)Nl4kY8#vqUBBD})u% z>W}`$UtLU85E~PhRZe7lUdMx^*>ytwrWPVTE-p4ECOLoIE)d@b4zS}p4mSoG=J8WW zLnD!p5FZ~G8yge*9@J6;61wq2shh?vHE#}upoNG}Ox$3^q!d=R;qiEP_25=J@c5r` zB;u2kKt@75!7h*3(nSKHrUqp85(BwDTZn{|l;otO4NB~X>G`HAyt^8gaD&^ULd2(~ zt}_UBeVCprj{Q&x9teQ=?Loog2_Z;NPfJZrNnSTBcjiY)OiXl2aXp?u7z@!XqJqqf zO-52;{@g+--o?>r-&Y6l_>IW|PB0=qD>E}ABRy^1u7V#+Wdsj@n4YaD`Q)*cLL_9r z&&t}Q6fG=Q5Qg$;e)>anOl~D{veecR@gK6Y-@nIe!P`}`v|5SxSK7$Ja1v-&RNBW+ z8%l`y+?)>|hzyVPlI68}u!G8prG*ACDzP!q(Xn||1PYOmmrJ0m+f|BBAFQpet}OSZ zZiGG>)KUs+A>#A%^YU_Ya`1L#WtFX`UzSEIQnrLXIx41sa4bOxL196`24&MQLQuAZ z9vn(^6j4D@5uTC1Vb}WLH(iX^LU8fMu?0vfDK0K5EW|T@+O^@~RF93&$H&JKy9a=e zA2%pOySBQRFj7xrohQ~VPDuJ?GI4rS9Yf?b;gd>)LD)gmOq z{?;xcUK>m|5%OS!)Y;$Lg~t;@a7+ozwt*Bvw(aITVMVm~HPjrRJ0rMw!!Du%k)tAV z>csrqjdeUoYQOx8i-~x`zWA7u79kDhHjjG=dc>!0j$35{R2`!cO{3LobHluS?^&*zKVw;(u;|U?yw)sBS*>4Tz5hPSEjN@VmLs_3K;FKgPkd?(Vq}I3V#$SeP38@E_mif2a zCkqiT2WDW_8DJX0xs$Iy&P>NLFCKH0ukCC~6TW zi@_Np9()B1WXP>E%qJII@W~3rvTr3wd~!$Xq|Sz2M7)B6y!<*tR>nj6((J0QriQw6 z?f9_&MixcnxHiEqBH_3qfr7#-k-a=i$PxXR`<^EEGl?Rp4yHhfc%|dV74Z~;U3fgm zn5uM^BBosk=^zm$9Xy^80%c_-rFF`tVFVX%&AWg@**r5{zIlVuzH->Uoe{37Te+=J-{|E4`_ z=&!G4{FMHt|8?QtDEV9b>h_m2h#LMD4+4Hi`B(T~Ul76T_}6$4utI#@1CRa(c#yHx zU;knMgkBdmmHab2_>RejfBz{SR6{ts{WtL7?}RLl{9AY&&imiPV~B8o;D-O`<0t$d z;kUs534Sa5AKf}e^J zcT2$^IIWYXkK-HP3CmaOutgt5R%;_GwYZZdj=YyYR;0=vD{?eXQLI2oE=xo1lNKUc zm^V#Ix?BTQt>fN#mo4@vM}nwu?s3sVWn{IMdF!?K;k2BQBGxD&M3gWH20P?T5I-0z z&KoZ!kRdCSqaa?SDwV2+tkME|kgqrM0;FB;^V8#G=- zH(Q&x*j;IJu_|-9TIOo=`6jm8?^e%4pN0p)oe`m(Z$rPm3-5}4-5nD(oD?^l3aU=% zOH1fa&m7CmpZr)b{IPVZs&b;b`g?udR8zxrbHi*)%lFT1GaYSn9qqH79dlh>^S%8G z17@K^{h$k&9dtBK0T_&z_Yn9S1IzfbiZ5#$g)8?i{tP))RS|XiSXEc$$2~=YaHTJO z6FO+y9TO818r`@vD#KVZ^_Mcv;xiume<(-OiMHG~Sf;p4M>^!+_c=76w969;lpH#2 zcX;Z9f-V)1Bg+ht@gBJ%U8@aXxEXc>mYp>We86*4ao49#58hHg@6~9ybNA(?6MNDi z`(4Q~ibE_~&=-yMLEi>Y4wz@x1Q%WT+Gv#BZbVbw?h1|s8XR?L?p-PgTZTLmp$_W{ zEOZ_;SZ19iamT3%v4nMnf#w6M$2AlaN!!4Un=%&j!piU!;i_{cX!~L_*a3|B31Q`>lDB@LZ>Xi_ z%`i8v%8nLE(zL9}DRRcTN4xKD;o>+6aoO3{% zLqR`c%rE<}qM%E#`nOvT6vXH6kQkG^YJY!5>(kk^q#fT~^{5mBjah7Wh8}mE;pkne zss8Le=x8VV(sclmICZAx%{OV*K+LFA?VjgNPoE*>_Btf=3qNAf9Q!sPih^a$KVV%k z1sv}^m)@B>MSZj0+<^rW>D91f*ed&(8W+?zt?IDuvolIYl!EH^jfY-F?I(>sx$nif z?D#9!O%FZnigx6gF__Tq%w9@;&y9{@(TLyIZL_k&COF6IwW|ZlJkX2Yw!2UGXwclj zed$W7p@wA)^G+&?>lz9$M7-YT8dB=DhN51DQ|B*N0 z(rC4;TqfGxBhC9pny(*v<;U?-{p^+L=eyw|)hlU><0y(uSywvN;G5DyLx9DLBKpyu zk{Nw#cHZ0C^WZpgdKSbdn!3;4WO77i?Lxp5Me?$50Acnmg(k@nG7E9O*VD;QM6-Am ze#%&$@ly^?F6tnw;vH5^pj$l1SuuaE^zRtJQP{h} znM(qyB#)pq<<7QYy^H_hLD?l$3H!5-Z zI)mOP2b0F3A{N2Nteq8X?3d-`lQ9QHVPDh%n92wxXUV>k&Ba((Z@1+SG4(bTr}+xy zVcBVW**Un3+-l$wn22!*bx4{C4E8;6abT81Wtf-eVp`nM{pnTTBd+^B7Ul_2Kl}Nd zUZdzvPo6rF?5Q0A0gq1fNkbW_{YzEr)J0WyJuqJMxtT8?+ghak!HQd5DYd(4UphIp z!sLK>i@%F-g{N=1{l0YLH;2!W>raKy)A>INGp5N}n-aaT8lH%H1btj&J*ObD96!8F zIrKx|je`SqnBS-C387UN9n>3FD8g;c>yCt#%gF)#vb{M}zMuJ$m^upV_499EVto%$ z7}5H4nO4%}!@|S@f5MA8GeLUM7{^?9!(v0L_?JCxz90L^p%q&6U;0>jMIUR7xgS=3 zh}5X<8?fo}F(o4-`)Fm{oOCG@XOnzOfBt-N?ud1-*4T^OHj`=a0sfuYl|Mw4DJT$Y z*f#}z_=aKUVv)ilZw%u;TF6Us9@y;XBG&oyA8q6o`1I zYh6OUi*t_)hsZk;r~vtS#rt>A4&*XpnY6a=R#@Ege{k=2th_hCc3USzLwp*Y597V{ zq~{Z@&hY7eokzV9EH+PiKI-?ZWOVUOWtjsV@zFCQ8JQe|-iNBylAX>;*7HSHj+4Qr zu5iled@Uk9Jw;Cw-80{9k2@6CM%uP~_KxnpoDvbs$Emr__U~+k;*)US3S0G5Uki9W z&%iCe$=~}lk>r_E(HDi9QM7OJYQTf(!b{j44+aobY;Uzo=auDF&a(MRLM~y+G%i-4 z_<}zryF=U8dY3%}pbYOlg^x{F4&#DhlxexkpNy$YG7Mf}-d7r#GMQUVcEc{+AUsnx z9xk%^3#_x1%NLSQon!)^cw3&2>wo#-H_t~52*VJbkHxYkRp6h8pxJmnhAcXtKE9`H zRa21;dZ9|bTxpkz3JDh3rVA}`RALILUqsbTQy=z|^nAm??wj=*jzpjhQsGaNE@-M4 z6tQEyy`e=xk&Ru>F)^pQCUf0aTdpS>r{x7tqGWtub!v_rc8ql$VmthAlA}3cz_?pI1SHq5r)v51`#X4t6k+!NPicOmSl=R+1fx3RPF~M$jiLc-%#Z=mzd8; z*|w{k^$OKa2gpK$7+_PwSf0su=D!GtAh)IDO!NE;_<8Tsy@@HxbC;qBrj1E-0r${7LI8$kUm zpZiELOLSl9^0|;J_0e6sp&Y}7Cqt4V1!&`1UM%}hUF`p0@bpH~s2-ox@ zMfWasdow(f^Ll+&xi@badk`j@vm$rahQmM46-{1XmjAjJr^YrV!3e!NY*Icy4L1s$ zQ01*1K6;9qht#&dF7TQdRYCWCc3mK0?i@GDaS5;4v|tboPm8>bhLd0Q|9<~-D9Ih} zl%0;WmcaA({wp~axqMLm#JFp-DO5g~13-uNRP?e5<9?`DvLwW9VZy~%M!GpfC!oKX z%2N{fX3g=#VK*O%ZPtu;ZWSLn5={8Z11h!o)D0+vg`_n zVLo@LtdM>DXv*{DI>f`dBR}qSt_FGs;0S}uw@{Kd6c-PL`nSKNo-TWPE5}WXqF^OO z&0QJW$2-+>i{XBr{{`}bx{zc+2Z{_5SO*tEP>psWUn+sK_@&O%{ym;y$g*|5{xIro5LH*trWx}+3tL@oz;JW__BuQ9*ZG!gXH z42y2ol$ft3Jqvh0g%n=CE@;S|qd!9_(kVwVSH2s1gR{Rkbm&l7h}UhtJ)t1CNI{mCz1 zPiDU8DBoF~&qIPyPKa}2dS{_V+m1q01L@@O@6=6{k{#u=BeGoM-R~*(!eM(A5o1=} z{CU3ZMjjPK*4Of;6d7R~UI3E*%)!O4#{h3~q6sx#tdJV}d7SUz%Y zD4W{f=-Cn~YlsvQLgiY$eBr}wHYro-%kLTL%0I`8%EaX@#j^t^Mva}vI#fylE$#xC zD4emC(r)-mabsw4WCFi!@~ATeCd>H!ERDA{hdsws?JJ?nZy&k{cO2Cw^R5lb(}M!W z+){JZ;BDkpc_?yX#tR9PWbkXHyxvj%K*Ix)|AuAN&iOG6Iwm7P7IT$ThM^9Z$~|A+ zwDwx)m`qd%u<*uUcaOC9^i@tXAd24Hq>hB7O6+?-_`+jT*U5G{sv>hJiH}LooV-#0 zMQRey$^*K=jW!ekOqr5*H(nW; z#$w(%G5V~=fAlG>X7onAWJfpUaoY4vTYMGUl~1SDa8AVDxx1tMmLCM!k;m~v>ql`I zIZ5#>f}*I>=SVo&)seodM$|>3&>~%^JUz)5^p!jBJ5)|{KYV@dQrvQERS*9JZQr@7 zwX2*j#U3#ME?vxLnV|N=C>!SYF<a zwSNs2Zt0BW?kW|~=Vl`94~=&r(8&ufLjfni`vMdEkySjstW+wEb+&cDju%eQBL8k0 zIoNIM{bm_)DHIHW4cF{<+Q}>}KYAJI;#R?YKQq8^1nLBY4OG`J>mcc%JKvF1Nq(70 z9Su*N6k9x8f^dMtt#`rItoL8CW0r6@d+5E*LuPR^#yn}<0ZA_QoS`dys?tJTLGNY` zjzBL0z2in>^-U>)2FBbllZC!c@~kfBy^Hs0y=AH8xk53N@l{N?zo0XWmxH{} znbL7jcdXIyBuf%qa6()0si~8NSLQlY1ctE(fXkLjag^R)>m0#=BrSDj+ScA=oyeXn zJU|D_3{P9#{n-3TCZE3VTfg1V9b7T$zd=-rQ}-8Dg(Y0Y}%a0$QaT zm%N(<0y9hVM5CDBe_P3%0(1vlD3H`eM#s9fYc8A_&~me=3rwCa^iU{qKDDo4GX{`NNKsbLhXsJ)am zthI56Ot}uc(pv9Dw*US-4#<7Sz{R(IU46V92gDQOa5_H%0~i_nhpr53NJWbry=*8TCK7u(xi&kqv&19bReE9QEL(bETKyZr7bLN4!rdNm^U8BNjAn zee#-7S7%bK&*IPHo-o)W$q;dzgD`IccoqV7lSshRA}C6)eqGujhbaXLwQoe$M~hwJ&F^LcsW?aZQiHcs|XD6#H@wLuAuisQoOy&DFsk9B2wxo zurO~=RW{9ALgbBJEs|ee?qFWM_ktx1v4D=6kd7Y}6P_!u645uH@OM5EAol1J{W)ym zBil+})N{Rsw0Yh)H1MSQ)-dOyJ5~;S&ndu*J1P>w3I&%4vyl{gu5ywgv%>FrWYtI4 z4QKDX6@NP%Frs2OcLeKIPLaSWf7oZWeA)1Buhv=KrXJZHS7pY*NfE+buFm9@bV$5$I8TFLI+xQR2oS2x*3MY#l0A)J0vgJ<+xit5DAIQ0orDI3}@|sI1fRQ|sDo zL7<5ZDfo!xWLm&=;9(2pD+Ijc>Q_?Dz1S~?`a5!rGpUru)Trxa{3tp?Ywk6Y`pG_Q z;0oc@f-{mlgfNoy^`XIiMnSf}2nohA1cM&3bAI!|+s@^KC7I&5s#$82rNVV&#|fir z%BhN>Nk!C{=ewu@$LyY%cb=t^g(}R8?yG!JBXM48V`m*Bezf+VI`BlxQxcmiaBFHc zvU*5r^^}3#4JO5L^;Gld=9pQGyE^OA(vPs(Q(1+(fpAB!f@3m%87`HK6XLWNU)3z5 z+V(F(jsm5uHQtP4k4`edzcv(KFdAlKk2_VWSeEN5xJqgDKA`Hwngtu^{&X*dKKC(j zC2V)*xaRewUi!8+a#sHN^4*LNZo1r+g*$*xwX2*}vkxy+rQAJW!!a=8t!)41Gesx~ z^1wc*Ib(2(Qi=v`F*SY?M~D`d-IbDLFij>yK+$jv8eHEtD&4=B(q!57lA`Ns#FN5z zfL2*$0b29AO1gRFv4$9N+CAw353c$ri!B&TU$$Voe+-ZhJ`5LjWwuafwzxS(2}GX3 zu)#17BVnG4;I2odc^0idmAp&sUHAioLK+O*1`d5QT$m>&{1JQ-E?rsn)C7>4kwQ>I zl<$tSA!u;pq{+;yN;?k@if%le8%}{#fsv6kN`9$rRI*g1EiR(FK>}R=;ll>o>B&g$ zHUgy|HUTmMhBR$uMQto>7YxxZdvQR)vjNaEADfop0Ro~R03n@l&MxXA0I>8E(+PK+ceg$%oe8Bb zWfp@obC$f_ne3|@cmIH`pHK$({ScSxV=qqb5YiB{%{40yq%Z z`j+7S+QTnz-Viz!E0KI%Fa6uW=0_1h&ra7SyBhCoy3uU7Pk+bh(M1E9fQ<9#9+kNE zlIsC7FT!8Ntk$N@RU6!tG8fqG*cTa?&XbV$#!$1Yxk`0Tp!qtNUTsrk%w(zMwaTSe zr)%HF>qitR*x~~HH5q!mGpc!Os5zKNoZM74n3o1H^y|ZdHV8uQctcu zSakF)giGO3guw-7SXx1^+a7m@oQoa$kZc*F8E!plsG&*kD;WdyNNj-xynrQ(jKtxF z{7kLnyuiU2MZQGOg3x(%_`p0;4i=Y05m-Z6H^*0<8289Qux=;G-9hLHQdf>>F}q>D4OYv|v^Abg zIHSV^#g^4j=Ir7yG@*ra#CQSKiI?4-QGgpfg0Y^;EvbaW4L(*U!eVM}05Mgp6ngz6lbW=7rSZYxbEWsx z<+4@yQV~f-ET2jR6iR%pKaa7Fbre8RI%EP&nq9VRohTQ8}}dM4zsI* z7pr+Pb1bDSJ`rseq~24+lC_(o?`gRGe1TlGhk*$0@u9pp08&G_Voe+DOBdZOJbQdb z^GmhdK>6d7r<2d|BDKZG(QdEN`}mvw zQ(Xi`2I+VCiv*7c11tvHVU5FIdrfMtMCUq+z= zFzMEn$1XFPebsWRB$$+7DClBp{%K{uq0eBKGK4ONa3)+VmTP1=i+htU%LJq}U<>Zr zdm_4oOOkfs%6wm0tli0(9!OzAPSB~EvJgQ9}X;|P_giaxyN5?2zGqL0CdoNwTQYpNH#CaSWYNpsJJliLr@VZ(~ zGktNK6tt?uw{X9ne99vC{#RvR8^}on1a0?0@#AlJK&LA1Aq`6_Dp;Neiq*jQI%*)_ zF7BYIUNw*TM!N@w24|q;CE9WTUEY2z{*9By@Zq=@t+8a#>~U;u4P^(M3f90aaxAw4lFtjU)qT8RY0zhjGs~jB3(>*gb`jU#>mEy#N z0U3#N9@SW`dDd{LhSG7mwf^&2_e33yOq8u#Qv}4FlQk2cCL4!(Jd@G?vCmYoXdbg4 z2>h5&WiX#$I;{)$l<}p2Z~+%|&e}-DjvV{6=DtX8!9-@NC@I9w1axK!-`i;@A#6yA z$nww?bF;EH;l!aUjDirh-7GGYz#ZcqJDoLBX%lZ$;^2ASCR~Thd?Nrstq}x#t~@*W z(52cY>+T_ws#;2`yL97OcpGWHIXYhJm^xmiWFajIhQ*B5mjrPXHeno6%+e#w_Q+Uf z@H~7R%L*W3#dn#Sx3;E|l|5rua$tly07+^)0n#T_&>z#+9ade$F4m?c6cyWZpdi># z05NLkd~`XnQLe0pl)_9=7+^dEAoLHyBQS-e<`PyVEqA`GjJ!8myH6K7%LRNr>N!vy z=}ULuE*&KV@vnsUupV=uyYK}czqAGi>2(^Yh_U-b3(Jcbh>SF6%T zU585>$Oieh$qJowxvY^HI)?3mAkyCD#^euA@2fxThNJ*un}IM2=uuu~Od)fNeg2ITx$*jA6dF@JMM=fYia;J1oD=yND`LQvu#< zb+fWgENvHPVG#80drwKAhIv#cF}-;Whs1J(L;ZW?Qmd}ahkY-dF95>08>-GndC(`) zT}`5ZLeOw1xtZd7mOf*$H%@zgFtBLg81GQQ{7AwCnhIqt*Us7BjMJ%lsh`5o+A?)Q z`ia%i{2v~1Gtdt$jYUNPNmt>=oR*>IIojbI4G`oRc7Pmdo8OF3AthaFy-VG5y4)`I zqNDbGI)9R0`BttDpC`W0uyV4?%o#z;^G{zfKUL^;-iy0y!nLP^R2XnU0?0GG=-ws? z>8X5aD39~0WD0QCIIGoY9D?H*g>|Kj@dL4q@uUzGFhq)dJda%B zGbqcaa?%`ssdY55Rvm67LJ7QcW|6L9T?x*0E))R{I~hF<+9yRDcZY5m2n!a!>B)++ub}XL9*{+Lr+K)g4Rixs---3BIxg%i zLXe3=&6vzwf^LoR&NO$WjFVRZHPFdoxs+<3FG_~#xEZ2jW*QZ}$QVEaD`zVm~cf#Ku5nn6V4U}+(qZ2eT#grl_F`RI%42pK7 zV3Y%t*{~v%XYwUoAEl>-8+tb|$OY83`5V|TXPE%O@147k#y0YnfwLEp&?g%~4ta^{ zb@1cad6>8M&V}*d5-OVNVNohifAI_^a^Oh_MgIG#SJ8!Ree`=i&{co*1+URG>`GatLiaM|C1d>xYrh57P8R8`iw>M_gS+)yR94%s_32}u0JipnsICyDVqjsYa?r<_gI(#yeL>(6 zTCknLO|Xs3t$@EsoFln&^=PAK!G2)qr1)W*&uB)V!)D^6b;(IY`IIh_9H5=K=y8kz z3T9(i-kbAyFiaUcUuc@>gL(J`I*hw`Q36_wmV64x9C|@}xGz`qw=u{8AnrgFMDS?^ z!lg|E?!u$A8__E#2dV-?%f-eIL7IraHlQ+f(Nf9Mx3o=f^=;CBcS?dgj0`Y8Lq#&# zBOHiMGKIRibhVWa$$Si$6X;;~fgr60u`ix^nr7sFh;7V0z|la4q=uTkU~z+cGiqac zLrQ;emwYUsQ5sb0O~K*vYZT1YD~eTP@fI+T2bCe z+@&(9(Y9f?!3b?*lRYiez-l7h-nIk^xf?n+27S zSGF;TD= z?jZwDAb}~E&AhS>3K=T{1F19=$kmWjI$p}L*8uX6=sc>!DeWC|ho1=mtHxVxAPzUKdvl2N`_i~Lc8UAJfR>PA=)ihq{6Weh*k+}+yr75ixbLc{U z%*9(FDHJ_ILIks{9ll8!&l?T2Z+g z+M-A>C1-u?rj0#ZnkJ%8E;3foz;oKBo|*vrbB#t9v`)!=^eq7ptqTT`WY|573nEH4 z9*`n)eCWB5wvsf^OSN1p0W>L|`rub)nHfkrpCm3d@(feJXST1zMH>Cf49yOK|9+5{YbeM?pY?ALwm5qyUa20th?Qh=`hU_FH&%iuFY1QjI+Q-K4e zsNMla?MZzsfN2YKOJ*ezf={VIvAf7S4Cf8=2CfYCmXszQIw2y`D0-w8If{X{j`PP&vy=7B!1pU3p4b8V)In-f<~td> zw)aB`H;K_m-Xo~zJzgZjQn~7U*+r&LWssF@3Yd;UJ1Dg=30 z05O#!lSBe#wFAx#Ja$*Sb4RWMp_NJT;VjL=c31GgqnINfUA9p0$V|F{KDk@M-N2RK z;cTORA@_~o=N;f+_T#TT$SHXQxu@D7vNw`(n)Q;bdFyq<2sJ3mUJlkYR`{xg^sC-G z+}nw+wx-wps|YyP;YD9#W1d}Vl$iYU=O>o5P8})3Uwry`lGKNeaVM3qzu(`FG6GqvGaltZYJmxSt;&16Ixl>Wu`0RxxBjUus}hTG>T=z*otxK zw!$~60t)I6_>qV_D#e)Op@kC(w4#x_IW!uX@P1)CC6DBX#idc4ze|U6bt3yv-%&bx zks2Dm51BMs%BCYl>+@ug+fVSy$sqIRRM6KxPY3SyBtFb?`V`8eJcPw$$b^w%s!0Gl zhN$AOM-OGdN7K0)h;3?Si2lpdB;?l+Dkiivol$J*Gw7Q6kJ&y!e4w8)yeJ<8q%130 z@!#Ejy4GpoUcm<;b^(Bg{{Reh?$nY%@xY*3FdIAL`rD(fj z&O9}ZviPPw7^!~c(c!%nA8{VT2e@xCd`ex7EvO>gu1jB@Pq+Zzy^jK`JdS#6@9A+3 zNSjG-H(k|tlY_N`?*@!q+!-B0;W0>we0#k4jb||8CFI(Ln1@jwO5$ zX!v}Dz2`^<#|0-AznJH{n2p~$Mn&&}+JgzeQMyOxXyaHID%&MqFt7wM*Go4wy%S=W z0ZNeQ{p7pN6l2E=^Cf_ikI%uC?U<7s%=#l1(almm2)*5Y|IugHDZ{x34K$+anKMWL zec3f3yw$gIUk2NeKsWS3e#ySC`j8OD0BV3`P#x#O4&4u6_038&YZEUzFHNLqP?D&@ z9o|Av6tNakN*5ikFGlXnz8zHhmf5(+;<|xqXYZqw;BM^?cbSX>_E75hsn7tGYCc$1 zx86cVAqHw$DdU6S2^xk?{~hp ziVxiWK3}^_(DtTdb{7JiQt`)6^6?|#7{=+;9Rp+53oIWx_Qqd;mvoRW2KVw%0JlOY zOj841o$}~g|J#wQkyU1va<%kU8 zSo`_WC9TF4b*{0_t{px;K~!+xqZkw8ukDg?oD;EO4vJrs1<1m4Vm{~H!zTlvHcNDO z;B{1_wAg|e#%s5rn2EK!o=8s=32fJRVe)R$tM+7WxU3E*-}*wC#~%{le+{lKRdpIhC4g zPfp!y*eR->KGXZeOU}VO%AzyfEbqv*lBch=)(Strx({xrFh>F(Rtm!xq?8T~0Pz0Q z>qWWQ$SGar+$&PFZ*4s88fCfh_8%&CP*5MW4!uy^`c+CSj*}jyINg&uk-XA=O*`mH z*=f9Me02jv1*e833M+S9iBy%y)wu>!3`7j6TF_cb?K}6`RnWeS-6>|qs@;U^S;Hsc zKoo+8He|ov>oOK89TT^6ZcPk)~JE0ZE zWZJM`IAH4%HQcb8n3}p$ccd|C7kDe;zWPejIx5<5=NYLyw?jQxEeFNbSd5yV3I))? zEewIYKW);x8zzmW)iXL*2JV-kSRU|(_7L^ZidMrxL^5LrJzHuzT$$2h!d1=W6O>?S zIJl6sD-{6;Zz-9Y6a5bYR7!ol0l_<}@H!IDtKHz;x{<~ABK(wnai6jp-1|`t<%pz4 zyKLNrhL**#)l*ex>{~^Riq$~hGlb&kGe4!eTLzE{nOYh5Ep;YLFJFqd;rG=?20;$I z_roGog!)F_Qdt~4AAu_a@Hg5Fin;xO9t}TJ-r)z!%eTg!D9E8FLslp>nKUA9`tx`p zCl4u1w`N?j449e6;AGsC#}7^LON-Rb;jg=tKHwqXb#?bR_CqFh)e;23|9T;S+B1(4 zrnpPX<6eH$=rOLsFpU_cihYW)s2IcJcfKM6ec{KOzlSYuSn4h+}LXKH)n6yK#Z`(<^#T5(ERRA%s5S z9-V)u@W~(hlB*H061mmT&ea_LVzuDAbjz7MKm6K__J7k1JfMzGkqF;Ig|R}?rBNJ2f=h_c z*MK@wk^_QM^#a8yCq^A@8k)^$uTysli&)Y&fk{0#EZddR_hND0YJNOMf`&ruMYxmG ziJqTVc6z28lQ{qd$|9WOAFqUfiQ8Uv|<(*ys6s~%_a5fpio@N2_aJ) z{eT;ot9r1T>!e`AE{;c#f+$8{))Vo3zQ7WQ^2B^*gHB29R(X4@Wy0@Xoa4#E+oQQadM81DFp=e%~jg zLQwUgf4MkrLCgZ+`GGYwBK_ zoEjxvjcXSiCBYkVpfyt|3rBoa?$Nb^I9LxBP73apzm7eHfXTX3ByJi>D>ryQ)HlA3 z&-QOoZ_aLME;`(4Vd|f$k;*Y{oJ<;jyY1<>u7=9*p`i<;%T9A=tx6MajV15Al@O=# zE@=jnBEy&?{NdRT4QG?;&OP8-nV$tZB8ZVG7;(r1w|i_#fn$Yt1M(4+zzG!kxL?+R zM7rEG$pb>BV1{ey#@CcYZ549FMNJQvz;K;45DXjC2wM zNYMtL4biA%flF~CKCj5XkpRB#1F-KCN!)5FVPN*Ydc?tP3`|$Qt`3C(u_*BD!XUPh zjw1%lp3Epi1pT8?ND^%Acwv8=E3%!%MOG=Idu10eZwL4`P?J>RPd{$Xql3j55lVrU zwiaTm-}de+e(B*yjip8)F421K;m~7OO8XXE&<*DJE!??)z(`yym|!2nRu4k2$dHfl#7$@mnZ}**__kzYjQVg60K}PuP`aCnUga z(2CrQ)65B=_e0a0NC?ydEoc}Eo+>cH+@^ddZJ3tA7H^eKe`DS2g>i)=lob7j?#tMU zD9tL*@IxL_doq1;by?BF#&O2WOq~LIYGb7Zbm3#EtYBVWD9#&P=-pmvSQQ|} z2cb&H6dq*Eqfdf4XNW{rvGhJ=b7C<#z(GT)Hd6&?>FuHpPN6{Eqf4H_T<|+~VLYo> z1nioxdqH;^Zp#3TFa!HOQA6oV!%ul`rVIiuqSFe%FqtaWe)NS{xr1wnUEK>@hv;E( zD|}OP0t#c&Q{6RQVPg5zfVl-0a@?)Af|0IRt@8n$S0^|>Xug*|t2zYwzAv~yL8$Ky z1H@*H6F5c!j@Xy3>j52WeEIR29RsYv)i2$}8p%C(;m_XobC?c7#u@3rU?S(N4k82r zl6b+XZV8CblLD5o#bz*BP^id=5Av+GpYJ#DZ?~O7@SyyBO}H*Nvru*krsqYoJ4|YM2UT<-q$h(0t#)U2jjQr0T`JXfLKWF5B&dC3qk^ea(|8qwE z=ZyT%8Tp?x^8ZiH$c4cFC5MX8U(d)%z@v1M_1`0(b!l1x0Ocv-?~&hrxxe{IW9 zJM>KIRXb=pIy*Y+&c(-^)t!tTl$Or8MSZkxuz9u1egd~Bhd;&tQ~qywpdLMP|5Eq! zlzt17_~?u|zy2V-j8~4Q8>?FEjnW(QSR8a~_r~8o*Sv#lE@|+(&^s1hy$``nC;GIH zi<-4w;*Tk_8?k>iI(>3{E$3a3#aUxpBhB6M&ki&{zCUu0YcyKMdgMgLT-xZ89ZClL z9gs)ymvURqzd0{7DnT~4(yh;*us3Bc2;Xm>;s0Up&BLK?-?-uLd}lD^&Wt6rFpV`* zNF|L-8gj>tA}NZyNQ;ofRF;`YwxUgvW!fw)Xhn*tY>}mviVP}BSu13i_q;~G-}5~0 zbG(1P$MYV?>u~&jZq3Z~y{_~8ocnp5;}~S*_(phX9^Rm*$nBzVbK*FwRa0_pB=wZI zIwn+Zwh5J>WZa*oZQOr2P?d@Ct3KziBW3NP<1bfH2_i4Si8wETmbbBEF8Ns_i=;NZ znRIx$Qh*Nb!6MQ1@%*`%;NGakr=rmwUA&x_CPR_a)KVLf8DOeXd6&4gpGBv;bF-~f zjF}7$nYC1nNunRpD_9zyjR!3bDF3*};i1vSCF4(*;T&?gh8p_>o5SWGWpfVU_v5kj zztR?qzTX=4*|pl?3k`2k5IEDwRGOL~gW1wSxIe3C_r!4)$BED@9pJ!W({^r)@`z0% zH@5TLh8EI^G1xFEMyg|`oT0T!A)B*6N-I@#>f*Hwc|R)-n`88tW-k#$CD=>ENL|#$ zgUkx{Oq}xSY&gA%YV$Z3+ZI(03k+~3hDqJQj<-fP%!k936BDSrX37$e6f-wJSJsQs#6eY z5b3MxmEb5vwXtL{l`oGf8z|A441V&;4g!l~WEpl(c9S>9-eyR-o+XfG0SSFoHPr;v ziz*ewpjD}50Sk%uE8qmi*45}Ez0x!c4(5=<8{Mq{XG%f`fgfH6o0FH4^V!1bqf7bZ z@r*3*$;!*IS19ooY2O9S^;e7N|N^9`c(QI3`5_s>lODfFzia|3qThT-$`UC5zn5VlX$!PIxOK zT_yR_UGF46)%TzM4er8O5r$~6rGiP~lnbNF_0bnbs@KC`X67C;K~CkJ)7B zJT%<3ig zkb!#!k>$j-_jQ;d$GnCXE`${lsDTauOaz&N)8xi~HClz^rb>+)NO1mKv1r6dy#i!x zJa%5B^7hptr3Ylqxgc6-*GxuIi$P@2JF-c{YdT+LE*ynv+zBXS^Y!o`H-qz{OBb(g zCoiTOMk17bq|ztZ9vFB0+Mc1pH?l{JDP16AmXqke<=vPo+qi%6zdvTY6&BWPFMDl| z9aa$YH&%)r6~m^ff;c=#H4#0(HQ;mg0dRowUqteZsl2gWZ-jfSXdj*afQE(-!~-47 z3bN`vR*PA~B#GBq0qZ=zuvoO(Ybb*C*sSy(N zg2nG99CD{fFU9-C6i4{1L}4$ zQ$(1X<7xMGmwJ0VvcEn;%9oEh+XuXOhV@BS}*v@!?=caY9J zJDoS`c5NL3O#+f#jOtm-lcDmQq?(0P_{cq3>kXtVbY$9g0J>S!=lo5Q2&C*N-Ghmg zn+P)hX38|)JhMb}6v$;$q=fCU67M@rMyyZo(V0yy&BE2GaOU<&ZAyWiAXULjlWiyA|sI)F%aCEhq zx5Dqj`lx+4(L} zf6v6k2ZjD6*2sEHiY%P1YA=c>EbjO;&FHk_~in>$j&?TcyX8;fR#_g(wEO|$aoz2~}bjaAaoX3KTdctc&% zoV&d#%UxyN7iW2oB=1b$n*gwvbs^>LcUSgw&`X!E%bl?|>w8-Bfv|kng9q-8biG~H zu{3*P>zaDy;RsuXo<%d-bPPF1fnXLPO{mV0dC=tXV`2wuS94|VKTl(TJA$KR8g}O8 ztGAldm&~C~TQHwm#u$(qI1<*T=ycEG7$|L^ty%}ctc*ygepPq-ckJ>mIqK_cRZE-p zoJ$7QCp6ZX6G(h@Kbv{YABxYoRR#OeKBW$!Te?<*#$9JdfBW_wrKk`Qjrp2 zV_~?V;}19ETetm&?zmKvRW+U|6G*hs<0k6L5!XfM-@1x5$(Lvr{@XC?hNpIKQXB4cR;dkU|@dDf)+Gi3jgAK!3%&$ag^4^Oda%LXu< zXK{9UUfj~eaVccCwYMfD)%R_}z)EGF$i{~w+I$%CzVY*W|;J6Oyv=uQOzgtaGYV=Hirp-fU^rwCMKeSZrBI-nP`*rmL@#Ezv*Y z8p&Cc7^Z(*D&hX_mB-u-O+HY#CIieLoxp(mv;9_RXS^HN+M=PU7V|n6OSjO+)?dta zj%8YC(Hnchii6o-s-0W2=G{o3T8AX`r!v+>cL(14{$M06v?iqq7?}`HZXDes!S|u| z>k=#T_Z8ms+))=b1010c2aFK2;@F^K*Q}$Z4Y$i%6k;k==BAfj#(u=`4P>mC6}kL9 z>dx_3KV350U1+ykLoLP{PF?Qf!#`gYt~G1yRO_cLBm@k{qehlyWg3LWrD}!~sl8{W zMekaX{wjgoZ2)u)wYlQd@l(S>>C`QG-WqtnrW$zx2yV#?%)FCu$xjrJq*Ace?r3O2 zPNb3Lcn2JNi^={au?xS6lIIaJkbz>;SXADS4@t23?W?x^`h>5Y1SPtsFbssW2=1_F zI)NiY<(LM7l&2JrP1`744Uve30SL!{y2j<9u4x<}YR(J4go54Q`zc1AuT?tdUQ`@l z76Cvg4!>GVJ(rkWBjr%U?vN0S%}~hDxllr*B}ws*F7PVKX*Dc9V)QtG1vFb|yYR*W z3U&yTy=Q`>I@+29ugp-B=CsR;X+bp%zftm8Ed7PALHf5R7#1A@Iy@Nto?CYMG=}`> z`^Ts+M)#3uPG7BT zXI-LLzBm73-Vd&Qwd6)8mwcQWJs0zD60HyYBna8}RY~IK)%b(o1;gcq)5l%w>qK@7 z8b|MrRK&K`b`F-sSu7PMvPDAtru~Y0`|gZJ?XbUFZi$Dpf1J(J67xqEh_HMH2kYc+ zY_9^Cg*0^hyS4gVEWq-rs)p<;taSfKl~tyWTF+HA{Y{r?)m(ALwD z$C~cdZ2S4STAF|4m+fb%+se|&1>v``X~P+Sc@1nZ`N75uv7D>{&dp$UJ?*)F#g0*x z=dY%B5c;z5b}~QFH&gr90(=op<=XwzG7%-V8zSEkkD=2vJx~`Xdufp}6|To?OMdk2U3)R8hZ>{wCrcfAyb>DEk-O(&0~A6h zabozVNNeWu{N4J5_=O7=IT&iE%J)aft1?Lx!Wmemkj%{bp+GP%l%1(13V#VoNhf-b z<6b>VADgB~F#U1quup7jHRt#+>H5#^`m^Qk2UwRU9y?Z&Q<7w+cA1&Gj$spuI zB$b12GjSc0rI@d`hQQCar~W`>1?P3AS+xJp(qPkiIwcwlb&_G$-!6BHq;OTa@%*k| zVVl2|?6p}g%^q$M;snQe03Ks&*QcOs+FY}_x7&Q-xg&~ zr(|KSnOb=)QV=Mv%I&jDdhX87dHZ0`W;@(c8C(%o6{ydXxFC~U9+uGfz(ltvPh#=b z#&>(aKUuoSIwulqu3^T|1y#A6npWMPJO>9Bkwld?Yfgr)y>b5{F z3^`3Jk$FtPOq0P^TKIEe-w$eGy{hijWEqC((vCc4)@(-v@pwm_EPk z!JAW

+&-WF8(Au7gw9497D$0I-m;tUfn)eaZYx#U3^-l)x@8k!7(H`XBC_W~q#A z+xy()Ev9z@t0Wie@HRy8icQ2%sw{Lhgvk+8V)d5 zTd?bmgI|2D%D=9-=dj6MS31Ro;dz~6f5qv7g;I+aw&*t9aPC)ddMRJn;8SQ|=hmDg zcb9k@Y7j%7&6zKmr{;5gw!PJ82D2IGts`ADwKnZ(X>pX`{bR4CqW+>--q7joq_^zn zE%r&|uH&8yft>9bGSy|3L46L-Bv$QNn;;cL#eC?z9&;qT){E{phc9!MX7`l8w<39F z1tSk$thU`GXinZNfy)sT-427kAMgWGr0z}M@0NAtlCgEpd2B=*pIJo&55+A9FZec? zYK)_hp5o2`u~NX!fOC5xe}E(WeHn%-99{a)?w96jjq5SzDxz6D=Q$}a*nQeR*n&GA zLAu!eD)5wNgDd5+2E&!WL3Rn!>u*>w=1Z%PFkU>$o@B&G%pWmsv@g>>v5P2}v92ek~x%}&;Uk)k1j+>il zzpH7k8X0uyTnPaeCj>0S_r^=PY}u%sip?*+vnEe3W8(!<&T+9OKn&Eu4#t7Yu5YeX z^fPkT+Pi^vo3_mSMz$qAR^$k?W5<^(#Z;=a&k9@`;hMH*dE5Ao=XQUg7*!Lw?$


4gsK+2k~mQ)t}yEO4Dt1kK}w~Bb5%rvC)hE*;+?+3khRGZN7ZS>ZspO4SUc!Tg( zlGBmwgX?rthIs)Z?{XW`eQ!a%+^55H59k?&*50Y7Rg(8BKx|0kIdP9DpN-bWES66M z*AG>MLxv181Itu-Lv9lMO)(scVUo#v;~g0?&w9`H-u8-eH~iR_%gMvRk+>M_AON@G zuYa%)=1Uu_F&S@^o}u$1!QY9O=G-7V>nnozw)RS^>~DH7bM3?Jp;!HCf85zcZQP8V zoFS*89qKub^z-gr`%l_Or$Wgh!cUOrX zmF$424Qdkh#!1Ef&A;&Z(giZV_};YX60{EhwttI|Gn$R_E|CqbyKNbs4|S3@8k(@{ zqUzhv>&Htyrf~9oBm@^6t(c&89u5tyqGFvN+gFax?X|~2krwGtDu8iqb>HnJXeUun zn$zLT3{QVKo?Pr@-*kr{1PTKI2`0q649Tp8T7m42v&U3= zyac>59|_N->a*BpaJ!g^Mwa^?HKuXDTvgh}YW+MoEjozGX^!XAI&o{~Y*m_f- z<~PXeY$Ex33A%K`mE*8GSKh^uF~l`P#5EmI9sJ}+G7b_bnw;&wOlhR!T)w)n^!v+l zdn?b&+H`>rqyH1JZTfe*;K_0iT@AwDv4~^Vplm`ku+li@{ySCVx5ln6Bo~hlwBWX8 zf3@*@HErV(kD4xHA1!RtH1L~{=eDpm)hPd))9`&PdKCd>$2bRJ<01Qw&LWWeGdOu} zlEpfLEhJe&^WHFrdnf|iusm<{pk5Lc32o;>yx!aM+Z)x38m-hgEOe~j;g*BqQ3J?j z+k61;y^W5YyWe_SRveL5?5_KC?AAcv_dGRf^S(}wsYY9jzeAxE9Xmqr_w29|1oad< zk_5_u%1mqQK`gMWocIA$x4#Bf7^%6Bk1bm zgj#0GFexEFOhq_+ZleTFDFw`@C?aQ=#N6_tCRs)`#uaHoQ78BA*`=|Yn}$ExFpa3q z?}Hjc4YOWc+KDLMpW_<1bG~H0knZ@+*)v;sK*wzs%jxP{F7{w;|NNWls4-7-`3oI{ zE5KoUyUxQJ`;iC=CBY9tLmxbfH7w*t@!FFgIpq7ESUNG>Wy92>d#1_E_BEp~xa%<^ zH|S~(!pL=d$qo&?&EmIW5Ji4`4$ z&W3CL<_4SA-LR6NZ6Jj*lwx>f6_0E~@X$Xp>=Yj$rhF9Zl=NHYeAb*Hw_K<*=YEA? z`0E_G>t(mrf2)(>b7!kM9?%QO8OpC_v7GioRmNR<1)vBvOhEMr-4F~0#Dd*Xqk&2wKjf8G0_u&$HL6tqpuSl8u)HXwL z>C+H$??yvA*)7ZGnup$2pr&M)QrSDYhAv3+PaWFQ8=kTwnhpt4pisTnq{lMTSD$zj zl>(xJ^$Oa*E}4fNy|3><5-w9pp-b?8>s=70X8$&rWw2|h#6CUB86(TiCx-JAM&=I3 zcBq2+LUkes4Xl^My91UBbfOh$$r?g)o96k5ES$ofXS+{==63scR^Mi^PnROIo@BC2 z$#CXM*9XDO^f8YA?<}gLf?TsSa~ajpLE^JZwp89s{as5P zY_+u1`a73x*Ae!2FAP-EwuJM^t^v_#ZCM-gcLRGH*F0i|8de}Kj z5@gy|F$yZ^STJ2+pr#91$^Bnm9?#TYphFyNix2vlI$-_8p<-`B|EAU3bn*U(L{U}_ zmSd&_Ir3*61S=@534?J@Di;hjVZAZ$CrYZCDRFN!1;?!VXSH`{gL5f4CsC?JhND1D z@`!Y9yCN6!?wFHVr-}8B2e}kU|8#cAbxh3o)gZeKGR$M$^A4%T)a8O7wW?UB;QKrK zegEWd{Aaa+v7^wLmqRv9ka~btGn^PMQGRn->Z!!4{g5HL6z(8!e}4p&>shd|2U5D% z4jWT00vA-iBX-`vG*M*^KL+2KVA>M;?D}=G$#Mlk{s8}#w}Udn_S;6%nrdc#$XNE( z*TxEFTOH5~vN;K1JkL4%eAz%>WuJrhL9E3aWAI(Tt5 z{_--`k~Td5ro5`|LA^$uu)d2IwpQPdwQ8E)V`j~M@W~n0dhZlrK*!X;>$3!{3ne1q zaS}ffSzzrw2?1kwH+R?!?Y%u{MO!VY2k2a;)Gg1+$d2`2r%SxT;_Hr%|NWQi+Jldh z><9SNQFC%+TazfH{$)8Tbb4gtqY&PP7>FaP5B3t*ae*^Q&s?cm2~)S?y~^d!2X{VT z?a-IYcj^Z?1B<6cS}Ybm6zskD{3`MzI|%2+daHHXuBf$}U`6J9E6=gJtsOgtVsUA( zKuYDrmyi%yO*)()m2gtja#=F(^t9-E%?njz_9Zlq9E^UX-Xq%b<6Ot{R8)VUWq_HV zU=SP)P^~9OP2<&awdUsr*tuH@gx3T7AKne7p?RjwZ01Bn*1-t6gTxZMzrV=KskEIk z9N9b`p`?U$LCF~+ibKfHpkjR}b?zn^PiA(i?Y;i#vg!5{oOQ-Lx>~>jtUZRaHssl? zw5wap^STdT;IRZ=yI0wq#Y7!8m~$Mcs}=<&7D)2U(bXoPI*4-&(*-lWVo9ngC-zG4 zGugZrbHb3b=e_?1-FgsRWVto@uK!GH7MN>FaG*$G-dLWoV*QUXc4= zR#>?927-kn4MXP;A`etcf`3jH6;kkiV5?P&v_j7Vi$mVv%N7L)z3O zWV-7z>*`^tbz7`x1`$z@X02}1)5o9+tD^C094S1ASiETV$O5L4zZHiw(@x(0tg{Fb zH_qimIfICLF6OLMrej!=Ib&Oty@SNZ2HT$#&8~(fPhXo8E)5*1D3lYGpTRW8koD7Z zfV7r3iFp?aw}!=TlEbNZ!2Ob$2fcVNoVfDaqU3G_XkY{IUBC-eQkNI)<3SF1UoVN< z3M;|aBwmv$J3BOgZzJp_sRh(igbNlOo3Ty{SeQWFBq8ACoUJQcp-(V3=CMe=_7Zl4 z>O;w{)2Jy*X=Ih%N5>YZ&W-jBNFvRS189J1w~`nWZ#zO*($V%6qLQHg!O-C@c8JY^X*?YJATWX^h|=*j$x>u|wp6af@f5l8U? z(eJr$g$=*N z@e@i~=%ILoO>>ZxB^auuJ$0tg`>W%WebuzDuA5JM{=s<1tIJx8KBatKF*Nofd6(Sv z(OECnULDoxa0ch&0>nXO=MRCYI;}@-T#PCrX>0-i=aY`0_raI<3im-;vf&xT_}NXi^VSxaqkIS{g`7w6%B zgDD(LezfWUF32W(dGVN-dpuLbN}x{cYEv(-?>Xc>)OKD2U;d57bCV1<={1Uw^8Yxp zWh0b;zVF(K6bB7e^KM{lNCs3vCF|l6>mA}y|E-=5F?fLU-sth_E7*@)3LF`Gi4L}d zz`ttyIdO!L6ropuVvOeSjhA1{+khK(2|G^8Ddr|Zun57X!*hs{3!3e*$IFo0!sz5a zX-fP|<~UJJI|nkD59eVEKQnAwY%h?z&8)kj2KM~zm91xI6Ys2HCX&DHpDp+8v}k;u z>ccyiH_evjd(50@A1U5yFQKC0Yyjz}YnZDo5w-IqT{e3BWa;tLw>Q^@$1=kijT%_7 zgF??dyuE@rD}T=-$gcb{7R-kz+w&!LZ}Iq=r)wV<+CTX23PDCCc|FSD`1PLpM^qXr z?Wl#HP}jKrZ-V2aT#zf}cUQ1G9?ly0^bYRo4W-UM4s_h2Cnd23^&wL6t${5KIh&XM zT0b8j&BjYUHY_bwhjulbFjDx$|NaclEfmcpQ0dqQsB+e`cuPi53G+mP)buvf?e$?H z>+{GX`}O*}ubzd3{yhb*W;rpK?zkx8; z6ETdu?lmCD3+G~`88V;*_YUw+%qEbnNw@GM$aVf;@yiS%6R)$M&#vmZ>P`&^)Dw(k zaI7SQJ=*-k8$Rks?d-W-{?$typbSDUR=5<7@&PNCQv>vgtNms#^^fzIpM=Ge?_5(t zxi?MxX`EE)z0v*g7+aZ8n2o#TeK(w#`C>TC`TWemFJ8|zP<_6VOr(N^cEP@~9KPtG zT-vC!AV-m7n}xa8$1(p_&FOdMQ@9s#0ptot4e+^ss=}WW7V!@QiyJ=R()}Aq$sX?K zZy$G^So8wwR;Fu9yszFu)vjV4M?I9SAqh0DVGu2!LCGQ&5nIrGOko|Cmm;5}kDm=w z;8uYTxM>Zxl`7{9>9QxkD_ZdYLw)?v9v1(pISNHYY3%%Fg^Jt{nF%&>oBhp|#5Jvl zkVCrxXkKFlD(OUvad#nk_9;Bzts+N%89m@>E}0GGUfeS*xyYF6xXe0dHKX8r>dgT7z(C+tXuG+1@WhYGjzdoIPFE z`($3xc==?B-9S4sf`3C3281OG&ySB+Xws|^(%REyW}n3au5?~iI=P`#HhXjk)X7=>(Z01+yt|P7jZa17KD8XmwG9eb z8$__Qn{JYcOVf=(f@)a2>RkS|Yb)F!2@pZ2JT!>(@0~bf>M6syJ7~qvbmBf5()qaJ zpKt3(D;AYRtS&?0#y?K6-~;^7gdm!ZJGzkQN)SLf9%T!n6tQj6&y;S*#dB)dG=1y< zY~&<&i*%gS3SU=E2|>&}f|eC?FQZ`mo!{Qt>sJroh}wdD@V>oOg8iq;V{W{BiAp!! z&JYE8BQKtN=&f-|HCw=PdZ@^;cEW4oI2YM`WRICGo!sRg@@E{%S2qm#rEa1HUi9?l z7=HK>dcjD`t&{!=DX9J~c*y;}UcV~l-#DaT5}>3a_6?@#KzbTvBcC2%tdAXALJz1r z%NE>xtwo}>(+v-NQMfgrUMIuZKn)0XfZEi=P_@W3Jhw5P-0~xFMWxR=pStM3#YHBx zQ&qxlsQ+PzIs01YJEyIPk%xHHcgrFJ-@Y zX-wtu9)uwe(t}E3MW3{axC%nc##8-O-2=AAj+!RUBec>3s;x=W%_xMz0lw_$noDkk z9A}c>k{PmL4Eo}rCtj$n;n_u$Xq-)L6gb1`CzQEPb=g+ix_;(;E%H0T4Fq}sj4OzX zHbmqJHg@o>m`~XJ6w%Vu(z)2VnmFd|MbsQkwW5gnfCijrDf#irt6yJVcYexxv~%Yl zsP$!q7!e%9^Z@(ieyZ02!Via{6*;ra>5Ut80jh-M)Rf}S`v9t-(I4$m4c+rHq~gTq z&SkjcCK7Zv*f!)p4NEm;QIk$l(xDT2vuIIl zUSG?JjsoA0tIst_nKj}O-;L-5-3y}#bE*U~ zxP5He8#l=txTg-@w1)+bEx`?VOe88$=acl&F_mm$8MQ23hLJnTy`SZ*f9Eq?>3YVBs-boJXcapy38}P(F;#L z=ld``g`}KYog1_wttlsZycb$O&yG!x;n+jr>%-l@PVYY2vt&c?PAGo& zPnNeJ039aDTUe)1Id=1?T;l=Gdh8W+78*VF?BwpO&77gL>1#=k^Kr6bHOE(21?n=F z?OFX;+6qeSp?Z{CAjbBYcZ8umD);||uIzH;C@B!2ALt=uNI)&Lc5-hUbXmpw3Jqo; z53xp_$`1Tq`?iTZWF-hbLg$Souj~|Udpr7i1|dKfm+9ncb-5^oCA-Fwqe2}(p*8&N z_Veprr9^o;{wM(&B}%?%%nDW4k*8F#nWqdQ_m=52YGF5$Ym^B2+4wQ74uTUVlc9b5 z1WLwidHZ7ygmo86zOJHj)xQZ3fGb?6tH?dSxLBt#Yy<7}@Tn8&cV}fo`4AeSL8o&Z z!CR+M2l&h8VKdvtSesA<=Tk28EOuxA21B}|Gt@U| zX4&`_mSCyW#QgK3owi_Cc4gFSoyqV_p-{ z`>%yX1T+fAmz!WUQnUR{t@E2@x~U2r z+g&d!+PFk?I}6eoAFL%qW2NRpec?;w)&}{b8u;%DqV>~=uJ_U5X6M*6J|rqUoUuVK z<`J~?^8x;mH}UH*Gdt;Ol;Mk$`bOg4CbQ*@2bIa#EjkJaFR z=a?(8ceS3C$zW2D4-|yAxo%^nZb^m^vK>dxZzu#{&c?{h&;FplycRYj?vyTfN=*??Ai>!aL zPwbg$PV}~@u&!f@we>fsKJV*&B$!voY^Y3)erQV$O?5p+Ed za9aY>!jtKVJQhuWdZ`n7lOG}4T)~1;<-t6pL1Kml0a1;(rzEYKPd?D@|NY3Z?L!Uu zIJNo|S2DAM+c`+`?0)>WcZSaK81jxl2f_AYPn+<(VA*S)-wo-FPC?tV+wC}4r ze=e@5#Z zL-=tLb2T*}z`?Wo;@`%79(oWCn$(KTeUD0eq<)6M@A-QJ=oD~=vqFCF|K&Ew^hvLh zSoJq_;G<@+HK@R2WNEwEuY|s?GBL;jgzzhye+FY|s^x4kaar(Y^r7k778-O0nDm5> zSuLjXbk#2Dwj8A`fZcvY%H+`EcX2$j9L%m;OQL{&n;3ftLQRF#5&1or-+kP4~n z88y-2E8TaM%?GeQRw6(NwChmYg5UFRZlP293?j{UA3b{S&Gqw|I%FuhV(!wMM3nL= zVhh&1Uw>lmo}pD0xy%Wb3-hog5K=FI&Qd{d@#3v5FH}2@E^m{sZMd;`*6&Zh^tW%W zdGpEli~`}x0Eb9zZY^pH4J&x{vOIIGE+C@bHvIeh-}$r8)76-mO!tO$m{6X3;)ELX za`Tp}7RYeg=t(EIK5xQ``*(IEu>=_ho|JQ}wz0lfIX%-B32J;Ql$VZQ6V4gxE0@wy zj@h#z_*|WEP1WnP`@V0^DZut#10njx&^spmTY_g1>$(Ua@b}(Y(x) z>_7Rru+|zdbXzT~p@`16$H4o|u_Gl!?_^j8&;M1Ni+ zvA!+TEPLk1OdsKXl?H{S!9lMV-=0}_Kh>$}+5M9@La9Z&H1G|+EZPX$^XI5T#rMIJ zTXUh0Vg^?eA{&MhsMi~6XMf%D%z0sCIS0%lcm>Hn$OqntWM;tH;RhPsKHW14jn<&$Bu|_e6rp4QA^(7K4&&e4oqA4C+g? zzVq5IB{c_C5!PeZuSB9ASZg=Q^l41T0p=AqNn3ME#Ue8MxKvuJg02p6q5sJCr`fS* zVqpWJN`y-vKfO%;`WkuyKJUDVyo?GGAGM0bNa+>p1YG%^+JD$XybLsr?09yTd!5YE zR~s3+P}cRpy4wccc;|z0Gni2=i-`BTm)ak@0f8b|kTpSP91xU7*!*kQX?gH@n2!0> z=s9xEnvf)jT0 z?!Hg>Kl|$L$DTmZsRoqVXlgk^XTh41g9OKY4m8UFro^_Wt8k7NZ9YvAH-0Eo#42;W zd{s>xBtCp^TjDvI0{tCXR({|Jw;f}=p{_r!NyF&{Bx<9PTE7$aI8N#$q>qI4qALy_ z`mMsvH6YB44wT^kcaLq;;R|0*sDNhjA$b-naz1UA7r0BRkr`?>HOY_0EZ-V;_o@8r zh*O{)tQZw}WUK_&eJ2*yY>Oz#j$M!ZHn`jnt&b1(w3vCmE;{{QV%18x=?2Xdj95-0 zvh$VDg1Uo14o)z9H^3}5dc5yOSKTv<;hh~6sQWy&DJL!+zwNLo{LNk2_l6s7QFTKB znE=roEk0JHAYq_Pb7*u~f156`tg zZJ8ShDhBu0+=YB2MXp;1TU)F{`JDV{S@w+B6+^2P{U`3n0ogPw$;a-89d|dp<7fHq z{gp>GP#O22VQ&H#fa)nHequ3K6KqT!sS@_u(elQH^sY2K{?xf+P&AH;JapeFHe zLI+`ENWJgdciwXVy362nD zT=;aIy>ED71%!TZ>Et!tPzoIY?AWyECFkIO*HFF#qJ(vjTZfyF?wH;jjaPeX1-YLk z2l@_$gAr6&dw)QRj_aWWX2)zfFj$g#v){M>c;Q)$1ZNF4pH2xC=e+l*CPZQ=;b@}P z9Jaq~WTHOvT;W(>(qHCIg(WwZ89~q9Teu=&T4d6^p{0!Ndg{Eys$f*zqhouIK{`V= zD8bMkr+iC(bPsX`2{%jS>ifWMdYC;Q7i@$*0%0i-b!(M*)J3`^_RTJRi_%P> zJ*7rnaQK?RcGh+q++@jCOsA>_Ba_ z>HGHeOwYGM?S%tImLR$=W%i?^ZR-km&cq$#2>Q0DQ)7VniJoLa2#9dt8LAb1c&_=& zW4V`Fm#A0ZQPETw=E1o0b=h9gVY9dWN1I;>GUCpQYZ{JE4_`PC>G$gT`II3QDr(_* z6Njq1QEGDEO7A?3H#=H38I`qf8z`%4 zQ;uN#XzREU<{$6hH*%Bx?~`j=2Wxa#KUZ(7*gSMJro?wvSn(}oZlgDi!xp%p3sI*x z8Jd)2f)HN43->;TP(q653^5B_C(108H9K&updfn2jgm!o`8!`LLR0&p+_^_S{Y{GQ zo(D-=xMj6Wt!OkeEP~awPy?$6cT?QC6_F5d^LG>ZAtdf(toY|;M^Gs+hI9T9Zq#DT zerfT$bY)Y>Tx=7HLYs|K0;n+_2!hZZFgG5nE5ix>of#lV(0-SKJoy=@8eg6KD5q6{ zH>#|}xq0!Lb3D_s6XjN0ja4eq%|mfwJ?Nh73*j8N4Md;3Sqp8aRiK9&#kesM8OyqB z&6?0#F5TbhOiM$3C?kHV3ELTtPjZ>hI-z_M1?_9XInWLV7YPw@x)tET=LRmyqE?YS~uE`k+f}r~`_&aa+{l1nMns z$7^iP=f2yRrX`79<{hBkztR4B95ekM*|&r$5G$+$8utyM1)>I{8Clk=$JOP zyK>s?(~u8EHV!q(LcyKNB~=0qm{35_n@V3d`BX`Y4Xp) z)kZiw1p6Tj$5jYs{cvkn7^#G@P=0hRI{f6dW6m^cx7CoG113y@b4*^oMS}1s@b&Ir zSZdhwV~5do z%>NLmv((#hVs>f-8#i1TbmkDXf4!s$Qmo?JsZew(l>Ly*LvY(eVmcwW5eWi_BitA= zwFa`LKaxQGEkzZ?6SEMk5Z^y$Fz2IF?Wbb9mCkTe{@gG{ch!O3aD`mBUx{ObN_T&5 zR1PqM>?L+gJg0DO(Fhn0hjJTNC=1q(+cP5T1%pf^G?Gs@a4x0^T2bZ4&WN!t-LcULTz| zi1MY5;6?aR`GEwUw-W>*F&Be=TCtOM z%bPM8CRonW2+}8pgqo&&Ru3VNCv$*s!5kgmu?HkkeKo<=!yOYM_KKl_uE~1dVZkdt zgzD3~(FI;$sn{H)q3xZbV`ktxiQy~0#|PQS)QCY6|A?&YT=d>aT&t|2@L zRXdXK48a__;L&wor12o-Lc3#RnHpiNqIX`@c%5XLgl>6&a~W7GSO@UtlH-_-Zjw7E zo+@BX^a3?>%d<-5-!ey*r`KiF7pw9T_MYiWC^!ox>`7`N1$fa2u2KWyMI;%%cMydsTzxJg42WdcOhVa`&G!y+h z4~L5=|AnzIPHM*lO~Ny-TW>u$8-Ju3+=gWU!N{kEZ-qy_wP;Kv4RnFESkZY22AF-0 z9b7ZGW(%n@;RPo2#4`nxmxWPwg2{k<%8rR74Q$pvxS2(gN24KtB}m&shxm>OPyNZR zHfVzd9~~}Smpa~){mQV0v^sBe)kZ?Vd?Gbo>dsqZ(-olEfR89Iu?HElM|ZIRJ=mr> zSSBdILzhRLx}H3!8^jGH1lE4F0`qA*CU|Qwx{CO*k46xHAyO=sZ4^GDLXxv4YsLB~ zZTv)=u_<>F;^oA5U_c@uvv+Rh=k?;-+XzNB49ahncJpb`&xMbUV7@2oBAgOQA8R;U z5IxZ~W3Xs&v-FV09q**iavvmUmZ*TBa*SGOq~;0@&Kup5_8$(j2*2FNHnK0th)0u9$Izw9#J^r?`XXROX z9^SdlaM{$rp#T3MWNKj0)W9IH-pB(M`~On|gQf-sO$`j18W;q3B&P-jO$`iMiDI^? zfk9IPgQf-sO$`j18W=P+FlcIEP|Vc8ps9gDQv-vh1_n(H44N7kG&L}2YGBaRz@Vvt zK~n>RrUnL04Ge<*m#Kk4Qv-vh1_n(H44N7kG&L}2YGBaRz@VvtK~n>RrUnKRrUnL04GfwZ7&J97Xlh{4)WD#rfk9IPgQf-sO$`j18W=P+FzEmMK$`#Ofk8%w z7?!&(*3o|TUotagV5fimZNplJVI=rZ5=NGUUlDWkBjHWdzga7t+3&u$9zC!2`aqGW zEkd6EW7p3QJ1$;pR`G4w({d=W(@)3m=A5eIPP27xmd&F*r^XzQN^-m+y;f6`i9gr6 zaPQr(djAh+TzuOKpFKOY{qw#avkie2M)P0fHFv$)mpLRFkN7`NtEnAKQDBM!{~w}& z*ArpY3Ss1d#4z2NU!LWUhML~J&C4A+QQq*FRlmAzMPgILhcoX+U0b_ab2Ac~f)+Fg z-OW1=I6U;(wysNIb%PQiU4Lo#txsJKS7kWd{rJGYXsA4U(ee$i_pdnh`#{91uG_I0 za+<*_n)*itStbo#J!a}_euiGi8ft&mpuC!){`5EF?V-}3AE73^p_@8oErUY?a~n2n z3eHm-4Bg*obOa*@j_43?Q3&eN&ZN-O@>3ODKgt1qYJ`V7`1VoYX1?YHwN=x zFYDuWbnLMDL@YAA4GS$Rt>;UHmM(5o%=Cg2G$P)`ynj8)PNMNCLnq`utCKQ{{!zKDCP?&&i5d90rFx`I&Pt)sG0SE38DoXnc5m zt;lJCY5`Mi@&SYJ(mj05S@8pM==><0_N2Y+0Uf=bB-Jca0N>6;fvq0kFnF(x`KNK( z-UabTMI)?-N8t(8urwV#>Z6g|h&#`iL#}Rh;h(yPTT%(yO4!rsqQvqiqD%gAqPy`N zIY~X(DV&-K-;A6DPlfMPg`>yK#qIR5lj;*=8GNfhL=bdxYdF{TNPM)De9+{Aj+q1> zH0EGflg*PnUm#w0=i^C)-+93Y0y|A^Dx zw|j^WS4~`Z0{t_GOv8O85D##SnewDeOgprwc&ms^F&sN9jihd#~6nD0#F<+hhy0mh(L#cV{Jog^I%!@MknwE)s@-u%_XR`J>_ zrY+Ef?}C-9TV3qCk`u(QX;G zPJQCjB$dU0gwMlf3X~cTQaxV~lD_mW6hj$y2DE~ilVX1CI7T-7j_r7gUWA8=0scR* z{)oto>0)T6QQqmnU zX2oEDuT`TaRl%opfqA1?v?!y!tZyz3tD%#7>Z8{YJU3O7@xI#GxzB)IsP^sUmv=Fyra@xzXR*|AglnfEbXgEW|=6}NaE(#%w#_w0BPO^fg zPwn@E>7+6{Y~cR@XP7ZlEh$RYP?``e(gq!ZWDrT;A8|yoK1jpuR>5vof)M<{&wnN&+TUF6d7&nlM#Q zeYDJJnw;Srf`t0SLciz0lu2|?@@!H&FkRgU+M2w^C!+Pw+lMA}8B|A0)&3L)f6-y! z=%g}1yJazZSe$v%y2AdpVKiV=Hy+qBdCGhEOl2{vS4Dm1Y?8`|~DXAC;J{%Po3}10BhMkz~jyTmb5a@Ximq$!Px(nV?4XU~t zp6990BOl;=kv5!E=aUo{Lx>}rBy}24M3PVe{DbbAq=8tKqJ8Re$%aGK3yKs@JdmdB+78(+?eA_aLwYLFCAaCZuUgn5qScm4MXHg14! z5cw3~HQKlrvu6-fnBiFTzW|YrByJLlG>midFCaP6%73&L;1}c?*&7_wectZ?%K)31 z2UyHyLs$d31{tD13QNpLv=B_02Q~J;EL#vQn$~$i_}ESYDYE|wmy<|$gyGs>aeUUq zm>S}=SSbH1?;xGDTn7K61j$TziZICGdGogo!n5VkPW}V(kN=QTN;%1LqP}C(Gl#8! ztS}QCj)q=GN`-k+{0E2JtxvS`WKw`Wz=!yc_Fs9=Z(V6G(yB@AJHKtjJ z$tuQ-*tq?%Vw%8&-OQ^`BEa(zvq(&OWZ`IFev{gKNH5P2DwGJ?OK7Ls%a)0KF`!e~ z4Gy>)u%tkBrI>Pp3=W^g$^EN3bmWhcLOZWsBN^HPQX=-gkPgPMO~~H>i!G5Q`;y)K z&bdd=`}^blyl-#K+}C|w=X}rie9t-82(_-6G3nQ!>7MA#kdM9(r)>xj$-Qry&vJ!7 zi@-f$&5%jkNk0mXl#`Z3e4JWT)NW@03<1~o2cE&6@w~!fB`&s8H;)S)X#1oMoYxV@ zJ`1>XKwQBR{>cIHS_2uKI&#H0;bfo$-+dB;(^SY&XAp&1gcjHZc(}Kg;JSo#^-wnW z=BB?!pZOmPrXbwclKo2LO+$M@dvK6JbFbl>JT!(saIyUjZPfH*qW}vnd)wnP>F5J* zgK;5EUF3Zj4)>=p)&}y%LD((QRBq4aISRI6KA!-P>#CapaZ@xg3qXth$0VI{__d>L^MZQ_;&=&g5evI zsMl};_-;OCQMBD2o6T7J_h@iLQ%3}+Ni4!OpUD-NuwDeSV0j`}|9`9*)=Hgqq;AXJ zP#!7V@7!l$L4)!Ce1rBsw1>Qn%!|?+fGku?u7W`J9oXidv*>)H*xtc&1TX1Q6M2b7 z8cS#DwX?9@qLczQ&hjQ++Wwyla0ttR@5YiwT^Z00 z_$Y047}@9mO`n7f^P<8odV3uJMm2P+=b@|8yk8V^q_xG?hU?H+0IRjv;@H!n51+&3 zXzNyMT9eP+#4k?Vko;ZP+0gE5u`d_!NrcX$%fScbiFIl))}GR+rs))z3ZBCMCls(Y zzTesijrMjsBX|OW4Hr0>$T{eM0^yOQmLGC87>b`P(^ns%Nqo3K)_I z&k%{g3iKiMFjx4iug!nQ9NH-0kvukyyrK<&&+s}riA}0f3q+(y^tn#zfT>_PebRg% z_%F-PKqFM8^oJIy(O*NGXtE-NNP0ujBKT<#yF!=Cpe0l84uU~od|SUr<$bXdIEo%P z%EUZ*CYl1Hd)ax*?y7(CjoQ_Owp064miAkVpq|4+-9CpRcd|tM7pgol1{>8h6&f zO+3KBQrZ1!5_RPb)%z8gKZ~dhgjWPxLAZi1pbpgoSr@?#>7_$AD(l~xwu_;-`Sjux&{WUp}Bv0{k=Mc6x(FDWcxo z|JfbBqZtI)jSjEB0FyZkm>y>pE((ew;OBB4Aj!m)S(8c8yM;@#shDbXEx^IdiKlqswebbgHwt; zyOvxRM*Rct43^V@uTJnjzX0Z;3jh00{6LC~za@q*a6r5pg3@N14dS_4rZOraIZeyA z;uWMY=0?K?($N~({-rP?S`%a|Fcw~ACji}OzK8BG8svTR#Ma)0yuuAg*dKKMU+_g6 zV-PbZjo{nEg7_1`qD1j@G+U2~W$8TSUi}`tYt8Xs`Y&;je~wmko?uC9PYC}h{-%C{ zc8yU3gFtc;GzFdt(kn=apGxawU2FUfPSD+JNWVP*X4|L<)4&X7mPBzO)0-_fb>zeZ zQvV#hD}40N!vBZN=FE$Koz8E!(_kkbLH9yJg0BT|bMSu<%wMZ3Pz~RFBADhyXu9a$ zaA4GWcsKOD^Zj?tIgBS^&d!Il!?d)1hlL@YIs= zT*&M&-=kp-bsV4_Lgi(n+r(K4NMvffC&{`!^{Nbwv>yzbbe8po8aYUD3|p0 zu~E8P+26%y%Gx3(9}svWqhPacz@p?4)&m)kjl&(ppn!Y8*mEnFzGwg|}GdM^jo? z7#{upW}Di*^2aGFrwsMJEi=veHgc&**i>2zFkC5w8e zcvrk^xR+7T8u!q(k_5E_-Q1tiIRk=&!~3{`)S6R&&nX_xurd=4c8)WgEN)lT`JzG0 zBB0cYw}N(N%%DI62N5vSc{MSmLMo*2cIx^_wFhYp)y?C^YSQEa+-?gf1_Uc~*ha!{ z<1uTyocRN}Ey2yJq$?F=4$!fh_qmXj{S9aVZ<#3-suaqivg`YPshRF>)4#R9^sBE6 z4Vbdzc1UFiR_y)NTe}0kMZ}#tBJ^ntPH=PSUo~6YXf?#jR!KT{5Zee)Z>u_&h=}XE zPknR^Pi0uys)EC!+vcr>)#S}+ITzu)DRWkG!@0`|ZEnMkp09qygGb}_Z$z-?}~>wp-!Q3Vw!RP=0%HL;T?( zr|#41>{RL41sV-+jGiiEZRW%s#P!*XJM}LotDF94U7G4qIw0NxTHz&(n!FG$#17@p ze(n7YFaO?;;x!Y=w; z&lNiZp+BS~7EU+j`#Bd5G@P&&8pZTBoB*rEO%RFq36j)vUB03d=F+o0mCLNjIJPRi zt%7<&fglDa9nMPafELvFq{htaGOfsm> zN_j}}dcvte@1$-z@#n!YVz%`%oGH$mXietkT9a*=gI8L8wD{{#rWVUjRfNx+D&Eo< z)*A3*q+!P8?9I+7S+Me6%s<+87U`7!kuzUM}G&sx$@n^mqeZ<4&5_`4lTg;DhR^B#7A1(_e8 z+R=5S%J;X^`&G2nfoXt^@+@)DCz(8sHQABt{?%oE!YnOqBUH=VPukB*1;gZQSNS|_ z*?o1Lo%3lH>Kx!;xPfOB;9wkP6aT&uTc5UTqh4#JM+O9@qPg_BD6C`-T_Fg$dS2P{ z20Oc2~XZbX@J2TV%wE-1WR2eF-Y2pc8Z2GVkxwuUu%c2>t~SJ+tIMX zBEU#l5>FLQ2e#=MY|~%KM1$e82hP~)Rp9&X(~ zTyj=fF?`^3MT#XSdUwsMb_V|ymwyfpYL3dZ*k_(rdM-UTBxo6#V`mM)FltuB5a0Hiy_17=oG?70aTQ$#V_~?3C(E3*iKRI>jSKBZnVRvfu z1p=0fPt6l!5!VY3c0>i2JInE}bF0&SygV)yN<1+1LE;z7{na(|=i+ORYhkj@;ReJSeK!PC; zr*sx2+WAMJG)YZj@?yF62lY(_OYcusiuEeHlK<|h?-#lZ@v0CO$oHCE^;FZ(+u%C< zyqSodaMogQ*I?Ku19O%hK3fg(C0tEAC)rRXEyD#K8^v;tWr6fq`lw$}+VJ9Xwz~-< zfWrgTh!!Xq`B1*iOk6?aiS7F;l@_$TyTHEyJb(U!Q}6?ZaFkin_~EX7gTIQ^rNkMIA=vV3&=A=Bbk_4{7`G4WK-3|<)Gjr zn#Hj_EqHfnFJ^&>CqXb%H{IQ8cuRM`jS8kMP!mb%~!(5itUJ9na)Fz8x!%l}HaFe>rU18O~H4gaDOo;BRFHpsAph|C5P5$JFeC{*x%n_COEnt z%>nH&^H7V{qOzF;k_h}5o<`nk;+O_w!Ubz)=I)MT4YA$@p?mI0LgtJr&4DfOr9}T# zIbmN%N|l}}yp70f@L~K_l^eogdzcp_Fzil17TENQtN?n!e@CN)_IS^!Ax8ZrWs}bn z$gSy;063c(lUF5w9@p7@RM695C8+SYwtfHH$BLlpZ45(ComQCI($N1i6M;=s#Kfvj z(*#2P+7UTUDwkjD%j?;_q$j1ZxK={@Kz!D&W&g19x}bt%SvZ}b0y+h)C*;@!q9qy~ z?lsQ_DD^SSx}V1Vco0AF)U$f@@viZ2kR=40wbvI43sl8x&sMn#cVCke5g;G4B13pE zu-zAskMoRDf#f{-c5CG(mCQs#m253QFw2)TwsZv&5($w;{Wo6Dq@}iAT8^VX>WiAj zzDg+!1HUUhG?A#Of+lW%8ttg|^2%PL;`zK=Gs!>aqOly$as~**8aq_no1gx;_R%d~ zd7X{#JTMZzER=i*-bnw82;u52;QsiE?iRf5U7~m{8AAOEp>~<_!V&z}N4&1^4(h$v zGxjSc+qQJb5-7H*L z74*C9T5wQDA)OqlRP{PM$?%ArmHAI|Sn$Z*DuvY53{mBykTszt4)%LsEcGi|f#{^T zKgmxXopUxNguX3ki+Q|jnL5a0@Kc&BYABQ{au5i*vQNc_1*P@0CG59-HqTEr`)c;U z=BbZ<94~VXm+0tC-Ta>;^EG^dVf`)6t6ITia>f2tAaiYuvk#TpcwAH7Ki4H1;w7}V z)X5QBr|6>q7B-XNSNJM&r()pG+iqsUkq^Rd){%nMw`Lid-tjgF&|I%Sehnpuzn)D; zJCF>Jnw*?Xtkwc&ICVgLt)IPXeRI#)43rp>0?d-HFsj2$F#R@S7x~|*O5D1L=^Fea z&e^TrNZw!Sz~@1q!l^PBrktal8o!pt9z!xJ1|UVjMTYWvY z9paT7sl??@Ig3wgPE2u%!Sho>79J=DeP{*`mkm$Wu4N=4a220lu-;nNFs9Q!Q!&{V$dmcXF=tm31_7qAa`4ck z31s|9?Z^J^y_2~7Whtpyi)$wc+G!mTAO%H97Ez)~{L6-CYs3Jdm3O^3@l+NfQcu^{ zPAn)Q3YGx-a797G(ovqsQdfrkfF`wz zkPTfE2KFEC%Nkt&G4Rw-(?7xY*8n`BLWm(#@Ew??27|K;2reLh-%)|`8Yk0DTWw|o z!Z)2K9pSgJc_Z=cdzCWn-ACr!9Guj~aNaCZXF;$3>BL+o;!3sTmw0)%7)bNlhFZ7( zQlh!|IxfGN0r9ifisv;kv-Oj;jodb|sxJeNZ_fOqbmbqm0KXasoCm1YaA>fLrcRp@_fHNhrdR}vK2_P^FI)0ug_xK`Mb)-ZSMRQ znPt}A)}F6kTU`HtryWiw@wS927Z3qTB^E>bt$pibomS2-vppQb(x`_b4Md(qUjcyc zu9|5_jkCL}ZjYq{I{H6f10AFon;Lw1<4pzMlc>GLwTensZITV=CC;v7o?Sy^s-uS= z8$MZ$SXD9d+E8tTG^k3Mv56dYd|n))_BA8;bDr3tixN}3hx_s`W)z0*NxJN&#b*|> z&RY5L1Nr`0If9X5-hJ+(2Pb zNlU~GNRi9G*vmY-mAF0wHcd8;m8Nl?xZ8U`r>8eVy1n(hBtV8Jg2W*~elLlB##`S- zd6zG~s{1p}<3|SLXX;zAuya72plAbex94(8{nwPsLy^g;`|S*B0~e>Ky^<4E*&y-o z1ToL_5LhPEbaZ4E|NJ_ycyFpjB#11Dy9@ldd=@Zms1Q6-Z2!K_?&NRJY{voPf2$08 zzilISewso4i)o4~t@<3Bj&e<|~a;$!|e8+ImTh>S`{~i7Xj4{nGico;iM?YQ8{2*d@B&yp4b8nv{WfNn!%{?82E6h zxa7oKF5^%d*DG^|zHtF-Oh1|l2pc$#iC?6^eARlCp$8-57dN*F`}i!#BUNnc(; zCzTV{MajEfQ8<+$%*=;yvIL5|vZSLNQUB`iA>VbYfEKa}SymF?)O(Fz5HE*bKn6c#Cc4Ak(&Z>J_gCJ{KpGu6}YZD(u*5!zA5f=1frQP9&5t$0SnlU ztQUIIVp)AQL$|6NZ_O_tDj#<=O`3_VWY7Wz9tPcPDVR(%$*8Mzn^@j1758^F2jP2w zjmNC{R!4pZX&8%tR=mnq$ux}uGR~;|K;uk-wq4(fsSLCNl+n)qQeFmt&SrMHW(OU%9J%Jh^czo6i>9l5~tdUET_aYN5rQ#P)l zrWyDCVlqVlwhU^lt&*@c&ZK23oE8dgFG8ulvODM_*Dmd^fBIJJ1LXTWXi5yB92^9} zIRQ4e#RVT0wXUrX1kx}iusc#?DeC~OaE$Azq)#xg)&4}&G(j;p39$_7_%e*GKz z!`##vXB1&64>6ukRw9MJYlK=o69gcnt+Q3+?MKZuP~!D}I9~ zotyH*m(<_8Afb9%0#IycP%Rh^wEPUjQB8}Q2h)FFup+>egB>_XSZKJ&#)F!qL`Y(9 z%PcHFJUdP1Rw1Ka6!SD2bQNgds8b*sp?Uy5OYOINcKZp#-tl80nGbB<90An_0o4Jv z8`p=cc~B!9?ykgLtMKJL6X!j{Rj*Vc!7&>XAAi`snoQ35#q(_3JEwB>!FACCZy5o6nBO{>m_Jdq8;b6oRnt6ei zJBY46hDE(8{yu+J{$`MdF`}A)SZXC$+7=`Kew*QggnNMcXr|J0NUNNuSI;u4r=8UjP=hP_vv*U8Jkgm&+fC$O@rz-2nS#z zE5azd?Di8IrKqJJD7?HSc43q*zt0^|&4SD$bXxXnj41`|+ zeB%r(-%?fCa#$@HY-tIZoYnGb1MTc|M?~E-b=c_FQ{dql%^Bt;!{a zF_|^jTJ>de?q(!{m!ga@apa!=uhSnSDG=NYBkgPwz#>CXDz|JlfG>Iu^|0ySM0sLT zvlumYkMlnRDc_S6YX_}X5Gpy0!Q>3K%6upq#?R5;Z~#h$rEqAjZ3bNb{G2c<{>-i zk%W&LIbacjQZo_E>(Foctz7&>Lc29T0TK)3X*1$4+vOy9GVF7&#!A9Jhgb;#AkQ0D z@B`L~1tASj6{K!2UgoC_7KZOhdi(P}rP@h#vZ#7pdEmqk0g?q=x(03S4SG`>Cwl3&p30O@zeo+F5lY=%K zT|F&+5Mi;*xVF_T^u%DViasVNz#*Vp12k}1k+h~JA!;~+T${@uh>uNi@YD0HdxZ*6qAbxk z7Hz-5@RJ+s?dyEQ{evv3_l$_TTc#r{0KWY6X>T($;mvB3Tx?_sTvH=RMK*Zu7L4Pw z8#u%VC4#p6p3Kq?v)fF;SQqW_GvJWmdx7B41nIaE@izcDFiPlq<^;0c*9NT52sG1k z{Pe2Aqg0;Q&VtjZ%Tl**v5tg<-E;F2zcEdxXNoEj@IExec>b4?j0NBhHICPUs-||# zQ`aDMQs)@)xxMuTjvwi1Fz&1i_|gZ`Q5lD5k|!O&zp)|Pm&VJl;&k}-RT_28qEyEH zsn+_#psJOeQ}SjLD_GQ7SS!EJS5O!)42111TO>>#PeUjI+^cki-PcTB2pSO9()jH_ zeXsa@yq@3ZSTB>IXbIy+*QAl$bx{_^!>7Ph?2LpKgS4p9LVyd9MAle8hA`GuSC zdS2#S*sM1D^5pln#yNKbXgm+0226Q9dxmr$cAxT>mm?hTc|2yVHX@5JS;QYDik;LR z4Al2JU#-lRpW0xwO^S5?%^{<*K*r{cv`PB77shOqq{jJATeA$QBIe%D1CTqMjvHOW z8zfbrs|Ps0s=t=|+a+w_w9k{WzC6@wy6>s9gXoS+$E>86IfYFw%0BE5mJ+dIX~Niz z0H?SpXx}-2dD#FU6@q6isT@Yxg4(jMHCWSc-pKKsq>Q8@a5w0ZOJwzxDG4`flJ1Fv zmXSmQ_8tCl=tt0u2mn+21P&U%2@>tuVn*He!H`=W&`+E5_0u!;rU=b{6p3{}T6nX% zsQ3ee>l$qtX@C7tmskbwfG-zE=|Veg%_|Np;1Q&)7$uO7?ShdtVHHXmYEI3FV;lcF$ca1*FJeX{3$UjBOzJzS=LJCyTHDD;HFzN$@aTS zBhU7LVT-m&VHT2g(VywE$KR}ptBH~sj5|)>o-185jP?J!Qp(wizfjuH6*@Y43DGixO z67rU*6wM%h8!x!WG}n|#91$9Va`Lt8X-+Z&;*l>uICt;gw!l(x> zBfB;)yt880?omwyT86Re>1+#$7IqYcX)!?RkygOX$=yGJynOAJ%z;yt4WFR{!&7M^ zm;iqpw>sJ)FbUX(;p7Ld%SD&~z%n0!Wjini#7fX`AF}%LbQtzgtUL`FAA^y>=EYCu zFZo;nN#Zu2+FDD@c$?y=UY8+HK-%26p^Xd%$bED=jb&{$+v_vv91hqf-_)14g*Xr< zF}!YuIb~H#M!t6HdhSwud1^*NYvNKisiUqkQjZEa%;Tm>Ck7?hEzItF=PfsZh+{ty z_0H1u!}~O1A=TBLK%Nj6%wM_wFqa=^z#r)K>nM7=!MgXV2~%LtPR0U!qBRrZ(dwDR z$ev#on~)j-0!K+4!wt*BM%Dg&F;{%q{&!+fc^Ki^Z%X3Y2QTFE(-o6vs?>UWwT5bT zMD7vi?Lr1*eMX`=EOk@tyt^#_U)3)Y>ZRmKf)J1cM>ZTbmw$@@E2(PhiwqL+XqZC*v^aB~ z2E?;II>s~CNTRpX>4RiAb%*ddw2f?E3-N2qm;;7=0Z(pqC9zW(`#=B%VW@P|u)$n; z;nS|K)5UrH^kf<2Qme2G`iFEw3#_kT<<_8i6NtIaXx|V2X8=2aAqz8X z0q@e(?VofN#r52fBg%3Pe*+ks4Apizh(x&{Qwaf7M@2;^m&?CyO-{4vzcGVh?_tbt zcR0o6yo7G`6zHIQ>7}?SNZY1g49&j-pv)iyNC;d9W*^!F7za{{k z!TB21SpXfMrLghO9=sC>4H_mj1)4o4TM^RdNnc=sUwv14zMlH{+HDz|53xAVadw_e z;=&HczktcSd@-arZVkXQa$f^ZBqqir!+uAJ1)~I8LPbWd%C=M_EMBb`*_5!FDy<`7 zDEhmjV4e1yQzK0=^9<`>URLw6*tQTvil87Rc2QUo^>%E>$!g=AzIr(Arn1Ie5ZDw} z05he76bOJ`&^EOxp0#+UCEGGTiJFW}$oZEgrc5BLBd)w(kWV?7@+SE5?BJzpoMeG^ z=mYH-c;t#MX3M6D={e%_A@^?ppi*==goe%?c?lz+$lz7C5LOOrfEe_ zOXFl~+3!k)^g+N=ZjVhe)`B;$^5Tytk*Ok6QrN8p$2Mk^OaS-bvXj#$knPPJ$)Zda zOG_qAe*%%ab7@Nb6h#vDcDK^0wxD`LRO#RS;zANy*>!4;vF16V0R&r*Y1uXHvmRnL zhg9J_x_j|;V&XDd1Gr5?xV;{n=Shy3E_B(QGT=WToHsK!W0&OJwYCR0cty2=cE41n zuV#K#X#VS&k(ojl$)aRwQS^oGJ{Zi)G&e_tZie++;)Lt<3lf_F-=)k*VNT{D2u?@xQr7lVXiU89Fji28^EgoEB+& z7$bIEkbodu8GVYCXC#y6s6YZMYxp9S=HlnrnIEpzp<@TyoVrY3L$sY{ERii%%M?O8b(%)9k zqb0zaoC$JcaLs(kF1NZ4IT!=Hh{ngl;y`6SeFTu5E~uJ9SMe9AKA%Y9^Vj+kO)5Q;T5}Cg`BG0WSb}FukyvpQt>Y~Dj$Z!nx zo`&4pP*)MPEW`AlBDsZ3)>j!?)EiAmhEki`Bop1Vs?W!s8cXfzr24uZ`GcBg`nYuF zGvElUT~K*l=RxNqk(#vrfPCmQ%8_FvsY=n%Z4zYwgA=`MBG5vP3oNg?G}e3J93tk8 z*vP)(&-u!!g<&_1JRkZqq_~WN$P*tQbri3&yR#jl4@NeK>Y%XDxHq$-y~xA=a=1cXFm`0VH1Fm}h6-Vd#^0K?IB^BrnsIU7fkSQXb zVo>4`QCEnGXk4^)+yxxt2a(Awd2cM44p$_uPbXsDi$&aa6)%UP-S46s6@a$4SPl0) z&#xu|G=m0`l}Ga?U>GTifTRro^~mjEo=uf;hhe|QV8~5hlY~RRyh)L4tRwfrCW7!& zC6TT^H-{6GWpo?V8BVwXA4n{C{9_BD7Z<*RE|@TDO~HXSWc-%|jW-c|y89RD?*46q zBoHB%vCOtJrwL~(=9CPd1HIenHkw&bCSGsCgBTC+scq2J;^Q7{A!;o_(sJwyRx5bK z+Q{+o10Z2i115quB67uN=~gEK!U32J)st%uSDw2~I4_h*jO**Y(Yi`zr|kRfsr<_< z)_j@8MS;n%Sm>1SHh~(OA|y+qSiBs1_HCsxJEp-Y$^yZ^P$j@Mnd$5YZO>f$1_S;% z2EQ>0+PKrJe7(K2>}^vGIQNpMK%2oeGl}e!ehD!q&krW7M5N&u%&F5-;wI*7P@zOx z;>K_~bAk&?w%3y@#Rb(1{zH%n{oz46@@Fm`V$sJtHt;9OPS`Gd`* z>#2D2#|~AojVMx=Xb_XCq=1ZovIe~RVd0wKtRErn@<|0_oQ>mzP-~XLq zz1jTB=!$yr63vYPcj5Kj;)XMzh3{aOC3peU!(jSro`?S7~(p=4RkF5v2%3ygeXuz-_rOlU@IK1 z{UPK^vPW5cFYi1!sYyI~Clbn2%idMqB$$_Cp+&~YS;@^eJ`2{fs2?`kIOd-Jp)t!fui=YBj( zqsEHYljA?oGGA+xmCepe!|nfq&}PVP-!V8D)@|(Dx~SNUbN^aSD+NN)1ML5CxAghQbHEK_1fWlH<4uDnncRffU9bfMX+bH*zVunLhtyYS~AymY09BeAXj^3Y--}DF@>NPfC~gY9*YQM(11(lsK_7UodRH!-hFNu(*$8G|ikd$|sSGY3eV0Y{&- zj4V8l$f_`wq|nV^V^ zjN4ZhU3(63bacELDgzpXpT#Z{Ua)eq-U8AR5+T3!*DV$c2?;Y?n1VA?8mm=w%n50T@8Dc)qVA1YgpD2$48wYp`K9l) zpR#-&@5VqQlQOKUQmSoys8SxUN->z>j7?&1uqzzOG|C%h{Fj(6hbUg_&fu?SNA*|< zelAI6oXofmn#+h{4Y1M(fc^@CUl77P_v_6o?H-QGTWZkLe-Qq*Mlk z`U&9_w;pmgv!S*Z`Df25ZIu{WeY%T+o?QIaXLY#=EOa=GkI+2^3c)f$Pa8H_lqTHu zXB?L!7QCgV)R8Yh1>`IMoXoAzdAPqku-__M#Gs#I=uL`WwiSsqCt`41*7_vie!=h8CkMWT#eD{ zQ#az%H!ZmK*c?V!wxavL%8)ZPH`2O|Ar0f~c`mt;ShcgLN zB{fuP>Ct80wnA3WA+HAzdk^d7C`Ki-oUO)`f3YB7xG|Sa&S8_mPn{3com{$S%gv;0 z)zse6jNPcW+fGJ{=3GdVEMH=~5|Cr8CPQk>+O1{pOh)lpELEAbu1{B)g1&N?nbVg+ z8hh|i*)d0t1ZZXQte+(V>OCuJ*VQAmB5pbA2l>^}XC=cXvBcB06RaUbds+ng1C; zT+6;BYLY3^4=S30y)!=@1h=e$v!LP{r)t70_2tcxt1j4j;gbeLs%5ogM`&<-Q%tu& zy!hTYXd%3=P2725`lau4wB3E{(Mp>+vvxZs9g!dJLV2Kk!g=sM+QfKsxYfyo&+Wq7 zrl<~sJXJCa{^que_)FnQ0F__O&aY#VQy4!De_#C|Ny)ekEZ;!7P*9Mf4(k$jR{R? zVbF!jq@ftQ&mPj;!}7cVeTNvIQ37fNP(|!@F-I3Tj+_iylmzic6*&uw)GOya`^ZXEs*^1`yh}XL$-92$ywLrC<9)>q#Jp(XhrYzvG?Wz4}-40jTJr zsks>jJ09`wEdHHPVB!JF#KGbW>MLwo?vv{~qPOFQ<9q1UHZ z#my&_)S$;ID=A0gQ+riq#XNg@z8v*|AzbS&czcmf-XCDr4#w#l#ms|-qss=>ty`yo zq@#NQ-eUP@sgRs8>94h+?y+;xz^0=!LaCg<)@cw1ejjcLFHlA$y;Twu23mH2c1ON1 ze6MIOJgUx+bQ?=uu%6BM@Lmk`bQ4}9RE?1xJ@mA4{Ca22BtNtE3xz^dgZEE$0|BAZ zW3DoND6-^Cn1{|x+(4*iqM!_r!eMNbBjc4-iQ`Hr58Yu@PkoA~-Ut;d{qHwd*~p)V zc=Bnq{CAE>46^BkXVUEYf)n$>BgLYZRuJ#8Arf~J_Utky?`t{)RsQ?Vk*h+e* z1g>JIyDsMh*M38XiQr*bMA8);k9qRxLWG}%ASaU+hvrD6LIDD(i>UMp>VP`L73bwN z=X_#51}tg)_|42K3W+R@d@F&IRq*3@!h9psA<#l#t_yH*u?VE9IRNc9-asw$<>i~6 z>jGe&AlVpVjosn7plWCcGb>i-bo@a8p^lF&PYJU7*IBDsarUVOUyn+r2Tk5 zI2nl!#3-Dn%f#{vlr=+vDvA;@hZz{4SG=X9O?Y9xJQoJvo@+mk9re>nuwbccn97%o zd44|ykBcWy&A0~H_Em9#*MqsMOaPZfVUIGX1C;9ianh&Sc(;b6Yz!+cmIZ@m7d0U( zb(qaCJ5R=Mmmf?0-n#NEkr0Y2R+{u#D$xQ?vzAPVpQ&9n)$mbU=nM4;WMtbng_aOw zi*M=Qq-GH#Q|$bF3XXfLYa;WZMNzon2mBUQ+;VwTx;PK4$@o*^g11{hd@508be@yY zQ^QlBz85}PWMvll8g6XNlB*7ZCb{<#DQ0emnKD6rwZ%HDT+`a78EB@>i$N( zGG5d2vxvS|T{&&q#KyDqm*T&)#QYJaJ`lM~pegSJlDCN)$EeO_JSr4x_3wFpjEQEb zqqbyM$913Ts*-wxd709SA1Nl+2~wQB>*IXXIq}wHRnu-< zS8am{6}IC@T|!!uI4Y_DkEthLNK)yhJa`|#)c;W%Mr&(h12Dy1fB%6xtzywvdNcUeNgYNxj+gfQ4$ z*luJg>YvA{-mg-5aEl(@ABudOW;fIbgTKUKGe~RIbsXqBgVAi^eo+UL{n@UhamKAE zyzRh>P5aREmg4OD!*k>buyK`xeT8MBabGmRuZSSfF*=j3X@a4Y(JHzhJF3zOqyZR3 z=+0fl1t;U$3;OG#bEc^hW5OwiE~*KJoPRbv{k_O<#KdULo$bq$fZbt@6?y?qBgBDZ z@8BqCJo1yg^#)203Ysz(ccvTIYmk-?Sa~Z{lJC()ZlsBDSHg(LuLXvM{o~ZhjchW| zL9n|6#0Rf%XuX}!CS=+IGhIn&{}U->;EX45ZdJt6u@Oe6i#pB3f!{YFl;eY>Fo}`S zmFpSYYCkS#iZayu(|T?!MWt4G74!Nf7~K3gm}=tYnW^lxHGPvj(c9*iyo(mcX)}xy zwXX`Mc31|l%z~ti$Aq+)hAe3<;*Qpb*V<>DU)fDWu zGT?h1w6eYw7mRyQ0c~Gc2BJqXI^jk#Ed1)tX6(U%40j9ObG(;?7PihzAg|VvZ%-)D z6JU_UrFqnNMPEBL7(Zzv3REB7LAbgkR`^<}w`KJKt!iWM5xZ-^R5A(e*;|h9(UWNdvb-74q~>fGiX$ zff5F?+xwpePt8)18Oc&NXGa0?%pvkn@^^R)z3UP=MHWPf7DKmCY)lAss>3dI!0TXQ zM%Es({n;(ylKH?MM>j+ji(JVJ{vKt^JOg_(J~BiXkbFU2fU1$3ivF80Dyox`06ud( zblhr+=?ID1A#Fx>%7_FsU`==Ajp6c(K{mi!F0hEW0lI2F_q};1j=>T^R`w`bA#_Jo z;+zf6)X9?YrO;jUewi)|R|z)2UeLEkJ$o+}l-)QjVO>X5&Rzw{qOVX;Oiy9Ng%}^d zAd@x8Yx9yZYS=Es;nZGTA0fd+j z6{ED+?j~b~1|JLMWrT%O^exI%#bDg15u}92E!ADckmX0Zif6KO)O4I){Od!s8R{zl z%@oDSYBqyVmZF0w-Y&y&5a>V^3czo-iDZ<}z|Z%UQUQwlacsWs z@2gZl!G<6}<<6HSh3Wl;a3?`rg<)XNjTa%*KeS2!ZS2iRA44Sp%SBSKtclLbHQknjs9=V^;!aNj z6zxC@AxNF2O9G&0aaJWNyCMN&}brc|;MPx71tl_P0tVs>?WIu3bxt z6>O0S(1e1?G&Ce2E;~#GTT26b6S25w!>ZWbs-K~|`@JLm4iFHoBC3E?SxXaRWR;H; zFr(MdbpG#<8`_JVaK0eeeoVFy6*H3+;MH& ztweYO44gR!)CK26Ku26NCUXvPco8tGGgvmuzSRSdjY@?=&xO{LanRCm%j_)iDqpP9 z_eXOKOyYWcJ+n74Q5NDU9&iTO@i|; zoPw*OBUr5IGHJf`Q`bpS!0J=iwOvVXs!8yMmq49?%aOsR4p5GD%K_G~Xx+=yn}JVp zyJi!cArl`_;q{gRbz_1r7_R+BS5sPhe^g7;jD|*^zTot&4Cxt+M+IX06vII=EkaYGdxG<^riZft@AE;XvM)RhOErhqlRXXsdlLFz zHA)bfVkVGwHo~j~>{wYGb(7_D)3KL1)bKFJVkgn?jgK3EA?k74I!{$>_@T zbvL=01bekyd{H0MU9j|ie963^FXeC-O~2xw;`SMY&}mMu#D=$@GN;N%MQXU^n*V^9 zIGgcXK}{xc9r*Yhd9v&uZANQ=*B3U>d6=pKp=3gN@Ao+4+YjPqLwYM*q_)JPvH5)P&OT#A8UC%;5v7ID(2hmBRQ?A=OtS%iw^J{%QVEEjzo(vF<;vzfdPZrg(m+ zj&v$teAg*0cuy3O2(rv#nTv;h{R1_L^IX1P&4cF=l5e#`_PAWsmSF{GRU2R=(@4gr zY$l_AVFBC^=G`@7Sx}*=oMgqg9xsu3X_X2DEr@mp#F?=YJ0A`;;T7C_*E5gBOC0d| z^)T+1jD!@mjLUIRc5nHdf62R`Q6&?ix(MvL=~=Zsq-8c%FyVZ7DqWN|XmS>jgwNf* z>?ki4OAy945~{O-&ei-RjRS@*bQICa@Mi6X)_#JFb8pfXq4)hC$&Y_QwpW(_2!_ zE=NjHOjE&;Qs|M`snR~^G_pCGa5hzu6=p$^uUEffe(BDi^u1;P6<9wgPKF*J(Ib!1 zjaWTlRAw!Cm+s8*6%f!DH1yE5d~ow9FI;#Fvi?P-!eqFn2|7gjdf!HX#19Y`eotF11mrN>_TRttXS{*Y0p4L4VPW7KPSIB?_j<+u9_-968ZfZmbktX%`1Zc* zHUcm5!JHUob#KFU7=@69punuEhGneHG!24`pLe$l;0SV!s2l_PPm^&dlXts|dT?JD zWYFNY^bmmVoC2}qek6yj?7nV5eIzSqcmCf>rXHpB0eB4(3N`j`(P?kf5(v#G_dyRf zQ3*^n_4rxBe{7V*l=FnlOdLkOQ7+XN=_FkA_0VSG{(4>aprwGsLBeyzyzfjHZceRJ1x+FVUU!7as zwLd!+%G&?17H2BzJofksmyXg=SSJ%5*OQETi`7?1ocZ^-kQ#foEY&0@L32f+Smi|1 zG1BV94$M-fhf_lT@5d9+5|U?bVMoDjq2Fz`rkD^wQ0suhffG|$6PJy-;`ic7!<)d%5|Ys0EJ!Jb z#L{qbuc2bB=WuPqMbq8Z@UjmTEr$nV59YorP`*1iDId0j?h%49OLCMa0>;c18$XsB z{(C0x4wp0N&rtE(9XJi6!Qx^=@&?>L5%|`4L36uZq;wu!HkO(d(EA@BK-; zKXi_*$zU7;^>6gzD1sM`RSUePLzMy7+1N#h3$j(nbt4H zEq)}``PcJ$|MhZQ`!%m23_|+(?bgVGC5QQ#Upu5I>fD5)!Q%{Qe0qmp30K5OVi)nX zX>A8^2pd^Po*08obEA_Qj6+u>?%kK7E~`aypm`jKU;VMZ_`Sn%xe++tKsAHbe1F7B zR1V(-b&1YUswp&hnsa<+%~8N^yXlDgSX2X&AqNcS-b{~Gj-36;Xr%U%Re(uGF7>Y;XS^k~z_e0N_~u6}2x=_A~-ipi3puSBvxAz}|bcub^qsLLgBK-hweGr!s zOy?B|g9R`Q@jA=GOx<>oF9lH#NQGOxPxPI@d@%qKzc{%kbTA0Rg2aYsLgNaM7>o`7 zyhlF>E;|wzoyp2uBJ*-8MNh(~aE?;zCo%PhiF0)NrMh2Q! zj4J4G*bhxoo`pLM3q^6|2v(heQA6b(#+o*AId14s!R#W40ftYZL8O-4zmEo6Ucmir zhkJ^_Y*y`H0+KoDerPao5DI}-@pAV>cMo-91s_PWY7Xkj5I`uF`Og5y z7v7NRHbU!`n#{JYJyQXvEt`@kW4Ga)>EgT?UAUYu%0$Ke)uYI=S;Qfz4wOr6)63r9 zoKd&qF2o=lc_vVf2A|XUfy+q}+^$Qj0xYzEv5m zG@*;7XS=9xw%Lo0!0!O)d<|{8)G#Hr8Ag|%LhT+YZ!HfX^>iAoPIU@l`hrvF%R|hD z6k&TExjz~@1E_yp#is(m`zIa)F7ub%e2}ghw5-}j zk(O^+n?`{;@NkL8Xkk2w+F4I3hf-JR9*PQo#=b(ajc}yY@=)sSg}~W<*)F+7411O` zFtBPdEu-i&u&9%VBkT;acmLb@?A(`M6R=8HOM<)yS~2=PEn?T|VWFi$;>0={m_Ckql5kB&ZPsTwv!rwlt3dP@Y}D@c7tYsCPk&ay~nry>rnqT1hU_B1XEKg(li3VeqCxDi4O$f1JuOnhmMg1emoDz&8jd z%LmIW%JBFZ^LBKoR$LdpeE5sVa&lFb%oMEho)MBlv0yU0%*t zWz5a!75f{zU+g#dn(_**1gE0Es?W*M$<}jUW{Q*Ry-ZN#!JBp>n_S&T&(A?>v=AjV zC`fMW{h81aW$d_FGAbQsbyjns8MQsbX~fe3uAZmM%s>ZEr!IW0%~=}~F?l@!0uI3U zVkot|ta{-EBclKXC`HdgViltVvNWC6b0AhL5-uJ5s)a|HaA{Do>(>8k?>oGry1sB{ zhQS#lIv}E=21abyQH*tfQ4z5dF&3~!5Uik}G;yvah=PJjq9OuTViK%GR7A#(4MdF% zkRl3Jn!reX-#&MSnD^HE4}MuID=RSEx#ygHcK!CZPobYH1V@DIgVl$f_iF3kHH9Gq z#wCkryO3Jfbo0n5fh{zAGE-S!H>0k|1kJ9$kZ2-X5xIwzt*MN~e7E?U0qR9U?~?Qr z&SGB)NQ`jZ7nlf!O$(?ia=B($$u;hPL043RrC=Rrm0Xb z&G;03@fJgnIZh|mu$?UNc6z?i=_qMBAZ%;<^2NrN0LU+WE8HwrZpQ05#VGQxrJVX` z`Y|~<)wc+70w070yQ|-Bc0E#_90Nf>i4u(9cl8Q=Djqn$OtAG|J&D2|G!fsSEXvbl zFB1|kb&o!0JKmLdPvJ?Rd-t%w@h8?uOgtsiBZUic%NpP%*Ej-vjkJoZnbTfuYP*rL zjz0HdO3G6S8sHHjMhOI$9w~ZM1XkDephBNA7Ohv$PWk}I;g{)>0;S1JeQUaQ(QqgD zcpT*pfXyDpZkMJ9_FXs}i}2GUOOUR}mmO&OkPf!@Cw)ZQ_egTS-VL6DDGc1@FqrqI zdHx7juc!2(YDa+jA&5OMv1bx+Rysnaap@;`S;t5+r2$H3k$f)>+Um(1|Gce%A@v zfld?6XoM&>6GFN30w{{Ky7IUBWR1W$GN=5#s;V;H547b6#!%I5WC34E~tpsX5gL8=h!0dS8|PVod7(L!_<9%4(=(_NR+i_J4DD_$8^xTxuk zZNg6$`8R*F>vrMGn;_>d7{+E1%K6P5b||gi#{ zID}cUS^CP`yPW@&rY|sK6HL~071^e|J&-l2A*5*+sZMpJDGK9(n(D}42sANc^~E&o7;r5PjJ#C_fgSfVX}H#imf zb4C8gl=cp;)m|T{B#}tA{j^PdkSNxsZ0?ZNcXE}aC+Zf1=Hb3hLJi6XxsxAT!buL~~Mxb+ns2Z!G{SK(qW zn_e!-A9nfYtKOssj4&~ZOi<)UcUaXWP~)`=nhS^!qDlEx6LcYC&Y#71z=$wg!`hfe z3%aLZ+Xy1eR8O$J8VYcNGvNc#Ky=JR*yFC=)GJGu`PO9w9X?o|pBMDVn81&=Nrl!& zm^(r}yPbQPQ^)#n8R4-}02xHRXj7Z>xr>U03xYIT%OB>Aa1?dFXcBor>WT1bs=$q! zTlc>eHZ^`!!K#7g8-5b}m=&RM*A)e|6Uz0yUI%5YuIhr~e8;aRxzA_(;&Mm$d{RKS zI{i+H6;cEw37d%poib4js;vAEX9Oi5=>X7a?$oNZWydLkq*Z+vo>j>8jmj#gq=l_J zpBAVw=q5z7;T**DyA3q&jGJbs<~AUu8H5BWs2kn@x!Gjq=ZWZcywA{nCPF0Gs+Q<7Y;v~83`P1q# zLl(btA1_vK`FR_6(_XpHRrvbStvsKp&p!SD4@udgXec#cb)^yw(|(a>q#b+8(8vdF z`)d#M%w9Cy$foWfe20CD^~G6n)tws;u=s7hqm}GJ`OR#JK~n&R@Nf#IK7xHfwgC+i ze4g)@jwWu~ql{OB+eD401JJ0{PjD&ZR{=18xTkB8mr_1Ql{M%~aaP|?r>bHKx+^h; z;H)Blao@Bdn*^5mgbRS$dVcfuC<6A^eEho=RRoF(iKd};&`@ge;oWv*yS-2wo_A*( zHxX!MwPK;Y(C|Iw#CsZfKic7Z*ltB*1kJeGvqk#cbc@t;fNXZ^hk}wDvGM*(EV$HCdImgz#Y{wreiI62B`JZf%>PhisC)@*V(c!=~(LEO@8- zWSYOVQ{x`oLECQoHkg%t+*o|L<)nfnZuEFWc2F1ns=`V*x*B~d)YAKD3itU=N@f`%F402G&k zoG+oy;MhZ^QQyt9k4XEwcVO!49kG=zd5^akpnc)@pTq`fCS_Y6G>xbzGq1o&K$bp` zvn!i|ZlLu`^T$xF&Y6`o1_;LRId#el82DWInjUd@V*Qbn(g-7WrEMj5ABQR5mB{JW zsH`q6E)GTt`Yq?&&M0yfJE|jhk{=`zngr8r!nm|c>sHk43wrD8b`hE@igiH=Mfo`; zWp}nw*?JNc+j~-YbK(xwWb2NsGm1Q=$Ug%zh_XzX7 z3?nlWxs9TQj;4_trSPBpFGmPGsUI!e|5Zw%v(0AJT2IxH#k0uaIQ7gDQUBIV>$=z- zw_dHml!$2{edyFy_>5TdD=wON-#@$>HINaYMSuB~Mq`9TfN)02bs~3;iH1^Xv9P<) zsR=pF&n-L7YoWMOcV{$C%X6if_uIsChrMj-k9g*u9DjHioTLY_S5?0|4>fuiW1uN` zoi=hXWD#aQ{ziauePLcu_Hi5k2}3-tkHPR1^wfkv#7OC&zGiL_-(DCQD@|wcyX2|g z&lF)m5}>6qYbuj8Kc{6nkTV}xpbkTxx~>FXUO0Kk*Tc9SL2=tNE(E+qdVUdWqeEaB zs_H-LcAg)YBg2Vib)Sm*?DX)`ygDto3%p;+vFW!XkfQD!q^~T!*duPRJR(4SB6)Je z$+@f7eksjt^h;WsSDAG(_K@oLJ(!2FuJ)21j-)5|`d zxIJ0)GzA^uSk-nGl#|Xav#0LDy{O{&DKn*QfLp$cYdy%wDfND7P((vNmx@ORvnH)P zWuG##=7vX2cEV!6dE|CpmX`b~OfJ2guv0slG5EY_WY3&o$;+!plAN~`9JQbxSQcs_ z3JsOQ1pq=o^gwUu=M8<#suQY4*ej<8sH2W`a4?@!^-D?dfejsUlNK-i^6^Sy)@T2G zgmzV<8pbK7y9!T9q9srIi8|faJ8zz2bkMIRr8D6Pc$zxcFqdA9e#^7JY#k7mF`o@; z5k&H72lQ@ae`g8LE^ocBo6s=O%;`_*^9vsF0?*0fsGnNITy$IKdo^{IgLzQNy{sDZ zx^Z8;TpQcwHa@Z`Kb_Y9<>Rk2GhgJEel)Fi>nhrPsTYP+s!zq%kM*r@ZCn?(#hoQr zCtJuH*DsGdy?#|q42HJA$Jqu~wRX?|vsbr!^n~srr@I0(T9t_+`8!2?-`9$T9~wIx zj!0~*D5!KQ{W^VF@sT631D~o2?c7hjS??BFSzufIx;*o0!n5@@we^?0#xHnL))0F@ z1EHFB#QbLR?WXE?s<6h(pI&OFRd_{wR;_jalKy!|mQP)>xoJgB8=qI5d|?$+wzd@> zoB%@kBBAVdnnS{@fsLIR`y1&NW7UE+p10oL(^B9Y^oDO^I8ObXfc9jfK&J1B6tJzV zM#;ei2 zY+ylw$jU$R=au30n4BX9JrCNV>OtgeRsIkir!$tETHq*-azs2k=np}bhU z2U6~fz4Cm3+VPYmyr-y*I}rN2g^9+(n|G*+h!D5QE4%PzNYf1-DoGH~@Dn%krhOlM zF>u5Cfl|21Rb!w~_G(8(c$>GT;5j)MG0{cTzd__)nfS5q(jzg$a9uX41=Zc?rV zc9m6}1C*_jG5eah6g~reW`Hmi#RaU^>uo>Tg*V4W9oe%gdirL;zV28AI5vBVcFt2= z@wwJeyf$NgGN`VFl+Xa|4B5izwv0L(lgQF{Nu^UD9m`-j=?I=V-`9Iu8rZK>N>NP7 zmd?GZ>{^C$#^@Tj4HHbNA_;8tOS;5>UuH%rD(_DS3e0Hk`K(pFhj^@s0A)rbB*9ww zxq_Q_=&kV6^?Te($^6ia)Vg__w>CMABjrR%>66@to=Ip+hi0zThpXvMUL31=(qTw_ z`pSKU5B~@fT|xl|E(=g>BgvWRzE|>bMNp4g_2#(m12kTa+PW=Dn`m>BWer_i<-Ta8 zev~C2ClObU94987BrcjDD8l(xu>N?wqRqwodh*7R79W&EHJDyKwMsMpHD(;)yzGHK z0gHK9THc7W)EQSt%@XC?kftFo7&d35X<1^IR@_;57nsHs#@UPjK)f!ZL9w4*O2%e& zV}bEXG<8|NBG8Etju)ua4>KD~u1wTu{#GLeUdb(3Rhv+9!+&9^O|PRYUQvK9%wC<2 zn7mJU&AP2i689s6FA|*ij;pI{h1Px2q=+6}sX}?UC8;&PRa994^MF`t2eup1SKpfy4HV^L0}p+B(3OnTthk2P{?A&iVu#E+#C zyVOGVJQvB#s*7oGllItRpxvHB*Z@K~b;bV1_*O=31{3n!{pS`P0H`C0lQDR0V8#7`JlY3!4OrGLX{*#~f z+Al@=fl=WM0$_}%LzK=CgK7|+cAheJ^GKN6G`Uu<3~N82uI%A;)dOwwDd{0h`@NBN z{@`m+z%H(SsgFt^IS%ReEf}&<5uXG;KKdN+4gFMWe;V>;XPAR|qcMftXgJ2HIlZL@ zym^B^slUXs*~tPl_z~+4Em;JJt{~c&^vCxV+;Md9w%^97RMlxmoW1tX&%;PDaxLW| zls`{Y9wfhC?5_PQmo1l3-IzivJoEauW~ru|G9^_ zCb`J;YC_GbZEtg%uDUU;0p1fr{bRt^$nxSZhbG)BUZ)Qz-XdcEuoTG$vH+HrN9j;a z4K+1t;)+j(-O61z)pt6Pq!>R|K+h5GFq9LaZaKo|-TlqzDeOCrI;M7r;?>$`06Qxe zR8)p9oCQw)yLt}B!t820^WgeJ18X|h)0y)HJoF0sXu3f7Vh+DOPU*jr{AJ#;iX9gue}%j4OGbJbVVCTN$55fA9# znlpqISIweRmuy%?5dV<&f?%1E;VPg}==v$K`XXU#U^3T~RF#y*)sFRjy8Fcr8YM@h zTsS3MVDv8}#ae-%yA<#XB-gNIH%IJkCs067nj?e5PWh9&m2Txcq%yKlKyCBdOHY?@zZso=Wb)KEK_h54gBVh)U5eJ z*+7!gz|Ax&p6+$J-cA~YlxMf{AEcaG=9)y_V5}qqk?Nsd3zT`hv8W=XNDoSU#k(X) z?&wfxP={sZt*T!;WXYWuS$E2vY|D_HrERAneJIouugJdBD^{V)`t+L`%4Fz*kqS-# zis|_0HR3IJRu8;9H50MQ%GpvJt2i_NXb3046p26EIef%XkDwyZa=>Aw_vN0Uw=E%Fs*f4xi#^l zJ$hx#lM}%cs!I{wN}e`%XtMQ*6tD-u#(>|J$qYQtLMC0{#Q_Dw@sbpTf1Nrk_&5xi zfcGG8rB6{KcaQeSXWx2cDrw`T4?Hf0^san1vj?$8)4?>paHeT{Z%{L z_iJ)mIhMxu@`gP=v;0|v-H@*vTa79{>c2FeX}p}0PeA(7v7j(UQVdZ)8Z=G0ypk)f zPBU!C{w>YEbVL4;9kPZ)VN9u2_7R;#Gmi<>)%%hEL{$AX>TQ>O7njU0qyT_u68KZD zfPzH+%MfLxOsVa;ninN`aSf#<6(;7B)|Djd7{+k<5upq(&yOF{NGk+QuNcr_5q0md8;u34K zn7`L;aBhni8Yx!Rx(W~Rjv-j6&SK6WTqH|;99pS=W*|H%Hf&XPFS<00LMSB4TlbhPlA;ysCOApFzL zw7i|kqV{aM4I?}t0E<)u=Y})ztg_yeIygSiS@n0`f{Ma z%nN^-`qj}lw^g!zTIe&4U21jbtY2ytm>#_1wxsp-4QI7l3-*p%6UHq*`TesW8ejQj zFFEB3w9rsMkx)YBht`mVa_b>_dFkch;+oRXr>W(Rz8}K;98`hTs>5-&RMAbxs#M0Q zHCLzEKmL4q_{Oej3S4*{#h#lVsDf)7!DrwyqHd8$@B8Zsysgfz}FtCh651}HEFdAGpE!wRo1V~N?Y;OCsTiYUQm5; z*4|<3ht+DL2i7EgeO!9!Lh+a@&_SIWb84$w{>Wbs(j@rzcK=R!MwwH$B3dqsrtC52 z#=H&^YFxbVv=2F+Z-e*l@Lv+X?sHVx=`?mce7Q zB%wNdVcNN*UDoH``P4cUS0Bv?56N5_oVjvB+`HCsZjwf^oANmA59Kpa>Dwur!B(6(Ck^)P3OA5CiZNmels`;+V$H->V9;b4E|5;wdUO!% zb0$`gXDS!L0VdPNKL&NC5Y^G1tovh4oFu0|)8|?r7aAyr(#v>Yh$?Bet{%^8eSQos zX)I(2cSx=3kMz*qAgI+TW~XPEVfZa`fO&3xxy-7*+iamBvIoYZU^eQFn(=tmqE(%d zo20X6Og37m8GlfezsYi?jxM*4=<^ze4h5cJ?f=F9=X6=2E{kY^GNJQerl@o|kUy3^a4LTKI*|gz3~Wis;wvro&Ct7Srkd-0BJOq}wub5zQ*` zDe#%f!l`KxxX^!>!HQ6LPbh)sIV<%$Htq^XdKNHw(hY>5rN19NPQ9DpG0h|{^!M;b z3Y(T{##c_>E@^tj;-{pE-#F3u5>~8T14^)#Xp!f~OXPUbcU%^D&K1wq+67WVED~qe zoLXM+iD5!3;&kV|LGrrkj`Dv%*Ks-R`Z}B-{TSgEF0t-ugd+q;&RG+D;)9sS^f3Fw zzK4n>Jp6C45yWH*8=1rZhV^!1-{_p_v5_ujc!WPpw*`&juA!M%&Dm#qC6vU5$@EPTSH zD>+B}1DhQes^>M@a|Px%wJbfX_G7*25XMx&)%y93a1VcAv2@^PI|w>Bc*PkoGlE_a zKI)>i>aK+4PU-BNPDb7_1?Gtzc>I&jrpraBxgMvvYo=s%74lIny>tG4-MYzhmteM$ ztiEr^ZovyCv)E3S>}0Bv=N)-KamR)Vyu&p^RB8VNDabvzH{?fV77!v3IUJC5Y%~(@LHm4#BeF@_GbcTz-5{12xMo3Mg)Ln;3Y`YI+CWY6#e4+ z;tc}SF8~1{_7gZgx&x8+l!Z1~U$@)d1EvuK6&HC-X0vXf{R!oe!YVNTrM(U+j`9!0 z5cvN|#PCbR7VI0`21C(QNa;tkm$)SShGEt8b(ZgD;wf)vVv{2NKHYU5zz5yaUN{MS z;Iwal;LV(%5H%9!ffg+%3^<+ku?OrtX^IuCx?>|B^0N!8{Q})tk~VHPUZC**G3po_ zvOvenUIARI`*2K_)RJW=TpI>?Swmp7NH_kp=v4VnYP)yr2@xbT#XfFCYpuQdmyHzW zYQ;I7+QbrQ<|@gD({Tk2!G%@%oxr;M#5dh5F=iq{w9#H&x0~2i07|W%)Y+-Mn*6HR z|8RzM;l?eRsCZt3WH|-6^qo4vfmHFG5~tls_QshAF2cm>-im@t7MamEx5ZGh&W%5| z;KzR;V~yGiN$~%rQ#v;OII_9Gj@aTY;6{^4xq(6+xGx7IZ^omAY<+c?H2=dmLy`nK z_}y>uEUnJg9PGJHe&d%A%9k_PLbR}-6$HXUOcXy53*QGt{`sGo^TK%D&4cC7m$ zZ?j2+p%X(?aIybmPT+>BXR2oq7q1!wKFW)v1*F{Bd)N{t`Tt%^Mw_wMBHql@u;~XJ z)PBM2{U-&Mm@i~SH0HX);>{350*Cr18ScwKH6B8vd zzcK1&G2+)>zn#6*=w73kS^duf@RqcfD}dFh-z*3Xv1>j|dLcxsS|W+>pcM+tJI;Zw zrUug`RSdyMY^whX53|lr?#mQ8(-P-klBmvq2<3c;wNbv7k#C3bI8w+t0g3} zWfJqJHobzAp|^vm6SW%r-&dy}Tr|Yj$W8gL`9|myIpnmyrD;-%NcNEhW7~^QYK4$C zmLvzbN&5W)qy;TYW~$df9r1~m=0oIS$TrGd@m;h{y!_G5vwSvunuC%UPmRQ-m*71s zMUyNkx6z-sw`Zcw)PUhN`kvos3I5tHss8DmBlZ}<0Nt==9YGtuLO-f6+&eu~op7PQ z`4v5ndY&k|H2Qjz+jYChP(iw~#g=Hvn|8s5S(`fyem_E1d4oejO z7!3t`F3c%&3pfie{cq?z9LKi6>3|;~Z;vsyg0D8W8%i19&(zDR-5rchP}bKBs@J>I z3WaZVA7&@O5gtPU=Qq9qreQ#6+W!0!J;HxC8x#Hl=?6M^_cn?g#!|$F6WIsRq3E&9 zS}PbNzpSQ0Crkq1=mZCBV>>M|2OUE5aiUDM@DF?l-*&d0;BUIrx@BrFH*@)rnmE+HK9PdvfXy^$At-Pnt8|!wM4yk<}<0>NutDU`g^3Zy{uHSL? z+C!wh;>!-aat%;6E!IfJn1K6(e(=pvvrd=uqVLEPlvG%-gokb|u0s6`y3EM;pfE8@ zgazLhtH<7HNyI|EH?Gp>&l??fm)U(LDb}f^5LLVhRM1~{U4SQKIV1MgFtF^uPLHVT zmI4>7Elgjzy?LLDi0R1V9#L)5M!=3>C$BLfT=H;;KHZ3uq62?Q>=~j&Lc7~6uw6yEroX)WMlRvA;OZ7BpAdE&QMTl z&&8?flHp85m5=ChNM{Ie+TSdT7hpUUkbhqz?}{DHt(%l3BDt?t^^+|oOpsR4mCq4K z9l%FUij|+RqmZiQ%}Rt`5+wp_;0zd5T^ZOoMr3PC5WEXTkK^wZ!~zEc2jb8;^&e=z zzPV^S7w)d^j_u;wl`=*`4uN9!vK`LqR}AJpkC`iq>q5Ok-u{~ByMrg%54 zXHQ|Ra;m=iy+4N$9duW8B@3X-u5CUXQ1%H z%;0}K6DWo`0^AdfXT5hgqz7|^zKt@ON-igPw%ko9%f0_y&Ryz9!WPIw5dh2DZ8hC2 z24jSJEX4pv|NpL>MzXy(SQTucr^$2RCot<|$6kq?A0Fl^INB2lpy%kw>+w(l94H?n zJVfY~LZK1+(~gZok^HWG3+AKJwlEi0r~bF3ke0aHoEFtxc7o}kYP_5LL_X8)p2-aU zaZ1nAdGZEdil(<^8p%Yt;OULc4bipT>A{T1ogHdCCbSR8l(q;)D1oZhW_sC~u3%fs9V$kh_z!!6)Dis}zDO6uaJE_+70eWHcP1vsR@KMTy|f{BFk zmr z`g^ACBGS5?m_^X1B!NZz1?>~;cLs@EK;3eN94ON;t`+l>`FjLG9#0=rt`V__9@k5E zmFS((=+_&vCjf6&KvsdzoF~hmaju@VVa0S3b5xu!5HNxcE%6S$QV0Ue2&%q;XTuzi z``2T@`&reKAE2~ba!ar2e%BuV_3vo^4N3BCx;>91HP+FA$6{1)>hmIu<3WV=v!dJZ zt`B*$$0|~sh+Hng-%{?rJMDm+avSWw2=7{vGl#0m(rBCMbZS)PgtsyJUa&3B^+XR+h zY=nCQ9IQNe?}`5kxNoztbD`Yz;xE}aRhRxOwHM0mMueh!S=zY2NUtdUMgK+*YPSbq8MA}RWbze6$P%Dbx(b~|K=*ec?o#k9etT2FxK4|965NlNz85_~{2*{BGv1x@ z5c^QHFaL{_l>EmfRp<)PLiPDWrHc2tH^P8yghUf}Nn$o+PsT|V0jjru|KIGS07jnM z9fyn793MDGN8>O zOdW0A#2AvYqkRQWZ*JaDdSU`?A2~6+LCKU}V*ZL_U|)KQt1yzFF#PlBNyM{B*9a`Q zcuLY#!4u{*DVHaU2AszT4*6muZ{asb=vdr)G}b2<^E#ANrq(pl)80|`k(28y3+&aG zm8C5&pbd5eMc_J@7D4U>d_M9I+H@(!2P9~i1UZ?#w)TpkYD%YTBv$5XAI_`h=1>+( r5&Z9;|Lws4cHn>6BDC>UW&F( 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/designer.metainfo.xml b/src/designer/src/designer/designer.metainfo.xml new file mode 100644 index 0000000..44f2090 --- /dev/null +++ b/src/designer/src/designer/designer.metainfo.xml @@ -0,0 +1,43 @@ + + + io.qt.Designer + Qt Widgets Designer + Design GUIs for Qt applications + + Qt Project + + CC0-1.0 + LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + Qt + +

Qt Widgets Designer lets you create and edit .ui files for your Qt application.

+
+ + + https://doc.qt.io/qt-6/images/designer-multiple-screenshot.png + + +
The main window
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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-widget-morph.png b/src/designer/src/designer/doc/images/designer-widget-morph.png new file mode 100644 index 0000000000000000000000000000000000000000..0435bf0c36fbaf625b430e066d9bc78311649d0c GIT binary patch literal 19303 zcmZ^~byS>B5G6`N65JhvyF+kycXto&4q<>0JXmmdcXyZI5+Jw_?hHEUJjieN?0dWW z{`h7%On3F|>gv9Ay1pB(tSE&9j}H$81%)IdEv^a$_3rWY!GnGO`X6F*0z@b%IVKr# z5p}QSlMHV=Omo~&DRpsiQux>sZN^_T@Y)}B^Xkym?fwcYt1+`1i|4b*>vh0@BMmBfho6lqY6TF5C8gu)B5ClJ9It=*(7CIY$qz2mB{0YA5zgyYOG>i;EsoZJt7kV&)=&_E{t5!HbEP$IZT(t|uDyt$PySKR3EjlPOq9w5mv5wOsX3 z@QUa!Sh?$)Q~N8XFq^3Ax`|VlX>brKe)b^s|M>jXW%4u&?jv2STEd?%Y3Gj^B*f+IcBbzMP9(ZMT7 zt0)*r-F>;jU%5^%R`BZl8|v#53OaRZufDBsjmd9AF*ZVLGvHWQ{qpJu8+}&-_>Wj3 zyr~?t*6y8uD-Ftg9f5=q^nl)9oi+VntLr)YwB$HY1mkz#!p$^2QCNSON&!qnx*uj6 zk<)4j181iqu4kQ~5g*cTdbw1JY7`Gj_P(Gjy`iqAA#7oo!Z&E_U4MuWKVkNEJ?6&V zVJ-J9GkU85y9_`4Vh7VLQP8_2JFv|X}JsX3Xc+Y675FKeuD;Aehp8x)gRaeNrSozs2X72+mCLR z8v5NOXlSfo;p?TlT2d4dxtkb0>0odb3ZEd#Af;QZ@o+6H=8fN9B>#maEqmc*!-W61 zyqu6f;YXto|0}Nbv_$b*HcrWoW(m}bFuQ-wA{+Lpf^e|e<0d(+NidehCtc*|na$e% zqlXH>(Y(p35fxo+QT$lvq%0XO{lj^Vu}^Uu`*+?GRAwIYF7eaG><35>RK99vYG%-+D(?NEHZg zlRK3TG4Q)XGv+#{jzKL3lD=x1d+kw3ylbTo54`r7O`3XoYI3C zC^uv4$%RVW@sPGX&Q-8iHXP}#L7V=$3$N2i@lnHhQFq|u=~s6Ra|v&69c!J#@EHtB z^$ieW@W zQon=t&HpNEVC|00ByB3v^Y}d-#2TRx^Lo{Qg+T$&LE^X+L^CKdVd~ zwUX`&?xN1`9!Une;Hy+T1omV=n~|Yb`d>&{hr{Exy^azJV`jveuB-twV}nBgn4m?i zUVG>Tvk*+R!jxeV%3wK%{Xxbg&haA>MwN51hmvd7&cH|s;0##26iXZJ5u343Y^+Uc zI%o#2{`N;sa#YN{uc+yVC9u!kSuX?mPxSFaa`V_W8XYS@-f%U8_{lU!pO{(g+$fd~ zeUhbxMSR3(h$6 zWaiD;u;U&z=!*QA0aI{Bb+a*}m) zs~ssW(MDz++r}=avN>U25VaxkH8RGt*^3~AgZYPYsqD57Bjrbhsw~1n4??MN@P^-& z`-@WhMW!G+*5S{BLeNMynG>OI^FeSV*rf{@oEwZGgWS%ne!5g3)z12t@ckYZ}kYU0*kA^ok}n zRF9Jc_<+$Tg;SKjGdGC6+*WV|xW5g54-j+AZlv*j3R5KX!T0RAxk$-{hCvMPKA(%4 zi)L{~!Jf1Wbe8t=Z}{t&#`@B#A4vR68S|2!uu@b+9>R%|8>aE} zu)XtyyNk=cE4F85^M0CqV62xy^v^+k@17-1KrCc?`XZUWLO+jPmBPt?wq#nq3YjrM zDcaZ2?byFf)?bb(re<9Am^s2M?~+q!soTIqYqyynmmlG=b8A_fzzXI;$R6e1YcX#0 z>wU>eYa9tL4HfU+XmFimgp-S2(%$|Ljlbpoczzo1EO$lSPqKDkANq%PSOMSBetrM> z;jgoPQU2VK`bmpCqglCz6ORo{%okWDGt29+=-|qFANi zI)37dmD5&r*&0{T-A%rF0_}F5KPP4M_Ye78fe?a%KTBF^wr?#D@1zcun+GPyF4v11 z^uY%iz(uc189~UhD+GH@DHndJXLvQu{m!Ll@=5J6HQJ_czv8>(eyJ@)7aJ@dNp?}g zW|jrqK800V%6BP8l_5u>7z1$qbEq@qitFoF4$HQ@386={7`JhZ6qkwHy%qlQqY$Xs z+IR46RS64a<5Nm;{Vck%2xG%yj%*@eE1R-ynCKJDCEcFglcReXY-r9Hma$FBk4D*s z#w8vkhPC$%8R6BDyG61Yz&gZ>36>Rby!EBu%`zCgFnqXK(!2kSV#Ke6l85L(W2B9{ zJ2-j9@lr{&?TUNFwxTZP^GPn9xs)5bM<-42xi-BUBzhNm!gtX^BAPVwNle zT4`55<3sxDK`tKz%zk94zT!_0KMDZ+q zD0p>+lH~K*s_oew0-)=Urh7^It0Rbez$||L>W+T;=U6qgJoOpOny3?$S{|QxCKt1q zm2D9TGH8}q13^oPbgZ;{Lao<@)rGNGl^_m^^QX9bkj_nJK|-<*%VHa!5qYJeegnn* zyKLnW%8tD4SchE8qupmqnzrsfcSY4yX(k6s4d{LPqbWut9%Wf8>RYu8|IID(<>$_$=v0X7hUX=bHIdWth?_a;~pp?3&v;e3_q&sHYF4p3TmZb@F#yY;YbN zjL$nqfEhl2@F?2YZ#_L~zLyo5lq6QP!B~P+t*2f$fEs;qi8sejhxuIjo=Wi50xi$I zL?Yd^fa8o+uMcjBz)KCe-*qWH`JRnLsD3Ht{LV@Gas9K(ewZiaEf~rNaKVaiB-XSV zZFI)>duCvGb>6JE2|*+1sGEE}@okRpGC`n?xOe}8kQnNp`vsx?O&$Pc41n712SfFM zp$0=i?_S$R{ufj*+^!L9%}|yOpxLPM#qI8V0Y6_tobOv7d;s6ekjr>qhyBmcYnN9q zzFK(=#WyQ&!Sm0=>mvVOMzM5o-ou8BEA^v0 zo|#?bR}hduuC6jhpLQYFtGkc-0qT@E^XqaC&}K(*S2sUV`oZUITxIrL(OY=&)XIIY z5Wj%Fdg&SqpBQ8VOB#|2RyU&L3(UD!9Tl;F-~a zyx2BOCBPL)4q_u4qSY?o?6RlChlJOTJypZXg=c$A^pf1bTRPcCil^inKmScvN8u(sOvaB) z-E#=$P@mk-*#g@25?srfHHJatQPS9~9d+cVQ;UG#An?IRKFs(xMEn3R+hS{SJYH&k z(zyngYZ`KeKzR?>eb>anHskvqTH({%s^D5|%h3b(z{I|cvk~P!@|Nay-QbH%t@pvY z^KSO}@n&CZH34;H{ap^_keeMOQY{ZoOo%|=w0S=pYoa5tM`n2UXM*q5>n7A!|8q9Z zTU$}Tcia4uWL7~F5O)L7Z@m?jX;)`DFJbDc@mZU8DSe?RoEyTa1`lm>p$E(M`MZ3p zUHEAikcMv?S$IB~t{*G9tm|2n};OLg!*sQvIS9Rv$ z=X%(i^O4&2Yv*9GQA=a$RomYg50$mK#Q33QpO^bkSrJR}8#vrF#JBKfsTjn;cEQcn zISsKTjJG7JPC`;$wVHx=hoeXJpyd8W?E4%6sC)Pjk*F`G>I_`c#DS5`VaCutu>}}_ z3dm!&iS_8FA2ZdxzvF#tl@aOG;4pwsYb^{qI9lyBUr|HD(rVU6u_W|n%>Pt)s;`SS z_0?$M_f7HgiY6RF6N{FCtqQXJR=g#php%$lydG)%^AyK$<7qDy^xN)0EC)p-DOy@K z&uTPZ@qU`g(;Z~)%4iVsxJoPWCgLobU-t6sGYIe#+zDYN`&m+(A@)U2gEw>?$#om^cu#niXD-)6#ci;7d35hO@!bkM zAG?^Ymz9Ul`RTnnBvAgpAW+=49mABLAJMIIR3@*Ip7LV&yRq{BLlz|2adsZnAjccr z?EaMOr6T;D>R*0?Qzq>}o-w=p5XJN|>h&RTLnZPjIzTw-ZsK!(^O*3@d*`cum~y|J2$ z1&>h$Nt#^M38G6zt$QmqR%tIdXd4ZCmN}d&;Zr`XfFW zTIM_B8h6hwN!`pAE164Z!CYlIFNq=YHPNZRV~>h4qu!4@x0a=%l=j}=zD;9J8SJ7T zja0@e9Be&NiOR4GNPqRInoqJR5jo^LXSnCI8;jB{{ZY~l3Z}l<(RA|`qn}t&hD8DVx2;)%SSA3yHu1qz9+#1FN^Hp&Se}j3A zc=B%^rj5-}sjX`j8sRbRU9+X=s~ZHI_8GOHf`dn-Z&TL06UjUtO=5;Ee(8Q+0@ou1 z1cfzV1|h#xIEp^0Vc|}+L+_itu|+?)8*U&*(A|7Mzw+Q0ePUl)hOet{lh6?W1-ev5 z1^Ncd3m5X7uTtLdciu@&kz%bQHrAi{d}LcH5o_1Iv61m<@3u)ZwkBVV;XOHgp4uvO zq>$+y`0Lmyc(+T9K_PP7UI5#!U0I@Jy`M&WiHnVaVU^2ZpH{LX8Zxxr3e)IrsaF`)Jxl?1M{il_t83q@ZKdto?p0NrC?8ZdO# zF17!1#&I<(weNG*Sp!pn0`?^d^g9rBP!#hebUXF&5g&aQEK;4Ffg9@1XIb0xUh))1 z2WL;@f)J%{r6TuQ3V423g-plkbbjaUYbYvIFeZ>~pf%R+{M@G8lYAq`VZ(v>g0XF; zeAs^^@w>xff-Omht)=arXY7gdS>_BTH)cQznBP?b4_ue?;&(`v6!3b{$8K&bD+ST9 zd*qC34ez1`E&IC-z{=rZt>6Zy^v>Z9$hpJ#U`o?4XDe zhrfdGtwjJ>?fq(L)~Wn6$djI?YA9J<1r+A$zSmXr(M$inF-LxMh7n;hooA}|tQxLA zujJPY{g9grqv4PGurufLT@W2|g+aXjKvIkr{Otp;|Bdu*@p*RprJ==IyJhReQV2)5 z=*N#pohE@Vb#)P=Feq zZ$q9&WF!3j$MB8$kq*)pN0_P&G7z~iprrmbu`W%@wS`4kz%+9Uk)nR5nEnB1 zy+(}F@aza+eNe=tkp{GgY0+%b?|iX3>G6H|Gb#m?`VLmMl#t{Y%=J#z=1-_4ucE_b z#@)-@tY-H4CJwGdilN8Xv=EaQ?EL#FhNSd>-GyP^=U${pdoev@ww_{Y5>?K`Rs6YY16eQONDSFO+$41mX9-EJ_7bA_Dlm zW#lk_rg#k2lZ7z|nLE7MtM9vGjJo#u?mFKsPyP6ade&U>Z+hTO3CX!nCDrK->*0oT zseB?jO+EIUDdSK}N2joIzvo(Uv^eJ%9C`L|j*fSG32k{Nigu&E`|gpUaQaUC{C&S0 zrB_Z*wf$9PZtea#K(Wzxu(_+t+sD<{TR2B{Y4`J{_D;`6S;MJx$mY>0U$utx$}>P& z1?zr0RK&FK13aL`;&Ej6Hs5vsrUt!PgM-Yf^lhRt!VcHi|2f z=y+!EVK8LsG&bx84|5FH+7Z5YQ+2h&*T>g>!lKr1xkPs9ZW1MEAchIFr5HXO#>Rel z!;)S!p{d4X#@B~ym2!2Nj)cm*FI)imFS;uO-rZg8ym{&qwe?J|wB!JNxFfGe&M33WC1QZ>Wi)t|s8+nud zS~ThDaKL{dEwm8dyFif+VRpfywKMly8UN+!E?%J+l~q~r3Cmg^NlgZoN$#Cxx1KO6 zx=`CdD2n0Nf%$jxV`VV9CtX$F;Kb3rx2YblgO5(R^#DI}cUA=4R|UTa#q#lN|zyNn^$8p*+&SOVLVy%__y$5L*bsF06!qwY#%5pAK@_Po+rmg$q&h=vY6|7|iW<+Rw#ZeXR$6YtWpHW~1R znnUpn>jiL7LPID0^g)+Pzy5j0WvCWmVMAyEY9OZL{A(HS$+~T$sw{u&BzwEnq@`$I z&Y!L0l42@~zHXGd#F@{vV&tlpEQKSVHByK3lFHSZ49t3SES0%a27m2>?2-3RC@o4V z6N;Ks+dQwmhiSZLTww<;}NP6xv5o5Cn%DrV?cyQ=7)YmeTaQwxUn58?}% za`*wPJ-@1#W?i&dC$1Hj4UL}#nd2>3)(qqXf2~Z-M%%Vwf6zQ+!T4$G?&IVFHTh4?qd^rJz}j zZx~wzwaw^~xmY7`^*-EsBg!6&{QPH~4bu{G?=XOL0c&dgYipiG2HC8Uk$Ws>pR^o9 zyEwV#CBI?Gpwwl>@rT=X7#WmYEagt&wKJi%^G;Lx>4%eiDN|PPGAP9Q!JW3Fwo)f! zomwAlJ}U~meo;(MAHt5ihqDV@I;JUwr3xCy&^{`~h)t(1mmEL4|KTG0It6sZY((fE zk)cA_=<1js%cDPMEYe*#ips%YZ=JK*1b|g8GHvpQ3?>T(m1=L)lqFf3(L)5XWHnRP z`j=##LdW>LJArxe&Oi7+yQ3?Xj~E+e#nQ!jxR!*=>(p9JAEu2|d*$3zd_JhDu4Lfx z*63tCc^?a&rhEy{s~RqmZ~Zv?<@I{z!@%XLF*Uh+P@%v_vf?3* zL@9(<_ReT@W8`PcD${O=|U{E8JhG2WAM(G;OGm^s~|YH=@B5yzFN>S0wMFs zQ%eq^F7E{a`-VdGJD}9M=ZtwJXI|=xSUC0k6w~5h{oxwO{1TaOTAdNzmn&D)aIQ)| z_w_2RfRDod09gc(;53VOcR$&4`Tgq!gIu`__YrrV0in%%HCwXI6X*)d)(-P|GZ44U zVjiwXfb`P=It09%9*d^HD#9&irMJx;;;uGJzK zkhO-Re@B6v#I|$eVcFU<=T#JJ4w;ZSvN*oF1SBIl3T#1Uj{hJOF0EC0 zcOJ=~v$v0WzGhr;Q_jnv1|dtX`0`1J2Foy5mRTDE&TUihnkk7nB=r z|0?s_fLbSzF<#F0xOqP5b0QYt3u%{Xz8l)*dM|$nOTc{m2CKmC-5rO=kP3ieu97#@ z2g}m>iCfpvIlG_La-1Qcg~jMFt|?3@5%!B717|30v)eueJGks3qS|ffG$qx)PE?iY zYTSA^T*9Q1C1-<0$UERu{BWC{IG+}3`5sC*u>nvl^Tw;Rz(2zYGqDF3Ym7H(C zt-&oP9J0c8^6I^Lc__;+uKL}OtO_!nt4W``Xvy*@=*7RgRjAI3^N1()t#ap)xW@1lD#pj0w==+cqjkS^&x z2qD7c)w6`1PS1g>RA2(_KLepcq3;Ja_%^&#{Yy1cbsIM9=eKG9TMcaZ#=8e7UhsZ( zpKofnT=|QFsyPivX!-7wl@P+41N?u@28o__MrSxa^|#XhnenC$zM4P0R0a~S{A-il z8a2w+493}0j?t8wGG=6PK z$?s|YlB@Kblk+=hrzYuKEglT#$OivQ48Ol++@{&!13nd!x74|jIV{E&T>}0s3nm4r zNCPPR#KVPnO@(uBB?q@&%T}MlV?=>wQ89h^#KEt9$=}*wqW{a9NkL{Rg6V0Fw?Wad zVDR{+1*am8`RBNM)q5fX5pVT-mu-Zd6Fc8Lq2$tw%q`2#4b>rn)tjNRpP$d2eaud8@qBzRk z6g;(R8ubdvpNMg3Ae^}yd@eHwjP-7w+D^f@V+5bdg>E=DLS3Jmy{<$0!M?8x_&3)I z_EUBuIGdGi(|ziAW!C6<&oi!}N~lfl0M{1q=7r#a4G9OaTcM7GDwk7Obc}2N2a%2$ ztdQIQ>-DkFm-?YkX5Df_{I~Vpk9%xNzK2TPw{7>IyO2syKQXW; zIr}*|QZsM@$4SWra(=(>M^{4ziUmsp)WP$#>CtH*a&}JJvO(ORd zqxZFrf=Y2Bx5!0bXWjU>%Eo@KD-WwyfG)2mLf6__M2jc$Wa<5mb9%P} z4K_w2IascTT>cF&O~W3q!Fmb15e z8wnGrkB}R@M&Z|MjBe<&JA!Nb4JZ~{G@!*6M20*71B3=SyRLkdI;9{JoVnoWe-y4y zK|L>zcD)?65(#;ahSh|5W^mN~C}>n=M{C2|l^|(!xeFRAG;c0D-+$0SMmLht;Atmu z`T!yi*=nI#q)YgI-fKhHy_QFa`Ktjk-m8X?_Fdu0NTeRDwL@k>MAa0aaFkL zLW*sJ<<)~(`vWghLBUYgZ_FsD5Jx9V__K4Q8~ybA@1Zhi79z_o^kvZjSvp>RT%1S5 zX!cXO4md6)px|=}Z7fSAJ>M5J;;s6#jHMF`Vb(qZIo_!b$n9No)=y<;DNt4qUP3H= z^TsW6xgbWas>;F|dBh&WqUMU!Yw`L-Dwx)t;oc5n_}zJhGCV<7m>s^{kR$f4_7zVUG_! zQl~Uz#2cE?Y)d;j!krf7%&6Eio1zxjfQ%4oceEFCN_j6%rRPd<6iS^x6&AULWO)a^y=qjqs%0 zO*ltPftG`2f)S;M5NJs z_g3#_Z(Ka$5k#kgQ)e6=>h zQ=^rV9F;l%_#T$Bp!yU=D;en5A~6j^uPv%RKh%**AT$=U+gPIM$ml=oBohw6@M@U@*#F^v`3|x}BS&Y5KRNhYEwFVK9QrU5dm8n)r)S zxCKgeT?taF6=(Uo_G8VQ-_@5sLkgm#9GsO!3F2(u4)6~Hp=#K}l&8a&p3wv_inxoZ zAK!)#0Gl!S?MlrzxZ@nPpd)T`;GEuiEUDA}f`0vpghbHlz2G*vftClOYq}XrRGzJA zvy<5#dG0mOe=E?RrB~vL@E#awJjKYr-GBeq%@U496 z?Ayk|yu6W?e^TXCJ6!5rON&ub$MU;#4O?oO$)3NQES~Timu8*9Rb3W=m-w3Bzk^>b zy`FQj>ppz6JzJGK2+evtrif^DM^%J6=VRa}d;-GbWw@Z(JZ0A6T5$O$&*aEWp2`O7 z%9sDT`perIa}+*4`blte>CHHQ%hl@3Cc_$7=4>Xd!8Bt7& ztMP_l_(<~g@cK7yYYS=AmS+VP=>X*f9^&23H_+m|B8eNBgttPsKViMy26TY@Y~Suz zv)H*PC}80C*V%Ug?N0AW3-wL)Cnr(r!s+&-R8Bu!;t#7ejOQsf<+&(?D{*4Wti5s& z>{|qPYa{%>DYdpTQ<%YweydR{E3&qTS`gD(7QkUf3ls*tan=oNq#J|?NRDL0)`7wv z0SD?j;-;>S#Qs`gL;Y(Xv~>T!tolvcKncXuJ*swMzE{ROb~B=)UX8biWU4E)^$2V~Y1JneEGahZ z!Xr(@rh?-YM`$t>tAlmpr}VS1Om>PRs_hp)99lmgw{2RAPkRbY{~W13>rj5&E#yZk zmXdEx)2nD5PB4DjGn!wTZhl!RjssJe(dtFdkfQp_2iMXw8~VK;V|jzQfn})2Us4mw z-^#ke+NqLRUQx_4Oec;;?Vd{-1p@#ecU8F3ci`9tFSKGUoZrE94flOoc8wtC2ik@N zpr4N4x@x=Xk?d=9Se`fa&QXb)IGuIg1o&>=Q>#uC7u)r8cP`KHKiJVf;MDU{m?0U` z@C8*Mziy}j;_89!)&WkiO-z}a2dgJL^5{TCT|J%*=vXN1x-mUBYM)C+-wI44ArR<; z&-_sv2qI8nD@Lvto$|UGbov@WeCFaVXTLdr>R|?W^l8HpX!>c8zBvPF7w-I!NWDuJF zGfgckbB%aRsHO||_x|;IvD*V^8a<-vY!+;|6LO`d|6?sSSN}+GXo$BE^YCXu2({z# z`m1jy$1df)4qxX)W>b<=p-ouzdYZxs>f0gmx(Ajv?@O^sKxzrLI{<*7l_lh^S*5=$YO8q+M!! z#Yv?`nSRzdZ=CQ5wT02o+Iz+?zGh;_<2!NHH1%=J!!+ z>~~+VHE*bo#+LSE)X+_4H9b6$3-(#A@zgI(d^atK1G@xy^YS`rnWRIZu~rv?3A(Lf z`1Y|Sh%1cYRCI{1EWWiU{F&w)f%UxgWD5BfLiB9g6nBep_J9d3Nrf@%^o9hOJ~{2M zHWl+RrG*W~D8!co7$NBf`yZ2SJ5E)quO`IfZd+Ry~LOiMb`EABkMDIXeU?=24+#=8{&$LwCeGb$>v zK$!R=cyE$jG)ta#oO z3x9a*iKCS*yxyz`!r!z`4mnQTdNk^vVWb)KqhZ7~4dxXf_`5r=#ttO0Qn()k4`_9N zd=L$mA1{^W;k0e2R4NUtCH$YmH+|Mld|@+)R+*egnm0=2TRTn-V=aY#qA88I*5|f{ zwL2!T5ww(&jw*q+r<$E4*5NX5QGuW#rw3lnK56PA%`K=E@#d7~i0FoSn2aP=p6&HN zpx|ydUF{>x4*7;fDJ_uP&4nz19OP?N!Awfgz2Cqv%W z;m*D-^6x6>$>CSaNy~O*1P8ioYXe=6bD2_U3T-L$=d~>eFWMY^t~`_7cx%lc7mfF! z{K3{WT_X>heM$3af9CQLqo^UL7OYar)$;B=$|BDVsKBGEE1WEON4xk2jUg`u@>x+9 z^z0^)Ii&TB1Zdk^@y<9E-S{!D#1U%EYHoOHq_X7H%$ND;D44rdV3r;xdbgh91!s2V zU}2J2pYt7@6aObMRT!JPrI2H`ist?1SUhAqMU5~%MMuJ2RBh@)r(d~0maR!g6?t3Z zqP~AnYD!18q!RQE%*6S?yyp3P&z1vN70-5e$ORZNAD+!20v$RMu6a34F)o8?D7uQs z7SJt=yX2mVq!-}i0>JVzgqv0li4;f~1xpqPGkh%jS%31rYT_sNiG;u(6$gK3{z4y~ z{~02WAqkx2DEVxn-m!6cNX1m37BOlhI$A!~*72=luFEu5cc8f2yzMenFW`y4-u|Yn_2v?b+{8c+X5_))b)quvks_3p@QS|F66#IvDp{hK( z6M~2(ts~f?0%Y|C@n;UH+21G}Bwe3govPH@0@Jwp9ja^lAMOlos zTPaNA9@#X~Y6(9}>-w!)U{b0C9dS%liUDnrKzQ%hzOjQw0<4t-tPqXNoA+ES1e7Y2 zr6o@4z|e}fK6T)P8>T3j@Bs*HBf@3$cxn^n0d34LIqljZtu*{Y6!y<&XtVMxlSFIP@VbvzOu-5%IjpdV;9q1ziH?*N^A;QlNWP*rAo8LR< zCWkt1n>%^YJ!Cr0BWd}*-2aTWd-QMZkE0t{ok__j%D;i>j;Hxi?810{IZp~|3;{<5 zI9wR<&7YOrc`{KYlrV7g{}rwqT=b1#_(XtSVEa!FvS7%j`<*kVMM(__!tn=~}*C z0Zqy+vfj3%ueD5}N_UmkX?gUalIuTx#DL{XXy-DUla$EugUGDxUz&8=xbj#|i}BPJ zj4IIBDBYoCv}3an7(N~Q^H^nUFBpBrBhOZHPG}JD{lGKIC#G{Ca+OBN^duK{!Q*| zVO?;shfkK%%sCfTck8jK7)srd%{o$4d~j4)!BpT`2Cp&w$aH^PM+g_)$Em&0C!~0h z`bql-DTQNn8KTgUW?^E3WtovV^^&#U#CZ1@&BEbantfHV5+yF>>o7_d>W>LeYk6$x z$b#2;Es3NMP4|Paa~z=P6nJu{>;O}4;0-YUxXc7=n6&YQO-$e!O%2=erVADQu0)-r z_<8D`e$l5+iYB)+xQ=5R(Ig@5FBRu{xdUQ*0y$)= z^8m+wZQp0KCIqg>5ck@a<+NJ+xTrfgIkP^YOkBzs@=n^^g((aJvB^e=71x{h2G*s zMcZ;5(3C<@HWAD^eMhvabq-&MFjoCb6IHz)R>zi2ZM|B*qjIf!*N4f%F+;~2 ztz*^$@=m|;l}BwV+FewPs+bqvx9wB^iRdd@-5V1i4 znXAH~X3zFjSW`tT9p3vMR^sAb>gp8Y4mk~aHR=dmK2Cez-BDVm{L{2l_0vH>SY&=uAx_l=Y?h`Y$LCUPf(RtlS4S!xIEsCMKTwc|cRMcytUkH8m1tl)|<5 zfBKD{BO`}4#{)j<_4sO-e-uCb&LQ09_Uo+-!b~Lyc83-2!cAd@IuQH3KHVSsqskDj z6AS%bPh8(lUe@KZ{~9J0FUGa5-ee}PLC?EYsi!KmQ1@ajkPW872mNmI8XB5dpSOqg ztr|6VJOMy|xG2fK;PcoXtnQWw>R%DS^-aaY^Y(6~cyGM^Y5_GDAt!YBvv%1>)d03) zjJvW=uN}iuxqwwNT4}oAA3$Od;`wc>0DUauZI>Ac2N@q_PW^r5f8%&>?;>g-dta6 zXYo12^hFVxi&F}?H2w>HK>8*0s{pra96E;dYJOAtRzyljav~nTceQ+fCMoelj+&aUyyd`K!JPxI zSfpw>*G-mUS3~Dt4a{wCKs?j`r;7BG1C^h>0ww$n?Q2Dn6f`|3l^gZ1eEIJ*gnuhj z=D+TQ*L?e}cKQ0}zuf!PIU0Qe%`N-bh0g<_0y+WtA?Ta|D@68KuNa*g^4{R zwryg=e8H!*K#nZo$lFa?*@`??>p)z&pT@Z%lBlt^ALUU*KDfapf8<(CS~B;%<&^8f zPVObN%!E(e<_UsiY=owIZ`^qsc0Jv$S?-y;i(j?+vii{**0Ag^wFh^BOmwnMc=+;Gne-eZtD?nun z#eLyv_8LA(0&iy8=T6Vlz+X3q3U&7vUpRC@9>#z4_HElJDGI5+mAN*zLnscbebX$% zMoQ1Uqu{pdSy)*EUZoh_7g0)o9YA5=1sZeh#0WtY(DF1{y0ekLdqO4LHj5rMnQKK!LN9_e$^g%?zf*Eo4gjr%K4!&>8Jba@osFB)6ka*2j%r zwuj6_Z_^^}ydg0{2Bl7V~2ZoiEc-Xg`hR$2EnN z78JC)c+n3BJYh$=stbmFj0zmRtrMdPUtge%SF+Pp>0Tyrix#g)2~wGnumwE7dwu2I zKY$>+<1yg;2pm)11~;Lm)y^w3SG{0A`KZXVyV0ej=CRWp9cy#bQX>f2oZLPwP}%8! z7cg{Aza&BN5QMomK$&ezq-sSI+(OAbN!==%H%r~v=p;Mbwaz$qln^M4T0GABq}T?WB)q1c1x9QigTp^fbVkoRBIAq9Nkd$^zX2H5Pa# z8)KYjIL)&aHje4$3%#h_cJ;LoZ2?T(s{~{VU&9QQSBuO-UQ$)v9Tz4NrDoqF;JkGX zeGQ3nKc@51qN1C8ii*2k&HO9icXKLfu8#d>s_vgUN_m8o3IAi{Pa%t|U>4v{l^+>8ZE#Qy50K zb?}#)iTOCx?qfqeO)~1f ze%k_|Y6f9Sj$YMcZvwLaB}4nCDf>@QX8g2o{M5DX0F_1kCfTYTgwcMJt^IFn^^51iZ|Ke|5f^S6|A|e!c~!G3;FG!{ZG>N0zptAG`IcYo068M zvs9s*4cK`Id+;9qQ6i^{VIVf%2r=bi_$Q`=;6|@l4JO~Ix88ECYmrxLGG0V znqS^jaBrH=mj*sfwB37Ma@Mtm!%;0MvP>{C>C#mXKj8LJdXHFbrjj!SG;6_HAsTsK}nd7-kS*2E#M- zJiol}dH;a>y3T!_>-#z1bD#6QvmUw^sCpG;<$(J8T-F}7NWavzypJTMqHZ?;^_nta ztoOn6pMY4i*7|BqlXZ*xIxm=c^#eaIy-mi*JpteAB4c8O#7$58cA?fx@QL&Jwe~gC zx=$MNG-i0WX#;6wa;M~qs)|+RsQocGL^v(*lo|oIJeVxSC~?V!>M9lYL*B22YIlKc z@u?_>hT%Yz9kr%i9LZ-ZRbn2rXztv!gu7=tSvjzmYh|)2r(OP7M(Iz_eC5JslvCt3 zbT+73aLf0V>`l#*KX%L-F#>7ftOoxQH85e)n8)1fy1zxr`8j8_@;5r0t&}Ko!_v1G ztrccS(~U^~q^IhgGGpC8V!%(#n|Y8N?k(l8^Rz%z&6qKF@%pDRP6}Vpiek^}R3P8= z?VFywN~FLxgQvektq?ShFGSEfeI?a(pH^PO(;9lnEq$%`;h~JPAXH(1#o^MQzju(_ zMWStO?{6m(myS;$Cb5c?A>+QT>`jq-(doXOmiDAmOXpkEdWZ6k!^m$Zgk_gj4LUCZ2#O(X=7_T)P4NZ{5(WFYs&~*siW?g^eU?L@tc6OAD9hT zhh{0pZ`g;ifwMRrZ2(^CF|W&J?3^&q^{eSfJFcYRjKG8&x`YDvHOnzVVNhHo0R$x=VU-5x2`vYH{C$ubGQ#zrjqeL~Q2{bMo;L8blhku^t{XLlIE z)Z=sR*9~;LG|x_x8=kay?dZfL;-babq4at~yASOVobdeW@E=h21-3N)=;Dixg%G#+ z%JDYetf_%!>sPOi{NT+h@sJY#HRdd&Tdk2n5(!W>*xdB5Xn5XOZlf1R<$H_b(5O1`=vf~Q5{!F+&6>N#HEzoV z9IMwjP>cwRLHN@vde&t-f3oAWG<s!0d_& zW7zfL9sj+MxsULWw}l;|+6#N^P!mUi+F`JjyP@~Z9?$y-*K`+m4lk#OJE?uw+b@@(lwo-i6obzCtLSkgLtofu& zM>WT5uFg=Z&UQU#betshu6(d+AW<_LP^3)b2P{bVQK3s!B)ihX!K`JojH=i2Oi_ir z*iDvLiJw_JB7+BD3x$M&gDN4oJ~NYHkq{brRaMV0Vs0YeEi&Vg3E2rD(NxYR_T1qn z+}G}!s4AQ+Cbdga_rd2xRlw_vqf%h3vS=oq8G!|V7rowlCLbL$5_!5J9-@}D638bJ1uWk=pzY*$7w}s82rZsvpQ#kg{RN0G#zfy zJk@s(Yl=>4rSd1L9Q#7lYz&a70x%3cOYc6rWvPAR(f&+E3w{O$wjl3>@{1_rdha(% zGdW*-Lkf-ujpp2f!kzfA;HK4`Iv;|p%^+52Azr*^Yn8Dlaf!ioGjy#0rgPFMu>J7$9 zqG@3Pe!8PWS~k`C7N4vx&3p0s>Gj;N=U}N$qq^vi>9bRG;xs|KU�i@w6>>U};B2 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 JwV2V5{|5`dlT-iz literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/designer-widget-tool.png b/src/designer/src/designer/doc/images/designer-widget-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..3018cd868d49499eae0d4b1036bdff0a3a54409e GIT binary patch literal 1863 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

y3^4Xq;>Se)G>D-r#sw@1QJsz_|e@fB&Am)1`mq z--EPYE=LNYIeo0H8LvStz~8^){?GM)Yq$U79~cedk-R*qUr1ycfCk@xZ_X<>b=Kk* ztPKx5!N2#wlb=ex!7E=lmp^17$(X%-i%_TxvGB+h4wX z866#EaeEl_vppnk#gwOSQ!6xUAJL-O7>Z@<{Ia{dJ2E>v`{(2@HSXEuS_b+= z9}nidp;QYBN_E73Sdf2EiNZbtQ$>X3Tt-fe7>Thp>bwq%K&i#;TOmfe)=^c3_Qv;y zV7wi^>4k~RsAruuVsbZsKJ0y;$?i0c8amj z0@bF;h(1!Xe5I9966}~J0 zB9{R41s0J}7|5?W0v#NF(sKAc-U8H8X~3H!zlis>&8@8BSVCE_X3d?`Em?l*9<^6E zXg&*M^8x^Qff!}yll#W2dP&yBMqB-=cP&6*Q?pEMd*Q2fCfkP}rI0X)TpxbW8Fu&p zG1o50--JWcK;LUXY?J9!^8r|z52x8^$~PcWR3w-Db~ab^6Y!O0gQaTECwzQ-pcFte zn&3^jR^>>FL1dxp;ZKxpAkR>I0gFOVSXii#$r~>aP!=CM5shKBRBJK_gx=>r2R8Rd zv)DO$d-$31*kCUxVm6>Wz#Z5C$>q@-s~kyS;v`t77$2q&zh&^yRaA~W{ixKx$v{g0 zZh5uUbyPU(X%t}5gd+g^JrRHbuC6S8*H-6U)Kp&A1FRH)BB4M(F%Smn`ec=K6F7tO z?s#TY{d6`++N>-57UmR()M{HKmT0%rkY-E_dHfTSASKy~LcLE*O?ksOkZV%EB^DpWV(TJZYn=uOR2 z{mgb~6R}4jm11qOfsOB9;CWsy_thJ(-LZa8pZw)LO@ByJ|uiuriva5}H}wa{8A1Qij7RDT$co(Pcl%NB5F5$g20 zOE@Fx;5ixsbRo0B+Vk?W>?bH{^+uq?I;0~`^_JegAd$NVRPsNVnAL=@^ zCtpG*Zk$$;Amcx?1DRH!Jrmn2DN79RbPlty^Z4K)c$@;=khd{->Dk9VTI%D$C8ixGS0Ili zr#nE2K#4{o$aZpYv-&ZVKM4IdJYyi5@HO(g4xc+*$B42m!&#vF$TcDAAYegZE_x;(Qq38`Qxw{JDP+>F2uwRlp1pJ5DbnYu#!-m* zlbA!(k`7S#u;{Z`e$VJX3Xl|!uh3`$$e-KrjJ?U-`voL#D5S`+tVIP6%!(39XZn6n zyKo$)!I&u<%>`BdqccB)NIu49g$yert3T;_c573!oh3?E+mP@@LBrCoe^MgXGv`o^ zpE&yZVJ1JQ56A=3hmDJGTY3CQ$d%)#Usj^Djv*YpWaZrO(5%pLyg6RpiqW4?Fv-6s z<)>KvXX-^n7&K~%YZRB-l25L<7au@7XsW-w0mR^u8eEo@!m95Lu~|eKt$Z@vD}ZH) zcxOxPTdXGOVuMxh59%-D()jQr3ml2=02W7!=NbUXmoP@msQG`ARZ1c%#9^k^r9W&k z8^v);Z0smuUWl`0YN$cU4o>z@}vYX%Gslf(9to;cBP2JpNw+F z1M-;8+MlE!ag?WW7_cqrkT&Vq0#GeRiRl&`FSiw_0^q2uoEm7>-flAsqM;cNwlJ3)e*KS_!dsq?ycLrrFQiT96;)Cqv+>T1M*J?QyD zI8P-5#UKncpAHhnnJJRT#VP9v{9>BT&(El#iUwH(;1i@{N%}2&OQXzHk7Mtp+DTLe z5jfUY)sf>0GVcJC5`H4Q?&3(a#$)fH7(T#re3eL}_!)Wzi|>#AJG7-jln^Djm1w5f z6%d_>rwrZ4nnSGs!HgyQoJpF~wxf*$i64LgV4?v@Wm3PuT z4TOm}F2I&buZf<360v?3i%cjFH>f{SZ07EjhnJF;)q5-P&Wo7Wq02aA1Q;IEOb^En zy+zD)g)ZmB89B_SHv%en-zZ2d74e{;Gz3L>-~Wg+04$3Oc8{vxZ7in}sO6o0WQ&hC zzyuCo8DXIRBrjwU=rxFnt9^%~d#mM{RrP1b7&(sboom_$h>}h-j7^NH*vyaZ+kRhA z@Fr_|r~Ri9`H>GmHfb&Iflf>*k8mNp#hE(X__oFCis6&#WY~v25A$n|K2=QqcN0TH zZwCW5xu*BZI+ZlKK2P$6COOmnQFp6a?a_Ten(3$eskVXHSR}}i5kTY;s8TG4XimR= zi479^qLu_KM_)0oyO%WOt6-F>w&vyJ$uJ4j;I=Ahw@o*~rpjtec`#JSAhR{pYUErD z_QQH8O?w0*%lbAZVJ}JG_$Cj+)zDA4YsqS5E`7d=Pj>5OJoiPEl0WA^-&!ZS6VhkJ z*hvYj#*F+~8p=|vq-rCXV)Q{tNmzStfC`1Cbluz6LpO3E*lIm9;_nb%piHdC+U!C# zinIa!DV%uHUnpOE&w-IP&kSH4=J3N74G^Mbwp5xAy-a!Rn!*=37_zgo$KwRPE7fu~ zaoI7R2nrJHg-5+d6Wu+_=$r8!Eu_eld7#GvH+1}?tzN=F+0fk+bmY8pn{3};>_cqN zk#54-M6mqR?XDGEE(*;qzPMBdc<;aN|^V0r@Nz2Hs)ITN20cf9Fv^!B6KS1uXTifA5bq1&6(>%wz|4{rGLd< za?oWyH=J{d};vifHm`@5U{&u?;4 zUM*9&JP`&PRBD}M?VW7ikekvasfNV;e{D+uZF_>93}4lgU+Tw&B{J{I%73Hk!|Ijz zcT0Ym7f76>t0#-0eT^n7Ljxbtoy=ZJZmFT6vXm+>xJOQLyw*V#;n-OU?}dQ{jm99M zqLLOv__@n+19FEfFt81-`&Vv0E%q7P+GCpW!Q_ljJ==ofu3-b|)yxeDJiB~Qedv4g z1ryxL4}SDi&ZguZ@K6bk-;Ml+XHpF?E>8yMH(+I~g|&Zt5iU;a#cT=dvmgD%KF9F@ zm=9&<`vZG@p8Ki)PD8a|Vd_I+zM7>)CLlMKAFiM%+AJYWc2XUC{ix z?;=dHzOJ%Um_xuSpTe#)|MnSp@z}4Z+{AMD z(?P#?iBg^2oyb|wOl_5+>ksl+wJdCI)230-{}S~J```V2`{R-8^^q=v6e=W{ald1YncERL4ts|hbFE*};WP(K1zAkdY?5ZH?W zpOKA3#LoxV6)$bAtZHk4)_Zdc3vxmpAq6{iFq&&M9Ua*RO|Gu406~5UzFk>_H9P({ zfGeHT?MNr0E#oyqO~co(>^zwOigLLzk?#7haE(}asS;(K4+3qq02m7A15lToi<+*y zj{mBlkcv_d?B*iIwfDwhtqTB}0I;JS0AHyB-7t9u05*agxw?jWHouoB6A}>6DFQU< z=RnS$Sja2s=oSDZ2|KL4I>`t6zOVq;kUrWkfq*Xvc(Ot+d+1Uhz?}R>pFmV4gOFpE z;dKCUJ78$2i}}4gKe;qGo+(2DMb>UvK&Q&W!XO6YlsJ`tOgdmM()SXDRB;5{DBu{3 zx;V(@y1E3!A;5DBA?4beucu8C>+bnXj(8c!wK))7j~7O%!kz;-1Z0K`P2FAD!Pc6Z znsgo^j1sOS=~urNQ(1T$j@E_%3yRxj&Obigp=m4%9SdoXqCt$Fk+DE8%>a1ZBuoA9 zVDquG_X7Z)$8M#4{y6SG7MQ1ewFU!Bs0yGru7J+5zuVj>Qbf1sZKG!5NAM z=hoYb2FLZ+fnxL(fY-TzY}oF>4%GCNN7t;tgXvVl^-YK3$dkPCz5!msJh^cjvwvK` zE&%kxf;wF76lv*A$4mkCEA*V&kI*%>aD8shlVioT9%sfsN!TeSBh{26{&9vd!RMKv zKFuYT9?1NkmHKc9uU?e^O#Hikn21eDfUhdy%6J@|4)+skKk#^CMF%a2{|I7z1)I~f zJI*)!G~`<-6GNT(5C^6ELTOD@Amc_QP@0mf1K)4Jh~B^B;l?)Y|7Ps`+-V4oHCnk3 zl*Chi6Vrr2DK&}F=*WrOZ_MftM)9v{x_yx?Zp<1;&SX=!)`4}C4wzPh^n^S%+i|6l zD&g`+Ej2MgS`w;)J7#Stsw*A7@{gB~cg_^$z}jbh*X*#+VAVi%4QbBpiQWuYVpu^B zAB-@ADO>Ra&__Qpan~)M0Vg!ydY%Wm3FxQ-F$2mH2`9Qnwjm<29A@qO=0w0^`>_32 zXc#9W^|*cd|E#o&cZ9D7i1gB6(8f4NeMvgd@BI-oq{hC! z?{Ol{OH+|7q~nH=l2Ac!CHqM%HnMKDnk>%@9+FQTKi=n%l2J?8Cf z9jQ;{ofAd;<3MaXA7Kv%98>1kkPLNn0Kj+M(Ji72w*w}}f);P1JC6{21BJyI3D{y(Y)6YK

VO!-$a)uExRfV)r>m15#YbYMD+dlwW`-Mlcx?+;C zc-y6{x*G6cwFWY$^nM%j)fLmD-;w3&7_PAae<440YHv7^_B9X~1A<@xc;=3MuoiF? zurlk_gXELS+B`3cpN?Mt2_a=%o*Tw@X6PVZli9gb|F0*+Iki+e(ibLQ8|TD(9+w>; zKnj#7tEw`$W?}vcI1AyR7BN=bCW$si{OGerH^J!WWm%VuC7$M`l$5mT-cBjRh*S@fmMiU0WVUBSElCy!CUArW%%QA0;q7~} z3*KnTPwqV$0QnsP#HIQUNbkCtaRqGc>>A&Q|5`x*S9>|pUDq);1MvZ8V{)9JnVA{B z)&^_w|7d;$uRA>!pN;+C&z}jxG;dQiw6r?>o)*ge4z0}odC1kBd8sGx+cG+9ghG>w zlTUas-bdJ3&CTq!+B;T#*N!3G;?rElgO;**d!!vty!s@+HohMp9l3brWeT{**ayg4 z`4*65SrE~6WLZYlM?WWe_oLCC5ynPyE+&>(U}j15i|hQk(mB_4f~olR@v^>(jz1R{ zw@;!EDTL7zN*khkr7I#Z+PHhgc(yMD zrKUjY2;7fZ5r!oh;ZGNsWiD?#Ci{kF0Ppqd*Vc6zif)O_IoG|<5PUnCPu*0B&MTuz zJ$^A=a? z%iyhkN52iVmUFiz?~PteC~*Hgw0$Rk5w+P4Vb!IY7d`j`6PP8lQ)jV6>-pP{o<+FW zRk^Q4YXwl(mXU(;>~$4#z7cJy4gO45MWH=`qbT#Ljl8hs)0aZ!sI)@mYAFLnX6v2S zYIbl-|MR7mg;g95jfhwE`X#C5Sc_7pzl3~0+z6Kc+HSoKPEGpAxxE)nr61!PXA|2v zRF6eDTK=P?*;^KANX^iTj;VPmj@T^HB$q*Pwcx;Ex`4sV>YX0Wa~sXJTi3EYh-jCV zC7IFQy^U6wiGA|hmI;sAhuKO^wRa=#`ujz|z-Ao0FuaLx}%hq zOcmhs8sH?t{gvr?uJjp!I@-({B48k*Lt`{N`RZr(lKYMB=Fhsbr5G93MY^2k-xB<1 zO0>V4p(Y}zgf;!N=)3!Ibu7Yd-2Xf`Pgo4yU_JHKUD|F#5)F0GE`1#73>hpX^(4mH zL)@CN$OFknDTjoJw&d#dl-{-$$bgZdnuFtoWmj0`*oJ?dEJG~VwLL*NdyjJ-t+lzu)AQLps1X{T9U@u|6gg{eFi zM8lqTKkaslRqwSSze)l{jvi?;>c;FP{Q7!2=fb2(^34amoG7ATbu4JR?R{-=uSnVtrRnLGR`~uEs|g zjyV|{BS7V+cLBDZo!L-L!-yu^o3Gvh)6 z4F~9x?V8^Uk@QD8Mt-qVHO$tWMjDbbH)lnJ8lg?RpSYciUvUKsTCi) zm3o^=({=ZHKvHhaTi)&`0?WH5TW;HZXU?BWA4sRKFH82@(S&miF1}h#qzaCaaC^RW zXu2+313izP)z*jR>t4^q2eYQUX74aUAzu!&f@+h1Hh6j%uJOyt$>Ka(=#-lVMGtyh z3q0)DeI{6FdL5vVqq;2a<=)3`89}fKy0a!9G>1?OyPlJu=BG2dKR>JX(VnzdP)47l zrof9a4l-FtCYF%zxH_Q0vM|%O{CJcv zhD+TD>O&<{!DED~qcMrrEf3$vNg}@Sjs7Ik;jK2_jzS^6Xp)4$lcM6txar~I4sU-n z=x+9;Ja}^&smtj&Tjsj;H2$&<&r8)uzGsCq>9L$A3C)x1D86HCgXt}ajFMBgrTYp2 zd>?a!@ztp(`D8R6wpK=iWmNM4RUj03SB94R%6_R)osnRw z&uH|Txsh03Hd(?j@~2|>45UEQ@e(Ke+k~WyCNlzhVJEvu>z#$6<4@xL=;pHD^7ge? zQ!SCjUSa--I3c~Z|8BE8{FN=_ift~WJKtLu9O9l)YmSLShcOW(%9_vhcFw|sM`ws1 zCjcW{QJALqySIe9KjP_J{9LoAxI?64CVlJ2G)dm#x4e>7b%Seh_|}yBL+DIH>_k*6 zs?D%)bC{T5FABG26`M2OCtX-r++S(zJ_m0(iYAlj*j}KP*{lCpIZ2=y8P@vc%vVS% z%u%r|r-SX0hP%uwLXqA=U$hFRSUx)2bj*M4Hjj?*Ay7qvln8L>Ek-75!kI349go8P z`llWoTwE3<%gV8&i_mN%WvnBLg42Z#OA z(tHbUlXY=ZEUx)%F*7o}4F-GCugJj?W>RS)sfYwqgC7xRLuBQmiT6}bL$gyb9(}k~ z*=?R59Gc#0)sx{o+#)=0xP3F$jdat>YrRCIafX+m&jSlc4n%|RV-wR5_h)KvRk`I= z-X_U*bN5IiPy2dq2&Q~h#s~6uRT7e?#dt18p}iftR`L z?Dol5FI|ls`8|91g8(6y(lU8!T8GC3lNiqiW=i$ks;EHi_}Eo^1shCoqqp6-1EymT zJr%Yn7QNia^`oYaKl{-vZKkn`8|=WZY}aEDjkRjuNAp9naN$NuwILj>BM$}rIty+@ zc&U|yrodA8mC4|}Z!DtexDGWraiP_^HYU_j(%orf(Q7HYy?F71?Hr)RU^HIjmsbpS zoWa-Fl;e81!|1m)Z|wW0!Ok*?#;+gD+eVs!XjgSWQHQnoU(2%mMNyT$vz`6`L=%;V zPUu*_!=kJe{UhH;9qWs_)c1D3o}u>EK!Jz-wrwGAN~s zCH9e>Fwr0AW85xUHI_UuNEJ=*S8c*dYDR*iFcipNBY3vY_O{<5T4R|9dGij3HA^5# ztL2ttGC2Z$KPJT2n#>r*sumdGq`MJR7TdBL(7_MnPSrB+RM09n5G5%}=Cq)twMc7-1|DGtWKy}r*^zRy1o?*d+2zORb$IP5&jjHzLU)i@ceQyPueTB;0 z4E`+js~1sLdy(pmhk3Ku*d)QR+M-LA!k8dtd z0NT&3f8^u?8F;LdM>?>ER^){|L&VfO>0?7As3&Z4cVhmF6Hj5srHYP23%S4u&fr_k z&mGb;>xI&qBh$?dOpor0BftFHQrmftmESSQcl)y2MsiWWJZx)wJ+%>Y%0S@32CTh& zBvvNLWb3n@BZ`oljmk64bF6NjojBI~Vp!G8__S5duW2*=C9=HLSwp$MA> zf{%red};$WHYg6q6bm$^egmrr+8USk@yS`%8zOdaeoF{k%yksSn KB&x)W0{<`PhQ@#Z literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/qtdesignerscreenshot.png b/src/designer/src/designer/doc/images/qtdesignerscreenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d71c986df9573980ff5e1cbac30f8c97eb2bcd57 GIT binary patch literal 144869 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 j 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 z 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%0A*baQG zC-Z??hWgU0v5Opt+Di@P2yG>*0XJUW_z42OMX%6S@7ul899?y1M_Se2AvYl!;VYJv^3o(x_-bcQm>TBgcruw@4tlb14<-!bZs`+==EvQ%S=nF zvZHS*jv!HQc!X*B#a9J>=U+=A&5i30^vp~K5u`I}CZn^KyFWu0o>5w(mL!(X<7e2! zUnhYpCW_l&y}%>cJgDE$J0T%rDimbeP|Xx?IhnGeuxn8DqQ0SK#zbQT@LR^PzO-ey z;z|M7@py17Tl{^Y&|y%i#!`MS=REpgrooyB52#?~27!LQ>o_NUX*0Y}?K{3Ed`M!L zpsPbb9b&T;SNUYA-HM7u&G6Gw)Eo}<=a$rZtJu^WLU zvN!fJIS`Hk0~1uE|6Xb}MfUdmhQTm?A@ud*Lv+J=E?lTfk#7t*YES+&lWprP9fu(p zhJe+J$m48{5Hl%ErZ4N7gqjRfkcV`o1r1sLS~0hKYe&T6@>ez;D~0jK+Oih zF7~j)YKojzK4mh6S>UK|W&33q94{-`;rAJzZOPamGB zI#rMu$-37{2?m4SL zqkWI5kp{8CL%h7dUaa~Xk1w9nDU(pvh+lYBueg^(qCX7&;bCcTtKI5@{ot^P*7Y#e z*2M#hyf1sd1PfCUK`gRd9Bl;)`@^8enWH8sO{3_)004x*F z3Qcd%>8t61v44EPq4u~_tjI%EGwlIQAKbi(^Wx%7PgCnKSbeq!K-nKUR( zDHS~9a(RyrQv-Ct{fJp2o-FDms6fBT)0tK$5xb;_Xs`qSxtIFvVY}8%NBv1S5bXQ< z%9;k71T(eTRFxHYOxX0)=+fOV<*mC~O)XaFxUV~AezKH( z6YE02g?Yd1u@&9D-t`0Aj$%P@NIho{koOi>`;~ee3PH2sMj`)qECO8Ki^E(DGX9l^ z-8dMS;FV@uqs}jZ+5WHY=O+vDm&xQj!mWN?D@ON6`?*ZsJ$_xb3l-5;;bqae(y>R2 zU59yrZ^S&-4{|Tx=S%x;O|^`vKi-2cHqc~qmaw$M8ZBqd$5;$o^P0H0D!iHsxQZv% z{+K@D6>?sPDA)AsQoOtezi4{X;s03zLWaFH9crfLV0Vu0vBZhdVV@tqt5z-eYI=oC z#32i{^Ao_5b~$+5Ba#SVE1jiFgX-WQvarO8Ip~8uN1si8dWT#&$<^1uMC3n`cP4Ya zr9@xPz4!Chvn(XJm6cn`v9S>5`c(30E@P4}{4&RR`BG#x^K5tQO?qcm_kNwrGVaXPl zy?E(?P6eKe^X@^&wHdGiy51irERWt^HEM%keyr?+-5N`zK`C0AdUFE<-wr1YtfmY) z-A_x}D!lBrz(o~MocW594?xzclcwaiF@*y@XOPk;ab$aZfTydL+Mltr04Fyb;E5Nu zBJZ*Wm6BO5Ajs`*VTrdWC{56F{$n%Ie@wj40fYRCJ6rKt3{@yacH|?M zzbICYN|NNN(G&)?L!MpMbO>a2GibsF|%f6%0}wbTVvI*H{t-VSfN?EO}( z$X+K1I*bCJ8A7vTXY%}<$`;7YH4--^KHET#1#d`dU>Q2hV;dF@3@6@yfn`6%V0JL^ z^fPoXvX1=}rz%w^cnsjCpZ@+ujz}q0Bzq&6Vk)*0WD1cv+iZy&uoz8a_r3uV4_X{M zoZos#_+0Zv{f<-+!?>lQlDt?(acS?KSer3N$-TCGl}YFhDBirJW5u z4d=~9zVGnoyXuPtAV~{>8_0#^(;_bgF84&fJ>!zPP?WHCc$^Q;9!_M&L0EfX`@Kt) z2#Gq<+~sZmyP6{dIP!yu@ybSEK*HlWHE%szB!2R-b5@&1HPl-) zo&~%ZV$%e;n^+^tfqMoUGMyD*TE5X{G`j?fFDcRFA-yZ zQTuJJ^?HlISZ!zczRUG61x_3s%h!s=ZDwYD6^0opmua@%>TCel0535e$IiQ9&dMS7 ztgjMU$v7xmkp%6r3syNpfQS}g!W0To0M!-@u-K^0kq`+0etC6iDv!B|N|Z+DsJ47< z2RpI%m*$K~;xM~S`@DeYXxkVkP*!jM--hT^rh!O8DxQ<7s#VO=AvTxG4TE$sJ{eF+ z6><;o@k-6t#|Ty!*FIbH0rO|ZkN1x}$c(M>%qjzX=h%cb;&|AnVXhz0haZOMM2q{S+Eq54WSI$PRMx%OD?hK1j=Wh=DJw6Y2xzSe-X zXZ@T+8Z{z!gu}3_4KxBJNn@fXnE)IdBx><@GUMIFx{+4(!(9{(E&(oPns}1=*i=7H zfjJLt->(To1l(O3)}L_xk$#))?zZ*jWAY#Im&DQYPqZ^eK6LPmQ&woRRZ5Q4kNtrE zwRiL@d3?_?y7lSR+w}&YKJAkQ7`1{(nrZtc?}l+tyW$s8p85SrGN0=KRj|RwyBSFk zid8W9YFxWGlw6p`EymV;^GTLIt6kH@4PvA*IZ;M;BJ6nayI@G3=3e=sHw`!a^xJsg zy!h>cYY-Uk-=|XFA#ecmz&A2!1>!$ZxpLt^&egBfOH!wpz3=jlE{G|u>864}l^akU zs;)d7`6)D`(uxc*I0FI&TLvKcXNGHRyQ&b@$V%h|Oks(KPrl3*aOt$V?xJLuH9x*< zcw>*mWvut4Xsdh(G1nFDFJF=ZnIC1(>4!>#ev)W8UtUx|Y=t^IBYoww`Pcv6TKTyB z3tBp%mU+K;?74K3uXlchyIzi#7qW{vA+2u)^ko;JhW{=_Z2KG~5lV;eWl~^r!sf?aX_Sz3QpGvqJ=519N!Z58n_CVvRLAxGPUI@?T(9TygV~ ztC0RQ?6lc>5>u^VS+uqKATa` zKQtzT5+MDD_kqXCw_go1dSr_c((X> zUO=PdnF$H58&vb0Hm4g!Mu0_KqW#nLntf^AV$n(YmwsOI%x4#*ECpPKZI!ZUQ%oJP zrH<&0DlYuZO1&y}97)n&tV6^=}0NL6xHcqOV1o!jE)NP>L+xL+=j(46e; z=$fj#*J;5+%36_Czs{5awf_$~r0V)ADtK1m;fzl{nO^NN_}QStZKc+l@guziLZ#&0 zjm77sa68u4!2R)5?6JnnpFOiha(V^k459;BIvjPO-+Im$%3c2;pb;shvE|?gQd9uM zn%BRFGLt<~QWL9Z z2niGAll_VLWcc~!S4Mnwk6)(HUzKurul<^cBrQTiNY)yi&D!sd`_ME?!=59kQl=aaD5LuK|=lBH4rr$cA$A#|a~! z$M@&c`WQlvt`|d#on;^(VyNO|!G=a5=?gGa{i0;I)|xLnV=9MD2R%TFj8tyeu$Z(Z zm-^n8#9&tPSP9vzx8NuaeNqLIf&;x&PM{uChO%j+50V*;#(?r}8hZP9@@60W>>lC4 z6Xg6Bjr-MSUfC=fmyw&#i>f!&N4G(9Vi=8>3T3|d6#ueQ9jf#%>_~DA@RbWz8S+p4 zSS4^xUt>C(L7f-nK$;<@Anw21#Tbt(I5+s2C_jO$lMH%T#*6Pgs7YrORvy-yWQSBSn2i@Qy&4jJMU_8qwE|91!iN@aT) z3`Rtcf4xF_gWpp97$9ap{o|-zqRo*^D=)dz+m-taxX7sy`V*=IC zO7q_@`zM>t4$Jip=NWj+B6jmv!8#3xdeu)QXzM}{uqTn+p2ulo?#Gd{%`llC+5DTElFp>g&=rxOa<4r`)UTiN*Vu^{k zZGt8@W-Tg}a{2D%Ru|PvCr;K+8RJJ{bZ%7+EIgFNpePVey!q-aUjckfabYx=pfMsu zyZ(z`9dsTh@-DC5YccZgU)z?6egYC5AL>StB+b;U(tT0`s1US zmSM3iCzW%gGV8fKO;h$R{{aGzpBqmXnKS=X87}+x=HW7^iyx-Zt^&)zOm%3TI5o1e zRtz*TfFpM%NWODh*h(VhI20&zE}*fGa&ffxhg zp6BnbC-Y;gEe^*UySA6S&h6i3;D16pUZfg%kpA6G3*5YSTxeHp_=r|-F?H?!Qy9o~ zKANs`4gTu>$@14)i{n2ao_c>Q5#y8bbwQ}P9hd!z>DAVyN=ZJ9rbZzbp&otUPy6JJJ?nigd>n8|;dqe+Fn(Z#?w%^Nnus7A7Nbu95;uqco6Vhap~v}} zu*-bfR^+bLWR6IlzmcU1sxwX9Pn{M@y@R`dry{i)cdDW89(8tO;;)|wKUoq9c-(ou zy>Q!JZ$|;qF+a6QNd%qG(sH+uj$U83C8q!6d(32cqhQgzI`3iZj>P_Le{9hpKH50H z-5Q(Y_dH>CZO0h~X=MvTjD5 zPYWC)W&uR5{H*)&Y-oLRE|87@l&~_)0halxVgk}Vx?aM5sP|(dWU8;{V>~&}ksJ7gsLv`EsMH((Tv<>9x~x zGK0hJX!?70hw({Jse)DORi3ZM0TToac`Eu=ihxFZ=*P&&VGv>^?6BG`ml$?DTSUm| zwUieKiwt6kc?^ULZ?vAR(QA1)WEZK{@B02jsDPQ&yGC3^s&1zk`=fjeC`8~o`j8Ol z$OHe=a=w(!r@oRuAniMMS~=ByWLRif?PeV`{5{+4W-l^ z_!{*0mecVXFelQE^1?O&es|c;dZ+788qkDF*^;)%6}_?URN8-rK| zbw+`EQ~dE9LRIyl90go`I1Fx z!{xQhOKl!VzZK(}m|^HIu1=OI*+4tyn^fZN2-v*)=L~MA(*?Ui9R@sXIaV&a`H&Kj zHOM~b@osnZ%^&KHr>LEPT`4(|*$v+u(Nrl3t<6nRYtqH+e+9%GCRb(M{+j&xN5^D7 z9eFAej0wO&Wj`MoH3Elxz@0(hi<#t~760^H5tpgA&K*FJ-5$#hH>!(#$GVRC;V@w8 z;qw<3G%baGQjdffY(h){_@6F!)W8S$%WtJGS=-Q(cR6l5oc8>fwoi>(*!uhrUL?-z%)5v?V*f2e)~gvD-; z{S58*yE))@9-<wPow>44A$-!EEbP24l>m@FS;vW z*$~C=E%qy9iI_%x0M2wU-9*Yyez!VFE$J(vljpHB?)S-WqB4~g=hlU9LqZ_}8)blq z1BMb?vRb>+gfj))-ZHVzQtkhHI_4jg7-ZnaBB0Z{Db4Rr&x^AM&La9bOG? zxgyR!IYv=)kuN|(J=Pg)I>=^fgE7Q6M+>1|E>N3L= z%)aVp)*uf|$49p)N#6^6F|`y9a%O=wMQZrHZka0P7-+fE*XpMBg>^U-B}v>vzw9Lt zTbw=c>kLoa2k}~cgM%$Xqtbd0Qi6ZhMW6+O$dx93B#4&|VsZ5E!R=GQpb#WwjFYG` zgq0{}D@Lc9v@0$nD5nSpj9raJevaH+KN)zFbWZ>!%i=U}DVn=>HbxHBb!EZ+R8PUx zyJ9z*6AmJ>T%0;u|LDpb{jgP^Ce)`Fnu#80(=Klv(hS#pVu>6j1#2jSWuM1=9lv~> z@5(I_)3yEjP7BXr;KRl@_wU-cVb`EKsEvGKJw@2U_C#i)#j{~x#Vm?qUJ|}}GF|Z; z)*9pdqeX44tP@s)3Xh|e={EZm1X!~g*PHhOS${?e@pi2iuYpMv@jO00JdIs%XR27C zv#ZU0+j78Ru8LUY4hSA~(nh68`t z{vpP)hQm+czgHHb6aJ>rIs)R!wi&8A8_GAO*O#I91&UT+u(mT{?6%<(8S(rBd0DS* zO*$XDwD-@Ii_6O%r=~DL>#)3av_Q^Zu4)w9G?1A;h1>h zEN}h~`Vk zOa+C7gJM)N*>I|n+fmUhIXC;z@baRHT(&>z4(^MMF@#pQ5u`auSS|dHnX4kT>wyZa ze6{qUa@=gyIm8c72=Iho`q%vObg2Bg-BtPO@zkQz=i6bTaTta5rL3}~Lyx;%Ia&J> z{$zID5{qEjRK>=G5^+tNKXq-%`tRgT4xe@3qJ~vq5VGB7pr$U1GlIE>tc@5O0fqZr z9pHl+e9ks-C`AH*zAaF;dm;-AIAuIo|acRsN`4I*F0}))-JZhBw!gJ$o(=# zQ0s*{7Rle`b?gYdZ&XYp4Gv#*)v=luJHl%+99Vg{UIgdX0$X>JUTns_^*wERToZ-R_?joEhj*ST*0ZhORO_VJe%dN|R35|`yUGG$0Cc?DuQSav#jSYCZ?ySpVx$fu(GK@(j)4-b7N`j zglS+Xr3<*@Y3I96*4I9#YjVgReDuF?EEQXTD`505Gy$vGxj~_-D2}S4KKT6dpP~2CBmg2CYemc@*r|;V z7a4q&hW&m~V-yx;`YM)$o39!uxXG5ZBI&W|Q+2wZl7fn~#vc8#k9_MQ@4?UqBZxiHDNrZ1K}QaYUycB0_}pM4-{s1?_xlPS~FtC z3q##q{6W)%jGSJ5gG!VR@nZ(o}H7)}fW@D$RjB<3vy3X2+__ss`1c#P5*xTM|dGI#Fz zRaOp`Z`S@3N`Z+}n|4Cf3EO%K5gzPZT<2dpvt|R3puZP6pc~)?!9kg%+@`uEKG8mI zn@bSV-*~*GRJa`2vF?9*A5J}5e*K0cZ4dVv>z_-HFHhvxIXMxbxOz%|@C@UyCRSfp zWQBIDGbqF`FDXVc=Bvmo)E@u=(5pj_sq7sWr6&E(ot~>B-6p zfId}M$1bB^Df!EmoaZQZ6SObb6yZ8=u~P3#CZKuLTaU zGma9-OQcV+yZ)%JoOC)&%Cv#MqQVw+skG>^JMbEo9s9h#C^wLbbmRTKmQ7VKTm#lJ zHtOhTOdr)>`1+_;p-xKA9!Hfa2OB6JkK8k`#eB~LSm~cLi1SUvGhkr4^245B@K#=v z^=Jw+ig|LuSa+?mjSDQ|=pG+K>4%Z=@gfC-wB{$A)9p7E-vf{?Exs&M&tepLDPuUUlJ>VR_y8w@-SgBZk-eN7~QH4sx zNDsVq)u+lai+Mukwhq`fB~bad#T}$MHSU#uflDq#-#fyQ(iy8p%Y2++xz6*tL9JxS z-Hie3ec=Gw#pISq`xOY9=Mf$aMY$@K90@3mba%0V)dqDuywX2$9pW4L-`vG4!;3K!PQ%;_QW_libvH(3 zGOLhb_>8J1I3|sd>^0zIy&xoujgy?u&1-4ZAN(qKqEm`c^%$3=i>8(%dp%;$LdPy( z5+3)7L1TPf0+`y2+)i=ts_9Y=mjuHzaLFC;@zGA|smPcG%k`227ujq^@u>A zswm(^RUdB0vcS$}?VlkP7ik{s6ifD=OL!if!_LN&PkMigoA-bS6n!H&U=@hqquI(- zS(NYz8?^qu3~u9;Tu2YP+9{&MGnk8o5LG~@0~l>>wZmJ|)#aO_x!*WDW*pf6B~yCB zbV{tFiR6M;|D1wtL>92|DKuC{K&TW1A5`&oh0(18ejk5K=QJ21C>?qV_BjLY76NtU zOL*#j<8C_R0Ic=}i$e z15FvVxikOzRI`mOPQnKua?eK4Ki5d5RD={`LlwTL-tjvL>;Wz*xE3@=JQ-wy0q0w4 zxX(zWG8ur9eTnC+;4b|Rg@)m#AWb3&Hw{laE{LJ(O7ROAFe{X_Jy5-s6a(Q~?=jcn z4Tt2)(aG{KdZ${;HeR%b3OmlO37MCTOqVMX?I4aie_b8wWh2dj{LH!YfxA!sFhXxT zcpdT*cIM|N%d?KOu+*u))e2c5=X$t3Bg8$zl6#RLAX%`&6V%-;xdB|xEbQyaHViaW zMo(Vg?}<-jnUgM{n890)Y=f1F?bwVBAzo#mSm3i8PsRZ{6p$d;PH zq^N(0!<5`Lbi1c38P<}c6$5cG^F9JW+Vm!ii?Tv>7X1+*tR3#H5-WRhHa<_) zbvKMCtp;6u-uzo(U>~7DinTN-*!O(kS<0ow=v5#FbAVk}vvJ&iix~;g293FDM^Fbb zq7RyXuYzMm^@GCKXg-UR_dth{qv*h`K@WcG*Nk7_r>Xo68hb|al)`r2*XE}}wGID>1T7xtD*^yq8`4AfsIKz5RfQiI*22u-x-Sc1; ziA38wTF&Qaz9DW@w-|zK#i0|(@x@*PVV$D?;A;TvK3Tkz1b4j~k|xPd z?;J*WsW#9$#{p2o41Oh74HowuE$Y; zXPUkNIlu;-Fo?H4Ug<>yhZVO%GF<8UaBtV+f=@rtk!hwBq6H&WL2Hq*k)*(H`oGY# zgvvAJh4PWl`#^s*6+xm45u68$A2?;8(0+4VCuG55p%D@6?>+2vA%j!u5zV$s5!GwI zT(#PWkQrT9U5Ckg#NV8-_axV%z!7t;>Og0R7xf<9`sh~Yjfo3k+R*lp@aNE%*&D-J zw>PUQ9`6n=)pB{cl==Bg3D^|1Wm+kjkmA<%St&y8VmUyawUUnCD2_c6Hy@l(bGpuR&yw z5a&ceV?)xXjVS@3{#GRrN)}ZB)xcAEJIl>8##3UohVKXf_9C zLz0ma2xk(uL(R>-p&*lR>}OtWkT_8={xw;wst(Uv2}2t%6gc0b`7Dx9mPEo+3;8&< z47@VkS`+aiG`pt*U>egPc0Od)0o6g<`Jgr2B~St=VQ{nw2^GKe012ta6?Z(pM{QhG zjOSZUYOlBKA4*Y|0VhF)&G9xrbF5mBY}iX=I5-7rhjNMOEVAR|+;|8~j8TH!q}R~U zT<_Ig3F4HXA_&TPEZpAbEfM|h-A zh2PP8XFC+PYXkUi1`V_|rlK_F%_2(q`D-`-y>)Vm?_VCKf`2=__!V&OmS|rJDSs2r znM@ywp1JJig#hTmvS+v2zL5@1vCE<=fA7Em2GQn5oV8U{1d5{_iomJ=Hve=3uLnhp znhYJItcQqSaV^5!j0JO^sUnD_7lCKVTQ5Pz%I1wsz(2I{RYVhmO*@y8`mz%w#luI?MY=W#U&tRatxv zm#a6=(cfO1bK+@{enKgr|NV-kYvtX9iWF0Ah(V?S?iMZ=f@D*UDG-Zn@*Zr!tVKL! zoX$!N>wP-eF@`btd6cdXG(d?3rAG$8p&y|p<-+3^1V>P^mpyX$$^NY|8&{J^n5qRD z>(^AF)!ADhDrTI%M=*}UXI4MdqzjMD`*oPgesO`99`)3|Ml>7NCjj1t)(aB;X z?)D$L;@HUWp4OZNzKaT z6`U>V#e_{!Ap|n6=M@!A1k7j)O4lEZ$|vPCWw(u$CDA)rM9wSZ(NM}~N^2v+Oe(Az zlHeS!iWyDhipc#eLZDF!l)OILtlj6+I~;ohT+hpE6Sr0?f4oyK8a;7R9-<9{G-SK` z+VC_2F-=H2VPkV>)f@-y24gF*e%M15D5x_)k^mirXZFQY(_=l(s z?#5DIZ;xQSe50zr57R&nH+M97Q2;ldi9>_^IEqeG~Cnl)E~?!*ZzM6qD!XC70z6G^q^dEdSTgla3LH48J+@LHxra~axp z>O@d-Lb|FHqkR3ZkaKr={_9pPiy_VmpxYus}9yZb~mB58_wqwhl~f zkZh`XDq|`K3w(~{Y@Dibk-Twir$qoapMjg@dk`lSb?yAq8EpWP?+w;K7~O1^I7W3{ z9&Vpv4s+04nY<*+k!BhwJ8w&uz-E+^9WL2fgMbi=1*P5Pbr*uHp zl{PFi_8&THg<>6`4MjOm%t!%Qs5oE9+%b5_I= zovg%~01oY9o$zg6HZMw%g7D7HaXoqh?P6t*kOdK4k}MKa=f-}ht96w^EYC$RyM&DzSY^MB;IR>LljAN*eg^6wdC|pLUu;fUH)ORU9BBccvWd6mkLRFua0q)+wis?n)=(FdUw=J>mszc>w|!^9#~bdNET z!upV0T^ISw=O>iVPKsbd@995=_v1sy=$QtI?!&a%4H9QfPEfrw>_B~J1O38bN9#F7 zk7|vaakz+R=>^1UpRj=^8Bp8Zh}TKKLl~pJyvAg7U%*ia8YUp|TNR#^itR{@4W~!T z(rQHe$@GP!olUhB1PE?6ajcd54$)r|Po@sXzGl?sE8)q~Es{{cv`r6lx@MN5MviYl z1o}SsI2JSecI^*Pl`_+6kRtfmQQ7otR}P_V{DErKl@P6kW{n-<|>@u*f&N=F*eF znAHFL*Kqp`D@mHgK{&gY!UbK7js6kKmylp096kQ6inz`2Vh*Qq4zJAwIa0)r-a$LA z4S=(BA25Z7%bqYI&m$JIA9vxT+(=_Lf@A4wOPWU~Nnl&-65&Q9Fa8Gp1#tiUE?rxq zlt$DM8p_ITD1NyPuCw|W6LI(MnWwq)+x`xZtNR+Y+;1d$)X zaZycz=3sXNU^O{my?AA!F6gEFmwZ-rQH1W5Z4qgD<{}u8NVrzsXhPP4Vxr_h9FEF~e_$#cMd-cuUmXhko(W1H3lyN4GJ)qC{cM%A-4F^ZHbjA8M4H6%SnQW_JV z$t_U@dg3C^vD3lRsKN9Nijw(fe@6WJz6-SUN0-|zI z6Bz{@;}}84%8-&bXFdF^YrcI-$Xv2@26M7@h_$)!B;fi+0eXGm)vFPbuue(0w8rG? zyN9HzX&?S;EcElsp$a4hivSDjVmoEX3TJ_P?t$>%9ResTu(XdflNnsM$Kb#`b72rLB`RRAfV^!ijr^qQOVER*-2gml69`7z6Zs zB95FX*dMtG;@DeZwYY##!(xHzhl)$RL_!*G6Gxc|OrsU&$Afm z&b>l7o#5FYFA-^tcA!7lW$1Yf;cjh-1K5GT&r{d^ErEv5V$-mduk}-01K+^wSi-RB zEKamy`66tCIDbeSqG?@oy>4A7Ym-|*+9!c*Mb2qz4$85_b;{fW=vKSkrgH$Ia7XQH z_2kL;_mW-QXN2w4i-Xs`X9e1d_IZ-snwd+_c! z<{mAXrXUrbOva@G;y||F26E z_ak0UQ4v9pzr7Xzx~{)#u0Nc7JmDuY`KJ4>WHiBxxWifTZgx~m=O`nJ#|sG#QZ(1d zJ_d;ml&G*Y&DLHgioY2E(7cGeQ0&#)1bVk@OMEmeC;5gSHRNQ+RL~~bs2IKC-k;(4 zNXAhgN2u&+b+|*fof0EsZ}sds%=4aw#3>U$uj|te0mEDUfzUSd!4+O)-Bu$LnpS_q z3LY<}t)NQmKnZUj7&=OA$S?*09^y+Fh?T7gyt=vYkl$oz;c6fY4#=vA?$UuPhu-+PAG=p5 zD;dZw8QM2$e|HqKd{DDxt(X%9U&6S>lou-wA@iSe;55SeoSL$t8P*O!*M7t;+2#67 z?stWs|CqXaxUn)0w9oteNo7*YVbie;u9(91**Vnolqg8BthY6CIs4L)-gj4J_jm`Xsn(7%TxfK04Z^o`yP z_)sUk%Q60k@uEaLT>&oPNR~h+OhCi(tlgoYO`vhv#(`g24V-&__0p9csC|gKpD+H) ziU7J|)LDR}{m+?Ty4)JG_nqs5WUV6C38q1~V4B`>;VbHzmP^Y4fI;v`=qa-bhrn~)e`fZgp+H5#F3PNXOoN;{!yFw17St6ybsfO?zpXP z-l&5Wm32HIzVabe;s0G;O~};#-`v8FB$))-;1mq%GB=uf3%@WKVfBw4QT^irr*fMxZOqB7`PsNS+Jt z%z)@7=|TBdW}YqzJgFB{TF?9IImRhHa;PcS2c?m6mhf~%$1xPT8)X^Y&sl=>vBc9- z?%H?6s-y07ioDRT=CWl`AlA`4go7B=CQ@-vzO1_t-QeH-;PwwS1-N;AtM&=0o^N@z zQNumq(&5i^gVo_VdgbzG%FX5YWcaN0PNj~0CR4~^394i2BSXseJ zNJ7O3jIV~~xU;wTLn=!lI7vg6-dkci@u-|dS6Ec*Y!&YG1DNGF}VKxy4+u z^pWF5m-wgKcg4WW9XLP#>hJ&A)X&ZkiXqh)o*q~8r@JG>zkjq1dL;g&^g1c~G3Zi! z(Iw+`b|u~Pwn?jxh*c{OARmb^BUREWbMsR%km(5BSzxjK0B0fSdy=faV!BylZ4V$Nsr@|Nxhnjv%w)NY(d@2RXk zP*R2=aD_lyBy(u)Pi{)D>%pZ6vsXpJFZ&4ny34emEgdYZZlJjfsEUhql(uvRd1FZw zG#vjncmOOWQ3a&H=CT*XxC2BB8uLl1pj8p?`6;yh&YzLe0j_^?UQYGao03-f-L{%Z z33%PClm0l(4qMsjYD|_3nw^_`hFljA$>|{di2q zEdI|iepOVQ>0p07dacXJ%&(he!HUSl!k1LupBH{ud}8K=)%Y~5E1=z9&g=oRUtMwU z``a^Z3|WZ!9TI*8Gll^$sH*{1+exMz<17V1hM-HsVGKj&e|3P4%*95DMipG%(nROJ533rMB}ZVKk+0#IFtxt8{<)gyAC7jut_x_d zAugnTXsg=GWK6yI;&4|qRCU)e=9F#x;A!fC-z(YZ{$jt)G$2L1|YiP_20sHwX0y) zHke^3k62EUw#0rQ(_n1eB5+V7z)vL@1I5aYv;0Wi?oFTri=o+LO0rO8(meyA5Fz7Y zm4E!wS-2L>G=!c8x=WT0^uu38Vbk^vTbWOOF`rzI?PVdjhKf&_N09HKZ4N0QjiGDe zr!PF>RGIzv%f|PGA*BF%zh7p*am6T7{(C{+Y2c@Af9SZP3vV8uu)CLOLQ$P3qD0q% zuLyVc_vz)&)P+o|Fh3K(qgCc8g-9Vf9jd63O}gP#IfM;;bsnhP@DW2?MIBE_6qjLr z5#YH7%$lKEkze--9u@vd!hp>U(?Ruxo7;GAZf-gLgJ1xyofN>zyPo(vvg;>f6NRjT~}V+JB9JucWbq%yrK-1cKx_P+?y-}e$x zdhy=@vBCwzA9fyUsm=zFe=dB;`Qq{NG@;W++^ACY#gn}qYuS&#zY`P(H9Rv~iDIoO z1;n5L6G6Q~9zW*a>$lR83h|EGhtgebHT|59#>#}39-?Om1jpM~4>$M*_;&}EUhyB# z>BPt<%y_D_S4dcu58Fy0$4LJ*Rex8LLBy398Q*2Kd;69LI=}X8gLS_!Aiaw8=49FP zQsR^}@VK>rL|f@w(MU05*Cf1;sw}h*eeGpWfI2%gx{!|)DLI8oo^pg~W#U%V4H}IJ zkLwdGiJ8~KeDUvo`#UQGs+=iLRgwtFb={}y7j5Tt?<^{hslQ*XbUk^sJuWAL_Qa&a z@9K+`e=qsiH>;Y3yiT0#YQpz>ySv6rZ)z)Z|BMn4@#@k*D?cl(pj`E%P#N~crlo`g zp9n9|mPPB=nq-G23qP+hn35Ve(8a;u85JlEr1|IkgeaPC+1|9#+%@>0mr(beIf zhr8YG6y~QG#1$r<`c>hfJ3LeX{PO_vn`rt6b(qcVO zd@|JH{39tJWCxOljxjNYT2BK6+{@~N6WYc+JWIYXZNIZ-hOs`~$KO8;#- z{tynnZG*Z;|Gm@c5Y!bwks{KJVr(SZV))01=J}5TQyNyy0&(waY+{MKcIV&Mm-~XK zXt$|qn!&LE-TT!-qqtm1*aycsfqMeHr6s8hZZo4A+1l+$8r#!ENL7&|S_%P%dv0|S zqj~ra@f6xLMwKtKYEltU%`i-ZDS=6Jx|o~gB*=9@UW3Khoj}lTQZd7EB1d@7n%ATi zdK?4LQ}hI}m(8GP>8hU|u8n6K#uuvo-JQUh@ZUDoD2k~@S9s)s@yQeu;4dT3dkfVh zQp@;0-Y+n$S1>%wxnvJbS#9&@HSKKm$aFGm-~aW<-77eJgQ1>`YcG#7)JV+vlxVz5 zXv<>AdL{WB*y84l0JM_I{FDU7+mP&z#uZipQg9`xFw-iOrap{1BHlw{AE#G{K+v!u zBR`d6=DTdFnrijBz&%ID`m@EQWM2b~}r#c}Q25a3E^d z-(OB&t=V}Cq_YG(7Xv#o-u%*8=qZ|w&EVoRV#700ieM{_0XX8+Vy+-Vy6b{Z6=sA6 zNUU;{yBJ@;7eOXdN_GvWXoGHp9qH0AQLPb@AQB=ZIr-cQtrE71Coy88Rhda7s`h$9 zVU|{Ocrkt;u?h#C1&05h-~ND(3$M@yJWL(J)S(~gDv&v~Jh9@Y(P?Lwo4i-Uv34ga z7>-uvTFlViP3W!iYF4~S2{Wi$756{eIk`%G_h+8NCMPa7Q#zW1q0#_}2pVka;eyeL z`r)&Kb}0B~#8x)OO7N>BN4h*Wb8|X7jT}f)h%R-b6tQes@AG;LCD|f=Sq@ObcP@0r zzpUN=cLTg^jp;9cnm*s|`ME&KfA)1I4%;%SoUCDAFqnRq*uY`{etS1`xAWui`>Umx zOfln@kO|hr6B?pFgd7J zH2%NFyJ3B4VGinw- z`OG{xb4KzO&Ivj+&KlGjVkOiXeKZBa&SE*J;G|7Y^28VD&adSDJv9HjY66haZZJpO z_}7m=m%1OOR*p}v&ynGIPu{e+7OPY+D2>4YRa~d%Z!gKh)|cO3J#Vm$e0O+^d;EML zc6V4)Hs144l!P~lbIc^1dFKETb)MeF5qU9f0%w4~sI2?H-z{u|Lmr6o!T0-F%Pdci zmG-`~OOjqtvrxjQ#!yFMzR|{bY}O$JIdyaFU6a^K!OBX(-cyrLZj2^1Gl7G3)#{|9 zH)QYu7&SDUEDy%eAXW?B7(o_ikE!^#7yMrf!*Cw6+}Xqw3(&`Mv2{CQmYrNThpzI( z8wok1xxU?b?B1L_d}ehqS(uM{7ht~?*UB;I(#~N}G9^?ME9FlO6?{Ov2^W#eaeNxh z1uvJnb(}jszq~*GxO)3k6$7b4$l3}H9^L+ z>5{**ep!{dd@`t(#HSO|N;A`9GD%S`LC0iI$7^BA=_#xILvarB%;oZlLlT8LZWWZW zsyPxeG|(M_{k++}mpnCP%%Cz7tz{3Oa70(e>05`-*1qruh{sMdZ60I(u)ALJ zkfopmz(dpM4MA1&C{M}0W17Q9m7xC;UPJO*0)O@^4fNkmgh9U;QjS*E`0I`n#ntII zhvHTB)!a1QX@o2B0k1*-s9VE3_YNCOwAr>l>yusDGQ64lKWc3gteIcK(NVDFj9XDE z!Hpifn!vxhAnqy+57w!gxgt1Wat?`r6g(89y%zB*i_ZdisQbeiy$Z>oOIo9LQ_@Z3 zawTPm5J`z4pkrF?@)s>J21KQRa-DI_91t=PbLv|YDd@#+mUMsc{%JbtI$NP*T>{YH zc0Sf@M`0iss!>UikL`X6;aGrCF%T#X4O5)V6R(C> zD}$F^h#H{x%JGzeScVXm6G;V0D3Dc}^fe_aAg zuumBNGXjS)giMx+TH-Z=s24Nj8!NWTJI#DCmxo_htHRQB*q4L&7{f`7d!sr%QvZH; zN^zx-bXOYR6eH0*(SbrN{&BM*iLe0OcbXHf!v<)OZUGK~CH}V!B&c-VW6wYEza&p3 zCnJ|M`aJdPzFVNCID*TJe1L4;xm(~v=!=8{0}v9E25M2CZ&sa|lL00ST4Ucihopf29xwEN(+AOJ$QQS7M#BDIB!!r+~S zV07HAFtb{8#eZ&}2E}bLY`(Is^#r0rX2p>X71O$bgF_c^F~^JN^SExJGN07-SavGV1i0~>O;mCS%DHTd%Y$GKV z%FlA#EXm5tt}9Wu6^fU|7*$yf#|MnH3@M(n7%3p~wP$?ak$MmL?WgEkMw95lV4PFv z4=rs}B2+4{%@k%89aBge4zwR&Ru=OEkY*TqSYUK@(aLbWMSoN~ln!%WV0Q?| zPy#hpIyyI6T+an^vXXN$Adgv(3J#Vf%~Nn%Y{rzq$~RAE&w;!`8dmz<1waK9PoDM& z@zL^r2bcWqgM8w3Q2RyKwb{-T%mfv+Cn7)QnjhFX@kt>*yj5U3VG~{(T3A*C6QpIH z`3hx?^T|j9nuLp&Wm<&=)8lV@YuK5X25gB--3G+V=glv`MfTudyAcqQV@VisFnN;Ep1M+ty)ysiYuPZZc*%j>KQr^Rk=Dl+ zM5bzP^q`K%&7Q)X*M3*1GM&Rx8?sCzarQ*w`!^Uldkm4VsMBfVQTd+0uG)}D{^wVl zfpo?ws=-#_7!%6z6h^60h_CBXS#3G0>I0H8cu{}5dY=~nSH{iqi#;D2zR{q(!>O0m z51$raW3zzJrW6fy6&z))IGb@1GS`NsY5NhbrxaWk1my8(q1bu?4(1t50y@iPwb&mW z01*u^Q^|1^I#~nm|48WO_(QZNUt80&l$ zByx^(ZTUJr04xO7_BX=e;{QlTfn&}fv!D~|4029>b((ptM`DZMtH49mGqdJhlsnRv#ZnT~^xD=@Jfu{VN5S6A2SIu4@igDwXyg&~N$*Su_n!hD)1HQ|V<`<8Gt)5lvBIcl4wvJ_=MN7}} zf{tuy#*H6Vwa~58&e3LJx`Gi1 zC+TrPz)NtI#syp*;ZH$^jzTYADAoe#z=CD!OXGZch!&49W{au;YbJp)w=~$0ijvi7 z@`$<>M-3%!1-({OX#|Qml_Z%!P~U)q9_w&op{#DcN+P)j;7Ee#Axgu)`?)w*_0!-* zvwg!ytxhqQ8*j4<-YluXzviNALnNLYPK)g`KYplWACpyrHwN6r@MGieuU!?_Oz6@f z+PcXqJ9WuA?SYyVAv>Yy#i&?Y=$Fw}s{Q~u(C5Kjc6{2n9seoC4&`tX1{@KD*9-}A z>|0j7EffcQ5elS?u!!aMD31Y?ByPic1b(zLonVp>5et=$+#ZTH26=!Y3`EiW`{YT= zpKOg%jsYG?JjIa)DIrX-03KQ}KG!-M0uKc7QBqGw)hvt+Mvcz%fGz$4J7&RioFdrmd+ZXb#{^;;Jxpz5Td&8Tz9LTNKd&K_U zU(?X@c5(U`)uZe@AyW*naU$3xYzVLy(he_e*srYUn@$iZRvwF#Z_!4Uak}pR z7!n;2_}&bVg&bE9b$Eo|kzE4sjmPYf)xq*pYeYi(UxZj_8$;dss9ylGfVk6CyKHZ{HwimqtpeN&wFt3RirSC|-mD%NnoCT=> zdke@5+D!e|zOw2c`^sm2URMc(A7P^#O?DGidLMbB3js;Xi-yZgMvHxjEtX#xM=i~Q zzL0mjSddx@lV-^ft^}%uV&|UDHpf6(%!S5#gW|rhWXnejEu-46!LY4*v1e-+u)WGD zyeh5e=b%vF-3pp6`TXoQ!1S8@F1nI4R~9hO%HtG^R^$H8*S$$ zXenIAlaqTPNAKrG994GsS03`umsDN^sXRg;pruoQbYp@KLp-<-N`!2JTo0;4p@9h8 zM$MpT&DI!sOqVEuLUtx{2|baBKqOFd*&UgEPl8$`**3cXjA>Bic%ck!rOgs&N*y{6 zxpaV-B}z@mC|zv`;F@SImU7<1ymMCoP$Eg-R8oFUx~FM3E8M)%n$YK{Sj3qAPztiN zgYo^;hu=4pf;)Bu+)n}*nZw_dywBVqoyC^s{~r9FlN!Slo74JAMQWOfy@vNq8Aq23$D8fDD&#q?7>8F zPnXoY)!o@~9@SUSCvjUWj!JsFY{6cc3Tu-|1JW;m%&oo>1wYvONKERBru)tP>rDc^&wQOb`$3!eCH z_2d`H6BzIZZPNp~VH1Z30@MQWKuN1#LeZ&A>kaeG4oz>))22WrHY7N(-UK;0_8ifd z0}A3^OJ4-ve%Xn>-#wQxynH+HeCpA2P@qv2FFdT-W#;7DL@pNTc%4zTh^)`(hS|aF zdxH*r;Zv2=w-Z2cSpOZ9W?q?G*P>CA&7YQ^e^z{-h$5_x7lohYitN5ippKPnTR*OUhi#AU)%hdC2x1!cJSv9&}?=yU2)9F+E39l9K7;1j_eI48;;q@!6KX9qt zW?1tDhkgK_y4B3iGeDFB6nRD+wYg{U?6CDhrr;N_;(sx70Q4YTL*kzq8Vls2w^vV+ zWrv5&wBv>2iKGsEO;RiUO05lRRzK9sfqjjdhc|N;R||R*=mUg;rcy|7vE7#fardn( zvwIYE^{e|k4v4?DB3%Lb6pvB`JtpbKP_&wqJ?-zw$t_VDR+svpE zFYdF}6HPeWadX>ldH3~D)QSGC{Axvw=#!~4U^L}0W9NNxs|FYezo8IrblGHivTO=;InBREVn%XAtz%vzUGk6EHSwg-NT6dA$IdK$Hlb>e} zPr5o@eR@bBe>RL#)tb8`Q~b{jXabPcXtUI5qNxMJZoH~`|C@aDkK)k_-j;g#S*9wP zhm{rbS74#u95Hr}avddrlx9T*Cw~nHVVe~;6rQZ(TM??Qqrg#E&T_ktSFs;gv|Ndc zVlH+yZ6+1DA~;!GSGyn10^Y~KzC>|IH?Rbk-Dd;9f! z@xY-o%l?yhxFnq$8GPMdr#;9$eGXSc4Tunk>CX6+2Z}Pz{TwKdxuL8 z!2W3Fon|-{gj~|v2Vm$N%)L)%eI?zP<+>3^b@uRku4;EIeR$-@HN{!~>4C6g_m#fO zrQ!9@Kh-*$wvl+WInti)r)m2*hurCGZe8)frCv2VMcQ7>%6`_X(EF-p} z|H2Z3hr{qF^OC>EmZSl!n2YKgk~q!;ypyb!L2|w4|8#ZM0Z}$x!)IZMC6`#brCgDa z?(Qz>?hXY(YFR1i4naz3K~h3mNFyE{kbE!nexB$4KK=$~u9-7uPR;MkPwZEJ zu*=Z9N=mWN7kEbP^tcDJ?Venkfc?h(EDo_UG@>~8nBOJK#n~}4lBLH|UQlDFj>40a z%@}wA4lqe=3aMLKSTHrpwBDp?nB?A1eo@|L{t$p`?%JEL4ZpVjHc8VcU8Y|l>N%DI zcxWyQHE4RV07QoxOXyhXV|+3*!TC+ZG6UuWD-s(&u?StoaVwLUJe}{QjaNnrw(W8 z%hf0+7Ha~@#>lsVIy7|s-lKwhH_e}JQgagB6 z*-Hr0YgF!;|JwVdRUR*eQX%ftSuN}h1hA9CVT*z8V{ajPi|-f8Ci=j>X`JSs0G@L? zx>xO0?9Fx`5d5H!=H{>+)T6g7k_6&$*d*8xA;r1gIiXr1r5iMn6iS1byr>kZ&vif) zBML}$3c}HU@0Ccq+9f;D{%Bp=Pg9EgISVXIc^Lfd!PW}1%I=jov6HG6QT)d4czr98p=%70noO=fGvjJvNUI*a}`6t z>a|ZPPD=)mN1p&tUVi$78OAdZHb5Nin;L2GZ{Q?EzjPZ-P$A=7k?$M=f>+_O+p#5a zClrs4t>dYGi_rT?0;o5+E@nI3DqIK5X601Ijy}vug+EZvizhImyiKAT?(~5G(n{$Y z$E4_Cjg7IWs@ zT!tEN@V=buYKnR@i?gZf>UqXcjT}<#2_m=Cim`sG8CIZh9<*4Z499cUPA+CG%cUDH zt$+G^01P3P7FDd{>LiwBsMXd}B-17Ij`CYR10FDf4noVZQZQ zJab1nY%rat`>NV`^A-b_>GazXO3%HO-vB`)-(_{VlW;rt9_rI~{k@8BN1 z=xNS1-c@Ci{ps;@OBrKF?4^fWE&e`iBq+okX?EUD`X0_jUzOqPwUm3KOR5<87`L5HTtSQQ% zM~_;V`2$oh+h6Ymu_Xi%4*L=jdw z?GH@<_)2I>_v`3f)e1{b)F~n?mEF+fE}`;c<_&g^M@?^urW~~nK$I#5a&&HUf_14D z(9G&cZ&r3nHUf4;&s#58KY|9bi1A>09V-=;R_sl2xARz$8WmI4Bl|CN#Ts-cjDhct z`P*DnBTi!2h$q-etl43BzPp_d#y?2T656s#zZa}?8L^Qsvva6inUIG0Zo0~*0kNW& zB}jp->6hQg)M>?+bC~NrV3W#Q{*&Ij)2I}XL}Aa90MWYX?2^O)YBQVpm6ZZGqA7G0 zv{{5-wp7XRxxg#W^)T>!`QbA8CwQ`BGloxC$1tBd0|&XRB2PX|Vnyt^lc7}`ek9*+ z`oor(o$5c^TCV^jc`qDr&gC?!lFmWHWzj(wd==uTu$LK;{k94K^1OiDm2EQ14+YH@7>43vUI{4IigsK0O5_Mv7@&i129Fzj6b*l2D1VV zk*fk1*qi3-%^MQZm3ROIR2anq#q@USrQWe9R7uBQ7U8=AvRQXEMplUOs)DH2-DYnG zj2BxjUHgb~UvjsNF!O}5!Y^v3Hf7P@f3Fwan067yJkhm06Y@A75NlW7ifQ+8PP?my z=y48*`(_NL+((0e!XzoMqO|f+t^v4|)grNA?$mnnRRA$^-p4M!hXb9XYF= z@lpgLvPs`gS1JrXpn;;CCsq>k3*f#3$i4hKYKg zO+u;xfm?!4a@|SF9x=TIv+OVoEQ`0Lthv3uEF)HgI#n&`#$F#IX+J0BZO6Wx~);02Pq1kklq0L&90_+nwL z>JPX?Kf)zVfkzmTKZJab==mzi!M^7p%$8NcC+}S}dm;(%@w+b!Njp($T}Gj1(5C_h zQyLdO6nDMlzQ+UrEpu=C&0^jbFy4e>&&2@54q zn>{BC)V16NRTo5UXk3u8Z&22^cpYT|mn=(O2=nI1Yx7#4;V(2Ir6GeN*+4HbGrI+k zvI#Bh_rUAxLgmy)>DW*3&XN>Q(gM~idPk-Gr8Dsx?j9k?8*RPG238PJxD09yM2eN=Jf zL()^=voTZ%2nOCXlFxZkQ!1--t&|p;Ffbx<+hRLN`WSd~ADI}@F$93C$g3E2aIR={ z8&Lfa41&V5P|=WCbY(P@&Nyl;#cCijsCmyN+Yq`uYZn&uPc~}CL{51b&Qf_prTewj z`iTP2D8uuo5h^<>8tP%hLi_0=9|P?Nmw8l%Cf8a11A#_Kj?|T0+d)fIG7W9Y-rj)^(%|sx!++|8#r^DnWW+(%P-i`hJh6 zlTa^Jl}EdxAz?)FTckM5)ZIsu29E1jrEX&>P1X~%_1a-adLJ$H-!woSC4&nxF;oh& z64HvGjNuV9$?)cN7)2yyfS+h~L=wKNf<)@wJURymfUMa>Xy{*QsU!Cd(WOwKiBDmF zYHsCWU*jivHRZ}ewN~KjITt}EXU_r;j7%PqTsPk?iQ9gQcM&2IWyqLH#pBx7K8zzc ze8|L93#CW(GzOTG$7g2|BPdXIlg0$1W|s~@=}6M$IYj(RV%A5aOP01O;Y?<~dSj`6 zroN2p(fmD>cn@=x8pcj1m{BO>fYl zoQi@6*+OdLV(*kmE1E{>qM1e)s&qc+E2PyQOiJ1z9OXzw?b51Z!$~wAm{4wd-iU>+ z$|Z@QXEMQWfI>bGtzFp#fA$e+%J2}f$*{k{@;?n&T*$PnXXRK|^;`c~iGbyC2dh4=6pTD6Sq9y8Ho6k2ow@5%Y|qTe%-) zWN>ixyoAPq_N|qcH%2#knWkA-iL|b#nn_i5Fk+*?x=$fyZ7JY$lU85q;7!FIE$SDS zhslp^YihFXpXGdw@pPM|>Daw7C)VSo?K{{DW*;QySB;yGRVx<^Ses6~%0@<3(pbq* zNy3;nRm}WX>&K?Y`dJb=+?nKnh>x9( zO~Nk1cKGJ{G9Me28jGj{Q=i!*WL&*$q*M}387mHfzUGriF0~*CA_>(A!t~-JkC6@0 zj*3*4qab3N=Jq|ZuRmkbGpn2R>Ro9bQ zIKQ^~siTk6p?KtEk*=n$=WuD4eFzOrtMfDUpKOQX?#10{cwga~Wb&fIjPLHdYCX{3 zJ*qnDx&m_0q`eOYQGF6R43NAD_n)QL3xv$dz zw`zE(XD?no?P(La{7&KLl8(M!>a=c+0i_a=o)__8)eB1EZa%()iR6crLV9edjAe~( zrB;sofg`iM4L_DdSb;TyS1}V2^zRTy|v*htroDIyveoobBbS#&AQB+ZR!#J*M zulXCIXL0GC{%i()rHCie^r@T6`Ib=V!=fLfJzHIkE1EFr!21&04BeKVhrRpQRKdNv z8hFHiKTx@x+@lyc;CG6Q10Y2x~e=xl7pj@PZ5UYZ3*xx{$OErjfI+tG^e8!ltl|Ff25sKFPeove=Er z;XELz`@O_^YH0VCk^XKCt@EPJ`74?n?>Xn+ahi8;PMAJjMTz&kdu3p18IhWkqwWE=%1bBj zD~`sb!FROix4>oPI@-_+uO1!yl45+7cuxVzV%;0FJ=erT(*qDjr7sIQq7ZkZ1M|7l zaD?o-S|d4yjRs@+XbhdULYiM?Ng5t_(Kw*VXCsRn&7@)AM~dMHI6wf2>%PmTCEd4!5Ig=MsXe@pXBmL8aVHsn2SCk$5AB#;Q=fJnKHDy}EoYmW$Z@aIlqEZ(f$Fp+Q>hBfiG1BB)3FjU)dB=Q+EUmL zP_XmRFe7LUlm%R-kAPx3tI3yzEOrY9&tI@-W@e7(Y3E>Ts{$ou06&VFqR&l+b?lGW z{kg8fN(!_bVVQUZK#()tTDcnV1W5byWg<9Cn^W|1mA?WdNFUi}A!db)DTzRL$q?Lw z##MZ37Bj!~RH+vj%fUTKnTd%%##A`9@|AJ81KL3Nf@sEWviNiSu6ou$=q}p|p!?}& zTK$UEz-a9gL{$ja&*)ccH>Y)2EyiBp)3qeh+*2_4_a>UK>T=ge0l3ZyI3tN7$+^W! z!EiVPDSA3>&7QV|j`0dGili$MLMT`>h1_m$0%}==Z#;v$zPjN!mAlA!q{Tu($z@QbmTS-y<5a zC6oeM_tsye$7pu5ytMaFyVONcQS&?!tq5pju!6Y|mNCI=i3)-^M(DK5MO0X-FK~Q2 zJ$jR}C5Uix^+_90 zJMxsHCo`+a$^*CZHSVVd5ojqgo=SfdC|6y0Zv4#bN23^1O?X5^h@s!&T4+Sr+15hE zohARD!v%)ubzp`*-1zcZMH=8W9&7rMV>MA z97VRjCRC#B>R5${b0P4|bQ;O_@^g8fKNcaBDGG^1`X4l5Qd0nY-uY*XVnE$x0WIJV z2UuA#ZieXs>3O_PF7B#h_<17^PCO~K_;LA~+2)s2Ugw#&Csq{iYj4e+Zw$?Xsx=OE zuloPBWsS5Y1v#mObH-0bV%05%YSl_vmjTU{z8-EKHH~0hLXl?T z0zVY(&(8DeK0xc@=lqd;54vZ85>~{dT{YY!7`e^RwVk0z#72x(S#xs!!`lChrloM# zm76=UTt9H(0W$@BwKbsLHM&n{L8M;)=f*=ol}2?GV(Xy( zLiI`r`Ux%&jb$@_y6&fzMrk6|2t5C^Csp&SpA3TxSEtyP>adg4PdDkDCYpzHS!0S{ zr39obf1=w?O#%wD?Ov7sOPmO|bblzoRU%CD#p%_{N^a8kSS`lSbJqfRBrcv2dBn{- z!2J9P>YQ`QVKg+P7{Xyh4_A1;XEpS#avaVZ|D$H_z7sJ8e-vTrLx?A! zzTK%0JWywT6KBg(Rf5!5U3&?%7!8_d5nujzs60d>{-8#_P0VD8gHuIKjTBIPDHVS; zekE~x)N>~oiIpbS%sWG#+M`-2^}RbZIPe=aXU}4`!|C~zPZfzGlWdn zrnE%OiTTa@Gm(+y@}RL7MNQZECI7M5|DGtqnE9Tz^A(&L5LX$JVU$T6C_*M3Yzw@! z@w9mt$z3Za-KhUbZ7FD{JD_;Yi~{85{yy8C?Z1j8HB&&gw$sl=#HZiIU386 z2)+RVcm}^hVt|w?>4xu4Y4H{c045bJpyjE+G&%02a)-_vwD@`k1&QkyzEOzx;!R)A zo2Yl5+RX8f;~~=P+{wbQzLH(e03N%UnbE!+ZxeQ#Yoy?@^T6&v{WP4CLW0pjePS#?6z2MlLefZXp$%<5>A8(sz(ePi(6;L! zvDNF_Ua=vsPOm#>=09=;wRNVfK;~iKFz}~?MG_uB2@Li^a2ZK>l~}!`3Yg2#rYPNp zw`H-P00so+lq(qb6iS_cjcM9f)z8ksaqzvC^>m>GKO8VFp@;NGKroq{9!t4*yi|wc z$zki6)h{uhJ%d;AHnz5e*xb5=nhUS`ex7cy`n0@6d;t=Bqfd`;*U`QwXIcL>U;Zvwm9#N{W9OS=9If)-asv#OcOJ5`g&7KMXkiZjSG61(u0-#;4Uk^dhI_2aG?A_AA8FyyO^PAG=rDMT7 z;}Miw#G+#U8!iTo>s-tAMWIGi8`Llko`z#tn4UXJ9o(|Z*mjX(WtRW4mG=F2ERu>Y zS}2UdHe>IS6XYf5=xAky>7^S>K%V*FZi@Gcv1u)?5g@ewH!Hn}lf3M?xgE&(pPP?S zv83ynnuq^vckiC);s@uCq-~zLF2XYvuHD0~#wlt3Wx>lk-)qMYYmc0-v{ti9S8+^N zCo1A_p1+xdm*KXzZ0t>{z$P&sN)61r(GS_y>qsS)uI_LGE{4gIvZ|5jB8RpH7h~?x zSM4Z&2P9{t??)dlkdZKXwnqk03tZiBg|{$&nW)w~>G3*T*O7BwTv{rOGFnI%`^vxI z@xlErEJewxmyz4MJ{g(oZIbj~IiPiyi)OLRbrJd*9cq{MxcY~~)FgvIy%P{TVA0iD zjkpWc@ETPV+@u2y6#u%lgfyo=(!MP^|IhmT>oBFi(;K?Z{boADpZX_``Cn&vrKZ&C z=XTa=;lEuPAUM2k0{Pyj3b~y`=uKSX<6y zsoUG3+;?ESA}xGpLjBp8zyJ3t<1t8E_TrUsn0k&VGasM#h=AW~;?-xQeFR$*-vtg< z@fM5faN0|~lOHw6!C(wUpp1Xd2)K0@F=AO9bgmZ0&zf18f%}C`eA#|eoz^h;-Fitx z@UFk#vVL(~wrAfhQ^C-%CQqNOcRSJlyS7-Kq=BP2Px?-2Y}d(FSBywuISvy=6NyOV zm(OV0Mz7CejT4Q9U9FW&zG*g034Gou-*(pF__tFlt*a%5N<;&r*>V18N=r!SN4VTY zMN4CJDlOqW*RoH>^8Wrdl**2#$F_bOUkfbzza<4Civ;Zq3>ZWX`X+n}aw)6QnGKFl zyU*(q6294wxIScC)Yc>=wnP^~Im-qeG|9{`Q>zs4fS?4}`?^E{K`&l>6&_J7pii3)TgoH-tk*4uwi@7YF z*RNdGy_o$6mkp)YBMV;=5^Bc3g_I;KhwZZSnxBsT!)r5FIjBf!9CKLLF1sk~b+t7~ zh<~5Ytwj(L5?(7EO|>V1e|Di*Gke$X*z@EdVpJ|>kS<`JXF`#;)*&4Zo8^2q2Yot(9j0Dx-@Wl#D}gjiNT$@Y=)2H_S8G&+g(<*-O0lSFm9b5jI8XB@oZ;cYCxHNu zQR0eG4-f2K_*+xHV(WB~CS#N_X~-eD?AezvrRdVv$*8*Ql}CruZaSj+E4p;UD$!pF zyRzTn0uKYj&INZ^Yk}&ypqJfJmVSKxEgwp<6iWs=RbDdptnsmVcoxC!OFQqiBL_Dg zz5EzdPr6#ImtiaI{GI*&pZ8OYC9Csev9bw#Neg{jk$2a={YG|=PvgXQG6y|jOiJ0;}0gyWixftT{NqI+L>P8vPf%pwDR55nR#Jz?idUgZ`6U#xJ6J{ywXyK zaPYI=J$sTRn^q|#|FqCh#?|i?_shen;F#`xu@~tb(r>OmkvUEoFTT>y{BVsbT`AA* z3jZ)eu&(FF!!%jUxQ~E#wx*FP?aU2EcVSG&?Hq-6qI8pZtli_6A3ual47n8!Yf$Nv z4e)|qZ0gbFQYUCm24)O{72%vGcVc=2_E?uX!ql=+x0y9ZvoV8jZ_(Fnlje9FCHJdWxwJ{#l+z5RMlXkXS*m#Z17}+7>+DBHli1=#i(1lGV5s~XL5in`8>8H* z3Sw~W((G-B-gnx~c*|8CCZ{W|ddiK`U%p9cA8%VpP-hiJu)YzqJGkNhte;hIbBsd- zW!TLXd%gPEL4hAz$}>x3H1F|D=ZGp?uk_d=k&RoGGf-S`csQSHZ19_+ZM=!?S zc%L=-9W&ZnC%Vul63)pSy6h`X<&=?lL1{p0=Qr8!T5jR@+!`QbbRmy<6EwC0>5DBF zn+s-*=M;AHz6`o9p1anOXU+fG`t9-bhidMGaF)%SZ+uy1^c_LfQ)m?Wm}TXz`u!Yc zuh12d^>e5DeS)doHK|%Gs-FAy3qzYRm7iBhk%Q6UE@`3ZMAuoi3OzQfN!#h$_6$Np z0&H}D0<}oB(}$$xLC~8i#Ebai(DybzHBR;=~>gs)5tp{wY#+RBrj~C$?HK>Ys zvL-Y=S@>3Vk@KmJH2kHN#QkhR5kbPwt-<^cOY$GY+AKKCM8 z`KkX(8&F30IESG9MdMs@ES_1J;@{|CT)GT)f4s{c|l z;^*~&hquP|=cXU$nKwIH{1L#q!64myhqaa$y!Aa{j@g)N;10@Ey@^n$%^~&!h;pab zMke26=EKCTAwyH?cyS5PMdG}27HLM9+jWtekJPa|F&Z7e-HZ~BjY?F@KfPLU<6))S z7Q3UFt^|M6)>6%B^xz`&$Gv_1ZL@V*%8bd^ZHb<`6t9Ky?*WL835xYfG@{&ISJ$y8fU>jt+CSdOZT(ApYs`ejLr;|kFB&X-2qVerA-$oFDh18z^03FqIIZ* zLr~RN7>K)qTe^PE9Ty5YMwayx^Y_}IYGxWyDIXx{?Gh-E>0r_Hx&$jtvXwTVyqdWq zJ$m4StjNA{TTLhsM!i9Xth=?ci5kI8ypq3|eT~qD;x*0Uq0ED0qI_cN4vWo*5dI`3 zdK;-50-@rj)`KaHr!j7VP45+D>S>D%t(oa~2i=%}Rj88N88syz3M{$z@$cntbhLRp z?}poFzwwM980;vPb}n&{AQ>)V1Ur{A=JD2xo9+dPi8WaXjPKF#}u-#ghh zbN-v&2gB6y9H%i&3MD#68a5zH2!Ewonhw2G&~0AMagm%eM9Q@m8&Cl~7Z_>a8WXpM zkEAaKy@)z1rsC$Mt-9~W{7eLFK77qe@AC`V(-=Uf$+&G%8Zm#FxZrkF^d;DT6(#WVK+Gvu&GUb#)fFLq3zJ-tf8O#est) z(GWqmd(y+8vai2otR$eXX{wWTO**3Ci5z;Flfu3D&J)NlBj$_V$8m7s$i)6*Y9mcG zCZlnITrkF&M>L*mYdZ10$~7dI?QMi78~SL|c*D>+v;L^!Q}o!T>#g563k+-9;Sa8^ z?MopPk#vXhly&OU62*1TGXdiXAGpA=PIM$Qh9|=m<6fm2Ge>8;D$?hlJmc>bB$>tM ztHgOeK@RW`#4CCVzvcWeA0^*^RkbI)6^m;v(WiRXP*18X#(1T93bF%c+yU3Crd=8E zaH4A$#a>(FQxUH1dedv~}>Tu&oQ9+!GpwRJ+gcE0(n#?Tk zkd?>u1xtbH!cCS`bUE1(#9>Nz>93Zrhjj|4a~mBd7<(CCe|yYfrRagI@(F>R^du4T z){FcZZ@w85`>Msg`{H@HwdBsU+WB|&tw2&_z?fxzX<@3&3U{!v5VfvT=hUy=o9RCJ z{V};uezj&tXHa_-Nk*6!EAJKFcPQq3GW}bG2oO5 zrL7iHGg#PI`X&D!_#^(vG0fw&tM`&4|L5q&ndP4sWC-QSJVTdfy>e7It-KUZCeZ?% zO(yKfI@E@r`+iIQLl)LGNgpZtWQ!6B`b_I!O(h&{sob(I%DPEtk?)j2p~{2u3?R%j zi4)O+VeF zWf)h!oR1ix&r-lC9|r$%%g6O9ODx5_KpCm_cO8F~CZU&pXIh>^2Ojtou-i-UvGgR{ z`@})oede`d(LQaW@Y#bh|lp zz?}*T{zf>-M(6&itwLNN%gm~*o5KY7#dQnnF@`=l^uu&UuFhr4{Yc{BF7x^FgjMpi z`tIX0lrni=AG7x42eryN?)>>kV76OjXp zwdg2{`xG{A&Z~2yhg*m0COWTS<~j6X+uKo{h+kz+cf|ydMg0L*l6hGQ*ON0?-N}yU z3hZA~OTyzNPkrxaqhim!AtwS&f^K&j!q!V%`QCvOig9w>g2K1g?R;;0_sm-@`QSU@ z=X6VD*Z8D>6-nV-KTLX6G-=26MMT{;E*i@$W(t89Z9NM55oRD0Z@$KrUNF||E7{j? zn-=(uWVw!sv6W5u5xNn-W6< zkBt3r%RAbqNv$XPr)u^hR9vLTMEyJj;$rtb-ZUoFg(7$-WERvQ%M*s z=aw_Kj4Kg&Sa*dW{76dHBbz~`0ZWZ2WFhVedUFG9FCduowZR%D0Mdc1ws}gNh#Rt{ zeXq+MhOd{9c!t|LA`*T9t1?Cud}W{MnVWA97YpbR8XiyS42Gmyto^nSOr7~EM2GG& zD2^6+n-jAIUYyMUx6H@+v%q1QhthTC?d#r|6J}MK#RCq>dtD7j_Bd4P06%m zn1sG)OuW>n;@hTG1!zd<6}+D;Q^OR%7xIM%-OaJSeocp6jz?<^qBz$|Z>EAc1#sDC z<*QiOd@==w-jw;8q13{;yFBZ>2s~T*G$Pwp^I{+&VS_O)PlaCZY~4rxNR24ifT8?I zn)Y@hhR^?H&@yu|=mSJ8NJy|4gt(|`Q_+3f)tOBsxXAho79ah3IPqdF|Cb!MGRVLn z{`&b8c2i$=c1YUlNe&>j&l1kD1$h@35@Sdq9dJau#i%`KfipchLlI0?ejfHm68bX2 zdB-WtVRI24j4*ALyPq{eeQ=(zp4;-W&<`UWjFUy$n%&((O387Lg|hBagoG~ZiG5Oa zf^DCn^!AksOMx>pxKaIy-1vl(P2^x1TkLFm5d5I?_maVa7MDEfJg0`WQ z?>Y^s?ncVkjc?Juf46!SVk6gfuPQr5!Gz&Dxm)Xu>?^NTvGiWn^tEG4{D&10mZ(vs zSISaIUq*+M+^gWeOzBrwfC9c0i56lE0VVW#%ze4e~v{0!$Zhy44 zGY5oNv)(S?TjzvW>-cizVb6DSq?&=3{j~iDLz$fy2rtjf$Ew|;x~PbJn%ER=u)DXlLB|V=IeYNQ@3#O?@6D!Y{!T17!gKcRm%2}r^X!Hyi8L67 z>fZZ2(Jjponuq!5e_$!I8<^*}kwlFDBa+eH z!T$b7Afz`6UR|y7`|cS$L+clDC$g zLrO&DCsF+)vEitdxW@eX8}<;R2*fQcEc{tm7#tc(PD)C8_fA+s0y#VT%+k{DV0Q%% zh!TrYM4o%vC>{o3_FBWiRTUL`dwU;0e$>=biHnaH6BUiT?-lxLpsOB)h23LUV(EaQ z@%2@tk53gW+YU()ONG<3vjPlpOH0WP_Vzx$zPGBT(-Y8)C!nOo;+(#-f}vLz3rMTH zkAi^P^rJOLnv=?Hrwf1PtSznGU0q`wC>#KZTG7ON6b+{*)hg_6#Gs2XtXq_g!J|il z1y0V12D@in*sU$9cLqhTJls`11_q9q&u1}r#1Wx*u*Uh&3iiCd`Tb*cu)U9E@Shad zDG3BCD~qh0T=5gp9EW>>_1|m7eh+DMcF9d5)z(8g9vB&F1vL_BJse71N|EOsa>Fgn z@LfJOvMWm=E=IQI=5Wl~e57}AyzIds4`Qi*5jxaAYR?O9`;#f&FJcu^*XGPFBJgVg zfsOJM4r@7@F}Kw~fRfNbWNqK#8;OJ>1?8&Z?=Ok1JeN~hSzc8U{9@9X<}KQ^OuFhV zMd4`t!yL0g@X-PtV(HHobZGgfH7^(~CIa`_wndz~hIlL8hn;LedB7S6D^NPH*mazW z0)qAZ#RoSXIO`Hx52X|6PT(3~2b>v+HFsSW%wwXSOwk_`E!#V6es%t;fB1|M7c zKL?lj9QD3Nu%bR)9SA$9m~w4BlHBrl$wV6-fm#G;iKunl>SX!X(k5^8F2Z84(q;Ri z6!dvKhHp$`pJ2dC=v%01Y-;h?#vt_PO(#lE%2eL;)dr|M{q-@NKzYPU=-bw}(wC&) zf;c(Yxu>Z`XTMXov=+l>b{v^D zJ$FP7ot~D)rIh9=tKGVi6cG6FcQ7H%_DJ{ikUbgv+&kE9w5wjCGBP&&kg#iG7RrHQ z%=oAxdp=r)6LLRWbo!L-ze=VPQyzXrCs&Rrc$3IUn{JSg@IP7`ULk9{6fr)Pc?%J7b;EaMu3#i8eDx1ZN1d&PPz zwB#9yRE(LJhr?(Pc49RacLh4;fcQ-iES#Wlk18R!Vy*TxhLnMUZ8h-%Qx%TifdiA1 zZghN^c_Dun0T{T=RfspJD}lhdvjlkYdLi6BGphK*1|7bq!ugcNN_R66D)@O0irj#SSycFx6jVeT zy!^stp`VC)%@Q?Fp#Cus%(%Z={IV=8l-M^~5M(Xx)UF&ty4K--)E`TuTvb(tOQnM} zmQR6%QKai*?e^b;*T(wNb%auePGZ4IYv6^{gl$;+@nFWg`tX_ua@5yvg)@9=R4CAC~+W>@iG@!vq-CYQ4(L8O?M_RGXkG`7;H!d;#f> zyV)N*AzPSAp$Ky_W=>b*`NznDV*exI{h}~e1UoJ$HpK|(h96;qV&)Ek}-Xg?p zr`f~VYP(}@KUSV&#c&QC$}LxgF9(Mz&WLHNIOeNiIfoup9_@M&J01O2$&$EH8igtT zmKe!kj-G88*HqB*ErnSOmn-v*5+Pk*XY&^7sDNLi`g3Xdy;`l)XTl3?gbB7N|XkhuE&1*Ir!KisKiM#UL>rEGTJ& zk}@XTM;KYO0pscqFdV8!o`7gq&;9Tah|-;JDnHK1xBm+=6+N~xo1wsS1|XaP5OX~~ zTZA}Ucs=A2YOR_p8lA8MiAcVlu>rfxKrewdVE+Uq z{|VYFcLtDe>%^{0RE95~<4wH`WkiLtyUNRjqck3sR(v(drjQREJOK>Jk=MDIYAcq- zo73XPrzYA#)>z+QD6%KykU&Z9&*AT)-K@;>n5?mqCCoE>Z0e=2wc9X|67$z5PRx$g z)z@PWSGg}<9B)Z+Z7Nh74o}VQw#EVsVbjj#`?Hv4Ma8NeoV&WpzGZUU!`68yASW5F zLb|6ovpYL$Rb|s!yG|AxWM?Bv9}w_5$4kXLoG027-Y3kok8BBQZEhw5zN0f9dXLd&EfvlC=X?-FRcE?bD`IZdOmlkvq6dJ zlx1K07S0eQC2|<*jNtj%h3)T`x}oQsPkP<=U>9AlMy}H4chK6@WIxp7NSLJ(v5o$CK;dkXVZ- z$VQ;#5)CBUdj?EFJ)^BBe>meV_dFU6AyLj*;0`2?b}^#JSooTuK|hb4t)n5TEk(_~ zE+Ml}&qDhaXrFvdxDR(23{`%CU{V!ep`5kQ)3VZiN!|L%;MG@|Iyf5zlqCt_G%(c0mO6Tn{NYFX7=M7Ry*v!UtZNtJ(K}z;kH}u^E}jZ4ZqI6 zLHF_13plP{1KipOAPa*$=3nX|(Qc0-T)Ozr?Frl!#!0-nLk!Jwb zeoBRh+CqT~*Z-15m$(Qc;eW`VPEhh{OoI8>C+;d(qFS1SiZ(A858Wzgu-hel)m9ew!`^J7AsL~6ncjC{g~(wit)oYY_4AjH}W zyquv`bp<}Yy}#Mge^~I~C819;Sj=S*sGya+`Wpw(J~59(rC+MO&>Qsn35mz?e<{S; z4m=ofP{<(k<)!*=rk`#Ue-3BxBa-9e;mijoTHv_Z#h1Mn3>$Ra@?W0f6p|43-W7 irmdXKAopY9wN!VzPiQHzn#&&#NOT?=XjZB}kNh8a2@+rc literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/rgbController-configure-connection1.png b/src/designer/src/designer/doc/images/rgbController-configure-connection1.png new file mode 100644 index 0000000000000000000000000000000000000000..d737bc2580e346f03346f974c28ad392bb1b6df6 GIT binary patch literal 19812 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

rlA zA`#3PiNc%Q_OgCYC^Qx>gsDNiZr<%tQjCV!r(jUMNzcNxd5}`~Eq&60`dNa1WkWyJ zG_-Ljg3Gen;>B^Qr>o52tB4Zx(kAV_2as?YN&T>#c{f3QXW~beG`Z0wZ*43GuXFp_v$5Fb?v1SzI1A4^G&E45$R=#q!LJD9;cWCew2atya$dY8`PCJvJ8woHZK%et4l-+QMw%B@>gR=WNY3qNQ@szA(rQlpd8TMa*+VI zXA;Eck{%eW{TUH0Oe+F_J}i82q4PF7ShOP&JS(9?mfWBf40(Q>ziADxy3&HhZw0rq zv@#d~7+Z??M;@oLIP+~|7eNd!CibH`+d0%#%se-ggh@u0r-Mm1jU9B=WRJBgEFQ*I zh#_JSkzO}VlG)!s8~_DQ;gCV@pR}4&aJrmM>|J`5yqJz?R({v8<{^#~tQqDG2d%K< z8hl{HLDu=S;D~03^!912AQ!;J^W#3E;GmL+1Cu^;E50ngf~$h?$H||8gQ4ijB|+$3 zAeYf83qh(5p6PjZ&|%<#>JY~}o$q<=kPV3n-;OqVb@by-tU`> z31x*f=&|=Ik5iVrO3e9>OGzE|Cw*);%;m_SRXKe(0* zS_s_I#(+|4U_>8#q7=A-*-tnf!f?nFQ9e%rC45HG@rKyu)c+>#)%}#W11_%xFD5=@ zIi%+p@}btK2bxNNWJhiK2rO(ka2Z-r3Dg(z_yTl9!^j$RUlM$ez_$T4I}*%W$jPRs z_oBxHTuLeEm$vW;gd%?AFVO4sP*l$2od`o&PHQNs-QHwG@se;uZ$K7!C={>*X5Agd zB`gVlxZjb-RuRgLG?EwcP}7owKomvoo!s+hk9jEMudXI*j0(a!`J`i;mbDI-X#pDq zL|d`N9(70}P6!VOptjX!jN@glB*tHeU}(P6WUvLV za#O%ll)~`#%*Z`hGUNH}@dRO$fESJPN$?b!3}Tqdr6=2he2W?Zx0q^zCU<6j!i3=-vh> zyLS|r*q(7{nIAp9AKOm++sJ_-KEeiy=}eg7%tv6uzlEQd7cqrtATRXHg73CMB)-XD z;xwf*`}Lpm?t|rhq|Ck#Y7%$|B2^z*F*>oX#%MS@>m`ZTt~t?&k}mS1G-p1stpIa} z5_@SIMO%0&?ycJw+#(eDa}^&fo2pXmWo-u1n2eYiR?JJV`{UNDu;aBdqJ>(bD_(uD zyE`N6pJX@VF3a657>jVexff&rJ&-$i~;FJ;NrcfJ-9rKb5UDc9TH4p8BI><5T`&OW^t`6HC;Vgr1;6(_;w z^Whg2+D}2G;8#!kDj5C=MhDf3!)+L`N7~J zu>Mtmpm z@Bk@L`2SbUf6t^rV}*XFqtlVgm@_? zCcX%*{6g9%jW&(4wK)$q-lGonIBs}=s)XF9w;;%lQtv-d22m#`Wj`@GkV$~gG;%z= zCXMNf%?7=29K2-TF~!baQE21*1$iZfaE_fl#QTS*hwwpyhL#T7ouoaelvyFH>GJt} z=dDt8PEMZI$C)%~GCWD6o4N3?bsJ6z+J5orLF31qDlNMbA z6LFTClm{DxK2b&qqZ_|IFgSy0ps0l#zKAVzdJI(3$Ubs-F_ox$p=ODi$L=*%w_%B< zZfxBlRg9`7UT!)-?m^-|3$V}RyBV>bqTZcSZ_~d986l>oNqZs{qIQKOrWeiIWD{>nVq_`=qAf9@ng1c;&G;cxe4ax*Asb8QLQk3YW)sP?~2N%z8E1Hb_;Dfh!7(Ol7sN zb{PB;6lRNyjc^C$(`id|=~zL7PYCc;I*pGtT87$yP|-pb9Cj!z420b+Lj_;XM&Lp zSBgVB8HKv5XQ^_K4EUU53~E{BXP^X7M8ujqx%OQAeBe$O&z?QJZLRu~&e=F9JcOda zi4*avoy~;$A7Ujt8C7u!sCW{`o%}Rw>=5`3&hDS}Bwc*kH;aE5{qydJ*?H$Sq-Y-d zwy@tKAUjZudFGGZzoRRz%lYRH&1%C}7P1^rMR{$d(&mdv%;JR#(UPe578S#L?(Nx| z3*FPmV>|Axi|3x}|BVLcepT8Wh;QyGn)YDVdE4@5loZcLyd()xGfnfjvJHkF$X&hy zcb0Od*BKl@N}9)HGJGKZ4ZVI*9ylg(fFe0BQ^e;?XDUdXUHHmmi1+++?>@rVVM zlc#2hX-iGW>VjiEervXI$sFUMi@bY@L=A?lDOL#?;Q*xuyYfYlY)KlbM?rle9UieJ6UwG!e?AxYEV*WqTOYCtcmt?ptWsNFIKoNW$7qBY>%QgF8xv2P`_buEB3Bugr>g3u(QZI}=Fa zSTY0MS*voFRI+q~oguprf3p{C7K|s)kM|km({6hpszkzB;;35S7Ufm-AKXQ}U;5*v zjF=%H#b+xVn9uP^Dm>Nz&soQA08vbV4b{06^xL{WAblBIrW0YfpZiMh);JlMszX_+ z)8w?%W0DU1faeJ6K5k?7+nlySrqXM#FQ#tpg^$cwjY{~$DN*pKlv?e&a|lirjDPxTlB zKY~@hRu1!jI}H||CK@xQEyNDzozN#vRsCA2%@V!}9ack*;^@8pwXoWyNLCbaasRsR z-WfP{dYTtqQ~-)a$7S)32wQ$Z-jVgzKaMvlEG#K1rMi~2-ydt+ti(EOp zC!rox*zC&28uZpH`t|a?Jl?Na0V@xlSKy#ycXwf7+Ma}GP>%^B)&RWmjz`nb)mYDc z*@I+>#>Bx$xKGySk$9~<7WCaOS9OG{%B3ombVh$wHSx>eH7=glpf0+^in zYocO~{{xYO{8smaxWPl&GIzLert>k#@c#ZTylQkgyyAu5O&zn}H%k6fZ9pN@v&?DC<*w%d2_D0Lc=j{8@s#@OYY$;Xzz@8jQreiK#b zu-QrJ?5KC%r~%WZ$tk8_%C*L~ru^+kTWN`Gm{_}}3 zI?H;L@@^*n=v7qBzc?WZ^X1fZe}$SxD6(co3$1?`SZGM?2KAFgbIt14@U~v`lhX*S zt8`Ccg@+mZ3D{o-ZV&@qo*dT@jcb4U#7tfw^Mt)UQT4=i4s**6HWm|XIa3r8>1M1< z&ukg`>`oA}RADT>q%2r2Ay30Dc+KE9=ir#^AW>-a__KDf4O=dm(cP}cKi+rT6xY)F<)R8g{u3979(H6u}BI-9+2uXHRL zIolDdIKaISzRXCalkXnuzj<;7{40!I^D!GJU;ey}ek+J06sj|wd%QO$@(S>+9VS+W z=i61Km5GJaXxU7VYTZl0ev;hv&7{J7D*l_=%UuYL8G$~!*<4TkNMcYK z!S$88-=dfSYN6xFY9UM&wb5w#@3e&D;IL`hXkW}n39mCTC`Hix3$^v^{CS3yyMYOI zmYI@AVm&@~(w6fcgQ({CsA&M(-KJf+sHlqYIL5P18vTByu28V}*FceqRNg_LzKiFX zW`;)N(}LDxXmp!v|Bqj))=#xb61n(|-&=;9KbZ&kMvpRgUcNZ%y-ST9-8Pho&BX6` z0LtZcS=QOi^s;DKd~bNBNOtkjG&R&hE>``Udx&xMKgaA)?jrcn4;3FZo;EDBcg+ z`+<24fBzg~*lKl8I)>FsX3;m12Sg_)Z`F4hkZa7@Aa(Xk9 z-yazh#rdUa zn&INYN?tE&+lRZ=mM|>TYH@x}_OMh!s5+Tg(m`*^OD%6R7)EMxa(f1P_iucct+jts zSU4RGnQ`R7VGgyftc5iqxBwRP)}o}6X1!6}kQGN_4RbnSoB@Yvw6rjnH_R)K+5G9r zFMvfaghkLdLMSQ3_DF>TQQLQLMx`hB9{yrj1pRtG;Z$nbu%Ivbz=jw8{&Gc+6rMe& zqvbyM?CWHqB^30O?tb#AedTarCJc)LA5r`UN{Va4WqNUB=OX# zylSk~``Ky;^mH-#u+zZpXiiE5o?6go?EBI#5xrsNus4F|^WOfDG zV0T1TLp(N3ds${~QQo#~aaspln4pL6THzH6y31u{ph^>x((6(9&R(u~bv49i5|v~o zBxMwf4eoPhhv`kJiAhy$I5d`~Rg3Ap;ZBEDD+zl7UI_z}nv_=6h~-FCXIFOY{kY1L zCr-8Yh=OaYAwJ!3cV=>8Nj+ZgfNx(LuFOfpc5_9Hw$xK6i(0!pe*b5yA!0eRgQT{( zAVZl{lGO2C8ttuc10FJ0%uo4>>5#3P8zz4WBDwOGd%SxW#*-& zls2F#U1wKA;QRaVG?&lI>Le>Is}m4@S9fb^a&iW)PvrBi1Q*h|_zA)y;193-!@uVXo_#to ze(Cpd`K>j{$I?n_YKu}5O37a2nq8Wm5q^&+_-|Ykds|jqQc-nXSypUL8*)()SX?Rf z7%o_L5^)rIzX9H;k!yBk$YqezNH}sQLt}OzzXk#eM2HX}LSTUi5h4T@h!7z{V1Wn` zA_NwQ5UrEo%AMcmy#fW-5WnEbK)2uQ@Lf4_y8Q?&5dBn0q1S2q7(H}q2fg!(5|zzj z+HKbJOKY@wF}X4&wQC%TAFpc=X}1;LY9}$Tt?T~L3UpV z3%7sbTiBVHp0j($p={o9Z)j>AOK5ck8xU9^`iaA0d2!KLUoUy@J;B>=FHD6S*rZ-R z5+6U~bpEhl0r{5RG;pe>H@>DXrm}Zy8gU&5FgOD6Nxh||<>O`adYUAmaVVutxFd^#zycAXZv%@V z_Tcgtxy89T`TqT(*w~pXX;fG^UQ8Rn@PJjHdZ|`2|fdg})(76bw zCtdI?6oY?eadC0VC2YpBCZ~fWPfF%rzVePa>-PHX5w|A~jN#$W2 z|Nm^JX&Rex+SQ_Ra@DQP+RZUbGLtkD(9*0(iBj8C5HypU5H~s85jcm-;eJ!O9jtZh z)&7*0VWD()cH3c>=gjAYaGoFM`-1cF!0eS5|1D~qtSSSgFl$1iWYzkZ-|xr{fGH2!V{#Z)Z)WiJ$(bWZ(MA?!jvyRV@`?CUfl|hIVG58_AVLl!dh(QX~W4g z3`Rd=;GWFrc+Tt{7`w9@9uuF(f!=oH)|ac*d|vu z&<7BIETy*j8C-dxIKSDZW(FVelR4_(!{KBEWC7HQEEH?9 z{N5>+D`gXR8TP-e@HIBt2M0DrMgpf#@0>hIG&Yiaeo1B4Z;|$Q{c!H=*-O`l^`2;U zJ3K*eJJ)>vN@wRa#uK%4p7TnADvfgVhd33qjQ0K1()#_p1`SmYp<&DI=bJlvhA>Zb z;R#!8Cdy(61q)@e@JEtmHI-y@SqmOOKdHDeslLmTD~*KGM7%Y*^{9;_wpm&k^%^;> zTkYSqT%`{R6~^ssRycKkMeo?#n@ue99xP-2gOBC%g+UfT&B#J7%c1eG*-RA2)|IX!lproMjr;zeU!9WgsoR{Zs-lm!`19J(r#Nux&E4cvEyy*MdQ1%rWb zI24OTH#d`La_T>u(CG<*EP&dPg>>(IpGg-fG?@8GpVMkF8!-L4PN&tVrzWLMP4*=b zT7sI#3zcjZ>-4EprO2d`(wF+12>BJV2*nRxQVfr~oO-=MsnV?Lv_^x$ZnL8PTGR_u z3_mvZpe+0u8ttzpbhrZ`3!qkHu_lwE(eq``qcbX*h({x6zu)Kec)gy0pNxbSqc1P30iD@3F{fHHk{}D9RxU2I>bFwy z8|i8lfv$Iqm_8Q$!#$Z|i7m0-_f%x!2#wVMN+QL`cw!4=0RUA;YX1_Wia6k^4P*fT zz@Pev2eJSF0Av9G0LTIW0FVU$03Zth0RH>3K=*w^4*&oFJ_~B4*^@ literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/rgbController-configure-connection2.png b/src/designer/src/designer/doc/images/rgbController-configure-connection2.png new file mode 100644 index 0000000000000000000000000000000000000000..f705cd6f486536300f0815b55e2cb7cf48704fa9 GIT binary patch literal 24013 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 B$z1>d literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/rgbController-final-layout.png b/src/designer/src/designer/doc/images/rgbController-final-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..f4fab0b18c811bee9e66819a3f5bc95dae2f65e4 GIT binary patch literal 8760 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 T8Grc214BhYQ@&F6ZOH!t-I?R( literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/rgbController-form-gridLayout.png b/src/designer/src/designer/doc/images/rgbController-form-gridLayout.png new file mode 100644 index 0000000000000000000000000000000000000000..5647c22a21de8cc3b35d467edb5483c33deec59e GIT binary patch literal 8711 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/rgbController-no-toplevel-layout.png b/src/designer/src/designer/doc/images/rgbController-no-toplevel-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..93e375c29a7c06abf048373e97e8a175e46ab0e2 GIT binary patch literal 1110 zcmV-c1gZOpP)D~b4-rRJOK zqi2>Hczmf8ipI}1hB*5$24;yeFH0BXyb+`zGn}D2rMU{Z?4-QQ%kzvO+aug`uYkm{ zP|vj+1L0x>K+$4UWVWpNNuj(vq@P3Po{K$C_8@C+0D08+q!D8g@!4S zX+~mQlud*yR{me)9np!W_*ypqS1ye3{MXbJ_d1qq4Um-`s5}jq*()$79_3pn%wNld16_!_x{#X=cGLu6&Fw z6%iP8mR99>EO$n#tbADa0Drm|>vb+koy+FC_}WTBy_b^Bzgk{JT@hqDBHbPS5qn{D zN6`7r&0KCR`m5d@M&>s*GWaO=?%G~Ii%c$vMrHuwol@VR3(ab((7}OGGDpw~Dcaar zx5^5^JlLvq{CuP}UtI9c=ij|byQc~%(y14TjrBq2Zc2J9I2}HiA-#2TZ#cPXdYJWB$7x+11Rh0f;cK0wR^cR1=7+U?WC368uCDS} ze0eivbMFt`3Ghz~R%rW!`0mi|E}8AWlqV}a?UL*LO|;%d8bvhPrSp+mghN;8y#%I^ z$hWnz**rsaYaWY7OXRIsNlGQ*cP%d(3f+E&e;eK5)-$|kWXe`YLK$Mm+(V7hWNRQB zh%Byhq?4hUXe2W`71dR~r0rMn-8Fnnzd3sT-Q3# zL{;k@GQEq|CbnH9Fljwi?)HTZPgB_NQma~GSE{%Wu6|1(H)FG!3yCol8GJp65)Y2^)om@LbKw0z43!u z%X{a{@1NJ4y9f~kBFm45?m4GT9sq7K;5_H5TucT+YZlPkWDslzHLrNXJ5BOO=Olg4 cub<)PA0{TagzIlMNB{r;07*qoM6N<$f`5V%p#T5? literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/rgbController-property-editing.png b/src/designer/src/designer/doc/images/rgbController-property-editing.png new file mode 100644 index 0000000000000000000000000000000000000000..a5c0aefa302df4e8d84941e26e7d80a1838f1ea5 GIT binary patch literal 6716 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/rgbController-screenshot.png b/src/designer/src/designer/doc/images/rgbController-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e62bb109806ba16327b8218498456d36081bb20b GIT binary patch literal 6726 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/images/rgbController-selectForLayout.png b/src/designer/src/designer/doc/images/rgbController-selectForLayout.png new file mode 100644 index 0000000000000000000000000000000000000000..368535857687832cf43463a2f623d8770c07fe1a GIT binary patch literal 15080 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/doc/qtdesigner.qdocconf b/src/designer/src/designer/doc/qtdesigner.qdocconf new file mode 100644 index 0000000..6d28c80 --- /dev/null +++ b/src/designer/src/designer/doc/qtdesigner.qdocconf @@ -0,0 +1,51 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qttools.qdocconf) + +project = QtDesigner +description = Qt Widgets Designer Manual +examplesinstallpath = designer + +# Path to the root of qttools (for automatic linking to source code) +url.sources.rootdir = ../../../../.. + +qhp.projects = QtDesigner + +qhp.QtDesigner.file = qtdesigner.qhp +qhp.QtDesigner.namespace = org.qt-project.qtdesigner.$QT_VERSION_TAG +qhp.QtDesigner.virtualFolder = qtdesigner +qhp.QtDesigner.indexTitle = Qt Widgets Designer Manual + +qhp.QtDesigner.subprojects = manual examples classes + +qhp.QtDesigner.subprojects.manual.title = Qt Widgets Designer +qhp.QtDesigner.subprojects.manual.indexTitle = Qt Widgets Designer module topics +qhp.QtDesigner.subprojects.manual.type = manual + +qhp.QtDesigner.subprojects.examples.title = Examples +qhp.QtDesigner.subprojects.examples.indexTitle = Qt Widgets Designer Examples +qhp.QtDesigner.subprojects.examples.selectors = fake:example +qhp.QtDesigner.subprojects.examples.sortPages = true + +qhp.QtDesigner.subprojects.classes.title = C++ Classes +qhp.QtDesigner.subprojects.classes.indexTitle = Qt Widgets Designer C++ Classes +qhp.QtDesigner.subprojects.classes.selectors = class fake:headerfile +qhp.QtDesigner.subprojects.classes.sortPages = true + +language = Cpp + +{headerdirs,sourcedirs} += .. \ + ../../../../uiplugin \ + ../../lib + +exampledirs = ../../../../../examples/designer \ + snippets + +imagedirs = images + +depends += qtdoc qtgui qtwidgets qtcore qtuitools qtquick qtcmake qmake + +# Auto-generate navigation linking based on "Qt Widgets Designer module topics": +navigation.toctitles = "Qt Widgets Designer module topics" +navigation.toctitles.inclusive = false +navigation.landingpage = "Qt Widgets Designer Manual" +navigation.cppclassespage = "Qt Widgets Designer C++ Classes" diff --git a/src/designer/src/designer/doc/snippets/CMakeLists.txt b/src/designer/src/designer/doc/snippets/CMakeLists.txt new file mode 100644 index 0000000..974b43f --- /dev/null +++ b/src/designer/src/designer/doc/snippets/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +add_subdirectory(autoconnection) +add_subdirectory(imagedialog) +add_subdirectory(multipleinheritance) +add_subdirectory(noautoconnection) + +add_subdirectory(singleinheritance) +add_subdirectory(uitools/calculatorform) diff --git a/src/designer/src/designer/doc/snippets/autoconnection/CMakeLists.txt b/src/designer/src/designer/doc/snippets/autoconnection/CMakeLists.txt new file mode 100644 index 0000000..ba19d45 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/autoconnection/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(autoconnection LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(autoconnection + imagedialog.cpp imagedialog.h imagedialog.ui main.cpp) + +set_target_properties(autoconnection PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(autoconnection PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/designer/src/designer/doc/snippets/autoconnection/autoconnection.pro b/src/designer/src/designer/doc/snippets/autoconnection/autoconnection.pro new file mode 100644 index 0000000..6937e8e --- /dev/null +++ b/src/designer/src/designer/doc/snippets/autoconnection/autoconnection.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +HEADERS = imagedialog.h +SOURCES = imagedialog.cpp \ + main.cpp diff --git a/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.cpp b/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.cpp new file mode 100644 index 0000000..8776e38 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "imagedialog.h" + +ImageDialog::ImageDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + okButton->setAutoDefault(false); + cancelButton->setAutoDefault(false); + + colorDepthCombo->addItem(tr("2 colors (1 bit per pixel)")); + colorDepthCombo->addItem(tr("4 colors (2 bits per pixel)")); + colorDepthCombo->addItem(tr("16 colors (4 bits per pixel)")); + colorDepthCombo->addItem(tr("256 colors (8 bits per pixel)")); + colorDepthCombo->addItem(tr("65536 colors (16 bits per pixel)")); + colorDepthCombo->addItem(tr("16 million colors (24 bits per pixel)")); + + connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); +} + +void ImageDialog::on_okButton_clicked() +{ + if (nameLineEdit->text().isEmpty()) { + QMessageBox::information(this, tr("No Image Name"), + tr("Please supply a name for the image."), QMessageBox::Cancel); + } else { + accept(); + } +} diff --git a/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.h b/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.h new file mode 100644 index 0000000..64d6b4e --- /dev/null +++ b/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.h @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +#include "ui_imagedialog.h" + +//! [0] +class ImageDialog : public QDialog, private Ui::ImageDialog +{ + Q_OBJECT + +public: + explicit ImageDialog(QWidget *parent = nullptr); + +private slots: + void on_okButton_clicked(); +}; +//! [0] + +#endif diff --git a/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.ui b/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.ui new file mode 100644 index 0000000..1c5e546 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/autoconnection/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/designer/src/designer/doc/snippets/autoconnection/main.cpp b/src/designer/src/designer/doc/snippets/autoconnection/main.cpp new file mode 100644 index 0000000..5c05efd --- /dev/null +++ b/src/designer/src/designer/doc/snippets/autoconnection/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ImageDialog *dialog = new ImageDialog; + dialog->show(); + return app.exec(); +} diff --git a/src/designer/src/designer/doc/snippets/designer.pro b/src/designer/src/designer/doc/snippets/designer.pro new file mode 100644 index 0000000..1445ca5 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/designer.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +SUBDIRS = autoconnection \ + imagedialog \ + multipleinheritance \ + noautoconnection \ + singleinheritance diff --git a/src/designer/src/designer/doc/snippets/imagedialog/CMakeLists.txt b/src/designer/src/designer/doc/snippets/imagedialog/CMakeLists.txt new file mode 100644 index 0000000..3ba7290 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/imagedialog/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(imagedialog LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(imagedialog + imagedialog.ui main.cpp) + +set_target_properties(imagedialog PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(imagedialog PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/designer/src/designer/doc/snippets/imagedialog/imagedialog.pro b/src/designer/src/designer/doc/snippets/imagedialog/imagedialog.pro new file mode 100644 index 0000000..c0afe03 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/imagedialog/imagedialog.pro @@ -0,0 +1,4 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +SOURCES = main.cpp diff --git a/src/designer/src/designer/doc/snippets/imagedialog/imagedialog.ui b/src/designer/src/designer/doc/snippets/imagedialog/imagedialog.ui new file mode 100644 index 0000000..1c5e546 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/imagedialog/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/designer/src/designer/doc/snippets/imagedialog/main.cpp b/src/designer/src/designer/doc/snippets/imagedialog/main.cpp new file mode 100644 index 0000000..83272df --- /dev/null +++ b/src/designer/src/designer/doc/snippets/imagedialog/main.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "ui_imagedialog.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QDialog *window = new QDialog; + Ui::ImageDialog ui; + ui.setupUi(window); + + window->show(); + return app.exec(); +} diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_default_extensionfactory.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_default_extensionfactory.cpp new file mode 100644 index 0000000..ab2b5d2 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_default_extensionfactory.cpp @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const + { + if (iid != Q_TYPEID(QDesignerContainerExtension)) + return nullptr; + + if (auto *widget = qobject_cast(object)) + return new MyContainerExtension(widget, parent); + + return nullptr; + } +//! [0] + + +//! [1] + QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const + { + auto *widget = qobject_cast(object); + if (!widget) + return nullptr; + + if (iid == Q_TYPEID(QDesignerTaskMenuExtension)) + return new MyTaskMenuExtension(widget, parent); + + if (iid == Q_TYPEID(QDesignerContainerExtension)) + return new MyContainerExtension(widget, parent); + + return nullptr; + } +//! [1] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_extension.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_extension.cpp new file mode 100644 index 0000000..b5ed7f4 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_extension.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *manager = formEditor->extensionManager(); + + auto *propertySheet = qt_extension(manager, widget); + + if(propertySheet) {...} +//! [0] + + +//! [1] + Q_DECLARE_EXTENSION_INTERFACE(MyExtension, "com.mycompany.myproduct.myextension") +//! [1] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_qextensionmanager.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_qextensionmanager.cpp new file mode 100644 index 0000000..0b79dd6 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_qextensionmanager.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + void MyPlugin::initialize(QDesignerFormEditorInterface *formEditor) + { + if (initialized) + return; + + auto *manager = formEditor->extensionManager(); + Q_ASSERT(manager != nullptr); + + manager->registerExtensions(new MyExtensionFactory(manager), + Q_TYPEID(QDesignerTaskMenuExtension)); + + initialized = true; + } +//! [0] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindow.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindow.cpp new file mode 100644 index 0000000..4a8f5df --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindow.cpp @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *formWindow = QDesignerFormWindowInterface::findFormWindow(myWidget); +//! [0] + + +//! [1] + QList forms; + + auto *manager = formEditor->formWindowManager(); + + for (int i = 0; i < manager->formWindowCount(); ++i) + forms.append(manager->formWindow(i)); +//! [1] + + +//! [2] + if (formWindow->isManaged(myWidget)) + formWindow->manageWidget(myWidget->childWidget); +//! [2] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp new file mode 100644 index 0000000..c655271 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *formWindow = QDesignerFormWindowInterface::findFormWindow(myWidget); + + formWindow->cursor()->setProperty(myWidget, myProperty, newValue); +//! [0] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp new file mode 100644 index 0000000..9427dbd --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *manager = formEditor->formWindowManager(); + auto *formWindow = manager->formWindow(0); + + manager->setActiveFormWindow(formWindow); +//! [0] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp new file mode 100644 index 0000000..9d806a8 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *objectInspector = formEditor->objectInspector(); + auto *manager = formEditor->formWindowManager(); + + objectInspector->setFormWindow(manager->formWindow(0)); +//! [0] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp new file mode 100644 index 0000000..3479883 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp @@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *propertyEditor = formEditor->propertyEditor(); + + connect(propertyEditor, &QDesignerPropertyEditorInterface::propertyChanged, + this, &MyClass::checkProperty); +//! [0] + + +//! [1] + void checkProperty(const QString &property, const QVariant &value) + { + auto *propertyEditor = formEditor->propertyEditor(); + + auto *object = propertyeditor->object(); + auto *widget = qobject_cast(object); + + if (widget && property == aProperty && value != expectedValue) + {...} + } +//! [1] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp new file mode 100644 index 0000000..a893306 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *widgetBox = formEditor->widgetBox(); + + widgetBox->load(); +//! [0] + + +//! [1] + QString originalFile = widgetBox->fileName(); + + widgetBox->setFileName("myWidgetBox.xml"); + widgetBox->save(); +//! [1] + + +//! [2] + widgetBox->setFileName(originalFile); + widgetBox->load(); +//! [2] + + +//! [3] + if (widgetBox->filename() != "myWidgetBox.xml") { + widgetBox->setFileName("myWidgetBox.xml"); + widgetBox->load(); + } +//! [3] + + diff --git a/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_uilib_formbuilder.cpp b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_uilib_formbuilder.cpp new file mode 100644 index 0000000..2120dca --- /dev/null +++ b/src/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_uilib_formbuilder.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + MyForm::MyForm(QWidget *parent) + : QWidget(parent) + { + QFormBuilder builder; + QFile file(":/forms/myWidget.ui"); + file.open(QFile::ReadOnly); + QWidget *myWidget = builder.load(&file, this); + file.close(); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(myWidget); + } +//! [0] + + +//! [1] + + + mywidget.ui + + +//! [1] + + diff --git a/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.cpp b/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.cpp new file mode 100644 index 0000000..f0483c0 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.cpp @@ -0,0 +1,75 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [1] +#include +//! [1] + + +//! [2] +void on__(); +//! [2] + + +//! [7] +class MyExtension: public QObject, + public QdesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACE(QDesignerContainerExtension) + + ... +} +//! [7] + + +//! [8] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerContainerExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast + (object)) + return new MyContainerExtension(widget, parent); + + return 0; +} +//! [8] + + +//! [9] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerContainerExtension))) { + return new MyContainerExtension(widget, parent); + + } else { + return 0; + } +} +//! [9] + + +//! [10] +void MyPlugin::initialize(QDesignerFormEditorInterface *formEditor) +{ + if (initialized) + return; + + QExtensionManager *manager = formEditor->extensionManager(); + Q_ASSERT(manager != 0); + + manager->registerExtensions(new MyExtensionFactory(manager), + Q_TYPEID(QDesignerTaskMenuExtension)); + + initialized = true; +} +//! [10] diff --git a/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.js b/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.js new file mode 100644 index 0000000..da1ad35 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.js @@ -0,0 +1,6 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [6] +widget.text = 'Hi - I was built ' + new Date().toString(); +//! [6] diff --git a/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.pro b/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.pro new file mode 100644 index 0000000..f09f257 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.pro @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#! [0] +QT += uitools +#! [0] + + +#! [3] +CONFIG += release +#! [3] + + +#! [4] +target.path = $$[QT_INSTALL_PLUGINS]/designer +INSTALLS += target +#! [4] + + +#! [5] +QT += script +#! [5] diff --git a/src/designer/src/designer/doc/snippets/multipleinheritance/CMakeLists.txt b/src/designer/src/designer/doc/snippets/multipleinheritance/CMakeLists.txt new file mode 100644 index 0000000..c7e3d8a --- /dev/null +++ b/src/designer/src/designer/doc/snippets/multipleinheritance/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(multipleinheritance LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(multipleinheritance + imagedialog.cpp imagedialog.h imagedialog.ui main.cpp) + +set_target_properties(multipleinheritance PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(multipleinheritance PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.cpp b/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.cpp new file mode 100644 index 0000000..1c34df2 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +ImageDialog::ImageDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + + colorDepthCombo->addItem(tr("2 colors (1 bit per pixel)")); + colorDepthCombo->addItem(tr("4 colors (2 bits per pixel)")); + colorDepthCombo->addItem(tr("16 colors (4 bits per pixel)")); + colorDepthCombo->addItem(tr("256 colors (8 bits per pixel)")); + colorDepthCombo->addItem(tr("65536 colors (16 bits per pixel)")); + colorDepthCombo->addItem(tr("16 million colors (24 bits per pixel)")); + + connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept); + connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); +} diff --git a/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.h b/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.h new file mode 100644 index 0000000..f6bc3b6 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +#include "ui_imagedialog.h" + +class ImageDialog : public QDialog, private Ui::ImageDialog +{ + Q_OBJECT + +public: + explicit ImageDialog(QWidget *parent = nullptr); +}; + +#endif diff --git a/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.ui b/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.ui new file mode 100644 index 0000000..1c5e546 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/designer/src/designer/doc/snippets/multipleinheritance/main.cpp b/src/designer/src/designer/doc/snippets/multipleinheritance/main.cpp new file mode 100644 index 0000000..5c05efd --- /dev/null +++ b/src/designer/src/designer/doc/snippets/multipleinheritance/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ImageDialog *dialog = new ImageDialog; + dialog->show(); + return app.exec(); +} diff --git a/src/designer/src/designer/doc/snippets/multipleinheritance/multipleinheritance.pro b/src/designer/src/designer/doc/snippets/multipleinheritance/multipleinheritance.pro new file mode 100644 index 0000000..6937e8e --- /dev/null +++ b/src/designer/src/designer/doc/snippets/multipleinheritance/multipleinheritance.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +HEADERS = imagedialog.h +SOURCES = imagedialog.cpp \ + main.cpp diff --git a/src/designer/src/designer/doc/snippets/noautoconnection/CMakeLists.txt b/src/designer/src/designer/doc/snippets/noautoconnection/CMakeLists.txt new file mode 100644 index 0000000..967bc64 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/noautoconnection/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(noautoconnection LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(noautoconnection + imagedialog.cpp imagedialog.h imagedialog.ui main.cpp) + +set_target_properties(noautoconnection PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(noautoconnection PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.cpp b/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.cpp new file mode 100644 index 0000000..e29c410 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "imagedialog.h" + +//! [0] +ImageDialog::ImageDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + okButton->setAutoDefault(false); + cancelButton->setAutoDefault(false); +//! [0] + + colorDepthCombo->addItem(tr("2 colors (1 bit per pixel)")); + colorDepthCombo->addItem(tr("4 colors (2 bits per pixel)")); + colorDepthCombo->addItem(tr("16 colors (4 bits per pixel)")); + colorDepthCombo->addItem(tr("256 colors (8 bits per pixel)")); + colorDepthCombo->addItem(tr("65536 colors (16 bits per pixel)")); + colorDepthCombo->addItem(tr("16 million colors (24 bits per pixel)")); + + connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); +//! [1] + connect(okButton, &QAbstractButton::clicked, this, &ImageDialog::checkValues); +} +//! [1] + +//! [2] +void ImageDialog::checkValues() +{ + if (nameLineEdit->text().isEmpty()) { + QMessageBox::information(this, tr("No Image Name"), + tr("Please supply a name for the image."), QMessageBox::Cancel); + } else { + accept(); + } +} +//! [2] diff --git a/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.h b/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.h new file mode 100644 index 0000000..611b901 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.h @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +#include "ui_imagedialog.h" + +//! [0] +class ImageDialog : public QDialog, private Ui::ImageDialog +{ + Q_OBJECT + +public: + explicit ImageDialog(QWidget *parent = nullptr); + +private slots: + void checkValues(); +}; +//! [0] + +#endif diff --git a/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.ui b/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.ui new file mode 100644 index 0000000..1c5e546 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/noautoconnection/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/designer/src/designer/doc/snippets/noautoconnection/main.cpp b/src/designer/src/designer/doc/snippets/noautoconnection/main.cpp new file mode 100644 index 0000000..5c05efd --- /dev/null +++ b/src/designer/src/designer/doc/snippets/noautoconnection/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ImageDialog *dialog = new ImageDialog; + dialog->show(); + return app.exec(); +} diff --git a/src/designer/src/designer/doc/snippets/noautoconnection/noautoconnection.pro b/src/designer/src/designer/doc/snippets/noautoconnection/noautoconnection.pro new file mode 100644 index 0000000..6937e8e --- /dev/null +++ b/src/designer/src/designer/doc/snippets/noautoconnection/noautoconnection.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +HEADERS = imagedialog.h +SOURCES = imagedialog.cpp \ + main.cpp diff --git a/src/designer/src/designer/doc/snippets/plugins/doc_src_qtdesigner.cpp b/src/designer/src/designer/doc/snippets/plugins/doc_src_qtdesigner.cpp new file mode 100644 index 0000000..6ac5db0 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/plugins/doc_src_qtdesigner.cpp @@ -0,0 +1,285 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [2] +QDesignerMemberSheetExtension *memberSheet = nullptr; +QExtensionManager manager = formEditor->extensionManager(); + +memberSheet = qt_extension(manager, widget); +int index = memberSheet->indexOf(setEchoMode); +memberSheet->setVisible(index, false); + +delete memberSheet; +//! [2] + + +//! [3] +class MyMemberSheetExtension : public QObject, + public QDesignerMemberSheetExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerMemberSheetExtension) + +public: + ... +} +//! [3] + + +//! [4] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerMemberSheetExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast + (object)) + return new MyMemberSheetExtension(widget, parent); + + return 0; +} +//! [4] + + +//! [5] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerMemberSheetExtension))) { + return new MyMemberSheetExtension(widget, parent); + + } else { + return 0; + } +} +//! [5] + + +//! [6] +class MyContainerExtension : public QObject, + public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) + +public: + MyContainerExtension(MyCustomWidget *widget, + QObject *parent = 0); + int count() const; + QWidget *widget(int index) const; + int currentIndex() const; + void setCurrentIndex(int index); + void addWidget(QWidget *widget); + void insertWidget(int index, QWidget *widget); + void remove(int index); + +private: + MyCustomWidget *myWidget; +}; +//! [6] + + +//! [7] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerContainerExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast + (object)) + return new MyContainerExtension(widget, parent); + + return 0; +} +//! [7] + + +//! [8] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerContainerExtension))) { + return new MyContainerExtension(widget, parent); + + } else { + return 0; + } +} +//! [8] + + +//! [9] +class MyTaskMenuExtension : public QObject, + public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) + +public: + MyTaskMenuExtension(MyCustomWidget *widget, QObject *parent); + + QAction *preferredEditAction() const; + QList taskActions() const; + +private slots: + void mySlot(); + +private: + MyCustomWidget *widget; + QAction *myAction; +}; +//! [9] + + +//! [10] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerTaskMenuExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast(object)) + return new MyTaskMenuExtension(widget, parent); + + return 0; +} +//! [10] + + +//! [11] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerContainerExtension))) { + return new MyContainerExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else { + return 0; + } +} +//! [11] + + +//! [12] +#include customwidgetoneinterface.h +#include customwidgettwointerface.h +#include customwidgetthreeinterface.h + +#include +#include + +class MyCustomWidgets: public QObject, public QDesignerCustomWidgetCollectionInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetCollectionInterface") + Q_INTERFACES(QDesignerCustomWidgetCollectionInterface) + +public: + MyCustomWidgets(QObject *parent = 0); + + QList customWidgets() const override; + +private: + QList widgets; +}; +//! [12] + + +//! [13] +MyCustomWidgets::MyCustomWidgets(QObject *parent) + : QObject(parent) +{ + widgets.append(new CustomWidgetOneInterface(this)); + widgets.append(new CustomWidgetTwoInterface(this)); + widgets.append(new CustomWidgetThreeInterface(this)); +} + +QList MyCustomWidgets::customWidgets() const +{ + return widgets; +} +//! [13] + + +//! [14] +Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface") +//! [14] + + +//! [15] +QDesignerPropertySheetExtension *propertySheet = nullptr; +QExtensionManager manager = formEditor->extensionManager(); + +propertySheet = qt_extension(manager, widget); +int index = propertySheet->indexOf(u"margin"_s); + +propertySheet->setProperty(index, 10); +propertySheet->setChanged(index, true); + +delete propertySheet; +//! [15] + + +//! [16] +class MyPropertySheetExtension : public QObject, + public QDesignerPropertySheetExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) + +public: + ... +} +//! [16] + + +//! [17] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerPropertySheetExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast + (object)) + return new MyPropertySheetExtension(widget, parent); + + return 0; +} +//! [17] + + +//! [18] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerPropertySheetExtension))) { + return new MyPropertySheetExtension(widget, parent); + + } else { + return 0; + } +} +//! [18] diff --git a/src/designer/src/designer/doc/snippets/singleinheritance/CMakeLists.txt b/src/designer/src/designer/doc/snippets/singleinheritance/CMakeLists.txt new file mode 100644 index 0000000..ac5c2be --- /dev/null +++ b/src/designer/src/designer/doc/snippets/singleinheritance/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(singleinheritanceinheritance LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(singleinheritanceinheritance + imagedialog.cpp imagedialog.h imagedialog.ui main.cpp) + +set_target_properties(singleinheritanceinheritance PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(singleinheritanceinheritance PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.cpp b/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.cpp new file mode 100644 index 0000000..4cf642d --- /dev/null +++ b/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +ImageDialog::ImageDialog(QWidget *parent) + : QDialog(parent) +{ + ui.setupUi(this); + + ui.colorDepthCombo->addItem(tr("2 colors (1 bit per pixel)")); + ui.colorDepthCombo->addItem(tr("4 colors (2 bits per pixel)")); + ui.colorDepthCombo->addItem(tr("16 colors (4 bits per pixel)")); + ui.colorDepthCombo->addItem(tr("256 colors (8 bits per pixel)")); + ui.colorDepthCombo->addItem(tr("65536 colors (16 bits per pixel)")); + ui.colorDepthCombo->addItem(tr("16 million colors (24 bits per pixel)")); + + connect(ui.okButton, &QAbstractButton::clicked, this, &QDialog::accept); + connect(ui.cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); +} diff --git a/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.h b/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.h new file mode 100644 index 0000000..6fd2df0 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.h @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +#include "ui_imagedialog.h" + +class ImageDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ImageDialog(QWidget *parent = nullptr); + +private: + Ui::ImageDialog ui; +}; + +#endif diff --git a/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.ui b/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.ui new file mode 100644 index 0000000..1c5e546 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/singleinheritance/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/designer/src/designer/doc/snippets/singleinheritance/main.cpp b/src/designer/src/designer/doc/snippets/singleinheritance/main.cpp new file mode 100644 index 0000000..5c05efd --- /dev/null +++ b/src/designer/src/designer/doc/snippets/singleinheritance/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ImageDialog *dialog = new ImageDialog; + dialog->show(); + return app.exec(); +} diff --git a/src/designer/src/designer/doc/snippets/singleinheritance/singleinheritance.pro b/src/designer/src/designer/doc/snippets/singleinheritance/singleinheritance.pro new file mode 100644 index 0000000..6937e8e --- /dev/null +++ b/src/designer/src/designer/doc/snippets/singleinheritance/singleinheritance.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +HEADERS = imagedialog.h +SOURCES = imagedialog.cpp \ + main.cpp diff --git a/src/designer/src/designer/doc/snippets/uitools/calculatorform/CMakeLists.txt b/src/designer/src/designer/doc/snippets/uitools/calculatorform/CMakeLists.txt new file mode 100644 index 0000000..ace6f3d --- /dev/null +++ b/src/designer/src/designer/doc/snippets/uitools/calculatorform/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#! [0] +cmake_minimum_required(VERSION 3.16) +project(calculatorform LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(calculatorform + calculatorform.ui main.cpp) + +set_target_properties(calculatorform PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(calculatorform PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) +#! [0] diff --git a/src/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.pro b/src/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.pro new file mode 100644 index 0000000..1a18378 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.pro @@ -0,0 +1,5 @@ +#! [0] +TEMPLATE = app +FORMS = calculatorform.ui +SOURCES = main.cpp +#! [0] diff --git a/src/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.ui b/src/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.ui new file mode 100644 index 0000000..dda0e62 --- /dev/null +++ b/src/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.ui @@ -0,0 +1,303 @@ + + + + + CalculatorForm + + + CalculatorForm + + + + 0 + 0 + 276 + 98 + + + + + 5 + 5 + 0 + 0 + + + + Calculator Builder + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + + + + 1 + + + 6 + + + + + label + + + + 1 + 1 + 45 + 19 + + + + Input 1 + + + + + + + inputSpinBox1 + + + + 1 + 26 + 45 + 25 + + + + true + + + + + + + + + label_3 + + + + 54 + 1 + 7 + 52 + + + + + + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2 + + + + 1 + 1 + 45 + 19 + + + + Input 2 + + + + + + + inputSpinBox2 + + + + 1 + 26 + 45 + 25 + + + + true + + + + + + + + + label_3_2 + + + + 120 + 1 + 7 + 52 + + + + = + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2_2_2 + + + + 1 + 1 + 37 + 17 + + + + Output + + + + + + + outputWidget + + + + 1 + 24 + 37 + 27 + + + + QFrame::Box + + + QFrame::Sunken + + + 0 + + + Qt::AlignAbsolute|Qt::AlignBottom|Qt::AlignCenter|Qt::AlignHCenter|Qt::AlignHorizontal_Mask|Qt::AlignJustify|Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing|Qt::AlignVCenter|Qt::AlignVertical_Mask + + + + + + + + + + + verticalSpacer + + + + 85 + 69 + 20 + 20 + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + horizontalSpacer + + + + 188 + 26 + 79 + 20 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + diff --git a/src/designer/src/designer/doc/snippets/uitools/calculatorform/main.cpp b/src/designer/src/designer/doc/snippets/uitools/calculatorform/main.cpp new file mode 100644 index 0000000..8e9c32b --- /dev/null +++ b/src/designer/src/designer/doc/snippets/uitools/calculatorform/main.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +#include "ui_calculatorform.h" +//! [0] +#include + +//! [1] +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QWidget widget; + Ui::CalculatorForm ui; + ui.setupUi(&widget); + + widget.show(); + return app.exec(); +} +//! [1] diff --git a/src/designer/src/designer/doc/src/designer-custom-widgets.qdoc b/src/designer/src/designer/doc/src/designer-custom-widgets.qdoc new file mode 100644 index 0000000..70b3470 --- /dev/null +++ b/src/designer/src/designer/doc/src/designer-custom-widgets.qdoc @@ -0,0 +1,105 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtdesigner-components.html + \title Creating and Using Components for Qt Widgets Designer + \brief How to create and use custom widget plugins. + \ingroup best-practices + + \section1 Creating Custom Widget Plugins + + When implementing a custom widget plugin for \QD, you must + subclass QDesignerCustomWidgetInterface to expose your custom + widget to \QD. A single custom widget plugin is built as a + separate library. If you want to include several custom widget + plugins in the same library, you must in addition subclass + QDesignerCustomWidgetCollectionInterface. + + To provide your custom widget plugin with the expected behavior + and functionality within \QD's workspace you can subclass the + associated extension classes: + + The QDesignerContainerExtension class allows you to add pages to a + custom multi-page container. The QDesignerTaskMenuExtension class + allows you to add custom menu entries to \QD's task menu. The + QDesignerMemberSheetExtension class allows you to manipulate a + widget's member functions which is displayed when configuring + connections using \QD's mode for editing signals and slots. And + finally, the QDesignerPropertySheetExtension class allows you to + manipulate a widget's properties which is displayed in \QD's + property editor. + + \image qtdesignerextensions.png + + In \QD the extensions are not created until they are required. For + that reason, when implementing extensions, you must also subclass + QExtensionFactory, i.e create a class that is able to make + instances of your extensions. In addition, you must make \QD's + extension manager register your factory; the extension manager + controls the construction of extensions as they are required, and + you can access it through QDesignerFormEditorInterface and + QExtensionManager. + + For a complete example creating a custom widget plugin with an + extension, see the \l {taskmenuextension}{Task Menu + Extension} or \l {containerextension}{Container + Extension} examples. + + \section1 Retrieving Access to \QD Components + + The purpose of the classes mentioned in this section is to provide + access to \QD's components, managers and workspace, and they are + not intended to be instantiated directly. + + \QD is composed by several components. It has an action editor, a + property editor, widget box and object inspector which you can + view in its workspace. + + \image qtdesignerscreenshot.png + + \QD also has an object that works behind the scene; it contains + the logic that integrates all of \QD's components into a coherent + application. You can access this object, using the + QDesignerFormEditorInterface, to retrieve interfaces to \QD's + components: + + \list + \li QDesignerActionEditorInterface + \li QDesignerObjectInspectorInterface + \li QDesignerPropertyEditorInterface + \li QDesignerWidgetBoxInterface + \endlist + + In addition, you can use QDesignerFormEditorInterface to retrieve + interfaces to \QD's extension manager (QExtensionManager) and form + window manager (QDesignerFormWindowManagerInterface). The + extension manager controls the construction of extensions as they + are required, while the form window manager controls the form + windows appearing in \QD's workspace. + + Once you have an interface to \QD's form window manager + (QDesignerFormWindowManagerInterface), you also have access to all + the form windows currently appearing in \QD's workspace: The + QDesignerFormWindowInterface class allows you to query and + manipulate the form windows, and it provides an interface to the + form windows' cursors. QDesignerFormWindowCursorInterface is a + convenience class allowing you to query and modify a given form + window's widget selection, and in addition modify the properties + of all the form's widgets. + + \section1 Creating User Interfaces at Run-Time + + The \c QtDesigner module contains the QFormBuilder class that + provides a mechanism for dynamically creating user interfaces at + run-time, based on UI files created with \QD. This class is + typically used by custom components and applications that embed + \QD. Standalone applications that need to dynamically generate + user interfaces at run-time use the QUiLoader class, found in + the QtUiTools module. + + For a complete example using QUiLoader, see + the \l {calculatorbuilder}{Calculator Builder example}. + + \sa {Qt Widgets Designer Manual}, {Qt UI Tools} +*/ diff --git a/src/designer/src/designer/doc/src/designer-examples.qdoc b/src/designer/src/designer/doc/src/designer-examples.qdoc new file mode 100644 index 0000000..6eb9dc8 --- /dev/null +++ b/src/designer/src/designer/doc/src/designer-examples.qdoc @@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \group examples-designer + \ingroup all-examples + \title Qt Widgets Designer Examples + \brief Using \QD to build your UI. + + \QD is a capable graphical user interface designer that lets you + create and configure forms without writing code. GUIs created with + \QD can be compiled into an application or created at run-time. + + The following examples illustrate how to create and use \QD forms + and how to create \QD custom widget plugins. +*/ + +/* + \list + \li \l{arthurplugin}{Arthur Plugin} + \li \l{calculatorbuilder}{Calculator Builder}\raisedaster + \li \l{calculatorform}{Calculator Form}\raisedaster + \li \l{calculatorform_mi}{Calculator Form/Multiple Inheritance}\raisedaster + \li \l{customwidgetplugin}{Custom Widget Plugin}\raisedaster + \li \l{taskmenuextension}{Task Menu Extension}\raisedaster + \li \l{containerextension}{Container Extension}\raisedaster + \endlist + + Examples marked with an asterisk (*) are fully documented. +*/ diff --git a/src/designer/src/designer/doc/src/designer-manual.qdoc b/src/designer/src/designer/doc/src/designer-manual.qdoc new file mode 100644 index 0000000..ae76c8e --- /dev/null +++ b/src/designer/src/designer/doc/src/designer-manual.qdoc @@ -0,0 +1,2977 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtdesigner-manual.html + + \title Qt Widgets Designer Manual + \ingroup qttools + \keyword Qt Widgets Designer + + \QD is the Qt tool for designing and building graphical user + interfaces (GUIs) with \l {Qt Widgets}. For user interface design with + \l {Qt Quick}, see \l {Qt Design Studio Manual} {Qt Design Studio}. + + You can compose and customize your windows or dialogs in a + what-you-see-is-what-you-get (WYSIWYG) manner, and test them using different + styles and resolutions. Widgets and forms created with \QD integrate + seamlessly with programmed code, using Qt's signals and slots mechanism, so + that you can easily assign behavior to graphical elements. All properties + set in \QD can be changed dynamically within the code. Furthermore, features + like widget promotion and custom plugins allow you to use your own + components with \QD. + + \note You have the option of using \l {Qt Quick} and + \l {Qt Design Studio Manual}{Qt Design Studio} for user interface + design rather than widgets. It is a much easier way to write many kinds of + applications. It enables a completely customizable appearance, + touch-reactive elements, and smooth animated transitions, taking advantage + of hardware acceleration. + + If you are new to \QD, you can take a look at the + \l{Getting To Know Qt Widgets Designer} document. For a quick tutorial on how to + use \QD, refer to \l{A Quick Start to Qt Widgets Designer}. + + \image designer-multiple-screenshot.png + + \section1 Table of Contents + + \list + \li \l{A Quick Start to Qt Widgets Designer} + \li \l{Qt Widgets Designer's Editing Modes} + \list + \li \l{Qt Widgets Designer's Widget Editing Mode}{Widget Editing Mode} + \li \l{Qt Widgets Designer's Signals and Slots Editing Mode} + {Signals and Slots Editing Mode} + \li \l{Qt Widgets Designer's Buddy Editing Mode} + {Buddy Editing Mode} + \li \l{Qt Widgets Designer's Tab Order Editing Mode} + {Tab Order Editing Mode} + \endlist + \li \l{Using Layouts in Qt Widgets Designer} + \li \l{Saving, Previewing and Printing Forms in Qt Widgets Designer} + \li \l{Using Containers in Qt Widgets Designer} + \li \l{Creating Main Windows in Qt Widgets Designer} + \li \l{Editing Resources with Qt Widgets Designer} + \li \l{Using Stylesheets with Qt Widgets Designer} + \li \l{Using a Designer UI File in Your C++ Application} + \li \l{Using a Designer UI File in Your Qt for Python Application} + \li Advanced Use + \list + \li \l{Customizing Qt Widgets Designer Forms} + \li \l{Using Custom Widgets with Qt Widgets Designer} + \li \l{Creating Custom Widgets for Qt Widgets Designer} + \li \l{Creating Custom Widget Extensions} + \li \l{Qt Widgets Designer's UI File Format} + \endlist + \endlist +*/ + + +/*! + \page designer-to-know.html + + + \title Getting to Know Qt Widgets Designer + + \image designer-screenshot.png + + \section1 Launching Designer + + Once you have installed Qt, you can start \QD in the same way as any other + application on the development host. You can also launch \QD directly from + Qt Creator. Qt Creator automatically opens all .ui files in the integrated + \QD, in \gui Design mode. + + Generally, the integrated \QD contains the same functions as the standalone + \QD. For more information about the differences, see the + \l{http://doc.qt.io/qtcreator/index.html}{Qt Creator Manual}. + + If you have large forms that do not fit in the Qt Creator \gui Design mode, + you can open them in the stand-alone \QD. + + \section1 The User Interface + + When used as a standalone application, \QD's user interface can be + configured to provide either a multi-window user interface (the default + mode), or it can be used in docked window mode. When used from within an + integrated development environment (IDE) only the multi-window user + interface is available. You can switch modes in the \gui Preferences dialog + from the \gui Edit menu. + + In multi-window mode, you can arrange each of the tool windows to suit your + working style. The main window consists of a menu bar, a tool bar, and a + widget box that contains the widgets you can use to create your user + interface. + + \target MainWindow + \table + \row + \li \inlineimage designer-main-window.png + \li \b{Qt Widgets Designer's Main Window} + + The menu bar provides all the standard actions for managing forms, + using the clipboard, and accessing application-specific help. + The current editing mode, the tool windows, and the forms in use can + also be accessed via the menu bar. + + The tool bar displays common actions that are used when editing a form. + These are also available via the main menu. + + The widget box provides common widgets and layouts that are used to + design components. These are grouped into categories that reflect their + uses or features. + \endtable + + Most features of \QD are accessible via the menu bar, the tool bar, or the + widget box. Some features are also available through context menus that can + be opened over the form windows. On most platforms, the right mouse is used + to open context menus. + + \target WidgetBox + \table + \row + \li \inlineimage designer-widget-box.png + \li \b{Qt Widgets Designer's Widget Box} + + The widget box provides a selection of standard Qt widgets, layouts, + and other objects that can be used to create user interfaces on forms. + Each of the categories in the widget box contain widgets with similar + uses or related features. + + You can display all of the available objects in a category by clicking + on the handle next to the category label. When in + \l{Qt Widgets Designer's Widget Editing Mode}{Widget Editing + Mode}, you can add objects to a form by dragging the appropriate items + from the widget box onto the form, and dropping them in the required + locations. + + \QD provides a scratch pad feature that allows you to collect + frequently used objects in a separate category. The scratch pad + category can be filled with any widget currently displayed in a form + by dragging them from the form and dropping them onto the widget box. + These widgets can be used in the same way as any other widgets, but + they can also contain child widgets. Open a context menu over a widget + to change its name or remove it from the scratch pad. + \endtable + + + \section1 The Concept of Layouts in Qt + + A layout is used to arrange and manage the elements that make up a user + interface. Qt provides a number of classes to automatically handle layouts + -- QHBoxLayout, QVBoxLayout, QGridLayout, and QFormLayout. These classes + solve the challenge of laying out widgets automatically, providing a user + interface that behaves predictably. Fortunately knowledge of the layout + classes is not required to arrange widgets with \QD. Instead, select one of + the \gui{Lay Out Horizontally}, \gui{Lay Out in a Grid}, etc., options from + the context menu. + + Each Qt widget has a recommended size, known as \l{QWidget::}{sizeHint()}. + The layout manager will attempt to resize a widget to meet its size hint. + In some cases, there is no need to have a different size. For example, the + height of a QLineEdit is always a fixed value, depending on font size and + style. In other cases, you may require the size to change, e.g., the width + of a QLineEdit or the width and height of item view widgets. This is where + the widget size constraints -- \l{QWidget::minimumSize()}{minimumSize} and + \l{QWidget::maximumSize()}{maximumSize} constraints come into play. These + are properties you can set in the property editor. For example, to override + the default \l{QWidget::}{sizeHint()}, simply set + \l{QWidget::minimumSize()}{minimumSize} and \l{QWidget::maximumSize()} + {maximumSize} to the same value. Alternatively, to use the current size as + a size constraint value, choose one of the \gui{Size Constraint} options + from the widget's context menu. The layout will then ensure that those + constraints are met. To control the size of your widgets via code, you can + reimplement \l{QWidget::}{sizeHint()} in your code. + + The screenshot below shows the breakdown of a basic user interface designed + using a grid. The coordinates on the screenshot show the position of each + widget within the grid. + + \image addressbook-tutorial-part3-labeled-layout.png + + \note Inside the grid, the QPushButton objects are actually nested. The + buttons on the right are first placed in a QVBoxLayout; the buttons at the + bottom are first placed in a QHBoxLayout. Finally, they are put into + coordinates (1,2) and (2,1) of the QGridLayout. + + To visualize, imagine the layout as a box that shrinks as much as possible, + attempting to \e squeeze your widgets in a neat arrangement, and, at the + same time, maximize the use of available space. + + Qt's layouts help when you: + + \list 1 + \li Resize the user face to fit different window sizes. + \li Resize elements within the user interface to suit different + localizations. + \li Arrange elements to adhere to layout guidelines for different + platforms. + \endlist + + So, you no longer have to worry about rearranging widgets for different + platforms, settings, and languages. + + The example below shows how different localizations can affect the user + interface. When a localization requires more space for longer text strings + the Qt layout automatically scales to accommodate this, while ensuring that + the user interface looks presentable and still matches the platform + guidelines. + + \table + \header + \li A Dialog in English + \li A Dialog in French + \row + \li \image designer-english-dialog.png + \li \image designer-french-dialog.png + \endtable + + The process of laying out widgets consists of creating the layout hierarchy + while setting as few widget size constraints as possible. + + For a more technical perspective on Qt's layout classes, refer to the + \l{Layout Management} documentation. +*/ + + +/*! + \page designer-quick-start.html + + + \title A Quick Start to Qt Widgets Designer + + Using \QD involves \b four basic steps: + + \list 1 + \li Choose your form and objects + \li Lay the objects out on the form + \li Connect the signals to the slots + \li Preview the form + \endlist + + \image rgbController-screenshot.png + + Suppose you would like to design a small widget (see screenshot above) that + contains the controls needed to manipulate Red, Green and Blue (RGB) values + -- a type of widget that can be seen everywhere in image manipulation + programs. + + \table + \row + \li \inlineimage designer-choosing-form.png + \li \b{Choosing a Form} + + You start by choosing \gui Widget from the \gui{New Form} dialog. + \endtable + + + \table + \row + \li \inlineimage rgbController-arrangement.png + \li \b{Placing Widgets on a Form} + + Drag three labels, three spin boxes and three vertical sliders on to your + form. To change the label's default text, simply double-click on it. You + can arrange them according to how you would like them to be laid out. + \endtable + + To ensure that they are laid out exactly like this in your program, you + need to place these widgets into a layout. We will do this in groups of + three. Select the "RED" label. Then, hold down \key Ctrl while you select + its corresponding spin box and slider. In the \gui{Form} menu, select + \gui{Lay Out in a Grid}. + + \table + \row + \li \inlineimage rgbController-form-gridLayout.png + \li \inlineimage rgbController-selectForLayout.png + \endtable + + + Repeat the step for the other two labels along with their corresponding + spin boxes and sliders as well. + + The next step is to combine all three layouts into one \b{main layout}. + The main layout is the top level widget's (in this case, the QWidget) + layout. It is important that your top level widget has a layout; otherwise, + the widgets on your window will not resize when your window is resized. To + set the layout, \gui{Right click} anywhere on your form, outside of the + three separate layouts, and select \gui{Lay Out Horizontally}. + Alternatively, you could also select \gui{Lay Out in a Grid} -- you will + still see the same arrangement (shown below). + + \image rgbController-final-layout.png + + \note Main layouts cannot be seen on the form. To check if you have a main + layout installed, try resizing your form; your widgets should resize + accordingly. Alternatively, you can take a look at \QD's + \gui{Object Inspector}. If your top level widget does not have a layout, + you will see the broken layout icon next to it, + \inlineimage rgbController-no-toplevel-layout.png + . + + When you click on the slider and drag it to a certain value, you want the + spin box to display the slider's position. To accomplish this behavior, you + need to connect the slider's \l{QAbstractSlider::}{valueChanged()} signal + to the spin box's \l{QSpinBox::}{setValue()} slot. You also need to make + the reverse connections, e.g., connect the spin box's \l{QSpinBox::} + {valueChanged()} signal to the slider's \l{QAbstractSlider::value()} + {setValue()} slot. + + To do this, you have to switch to \gui{Edit Signals/Slots} mode, either by + pressing \key{F4} or selecting \gui{Edit Signals/Slots} from the \gui{Edit} + menu. + + \table + \row + \li \inlineimage rgbController-signalsAndSlots.png + \li \b{Connecting Signals to Slots} + + Click on the slider and drag the cursor towards the spin box. The + \gui{Configure Connection} dialog, shown below, will pop up. Select the + correct signal and slot and click \gui OK. + \endtable + + \image rgbController-configure-connection1.png + + Repeat the step (in reverse order), clicking on the spin box and dragging + the cursor towards the slider, to connect the spin box's + \l{QSpinBox::}{valueChanged()} signal to the slider's + \l{QAbstractSlider::value()}{setValue()} slot. + + You can use the screenshot below as a guide to selecting the correct signal + and slot. + + \image rgbController-configure-connection2.png + + Now that you have successfully connected the objects for the "RED" + component of the RGB Controller, do the same for the "GREEN" and "BLUE" + components as well. + + Since RGB values range between 0 and 255, we need to limit the spin box + and slider to that particular range. + + \table + \row + \li \inlineimage rgbController-property-editing.png + \li \b{Setting Widget Properties} + + Click on the first spin box. Within the \gui{Property Editor}, you will + see \l{QSpinBox}'s properties. Enter "255" for the + \l{QSpinBox::}{maximum} property. Then, click on the first vertical + slider, you will see \l{QAbstractSlider}'s properties. Enter "255" for + the \l{QAbstractSlider::}{maximum} property as well. Repeat this + process for the remaining spin boxes and sliders. + \endtable + + Now, we preview your form to see how it would look in your application - + press \key{Ctrl + R} or select \gui Preview from the \gui Form menu. Try + dragging the slider - the spin box will mirror its value too (and vice + versa). Also, you can resize it to see how the layouts that are used to + manage the child widgets, respond to different window sizes. +*/ + + +/*! + \page designer-editing-mode.html + \previouspage Getting to Know Qt Widgets Designer + \nextpage Using Layouts in Qt Widgets Designer + + \title Qt Widgets Designer's Editing Modes + + \QD provides four editing modes: \l{Qt Widgets Designer's Widget Editing Mode} + {Widget Editing Mode}, \l{Qt Widgets Designer's Signals and Slots Editing Mode} + {Signals and Slots Editing Mode}, \l{Qt Widgets Designer's Buddy Editing Mode} + {Buddy Editing Mode} and \l{Qt Widgets Designer's Tab Order Editing Mode} + {Tab Order Editing Mode}. When working with \QD, you will always be in one + of these four modes. To switch between modes, simply select it from the + \gui{Edit} menu or the toolbar. The table below describes these modes in + further detail. + + \table + \header \li \li \b{Editing Modes} + \row + \li \inlineimage designer-widget-tool.png + \li In \l{Qt Widgets Designer's Widget Editing Mode}{Edit} mode, we can + change the appearance of the form, add layouts, and edit the + properties of each widget. To switch to this mode, press + \key{F3}. This is \QD's default mode. + + \row + \li \inlineimage designer-connection-tool.png + \li In \l{Qt Widgets Designer's Signals and Slots Editing Mode} + {Signals and Slots} mode, we can connect widgets together using + Qt's signals and slots mechanism. To switch to this mode, press + \key{F4}. + + \row + \li \inlineimage designer-buddy-tool.png + \li In \l{Qt Widgets Designer's Buddy Editing Mode}{Buddy Editing Mode}, + buddy widgets can be assigned to label widgets to help them + handle keyboard focus correctly. + + \row + \li \inlineimage designer-tab-order-tool.png + \li In \l{Qt Widgets Designer's Tab Order Editing Mode} + {Tab Order Editing Mode}, we can set the order in which widgets + receive the keyboard focus. + \endtable + +*/ + + +/*! + \page designer-widget-mode.html + \previouspage Qt Widgets Designer's Editing Modes + \nextpage Qt Widgets Designer's Signals and Slots Editing Mode + + \title Qt Widgets Designer's Widget Editing Mode + + \image designer-editing-mode.png + + In the Widget Editing Mode, objects can be dragged from the main window's + widget box to a form, edited, resized, dragged around on the form, and even + dragged between forms. Object properties can be modified interactively, so + that changes can be seen immediately. The editing interface is intuitive + for simple operations, yet it still supports Qt's powerful layout + facilities. + + + To create and edit new forms, open the \gui File menu and select + \gui{New Form...} or press \key{Ctrl+N}. Existing forms can also be edited + by selecting \gui{Open Form...} from the \gui File menu or pressing + \key{Ctrl+O}. + + At any point, you can save your form by selecting the \gui{Save From As...} + option from the \gui File menu. The UI files saved by \QD contain + information about the objects used, and any details of signal and slot + connections between them. + + + \section1 Editing A Form + + By default, new forms are opened in widget editing mode. To switch to Edit + mode from another mode, select \gui{Edit Widgets} from the \gui Edit menu + or press the \key F3 key. + + Objects are added to the form by dragging them from the main widget box + and dropping them in the desired location on the form. Once there, they + can be moved around simply by dragging them, or using the cursor keys. + Pressing the \key Ctrl key at the same time moves the selected widget + pixel by pixel, while using the cursor keys alone make the selected widget + snap to the grid when it is moved. Objects can be selected by clicking on + them with the left mouse button. You can also use the \key Tab key to + change the selection. + + The widget box contains objects in a number of different categories, all of + which can be placed on the form as required. The only objects that require + a little more preparation are the \gui Container widgets. These are + described in further detail in the \l{Using Containers in Qt Widgets Designer} + chapter. + + + \target SelectingObjects + \table + \row + \li \inlineimage designer-selecting-widget.png + \li \b{Selecting Objects} + + Objects on the form are selected by clicking on them with the left + mouse button. When an object is selected, resize handles are shown at + each corner and the midpoint of each side, indicating that it can be + resized. + + To select additional objects, hold down the \key Control key and click on + them. If more than one object is selected, the current object will be + displayed with resize handles of a different color. + + To move a widget within a layout, hold down \key Shift and \key Control + while dragging the widget. This extends the selection to the widget's + parent layout. + + Alternatively, objects can be selected in the + \l{The Object Inspector}{Object Inspector}. + \endtable + + When a widget is selected, normal clipboard operations such as cut, copy, + and paste can be performed on it. All of these operations can be done and + undone, as necessary. + + The following shortcuts can be used: + + \target ShortcutsForEditing + \table + \header \li Action \li Shortcut \li Description + \row + \li Cut + \li \key{Ctrl+X} + \li Cuts the selected objects to the clipboard. + \row + \li Copy + \li \key{Ctrl+C} + \li Copies the selected objects to the clipboard. + \row + \li Paste + \li \key{Ctrl+V} + \li Pastes the objects in the clipboard onto the form. + \row + \li Delete + \li \key Delete + \li Deletes the selected objects. + \row + \li Clone object + \li \key{Ctrl+drag} (leftmouse button) + \li Makes a copy of the selected object or group of objects. + \row + \li Preview + \li \key{Ctrl+R} + \li Shows a preview of the form. + \endtable + + All of the above actions (apart from cloning) can be accessed via both the + \gui Edit menu and the form's context menu. These menus also provide + funcitons for laying out objects as well as a \gui{Select All} function to + select all the objects on the form. + + Widgets are not unique objects; you can make as many copies of them as you + need. To quickly duplicate a widget, you can clone it by holding down the + \key Ctrl key and dragging it. This allows widgets to be copied and placed + on the form more quickly than with clipboard operations. + + + \target DragAndDrop + \table + \row + \li \inlineimage designer-dragging-onto-form.png + \li \b{Drag and Drop} + + \QD makes extensive use of the drag and drop facilities provided by Qt. + Widgets can be dragged from the widget box and dropped onto the form. + + Widgets can also be "cloned" on the form: Holding down \key Ctrl and + dragging the widget creates a copy of the widget that can be dragged to + a new position. + + It is also possible to drop Widgets onto the \l {The Object Inspector} + {Object Inspector} to handle nested layouts easily. + \endtable + + \QD allows selections of objects to be copied, pasted, and dragged between + forms. You can use this feature to create more than one copy of the same + form, and experiment with different layouts in each of them. + + + \section2 The Property Editor + + The Property Editor always displays properties of the currently selected + object on the form. The available properties depend on the object being + edited, but all of the widgets provided have common properties such as + \l{QObject::}{objectName}, the object's internal name, and + \l{QWidget::}{enabled}, the property that determines whether an + object can be interacted with or not. + + + \target EditingProperties + \table + \row + \li \inlineimage designer-property-editor.png + \li \b{Editing Properties} + + The property editor uses standard Qt input widgets to manage the + properties of objects on the form. Textual properties are shown in line + edits, integer properties are displayed in spinboxes, boolean + properties are displayed in check boxes, and compound properties such + as colors and sizes are presented in drop-down lists of input widgets. + + Modified properties are indicated with bold labels. To reset them, click + the arrow button on the right. + + Changes in properties are applied to all selected objects that have the + same property. + \endtable + + Certain properties are treated specially by the property editor: + + \list + \li Compound properties -- properties that are made up of more than one + value -- are represented as nodes that can be expanded, allowing + their values to be edited. + \li Properties that contain a choice or selection of flags are edited + via combo boxes with checkable items. + \li Properties that allow access to rich data types, such as QPalette, + are modified using dialogs that open when the properties are edited. + QLabel and the widgets in the \gui Buttons section of the widget box + have a \c text property that can also be edited by double-clicking + on the widget or by pressing \gui F2. \QD interprets the backslash + (\\) character specially, enabling newline (\\n) characters to be + inserted into the text; the \\\\ character sequence is used to + insert a single backslash into the text. A context menu can also be + opened while editing, providing another way to insert special + characters and newlines into the text. + \endlist + + + \section2 Dynamic Properties + + The property editor can also be used to add new + \l{QObject#Dynamic Properties}{dynamic properties} to both standard Qt + widgets and to forms themselves. Since Qt 4.4, dynamic properties are added + and removed via the property editor's toolbar, shown below. + + \image designer-property-editor-toolbar.png + + To add a dynamic property, click on the \gui Add button + \inlineimage designer-property-editor-add-dynamic.png + . To remove it, click on the \gui Remove button + \inlineimage designer-property-editor-remove-dynamic.png + instead. You can also sort the properties alphabetically and change the + color groups by clickinig on the \gui Configure button + \inlineimage designer-property-editor-configure.png + . + + \section2 The Object Inspector + \table + \row + \li \inlineimage designer-object-inspector.png + \li \b{The Object Inspector} + + The \gui{Object Inspector} displays a hierarchical list of all the + objects on the form that is currently being edited. To show the child + objects of a container widget or a layout, click the handle next to the + object label. + + Each object on a form can be selected by clicking on the corresponding + item in the \gui{Object Inspector}. Right-clicking opens the form's + context menu. These features can be useful if you have many overlapping + objects. To locate an object in the \gui{Object Inspector}, use + \key{Ctrl+F}. + + Since Qt 4.4, double-clicking on the object's name allows you to change + the object's name with the in-place editor. + + Since Qt 4.5, the \gui{Object Inspector} displays the layout state of + the containers. The broken layout icon ###ICON is displayed if there is + something wrong with the layouts. + + \endtable +*/ + + +/*! + \page designer-layouts.html + \previouspage Qt Widgets Designer's Widget Editing Mode + \nextpage Qt Widgets Designer's Signals and Slots Editing Mode + + \title Using Layouts in Qt Widgets Designer + + Before a form can be used, the objects on the form need to be placed into + layouts. This ensures that the objects will be displayed properly when the + form is previewed or used in an application. Placing objects in a layout + also ensures that they will be resized correctly when the form is resized. + + Once widgets have been inserted into a layout, it is not possible to move + and resize them individually because the layout itself controls the + geometry of each widget within it, taking account of the hints provided by + spacers. Spacers can be added to the layout to influence the geometries of + the widgets. + + Layouts can be nested to form a hierarchy. For example, to achieve a + typical dialog layout with a horizontal row of buttons, the dialog + elements can be laid out using a vertical box layout with a horizontal + box layout containing the buttons at the bottom. For an introduction to + the Qt layout system, refer to \l{Layout Management}. + + To break a layout, press \key{Ctrl+0} or choose \gui{Break Layout} from + the form's context menu, the \gui Form menu or the main toolbar. + + \section1 Setting A Top Level Layout + + The form's top level layout can be set by clearing the selection (click the + left mouse button on the form itself) and applying a layout. A top level + layout is necessary to ensure that your widgets will resize correctly when + its window is resized. To check if you have set a top level layout, preview + your widget and attempt to resize the window by dragging the size grip. + + \table + \row + \li \inlineimage designer-set-layout.png + \li \b{Applying a Layout} + + To apply a layout, you can select your choice of layout from the + toolbar shown on the left, or from the context menu shown below. + \endtable + + Similary, top level layouts are set on container widgets (QGroupBox) + or on pages of page-based container widgets (QTabWidget, QToolBox + and QStackedWidget), respectively. The container widget needs to be + selected for this to succeed. + + Top level layouts are not visible as separate objects in the Object + Inspector. Their properties appear below the widget properties of the + main form, container widget, or page of a container widget in the + Property Editor. + + \image designer-set-layout2.png + + + \section1 Layout Objects + + Layout objects are created by applying a layout to a group of + existing objects. This is achieved by selecting the objects that you need + to manage and applying one of the standard layouts using the main toolbar, + the \gui Form menu, or the form's context menu. + + The layout object is indicated by a red frame on the form and appears as + an object in the Object Inspector. Its properties (margins and constraints) + are shown in the Property Editor. + + The layout object can be selected and placed within another layout along + with other widgets and layout objects to build a layout hierarchy. + + When a child layout object is selected, its parent layout object can be + selected by pressing down the \key Shift key while clicking on it. This + makes it possible to select a specific layout in a hierarchy, which is + otherwise difficult due to the small frame. + + + \section1 Inserting Objects Into a Layout + \target InsertingObjectsIntoALayout + + Objects can be inserted into an existing layout by dragging them from + their current positions and dropping them at the required location. A + blue cursor is displayed in the layout as an object is dragged over + it to indicate where the object will be added. + + \image designer-layout-inserting.png + \caption Inserting Objects into a Layout + + \section1 Layout Types + \section2 Horizontal and Vertical (Box) Layouts + + The simplest way to arrange objects on a form is to place them in a + horizontal or vertical layout. Horizontal layouts ensure that the widgets + within are aligned horizontally; vertical layouts ensure that they are + aligned vertically. + + Horizontal and vertical layouts can be combined and nested to any depth. + However, if you need more control over the placement of objects, consider + using the grid layout. + + + \section2 The Grid Layout + + Complex form layouts can be created by placing objects in a grid layout. + This kind of layout gives the form designer much more freedom to arrange + widgets on the form, but can result in a much less flexible layout. + However, for some kinds of form layout, a grid arrangement is much more + suitable than a nested arrangement of horizontal and vertical layouts. + + + \section2 The Form Layout + + The QFormLayout + class manages widgets in a two-column form; the left column holds labels + and the right column holds field widgets such as line edits, spin boxes, + etc. The QFormLayout class adheres to various platform look and feel + guidelines and supports wrapping for long rows. + + \image designer-form-layout.png + + The UI file above results in the previews shown below. + + \table + \header + \li Windows XP + \li \macos + \li Cleanlooks + \row + \li \inlineimage designer-form-layout-windowsXP.png + \li \inlineimage designer-form-layout-macintosh.png + \li \inlineimage designer-form-layout-cleanlooks.png + \endtable + + + \section2 Splitter Layouts + + Another common way to manage the layout of objects on a form is to place + them in a splitter. These splitters arrange the objects horizontally or + vertically in the same way as normal layouts, but also allow the user to + adjust the amount of space allocated to each object. + + \image designer-splitter-layout.png + + Although QSplitter is a container widget, \QD treats splitter objects as + layouts that are applied to existing widgets. To place a group of widgets + into a splitter, select them + \l{Qt Widgets Designer's Widget Editing Mode#SelectingObjects}{as described here} + then apply the splitter layout by using the appropriate toolbar button, + keyboard shortcut, or \gui{Lay out} context menu entry. + + + \section1 Shortcut Keys + + In addition to the standard toolbar and context menu entries, there is also + a set of keyboard shortcuts to apply layouts on widgets. + + \target LayoutShortcuts + \table + \header + \li Layout + \li Shortcut + \li Description + \row + \li Horizontal + \li \key{Ctrl+1} + \li Places the selected objects in a horizontal layout. + \row + \li Vertical + \li \key{Ctrl+2} + \li Places the selected objects in a vertical layout. + \row + \li Grid + \li \key{Ctrl+5} + \li Places the selected objects in a grid layout. + \row + \li Form + \li \key{Ctrl+6} + \li Places the selected objects in a form layout. + \row + \li Horizontal splitter + \li \key{Ctrl+3} + \li Creates a horizontal splitter and places the selected objects + inside it. + \row + \li Vertical splitter + \li \key{Ctrl+4} + \li Creates a vertical splitter and places the selected objects + inside it. + \row + \li Adjust size + \li \key{Ctrl+J} + \li Adjusts the size of the layout to ensure that each child object + has sufficient space to display its contents. See + QWidget::adjustSize() for more information. + \endtable + + \note \key{Ctrl+0} is used to break a layout. + +*/ + + +/*! + \page designer-preview.html + \previouspage Using Layouts in Qt Widgets Designer + \nextpage Qt Widgets Designer's Buddy Editing Mode + \title Saving, Previewing and Printing Forms in Qt Widgets Designer + + Although \QD's forms are accurate representations of the components being + edited, it is useful to preview the final appearance while editing. This + feature can be activated by opening the \gui Form menu and selecting + \gui Preview, or by pressing \key{Ctrl+R} when in the form. + + \image designer-dialog-preview.png + + The preview shows exactly what the final component will look like when used + in an application. + + Since Qt 4.4, it is possible to preview forms with various skins - default + skins, skins created with Qt Style Sheets or device skins. This feature + simulates the effect of calling \c{QApplication::setStyleSheet()} in the + application. + + To preview your form with skins, open the \gui Edit menu and select + \gui{Preferences...} + + You will see the dialog shown below: + + \image designer-preview-style.png + + The \gui{Print/Preview Configuration} checkbox must be checked to activate + previews of skins. You can select the styles provided from the \gui{Style} + drop-down box. + + \image designer-preview-style-selection.png + + Alternatively, you can preview custom style sheet created with Qt Style + Sheets. The figure below shows an example of Qt Style Sheet syntax and the + corresponding output. + + \image designer-preview-stylesheet.png + + Another option would be to preview your form with device skins. A list of + generic device skins are available in \QD, however, you may also use + other QVFB skins with the \gui{Browse...} option. + + \image designer-preview-deviceskin-selection.png + + + \section1 Viewing the Form's Code + + Since Qt 4.4, it is possible to view code generated by the User Interface + Compiler (uic) for the \QD form. + + \image designer-form-viewcode.png + + Select \gui{View Code...} from the \gui{Form} menu and a dialog with the + generated code will be displayed. The screenshot below is an example of + code generated by the \c{uic}. + + \image designer-code-viewer.png + + \section1 Saving and Printing the Form + + Forms created in \QD can be saved to an image or printed. + + \table + \row + \li \inlineimage designer-file-menu.png + \li \b{Saving Forms} + + To save a form as an image, choose the \gui{Save Image...} option. The file + will be saved in \c{.png} format. + + \b{Printing Forms} + + To print a form, select the \gui{Print...} option. + + \endtable +*/ + + +/*! + \page designer-connection-mode.html + \previouspage Using Layouts in Qt Widgets Designer + \nextpage Qt Widgets Designer's Buddy Editing Mode + + + \title Qt Widgets Designer's Signals and Slots Editing Mode + + \image designer-connection-mode.png + + In \QD's signals and slots editing mode, you can connect objects in a form + together using Qt's signals and slots mechanism. Both widgets and layouts + can be connected via an intuitive connection interface, using the menu of + compatible signals and slots provided by \QD. When a form is saved, all + connections are preserved so that they will be ready for use when your + project is built. + + + For more information on Qt's signals and sltos mechanism, refer to the + \l{Signals and Slots} document. + + + \section1 Connecting Objects + + To begin connecting objects, enter the signals and slots editing mode by + opening the \gui Edit menu and selecting \gui{Edit Signals/Slots}, or by + pressing the \key F4 key. + + All widgets and layouts on the form can be connected together. However, + spacers just provide spacing hints to layouts, so they cannot be connected + to other objects. + + + \target HighlightedObjects + \table + \row + \li \inlineimage designer-connection-highlight.png + \li \b{Highlighted Objects} + + When the cursor is over an object that can be used in a connection, the + object will be highlighted. + \endtable + + To make a connectionn, press the left mouse button and drag the cursor + towards the object you want to connect it to. As you do this, a line will + extend from the source object to the cursor. If the cursor is over another + object on the form, the line will end with an arrow head that points to the + destination object. This indicates that a connection will be made between + the two objects when you release the mouse button. + + You can abandon the connection at any point while you are dragging the + connection path by pressing \key{Esc}. + + \target MakingAConnection + \table + \row + \li \inlineimage designer-connection-making.png + \li \b{Making a Connection} + + The connection path will change its shape as the cursor moves around + the form. As it passes over objects, they are highlighted, indicating + that they can be used in a signal and slot connection. Release the + mouse button to make the connection. + \endtable + + The \gui{Configure Connection} dialog (below) is displayed, showing signals + from the source object and slots from the destination object that you can + use. + + \image designer-connection-dialog.png + + To complete the connection, select a signal from the source object and a + slot from the destination object, then click \key OK. Click \key Cancel if + you wish to abandon the connection. + + \note If the \gui{Show all signals and slots} checkbox is selected, all + available signals from the source object will be shown. Otherwise, the + signals and slots inherited from QWidget will be hidden. + + You can make as many connections as you like between objects on the form; + it is possible to connect signals from objects to slots in the form itself. + As a result, the signal and slot connections in many dialogs can be + completely configured from within \QD. + + \target ConnectingToTheForm + \table + \row + \li \inlineimage designer-connection-to-form.png + \li \b{Connecting to a Form} + + To connect an object to the form itself, simply position the cursor + over the form and release the mouse button. The end point of the + connection changes to the electrical "ground" symbol. + \endtable + + + \section1 Editing and Deleting Connections + + By default, connection paths are created with two labels that show the + signal and slot involved in the connection. These labels are usually + oriented along the line of the connection. You can move them around inside + their host widgets by dragging the red square at each end of the connection + path. + + \target ConnectionEditor + \table + \row + \li \inlineimage designer-connection-editor.png + \li \b{The Signal/Slot Editor} + + The signal and slot used in a connection can be changed after it has + been set up. When a connection is configured, it becomes visible in + \QD's signal and slot editor where it can be further edited. You can + also edit signal/slot connections by double-clicking on the connection + path or one of its labels to display the Connection Dialog. + \endtable + + \target DeletingConnections + \table + \row + \li \inlineimage designer-connection-editing.png + \li \b{Deleting Connections} + + The whole connection can be selected by clicking on any of its path + segments. Once selected, a connection can be deleted with the + \key Delete key, ensuring that it will not be set up in the UI + file. + \endtable +*/ + + +/*! + \page designer-buddy-mode.html + \previouspage Qt Widgets Designer's Signals and Slots Editing Mode + \nextpage Qt Widgets Designer's Tab Order Editing Mode + + \title Qt Widgets Designer's Buddy Editing Mode + + \image designer-buddy-mode.png + + One of the most useful basic features of Qt is the support for buddy + widgets. A buddy widget accepts the input focus on behalf of a QLabel when + the user types the label's shortcut key combination. The buddy concept is + also used in Qt's \l{Model/View Programming}{model/view} framework. + + + \section1 Linking Labels to Buddy Widgets + + To enter buddy editing mode, open the \gui Edit menu and select + \gui{Edit Buddies}. This mode presents the widgets on the form in a similar + way to \l{Qt Widgets Designer's Signals and Slots Editing Mode}{signals and slots + editing mode} but in this mode, connections must start at label widgets. + Ideally, you should connect each label widget that provides a shortcut with + a suitable input widget, such as a QLineEdit. + + + \target MakingBuddies + \table + \row + \li \inlineimage designer-buddy-making.png + \li \b{Making Buddies} + + To define a buddy widget for a label, click on the label, drag the + connection to another widget on the form, and release the mouse button. + The connection shown indicates how input focus is passed to the buddy + widget. You can use the form preview to test the connections between + each label and its buddy. + \endtable + + + \section1 Removing Buddy Connections + + Only one buddy widget can be defined for each label. To change the buddy + used, it is necessary to delete any existing buddy connection before you + create a new one. + + Connections between labels and their buddy widgets can be deleted in the + same way as signal-slot connections in signals and slots editing mode: + Select the buddy connection by clicking on it and press the \key Delete + key. This operation does not modify either the label or its buddy in any + way. +*/ + + +/*! + \page designer-tab-order.html + \previouspage Qt Widgets Designer's Buddy Editing Mode + \nextpage Using Containers in Qt Widgets Designer + + \title Qt Widgets Designer's Tab Order Editing Mode + + \image designer-tab-order-mode.png + + Many users expect to be able to navigate between widgets and controls + using only the keyboard. Qt lets the user navigate between input widgets + with the \key Tab and \key{Shift+Tab} keyboard shortcuts. The default + \e{tab order} is based on the order in which widgets are constructed. + Although this order may be sufficient for many users, it is often better + to explicitly specify the tab order to make your application easier to + use. + + + \section1 Setting the Tab Order + + To enter tab order editing mode, open the \gui Edit menu and select + \gui{Edit Tab Order}. In this mode, each input widget in the form is shown + with a number indicating its position in the tab order. So, if the user + gives the first input widget the input focus and then presses the tab key, + the focus will move to the second input widget, and so on. + + The tab order is defined by clicking on each of the numbers in the correct + order. The first number you click will change to red, indicating the + currently edited position in the tab order chain. The widget associated + with the number will become the first one in the tab order chain. Clicking + on another widget will make it the second in the tab order, and so on. + + Repeat this process until you are satisfied with the tab order in the form + -- you do not need to click every input widget if you see that the + remaining widgets are already in the correct order. Numbers, for which you + already set the order, change to green, while those which are not clicked + yet, remain blue. + + If you make a mistake, simply double click outside of any number or choose + \gui{Restart} from the form's context menu to start again. If you have many + widgets on your form and would like to change the tab order in the middle or + at the end of the tab order chain, you can edit it at any position. Press + \key{Ctrl} and click the number from which you want to start. + Alternatively, choose \gui{Start from Here} in the context menu. + +*/ + + +/*! + \page designer-using-containers.html + \previouspage Qt Widgets Designer's Tab Order Editing Mode + \nextpage Creating Main Windows in Qt Widgets Designer + + + \title Using Containers in Qt Widgets Designer + + Container widgets provide high level control over groups of objects on a + form. They can be used to perform a variety of functions, such as managing + input widgets, providing paged and tabbed layouts, or just acting as + decorative containers for other objects. + + \image designer-widget-morph.png + + \QD provides visual feedback to help you place objects inside your + containers. When you drag an object from the widget box (or elsewhere) on + the form, each container will be highlighted when the cursor is positioned + over it. This indicates that you can drop the object inside, making it a + child object of the container. This feedback is important because it is + easy to place objects close to containers without actually placing them + inside. Both widgets and spacers can be used inside containers. + + Stacked widgets, tab widgets, and toolboxes are handled specially in \QD. + Normally, when adding pages (tabs, pages, compartments) to these containers + in your own code, you need to supply existing widgets, either as + placeholders or containing child widgets. In \QD, these are automatically + created for you, so you can add child objects to each page straight away. + + Each container typically allows its child objects to be arranged in one or + more layouts. The type of layout management provided depends on each + container, although setting the layout is usually just a matter of + selecting the container by clicking it, and applying a layout. The table + below shows a list of available containers. + + \table + \row + \li \inlineimage designer-containers-frame.png + \li \b Frames + + Frames are used to enclose and group widgets, as well as to provide + decoration. They are used as the foundation for more complex + containers, but they can also be used as placeholders in forms. + + The most important properties of frames are \c frameShape, + \c frameShadow, \c lineWidth, and \c midLineWidth. These are described + in more detail in the QFrame class description. + + \row + \li \inlineimage designer-containers-groupbox.png + \li \b{Group Boxes} + + Group boxes are usually used to group together collections of + checkboxes and radio buttons with similar purposes. + + Among the significant properties of group boxes are \c title, \c flat, + \c checkable, and \c checked, as described in the \l QGroupBox + class documentation. Each group box can contain its own layout, and + this is necessary if it contains other widgets. To add a layout to the + group box, click inside it and apply the layout as usual. + + \row + \li \inlineimage designer-containers-stackedwidget.png + \li \b{Stacked Widgets} + + Stacked widgets are collections of widgets in which only the topmost + layer is visible. Control over the visible layer is usually managed by + another widget, such as combobox, using signals and slots. + + \QD shows arrows in the top-right corner of the stack to allow you to + see all the widgets in the stack when designing it. These arrows do not + appear in the preview or in the final component. To navigate between + pages in the stack, select the stacked widget and use the + \gui{Next Page} and \gui{Previous Page} entries from the context menu. + The \gui{Insert Page} and \gui{Delete Page} context menu options allow + you to add and remove pages. + + \row + \li \inlineimage designer-containers-tabwidget.png + \li \b{Tab Widgets} + + Tab widgets allow the developer to split up the contents of a widget + into different labelled sections, only one of which is displayed at any + given time. By default, the tab widget contains two tabs, and these can + be deleted or renamed as required. You can also add additional tabs. + + To delete a tab: + \list + \li Click on its label to make it the current tab. + \li Select the tab widget and open its context menu. + \li Select \gui{Delete Page}. + \endlist + + To add a new tab: + \list + \li Select the tab widget and open its context menu. + \li Select \gui{Insert Page}. + \li You can add a page before or after the \e current page. \QD + will create a new widget for that particular tab and insert it + into the tab widget. + \li You can set the title of the current tab by changing the + \c currentTabText property in the \gui{Property Editor}. + \endlist + + \row + \li \inlineimage designer-containers-toolbox.png + \li \b{ToolBox Widgets} + + Toolbox widgets provide a series of pages or compartments in a toolbox. + They are handled in a way similar to stacked widgets. + + To rename a page in a toolbox, make the toolbox your current pange and + change its \c currentItemText property from the \gui{Property Editor}. + + To add a new page, select \gui{Insert Page} from the toolbox widget's + context menu. You can add the page before or after the current page. + + To delete a page, select \gui{Delete Page} from the toolbox widget's + context menu. + + \row + \li \inlineimage designer-containers-dockwidget.png + \li \b{Dock Widgets} + + Dock widgets are floating panels, often containing input widgets and + more complex controls, that are either attached to the edges of the + main window in "dock areas", or floated as independent tool windows. + + Although dock widgets can be added to any type of form, they are + typically used with forms created from the + \l{Creating Main Windows in Qt Widgets Designer}{main window template}. + + \endtable +*/ + + +/*! + \page designer-creating-mainwindows.html + \previouspage Using Containers in Qt Widgets Designer + \nextpage Editing Resources with Qt Widgets Designer + + \title Creating Main Windows in Qt Widgets Designer + + \QD can be used to create user interfaces for different purposes, and + it provides different kinds of form templates for each user interface. The + main window template is used to create application windows with menu bars, + toolbars, and dock widgets. + + \omit + \image designer-mainwindow-example.png + \endomit + + Create a new main window by opening the \gui File menu and selecting the + \gui{New Form...} option, or by pressing \key{Ctrl+N}. Then, select the + \gui{Main Window} template. This template provides a main application + window containing a menu bar and a toolbar by default -- these can be + removed if they are not required. + + If you remove the menu bar, a new one can be created by selecting the + \gui{Create Menu Bar} option from the context menu, obtained by + right-clicking within the main window form. + + An application can have only \b one menu bar, but \b several + toolbars. + + + \section1 Menus + + Menus are added to the menu bar by modifying the \gui{Type Here} + placeholders. One of these is always present for editing purposes, and + will not be displayed in the preview or in the finished window. + + Once created, the properties of a menu can be accessed using the + \l{Qt Widgets Designer's Widget Editing Mode#The Property Editor}{Property Editor}, + and each menu can be accessed for this purpose via the + \l{Qt Widgets Designer's Widget Editing Mode#The Object Inspector}{The Object Inspector}. + + Existing menus can be removed by opening a context menu over the label in + the menu bar, and selecting \gui{Remove Menu 'menu_name'}. + + + \target CreatingAMenu + \div {class="float-left"} + \inlineimage designer-creating-menu1.png + \inlineimage designer-creating-menu2.png + \br + \inlineimage designer-creating-menu3.png + \inlineimage designer-creating-menu4.png + \enddiv + + \section2 Creating a Menu + + Double-click the placeholder item to begin editing. The menu text, + displayed using a line edit, can be modified. + + Insert the required text for the new menu. Inserting an + ampersand character (&) causes the letter following it to be + used as a mnemonic for the menu. + + Press \key Return or \key Enter to accept the new text, or press + \key Escape to reject it. You can undo the editing operation later if + required. + + \div {class="clear-both"} + \enddiv + + Menus can also be rearranged in the menu bar simply by dragging and + dropping them in the preferred location. A vertical red line indicates the + position where the menu will be inserted. + + Menus can contain any number of entries and separators, and can be nested + to the required depth. Adding new entries to menus can be achieved by + navigating the menu structure in the usual way. + + \target CreatingAMenuEntry + \div {class="float-right"} + \inlineimage designer-creating-menu-entry1.png + \inlineimage designer-creating-menu-entry2.png + \br + \inlineimage designer-creating-menu-entry3.png + \inlineimage designer-creating-menu-entry4.png + \enddiv + + \section2 Creating a Menu Entry + + Double-click the \gui{Type Here} placeholder to begin editing, or + double-click \gui{Add Separator} to insert a new separator line after + the last entry in the menu. + + The menu entry's text is displayed using a line edit, and can be + modified. + + Insert the required text for the new entry, optionally using + the ampersand character (&) to mark the letter to use as a + mnemonic for the entry. + + Press \key Return or \key Enter to accept the new text, or press + \key Escape to reject it. The action created for this menu entry will + be accessible via the \l{#TheActionEditor}{Action Editor}, and any + associated keyboard shortcut can be set there. + + \div {class="clear-both"} + \enddiv + + Just like with menus, entries can be moved around simply by dragging and + dropping them in the preferred location. When an entry is dragged over a + closed menu, the menu will open to allow it to be inserted there. Since + menu entries are based on actions, they can also be dropped onto toolbars, + where they will be displayed as toolbar buttons. + + \section1 Toolbars + + \div {class="float-left"} + \inlineimage designer-creating-toolbar.png + \enddiv + + \section2 Creating and Removing a Toolbar + + Toolbars are added to a main window in a similar way to the menu bar: + Select the \gui{Add Tool Bar} option from the form's context menu. + Alternatively, if there is an existing toolbar in the main window, you can + click the arrow on its right end to create a new toolbar. + + Toolbars are removed from the form via an entry in the toolbar's context + menu. + + \div {class="clear-both"} + \enddiv + + \section2 Adding and Removing Toolbar Buttons + + Toolbar buttons are created as actions in the + \l{#TheActionEditor}{Action Editor} and dragged onto the toolbar. + Since actions can be represented by menu entries and toolbar buttons, + they can be moved between menus and toolbars. + + \div {class="float-right"} + \inlineimage designer-adding-toolbar-action.png + \inlineimage designer-removing-toolbar-action.png + \enddiv + + To share an action between a menu and a toolbar, drag its icon from the + action editor to the toolbar rather than from the menu where its entry is + located. See \l{#Adding an Action}{Adding an Action} for more information + about this process. + + Toolbar buttons are removed via the toolbar's context menu. + + \div {class="clear-both"} + \enddiv + + \section1 Actions + + With the menu bar and the toolbars in place, it's time to populate them + with actions. New actions for both menus and toolbars are created in the + action editor window, simplifying the creation and management of actions. + + \target TheActionEditor + \div {class="float-left"} + \inlineimage designer-action-editor.png + \enddiv + + \section2 The Action Editor + + Enable the action editor by opening the \gui Tools menu, and switching + on the \gui{Action Editor} option. + + The action editor allows you to create \gui New actions and \gui Delete + actions. It also provides a search function, \gui Filter, using the + action's text. + + \QD's action editor can be viewed in the classic \gui{Icon View} and + \gui{Detailed View}. The screenshot below shows the action editor in + \gui{Detailed View}. You can also copy and paste actions between menus, + toolbars and forms. + + \div {class="clear-both"} + \enddiv + + \section2 Creating an Action + + To create an action, use the action editor's \gui New button, which will + then pop up an input dialog. Provide the new action with a \gui Text -- + this is the text that will appear in a menu entry and as the action's + tooltip. The text is also automatically added to an "action" prefix, + creating the action's \gui{Object Name}. + + In addition, the dialog provides the option of selecting an \gui Icon for + the action, as well as removing the current icon. + + Once the action is created, it can be used wherever actions are applicable. + + \div {class="clear-left"} + \enddiv + + \target AddingAnAction + \div {class="float-right"} + \inlineimage designer-adding-menu-action.png + \inlineimage designer-adding-toolbar-action.png + \enddiv + + \section2 Adding an Action + + To add an action to a menu or a toolbar, simply press the left mouse + button over the action in the action editor, and drag it to the + preferred location. + + \QD provides highlighted guide lines that tell you where the action + will be added. Release the mouse button to add the action when you have + found the right spot. + + \div {class="clear-right"} + \enddiv + + \section1 Dock Widgets + + Dock widgets are \l{Using Containers in Qt Widgets Designer}{container widgets} + as well. They can be added to a form by dropping them onto the desired + dock area. + + \target AddingADockWidget + + \div {class="float-left"} + \inlineimage designer-adding-dockwidget.png + \enddiv + + \section2 Adding a Dock Widget + + To add a dock widget to a form, drag one from the \gui Containers section + of the widget box, and drop it onto the main form area. Do not add the + dock widget to an existing layout. Instead, open the \gui{Property Editor} + and enable the \gui{docked} property to place it in a dock area. + + Note that it is sometimes easier to configure a dock widget if it is added + to a form before a layout is applied to the central widget. For example, + it is possible to undock it and resize it, making it more convenient to + add child widgets. + + Dock widgets can be optionally floated as independent tool windows. + Hence, it is useful to give them window titles by setting their + \l{QDockWidget::}{windowTitle} property. This also helps to identify them on the + form. + + \div {class="clear-both"} + \enddiv +*/ + + +/*! + \page designer-resources.html + \previouspage Creating Main Windows in Qt Widgets Designer + \nextpage Using Stylesheets with Qt Widgets Designer + + \title Editing Resources with Qt Widgets Designer + + \image designer-resources-editing.png + + \QD fully supports the \l{The Qt Resource System}{Qt Resource System}, + enabling resources to be specified together with forms as they are + designed. To aid designers and developers manage resources for their + applications, \QD's resource editor allows resources to be defined on a + per-form basis. In other words, each form can have a separate resource + file. + + \section1 Defining a Resource File + + To specify a resource file you must enable the resource editor by opening + the \gui Tools menu, and switching on the \gui{Resource Browser} option. + + \target ResourceFiles + \table + \row + \li \inlineimage designer-resource-browser.png + \li \b{Resource Files} + + Within the resource browser, you can open existing resource files or + create new ones. Click the \gui{Edit Resources} button + \inlineimage designer-edit-resources-button.png + to edit your resources. To reload resources, click on the \gui Reload + button + \inlineimage designer-reload-resources-button.png + . + \endtable + + + Once a resource file is loaded, you can create or remove entries in it + using the given \gui{Add Files} + \inlineimage designer-add-resource-entry-button.png + and \gui{Remove Files} + \inlineimage designer-remove-resource-entry-button.png + buttons, and specify resources (e.g., images) using the \gui{Add Files} + button + \inlineimage designer-add-files-button.png + . Note that these resources must reside within the current resource file's + directory or one of its subdirectories. + + + \target EditResource + \table + \row + \li \inlineimage designer-edit-resource.png + \li \b{Editing Resource Files} + + Press the + \inlineimage designer-add-resource-entry-button.png + button to add a new resource entry to the file. Then use the + \gui{Add Files} button + \inlineimage designer-add-files-button.png + to specify the resource. + + You can remove resources by selecting the corresponding entry in the + resource editor, and pressing the + \inlineimage designer-remove-resource-entry-button.png + button. + \endtable + + + \section1 Using the Resources + + Once the resources are defined you can use them actively when composing + your form. For example, you might want to create a tool button using an + icon specified in the resource file. + + \target UsingResources + \table + \row + \li \inlineimage designer-resources-using.png + \li \b{Using Resources} + + When changing properties with values that may be defined within a + resource file, \QD's property editor allows you to specify a resource + in addition to the option of selecting a source file in the ordinary + way. + + \row + \li \inlineimage designer-resource-selector.png + \li \b{Selecting a Resource} + + You can open the resource selector by clicking \gui{Choose Resource...} + to add resources any time during the design process. + +\omit +... check with Friedemann +To quickly assign icon pixmaps to actions or pixmap properties, you may +drag the pixmap from the resource editor to the action editor, or to the +pixmap property in the property editor. +\endomit + + \endtable +*/ + + +/*! + \page designer-stylesheet.html + \previouspage Editing Resources with Qt Widgets Designer + \nextpage Using a Designer UI File in Your C++ Application + + \title Using Stylesheets with Qt Widgets Designer + + Since Qt 4.2, it is possible to edit stylesheets in \QD with the stylesheet + editor. + + \target UsingStylesheets + \table + \row + \li \inlineimage designer-stylesheet-options.png + \b{Setting a Stylesheet} + + The stylesheet editor can be accessed by right-clicking a widget + and selecting \gui{Change styleSheet...} + + \row + \li \inlineimage designer-stylesheet-usage.png + \endtable + +*/ + + +/*! + \page designer-using-a-ui-file.html + \previouspage Using Stylesheets with Qt Widgets Designer + \nextpage Using a Designer UI File in Your Qt for Python Application + + \keyword Using a Designer UI File in Your Application + \title Using a Designer UI File in Your C++ Application + + Qt Widgets Designer UI files represent the widget tree of the form in XML format. The + forms can be processed: + + \list + \li \l{Compile Time Form Processing}{At compile time}, which means that forms + are converted to C++ code that can be compiled. + \li \l{Run Time Form Processing}{At runtime}, which means that forms are processed + by the QUiLoader class that dynamically constructs the widget tree while + parsing the XML file. + \endlist + + \tableofcontents + \section1 Compile Time Form Processing + + You create user interface components with \QD and use Qt's integrated build tools, + \l{qmake Manual}{qmake} and \l{User Interface Compiler (uic)}{uic}, to generate code + for them when the application is built. The generated code contains the form's user + interface object. It is a C++ struct that contains: + + \list + \li Pointers to the form's widgets, layouts, layout items, + button groups, and actions. + \li A member function called \c setupUi() to build the widget tree + on the parent widget. + \li A member function called \c retranslateUi() that handles the + translation of the string properties of the form. For more information, + see \l{Reacting to Language Changes}. + \endlist + + The generated code can be included in your application and used directly from + it. Alternatively, you can use it to extend subclasses of standard widgets. + + A compile time processed form can be used in your application with one of + the following approaches: + + \list + \li \l{The Direct Approach}: you construct a widget to use as a placeholder + for the component, and set up the user interface inside it. + \li \l{The Single Inheritance Approach}: you subclass the form's base class + (QWidget or QDialog, for example), and include a private instance + of the form's user interface object. + \li \l{The Multiple Inheritance Approach}: you subclass both the form's base + class and the form's user interface object. This allows the widgets + defined in the form to be used directly from within the scope of + the subclass. + \endlist + + To demonstrate, we create a simple Calculator Form application. It is based on the + original \l{Calculator Form} example. + + The application consists of one source file, \c main.cpp and a UI + file. + + The \c{calculatorform.ui} file designed with \QD is shown below: + + \image directapproach-calculatorform.png + + When using \c CMake to build the executable, a \c{CMakeLists.txt} + file is required: + + \snippet uitools/calculatorform/CMakeLists.txt 0 + + The form is listed among the C++ source files in \c qt_add_executable(). + The option \c CMAKE_AUTOUIC tells \c CMake to run the \c uic tool + to create a \c ui_calculatorform.h file that can be used + by the source files. + + When using \c qmake to build the executable, a \c{.pro} file is required: + + \snippet uitools/calculatorform/calculatorform.pro 0 + + The special feature of this file is the \c FORMS declaration that tells + \c qmake which files to process with \c uic. In this case, the + \c calculatorform.ui file is used to create a \c ui_calculatorform.h file + that can be used by any file listed in the \c SOURCES declaration. + + \note You can use Qt Creator to create the Calculator Form project. It + automatically generates the main.cpp, UI, and a project file for the + desired build tool, which you can modify. + + \section2 The Direct Approach + + To use the direct approach, we include the \c ui_calculatorform.h file + directly in \c main.cpp: + + \snippet uitools/calculatorform/main.cpp 0 + + The \c main function creates the calculator widget by constructing a + standard QWidget that we use to host the user interface described by the + \c calculatorform.ui file. + + \snippet uitools/calculatorform/main.cpp 1 + + In this case, the \c{Ui::CalculatorForm} is an interface description object + from the \c ui_calculatorform.h file that sets up all the dialog's widgets + and the connections between its signals and slots. + + The direct approach provides a quick and easy way to use simple, self-contained + components in your applications. However, componens created with \QD often + require close integration with the rest of the application code. For + instance, the \c CalculatorForm code provided above will compile and run, + but the QSpinBox objects will not interact with the QLabel as we need a + custom slot to carry out the add operation and display the result in the + QLabel. To achieve this, we need to use the single inheritance approach. + + \section2 The Single Inheritance Approach + + To use the single inheritance approach, we subclass a standard Qt widget and + include a private instance of the form's user interface object. This can take + the form of: + + \list + \li A member variable + \li A pointer member variable + \endlist + + \section3 Using a Member Variable + + In this approach, we subclass a Qt widget and set up the user interface + from within the constructor. Components used in this way expose the widgets + and layouts used in the form to the Qt widget subclass, and provide a + standard system for making signal and slot connections between the user + interface and other objects in your application. + The generated \c{Ui::CalculatorForm} structure is a member of the class. + + This approach is used in the \l{Calculator Form} example. + + To ensure that we can use the user interface, we need to include the header + file that \c uic generates before referring to \c{Ui::CalculatorForm}: + + \snippet calculatorform/calculatorform.h 0 + + The project file must be updated to include \c{calculatorform.h}. + For \c CMake: + + \snippet calculatorform/CMakeLists.txt 1 + + In specific cases, such as the example below where the include directive + uses a relative path, \l{qt6_add_ui}{qt_add_ui} can be used to generate the + \c{ui_calculatorform.h} file instead of relying on + \l{CMake AUTOUIC Documentation}{AUTOUIC}. + + \l{When to prefer qt_add_ui over AUTOUIC} + + \code + #include "src/files/ui_calculatorform.h" + \endcode + + \code + qt_add_ui(calculatorform SOURCES calculatorform.ui + INCLUDE_PREFIX src/files) + \endcode + + For \c qmake: + + \snippet calculatorform/calculatorform.pro 0 + + The subclass is defined in the following way: + + \snippet calculatorform/calculatorform.h 1 + + The important feature of the class is the private \c ui object which + provides the code for setting up and managing the user interface. + + The constructor for the subclass constructs and configures all the widgets + and layouts for the dialog just by calling the \c ui object's \c setupUi() + function. Once this has been done, it is possible to modify the user + interface as needed. + + \snippet calculatorform/calculatorform.cpp 0 + + We can connect signals and slots in user interface widgets in the usual + way by adding the on_ - prefix. For more information, + see \l{widgets-and-dialogs-with-auto-connect}. + + The advantages of this approach are its simple use of inheritance to + provide a QWidget-based interface, and its encapsulation of the user + interface widget variables within the \c ui data member. We can use this + method to define a number of user interfaces within the same widget, each + of which is contained within its own namespace, and overlay (or compose) + them. This approach can be used to create individual tabs from existing + forms, for example. + + \section3 Using a Pointer Member Variable + + Alternatively, the \c{Ui::CalculatorForm} structure can be made a pointer + member of the class. The header then looks as follows: + + \code + + namespace Ui { + class CalculatorForm; + } + + class CalculatorForm : public QWidget + ... + virtual ~CalculatorForm(); + ... + private: + Ui::CalculatorForm *ui; + ... + + \endcode + + The corresponding source file looks as follows: + + \code + #include "ui_calculatorform.h" + + CalculatorForm::CalculatorForm(QWidget *parent) : + QWidget(parent), ui(new Ui::CalculatorForm) + { + ui->setupUi(this); + } + + CalculatorForm::~CalculatorForm() + { + delete ui; + } + \endcode + + The advantage of this approach is that the user interface object can be + forward-declared, which means that we do not have to include the generated + \c ui_calculatorform.h file in the header. The form can then be changed without + recompiling the dependent source files. This is particularly important if the + class is subject to binary compatibility restrictions. + + We generally recommend this approach for libraries and large applications. + For more information, see \l{Creating Shared Libraries}. + + \section2 The Multiple Inheritance Approach + + Forms created with \QD can be subclassed together with a standard + QWidget-based class. This approach makes all the user interface components + defined in the form directly accessible within the scope of the subclass, + and enables signal and slot connections to be made in the usual way with + the \l{QObject::connect()}{connect()} function. + + We need to include the header file that \c uic generates from the + \c calculatorform.ui file, as follows: + + \snippet ../designer/calculatorform_mi/calculatorform.h 0 + + The class is defined in a similar way to the one used in the + \l{The Single Inheritance Approach}{single inheritance approach}, except that + this time we inherit from \e{both} QWidget and \c{Ui::CalculatorForm}, + as follows: + + \snippet ../designer/calculatorform_mi/calculatorform.h 1 + + We inherit \c{Ui::CalculatorForm} privately to ensure that the user + interface objects are private in our subclass. We can also inherit it with + the \c public or \c protected keywords in the same way that we could have + made \c ui public or protected in the previous case. + + The constructor for the subclass performs many of the same tasks as the + constructor used in the \l{The Single Inheritance Approach} + {single inheritance} example: + + \snippet ../designer/calculatorform_mi/calculatorform.cpp 0 + + In this case, the widgets used in the user interface can be accessed in the + same say as a widget created in code by hand. We no longer require the + \c{ui} prefix to access them. + + \section2 Reacting to Language Changes + + Qt notifies applications if the user interface language changes by sending an + event of the type QEvent::LanguageChange. To call the member function + \c retranslateUi() of the user interface object, we reimplement + \c QWidget::changeEvent() in the form class, as follows: + + \code + void CalculatorForm::changeEvent(QEvent *e) + { + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } + } + \endcode + + \section1 Run Time Form Processing + + Alternatively, forms can be processed at run time, producing dynamically- + generated user interfaces. This can be done using the QtUiTools module + that provides the QUiLoader class to handle forms created with \QD. + + + \section2 The UiTools Approach + + A resource file containing a UI file is required to process forms at + run time. Also, the application needs to be configured to use the QtUiTools + module. This is done by including the following declarations in a \c CMake + project file, ensuring that the application is compiled and linked + appropriately. + + \snippet ../uitools/textfinder/CMakeLists.txt 0 + \snippet ../uitools/textfinder/CMakeLists.txt 1 + + For \c qmake: + + \snippet manual/doc_src_designer-manual.pro 0 + + The QUiLoader class provides a form loader object to construct the user + interface. This user interface can be retrieved from any QIODevice, e.g., + a QFile object, to obtain a form stored in a project's resource file. The + QUiLoader::load() function constructs the form widget using the user + interface description contained in the file. + + The QtUiTools module classes can be included using the following directive: + + \snippet manual/doc_src_designer-manual.cpp 1 + + The QUiLoader::load() function is invoked as shown in this code from the + \l{Text Finder} example: + + \snippet ../uitools/textfinder/textfinder.cpp 4 + + In a class that uses QtUiTools to build its user interface at run time, we + can locate objects in the form using QObject::findChild(). For example, in the + following code, we locate some components based on their object names and + widget types: + + \snippet ../uitools/textfinder/textfinder.cpp 1 + + Processing forms at run-time gives the developer the freedom to change a + program's user interface, just by changing the UI file. This is useful + when customizing programs to suit various user needs, such as extra large + icons or a different colour scheme for accessibility support. + + + \section1 Automatic Connections + + The signals and slots connections defined for compile time or run time + forms can either be set up manually or automatically, using QMetaObject's + ability to make connections between signals and suitably-named slots. + + Generally, in a QDialog, if we want to process the information entered by + the user before accepting it, we need to connect the clicked() signal from + the \gui OK button to a custom slot in our dialog. We will first show an + example of the dialog in which the slot is connected by hand then compare + it with a dialog that uses automatic connection. + + + \section2 A Dialog Without Auto-Connect + + We define the dialog in the same way as before, but now include a slot in + addition to the constructor: + + \snippet noautoconnection/imagedialog.h 0 + + The \c checkValues() slot will be used to validate the values provided by + the user. + + In the dialog's constructor we set up the widgets as before, and connect + the \gui Cancel button's \l{QPushButton::clicked()}{clicked()} signal to + the dialog's reject() slot. We also disable the + \l{QPushButton::autoDefault}{autoDefault} property in both buttons to + ensure that the dialog does not interfere with the way that the line edit + handles return key events: + + \snippet noautoconnection/imagedialog.cpp 0 + \dots + \snippet noautoconnection/imagedialog.cpp 1 + + We connect the \gui OK button's \l{QPushButton::clicked()}{clicked()} + signal to the dialog's checkValues() slot which we implement as follows: + + \snippet noautoconnection/imagedialog.cpp 2 + + This custom slot does the minimum necessary to ensure that the data + entered by the user is valid - it only accepts the input if a name was + given for the image. + + \section2 Widgets and Dialogs with Auto-Connect + + Although it is easy to implement a custom slot in the dialog and connect + it in the constructor, we could instead use QMetaObject's auto-connection + facilities to connect the \gui OK button's clicked() signal to a slot in + our subclass. \c{uic} automatically generates code in the dialog's + \c setupUi() function to do this, so we only need to declare and + implement a slot with a name that follows a standard convention: + + \snippet manual/doc_src_designer-manual.cpp 2 + + \note When renaming widgets in the form, the slot names need to be + adapted accordingly, which can become a maintenance problem. + For this reason, we recommend against using this in new code. + + Using this convention, we can define and implement a slot that responds to + mouse clicks on the \gui OK button: + + \snippet autoconnection/imagedialog.h 0 + + Another example of automatic signal and slot connection would be the + \l{Text Finder} with its \c{on_findButton_clicked()} + slot. + + We use QMetaObject's system to enable signal and slot connections: + + \snippet ../uitools/textfinder/textfinder.cpp 2 + + This enables us to implement the slot, as shown below: + + \snippet ../uitools/textfinder/textfinder.cpp 6 + \dots + \snippet ../uitools/textfinder/textfinder.cpp 8 + + Automatic connection of signals and slots provides both a standard naming + convention and an explicit interface for widget designers to work to. By + providing source code that implements a given interface, user interface + designers can check that their designs actually work without having to + write code themselves. +*/ + +/*! + \page designer-using-a-ui-file-python.html + \previouspage Using a Designer UI File in Your C++ Application + \nextpage Using Custom Widgets with Qt Widgets Designer + + \title Using a Designer UI File in Your Qt for Python Application + + \section1 Converting the Form to Python Code + + To demonstrate, we use the Qt Widgets animation easing example. + + The application consists of one source file, \c easing.py, a UI + file \c form.ui, a resource file \c easing.qrc and the project + file, \c{easing.pyproject} file in the YAML format: + + \code + { + "files": ["easing.qrc", "ui_form.py", "easing.py", "easing_rc.py", + "form.ui"] + } + \endcode + + The UI file is converted to Python code building the form using the + \l{User Interface Compiler (uic)}: + + \code + uic -g python form.ui > ui_form.py + \endcode + + Since the top level widget is named \c Form, this results in a Python + class named \c Ui_Form being generated. It provides a function + \c setupUi(), taking the widget as parameter, which is called to + create the UI elements: + + \code + from ui_form import Ui_Form + ... + class Window(QtWidgets.QWidget): + def __init__(self, parent=None): + super(Window, self).__init__(parent) + + self.m_ui = Ui_Form() + self.m_ui.setupUi(self) + \endcode + + Later on, the widgets can be accessed via the \c Ui_Form class: + + \code + self.m_ui.graphicsView.setScene(self.m_scene) + \endcode + + Besides \c setupUi(), \c Ui_Form provides another method + \c retranslateUi(), which can be called in reaction to + a QEvent of type QEvent.LanguageChange, which indicates + a change in the application language. + + \section2 The UiTools Approach + + The QUiLoader class provides a form loader object to construct the user + interface at runtime. This user interface can be retrieved from any + QIODevice, e.g., a QFile object. The QUiLoader::load() function + constructs the form widget using the user interface description + contained in the file. + + It is demonstrated by the uiloader example: + + \code + from PySide2.QtUiTools import QUiLoader + + if __name__ == '__main__': + # Some code to obtain the form file name, ui_file_name + app = QApplication(sys.argv) + ui_file = QFile(ui_file_name) + if not ui_file.open(QIODevice.ReadOnly): + print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString())) + sys.exit(-1) + loader = QUiLoader() + widget = loader.load(ui_file, None) + ui_file.close() + if not widget: + print(loader.errorString()) + sys.exit(-1) + widget.show() + sys.exit(app.exec_()) + \endcode + + \section1 Resource imports + + \section2 Single directory usage + + When using icons from \l{The Qt Resource System}{resource files}, say + \c resources.qrc, \c uic will generate an import of the form: + + \code + import resources_rc + \endcode + + This assumes that a file \c resources_rc.py generated by calling the + \l {Resource Compiler (rcc)} tool (passing the \c {-g python} + command line option) exists in the same directory as the form source. + + \c uic has a command line option \c --rc-prefix causing the \c rc indicator + to be prepended: + + \code + import rc_resources + \endcode + + The command line option \c --from-imports causes the imports to be generated + relative to '.': + + \code + from . import resources_rc + \endcode + + \section2 Directory trees + + Some projects have more complicated directory trees, for example: + + \badcode + project + resources (resources.qrc) + ui (.ui files) + \endcode + + The resource file is then not in the same directory as the form source + and the \c .ui files typically have relative paths to the resource files: + + \badcode + + \endcode + + In this case, the command line option \c --absolute-imports can be used + to generate an absolute import in Python, resulting in: + + \code + import resources.resources_rc + \endcode + + based on the assumption that \c .. is the root directory of the project + contained in the Python import path list. + + For more deeply nested trees, it is possible to use the + command line option \c {--python-paths } to pass a Python + import path list. \c uic will then try to determine the project root + by matching the form file path against the path components. + + If \c {--python-paths} is not given, the environment variable + \c PYTHONPATH is by default checked. +*/ + +/*! + \page designer-customizing-forms.html + \previouspage Using a Designer UI File in Your Qt for Python Application + \nextpage Using Custom Widgets with Qt Widgets Designer + + \title Customizing Qt Widgets Designer Forms + + \image designer-form-settings.png + + When saving a form in \QD, it is stored as a UI file. Several form + settings, for example the grid settings or the margin and spacing for the + default layout, are stored along with the form's components. These settings + are used when the \l uic generates the form's C++ code. For more + information on how to use forms in your application, see the + \l{Using a Designer UI File in Your C++ Application} section. + + + \section1 Modifying the Form Settings + + To modify the form settings, open the \gui Form menu and select \gui{Form + Settings...} + + In the forms settings dialog you can specify the \gui Author of the form. + + You can also alter the margin and spacing properties for the form's default + layout (\gui {Layout Default}). These default layout properties will be + replaced by the corresponding \gui {Layout Function}, if the function is + specified, when \c uic generates code for the form. The form settings + dialog lets you specify functions for both the margin and the spacing. + + \target LayoutFunction + \table + \row + \li \inlineimage designer-form-layoutfunction.png + \li \b{Layout Function} + + The default layout properties will be replaced by the corresponding + \gui{Layout Function}, when \c uic generates code for the form. This is + useful when different environments requires different layouts for the same + form. + + To specify layout functions for the form's margin and spacing, check the + \gui{Layout Function} group box to enable the line edits. + \endtable + + You can also specify the form's \gui{Include Hints}; i.e., provide a list + of the header files which will then be included in the form window's + associated UI file. Header files may be local, i.e., relative to the + project's directory, \c "mywidget.h", or global, i.e. part of Qt or the + compilers standard libraries: \c . + + Finally, you can specify the function used to load pixmaps into the form + window (the \gui {Pixmap Function}). +*/ + + +/*! + \page designer-using-custom-widgets.html + \previouspage Customizing Qt Widgets Designer Forms + \nextpage Creating Custom Widgets for Qt Widgets Designer + + \title Using Custom Widgets with Qt Widgets Designer + + \QD can display custom widgets through its extensible plugin mechanism, + allowing the range of designable widgets to be extended by the user and + third parties. Alternatively, it is possible + to use existing widgets as placeholders for widget classes that provide + similar APIs. + + + \section1 Handling Custom Widgets + + Although \QD supports all of the standard Qt widgets, some specialized + widgets may not be available as standard for a number of reasons: + + \list + \li Custom widgets may not be available at the time the user interface + is being designed. + \li Custom widgets may be platform-specific, and designers may be + developing the user interface on a different platform to end users. + \li The source code for a custom widget is not available, or the user + interface designers are unable to use the widget for non-technical + reasons. + \endlist + + In the above situations, it is still possible to design forms with the aim + of using custom widgets in the application. To achieve this, we can use + the widget promotion feature of \QD. + + In all other cases, where the source code to the custom widgets is + available, we can adapt the custom widget for use with \QD. + + + \section2 Promoting Widgets + + \image designer-promoting-widgets.png + + If some forms must be designed, but certain custom widgets are unavailble + to the designer, we can substitute similar widgets to represent the missing + widgets. For example, we might represent instances of a custom push button + class, \c MyPushButton, with instances of QPushButton and promote these to + \c MyPushButton so that \l{uic.html}{uic} generates suitable code for this + missing class. + + When choosing a widget to use as a placeholder, it is useful to compare the + API of the missing widget with those of standard Qt widgets. For + specialized widgets that subclass standard classes, the obvious choice of + placeholder is the base class of the custom widget; for example, QSlider + might be used for specialized QSlider subclasses. + + For specialized widgets that do not share a common API with standard Qt + widgets, it is worth considering adapting a custom widget for use in \QD. + If this is not possible then QWidget is the obvious choice for a + placeholder widget since it is the lowest common denominator for all + widgets. + + To add a placeholder, select an object of a suitable base class and choose + \gui{Promote to ...} from the form's context menu. After entering the class + name and header file in the lower part of the dialog, choose \gui{Add}. The + placeholder class will now appear along with the base class in the upper + list. Click the \gui{Promote} button to accept this choice. + + Now, when the form's context menu is opened over objects of the base class, + the placeholder class will appear in the \gui{Promote to} submenu, allowing + for convenient promotion of objects to that class. + + A promoted widget can be reverted to its base class by choosing + \gui{Demote to} from the form's context menu. + + + \section2 User Defined Custom Widgets + + Custom widgets can be adapted for use with \QD, giving designers the + opportunity to configure the user interface using the actual widgets that + will be used in an application rather than placeholder widgets. The process + of creating a custom widget plugin is described in the + \l{Creating Custom Widgets for Qt Widgets Designer} chapter of this manual. + + To use a plugin created in this way, it is necessary to ensure that the + plugin is located on a path that \QD searches for plugins. Generally, + plugins stored in \c{$QTDIR/plugins/designer} will be loaded when \QD + starts. Further information on building and installing plugins can be found + \l{Creating Custom Widgets for Qt Widgets Designer#BuildingandInstallingthePlugin} + {here}. You can also refer to the \l{How to Create Qt Plugins} + {Plugins HOWTO} document for information about creating plugins. +*/ + + +/*! + \page designer-creating-custom-widgets.html + \previouspage Using Custom Widgets with Qt Widgets Designer + \nextpage Creating Custom Widget Extensions + + \title Creating Custom Widgets for Qt Widgets Designer + + \QD's plugin-based architecture allows user-defined and third party custom + widgets to be edited just like you do with standard Qt widgets. All of the + custom widget's features are made available to \QD, including widget + properties, signals, and slots. Since \QD uses real widgets during the form + design process, custom widgets will appear the same as they do when + previewed. + + The \l QtDesigner module provides you with the ability to create custom + widgets in \QD. + + + \section1 Getting Started + + To integrate a custom widget with \QD, you require a suitable description + for the widget and an appropriate project file. + + + \section2 Providing an Interface Description + + To inform \QD about the type of widget you want to provide, create a + subclass of QDesignerCustomWidgetInterface that describes the various + properties your widget exposes. Most of these are supplied by functions + that are pure virtual in the base class, because only the author of the + plugin can provide this information. + + \table + \header + \li Function + \li Description of the return value + \row + \li \c name() + \li The name of the class that provides the widget. + \row + \li \c group() + \li The group in \QD's widget box that the widget belongs to. + \row + \li \c toolTip() + \li A short description to help users identify the widget in \QD. + \row + \li \c whatsThis() + \li A longer description of the widget for users of \QD. + \row + \li \c includeFile() + \li The header file that must be included in applications that use + this widget. This information is stored in UI files and will + be used by \c uic to create a suitable \c{#includes} statement + in the code it generates for the form containing the custom + widget. + \row + \li \c icon() + \li An icon that can be used to represent the widget in \QD's + widget box. + \row + \li \c isContainer() + \li True if the widget will be used to hold child widgets; + false otherwise. + \row + \li \c createWidget() + \li A QWidget pointer to an instance of the custom widget, + constructed with the parent supplied. + \note createWidget() is a factory function responsible for + creating the widget only. The custom widget's properties will + not be available until load() returns. + \row + \li \c domXml() + \li A description of the widget's properties, such as its object + name, size hint, and other standard QWidget properties. + \row + \li \c codeTemplate() + \li This function is reserved for future use by \QD. + \endtable + + Two other virtual functions can also be reimplemented: + + \table + \row + \li \c initialize() + \li Sets up extensions and other features for custom widgets. Custom + container extensions (see QDesignerContainerExtension) and task + menu extensions (see QDesignerTaskMenuExtension) should be set + up in this function. + \row + \li \c isInitialized() + \li Returns true if the widget has been initialized; returns false + otherwise. Reimplementations usually check whether the + \c initialize() function has been called and return the result + of this test. + \endtable + + + \section2 Notes on the \c{domXml()} Function + + The \c{domXml()} function returns a UI file snippet that is used by + \QD's widget factory to create a custom widget and its applicable + properties. + + Since Qt 4.4, \QD's widget box allows for a complete UI file to + describe \b one custom widget. The UI file can be loaded using the + \c{} tag. Specifying the tag allows for adding the + element that contains additional information for custom widgets. The + \c{} tag is sufficient if no additional information is required + + If the custom widget does not provide a reasonable size hint, it is + necessary to specify a default geometry in the string returned by the + \c domXml() function in your subclass. For example, the + \c AnalogClockPlugin provided by the \l{customwidgetplugin} + {Custom Widget Plugin} example, defines a default widgetgeometry in the + following way: + + \dots + \snippet customwidgetplugin/customwidgetplugin.cpp 11 + \dots + + An additional feature of the \c domXml() function is that, if it returns + an empty string, the widget will not be installed in \QD's widget box. + However, it can still be used by other widgets in the form. This feature + is used to hide widgets that should not be explicitly created by the user, + but are required by other widgets. + + A complete custom widget specification looks like: + + \code + displayname="MyWidget"> + + + + widgets::MyWidget + addPage + + + + Explanatory text to be shown in Property Editor + + + + + \endcode + + Attributes of the \c{} tag: + \table + \header + \li Attribute + \li Presence + \li Values + \li Comment + \row + \li \c{language} + \li optional + \li "c++", "jambi" + \li This attribute specifies the language the custom widget is intended for. + It is mainly there to prevent C++-plugins from appearing in Qt Jambi. + \row + \li \c{displayname} + \li optional + \li Class name + \li The value of the attribute appears in the Widget box and can be used to + strip away namespaces. + \endtable + + The \c{} tag tells \QD and \l uic which method should be used to + add pages to a container widget. This applies to container widgets that require + calling a particular method to add a child rather than adding the child by passing + the parent. In particular, this is relevant for containers that are not a + a subclass of the containers provided in \QD, but are based on the notion + of \e{Current Page}. In addition, you need to provide a container extension + for them. + + The \c{} element can contain a list of property meta information. + + The tag \c{} may be used to specify a tool tip to be shown in Property Editor + when hovering over the property. The property name is given in the attribute \c name and + the element text is the tooltip. This functionality was added in Qt 5.6. + + For properties of type string, the \c{} tag can be used. + This tag has the following attributes: + + \table + \header + \li Attribute + \li Presence + \li Values + \li Comment + \row + \li \c{name} + \li required + \li Name of the property + \row + \li \c{type} + \li required + \li See below table + \li The value of the attribute determines how the property editor will handle them. + \row + \li \c{notr} + \li optional + \li "true", "false" + \li If the attribute is "true", the value is not meant to be translated. + \endtable + + Values of the \c{type} attribute of the string property: + + \table + \header + \li Value + \li Type + \row + \li \c{"richtext"} + \li Rich text. + \row + \li \c{"multiline"} + \li Multi-line plain text. + \row + \li \c{"singleline"} + \li Single-line plain text. + \row + \li \c{"stylesheet"} + \li A CSS-style sheet. + \row + \li \c{"objectname"} + \li An object name (restricted set of valid characters). + \row + \li \c{"url"} + \li URL, file name. + \endtable + + \section1 Plugin Requirements + + In order for plugins to work correctly on all platforms, you need to ensure + that they export the symbols needed by \QD. + + First of all, the plugin class must be exported in order for the plugin to + be loaded by \QD. Use the Q_PLUGIN_METADATA() macro to do this. Also, the + QDESIGNER_WIDGET_EXPORT macro must be used to define each custom widget class + within a plugin, that \QD will instantiate. + + + \section1 Creating Well Behaved Widgets + + Some custom widgets have special user interface features that may make them + behave differently to many of the standard widgets found in \QD. + Specifically, if a custom widget grabs the keyboard as a result of a call + to QWidget::grabKeyboard(), the operation of \QD will be affected. + + To give custom widgets special behavior in \QD, provide an implementation + of the initialize() function to configure the widget construction process + for \QD specific behavior. This function will be called for the first time + before any calls to createWidget() and could perhaps set an internal flag + that can be tested later when \QD calls the plugin's createWidget() + function. + + + \target BuildingandInstallingthePlugin + \section1 Building and Installing the Plugin + + \section2 A Simple Plugin + + The \l{Custom Widget Plugin} demonstrates a simple \QD plugin. + + The project file for a plugin must specify the headers and sources for + both the custom widget and the plugin interface. Typically, this file only + has to specify that the plugin's project will be built as a library, but + with specific plugin support for \QD. For \c CMake, this is done with + the following declarations: + + \snippet customwidgetplugin/CMakeLists.txt 0 + \snippet customwidgetplugin/CMakeLists.txt 1 + \snippet customwidgetplugin/CMakeLists.txt 2 + + The link libraries list specifies \c Qt::UiPlugin. This indicates that + the plugin uses the abstract interfaces QDesignerCustomWidgetInterface + and QDesignerCustomWidgetCollectionInterface only and has no linkage + to the \QD libraries. When accessing other interfaces of \QD that have + linkage, \c Designer should be used instead; this ensures that the plugin + dynamically links to the \QD libraries and has a run-time dependency on + them. + + It is also necessary to ensure that the plugin is installed together with + other \QD widget plugins: + + \snippet customwidgetplugin/CMakeLists.txt 3 + \snippet customwidgetplugin/CMakeLists.txt 4 + + For \c qmake: + + \snippet customwidgetplugin/customwidgetplugin.pro 0 + \snippet customwidgetplugin/customwidgetplugin.pro 2 + + The \c QT variable contains the keyword \c uiplugin, which is + the equivalent of the \c Qt::UiPlugin library. + + It is also necessary to ensure that the plugin is installed together with + other \QD widget plugins: + + \snippet manual/doc_src_designer-manual.pro 4 + + The \c $[QT_INSTALL_PLUGINS] variable is a placeholder to the location of + the installed Qt plugins. You can configure \QD to look for plugins in + other locations by setting the \c QT_PLUGIN_PATH environment variable + before running the application. + + \note \QD will look for a \c designer subdirectory in each path supplied. + + See QCoreApplication::libraryPaths() for more information about customizing + paths for libraries and plugins with Qt applications. + + If plugins are built in a mode that is incompatible with \QD, they will + not be loaded and installed. For more information about plugins, see the + \l{plugins-howto.html}{Plugins HOWTO} document. + + \section2 Splitting up the Plugin + + The simple approach explained above introduces a problem particularly + when using the other interfaces of \QD that have linkage: + The application using the custom widget will then depend on + \QD headers and libraries. In a real world scenario, this is not desired. + + The following sections describe how to resolve this. + + \section3 Linking the Widget into the Application + + When using \c qmake, the source and header file of the custom widget + can be shared between the application and \QD by creating a \c{.pri} + file for inclusion: + + \code + INCLUDEPATH += $$PWD + HEADERS += $$PWD/analogclock.h + SOURCES += $$PWD/analogclock.cpp + \endcode + + This file would then be included by the \c{.pro} file of the plugin and + the application: + + \code + include(customwidget.pri) + \endcode + + When using \c CMake, the source files of the widget can similarly be + added to the application project. + + \section3 Sharing the Widget Using a Library + + Another approach is to put the widget into a library that is linked to + the \QD plugin as well as to the application. It is recommended to + use static libraries to avoid problems locating the library at run-time. + + For shared libraries, see \l{sharedlibrary.html}{Creating Shared Libraries}. + + \section3 Using the Plugin with QUiLoader + + The preferred way of adding custom widgets to QUiLoader is to subclass it + reimplementing QUiLoader::createWidget(). + + However, it is also possible to use \QD custom widget plugins + (see QUiLoader::pluginPaths() and related functions). To avoid having + to deploy the \QD libraries onto the target device, those plugins should + have no linkage to the \QD libraries (\c {QT = uiplugin}, see + \l{Creating Custom Widgets for Qt Widgets Designer#BuildingandInstallingthePlugin}). + + \section1 Related Examples + + For more information on using custom widgets in \QD, refer to the + \l{customwidgetplugin}{Custom Widget Plugin} and + \l{taskmenuextension}{Task Menu Extension} examples for more + information about using custom widgets in \QD. Also, you can use the + QDesignerCustomWidgetCollectionInterface class to combine several custom + widgets into a single library. +*/ + + +/*! + \page designer-creating-custom-widgets-extensions.html + \previouspage Creating Custom Widgets for Qt Widgets Designer + \nextpage Qt Widgets Designer's UI File Format + + \title Creating Custom Widget Extensions + + Once you have a custom widget plugin for \QD, you can provide it with the + expected behavior and functionality within \QD's workspace, using custom + widget extensions. + + + \section1 Extension Types + + There are several available types of extensions in \QD. You can use all of + these extensions in the same pattern, only replacing the respective + extension base class. + + QDesignerContainerExtension is necessary when implementing a custom + multi-page container. + + \table + \row + \li \inlineimage designer-manual-taskmenuextension.png + \li \b{QDesignerTaskMenuExtension} + + QDesignerTaskMenuExtension is useful for custom widgets. It provides an + extension that allows you to add custom menu entries to \QD's task + menu. + + The \l{taskmenuextension}{Task Menu Extension} example + illustrates how to use this class. + + \row + \li \inlineimage designer-manual-containerextension.png + \li \b{QDesignerContainerExtension} + + QDesignerContainerExtension is necessary when implementing a custom + multi-page container. It provides an extension that allows you to add + and delete pages for a multi-page container plugin in \QD. + + The \l{containerextension}{Container Extension} example + further explains how to use this class. + + \note It is not possible to add custom per-page properties for some + widgets (e.g., QTabWidget) due to the way they are implemented. + \endtable + + \table + \row + \li \inlineimage designer-manual-membersheetextension.png + \li \b{QDesignerMemberSheetExtension} + + The QDesignerMemberSheetExtension class allows you to manipulate a + widget's member functions displayed when connecting signals and slots. + + \row + \li \inlineimage designer-manual-propertysheetextension.png + \li \b{QDesignerPropertySheetExtension, + QDesignerDynamicPropertySheetExtension} + + These extension classes allow you to control how a widget's properties + are displayed in \QD's property editor. + \endtable + +\omit + \row + \li + \li \b {QDesignerScriptExtension} + + The QDesignerScriptExtension class allows you to define script + snippets that are executed when a form is loaded. The extension + is primarily intended to be used to set up the internal states + of custom widgets. + \endtable +\endomit + + + \QD uses the QDesignerPropertySheetExtension and the + QDesignerMemberSheetExtension classes to feed its property and signal and + slot editors. Whenever a widget is selected in its workspace, \QD will + query for the widget's property sheet extension; likewise, whenever a + connection between two widgets is requested, \QD will query for the + widgets' member sheet extensions. + + \warning All widgets have default property and member sheets. If you + implement custom property sheet or member sheet extensions, your custom + extensions will override the default sheets. + + + \section1 Creating an Extension + + To create an extension you must inherit both QObject and the appropriate + base class, and reimplement its functions. Since we are implementing an + interface, we must ensure that it is made known to the meta object system + using the Q_INTERFACES() macro in the extension class's definition. For + example: + + \snippet manual/doc_src_designer-manual.cpp 7 + + This enables \QD to use the qobject_cast() function to query for supported + interfaces using a QObject pointer only. + + + \section1 Exposing an Extension to Qt Widgets Designer + + In \QD the extensions are not created until they are required. For this + reason, when implementing extensions, you must subclass QExtensionFactory + to create a class that is able to make instances of your extensions. Also, + you must register your factory with \QD's extension manager; the extension + manager handles the construction of extensions. + + When an extension is requested, \QD's extension manager will run through + its registered factories calling QExtensionFactory::createExtension() for + each of them until it finds one that is able to create the requested + extension for the selected widget. This factory will then make an instance + of the extension. + + \image qtdesignerextensions.png + + + \section2 Creating an Extension Factory + + The QExtensionFactory class provides a standard extension factory, but it + can also be used as an interface for custom extension factories. + + The purpose is to reimplement the QExtensionFactory::createExtension() + function, making it able to create your extension, such as a + \l{containerextension}{MultiPageWidget} container extension. + + You can either create a new QExtensionFactory and reimplement the + QExtensionFactory::createExtension() function: + + \snippet manual/doc_src_designer-manual.cpp 8 + + or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to enable the factory to + create your custom extension as well: + + \snippet manual/doc_src_designer-manual.cpp 9 + + + \section2 Accessing Qt Widgets Designer's Extension Manager + + When implementing a custom widget plugin, you must subclass the + QDesignerCustomWidgetInterface to expose your plugin to \QD. This is + covered in more detail in the + \l{Creating Custom Widgets for Qt Widgets Designer} section. The registration of + an extension factory is typically made in the + QDesignerCustomWidgetInterface::initialize() function: + + \snippet manual/doc_src_designer-manual.cpp 10 + + The \c formEditor parameter in the + QDesignerCustomWidgetInterface::initialize() function is a pointer to \QD's + current QDesignerFormEditorInterface object. You must use the + QDesignerFormEditorInterface::extensionManager() function to retrieve an + interface to \QD's extension manager. Then you use the + QExtensionManager::registerExtensions() function to register your custom + extension factory. + + + \section1 Related Examples + + For more information on creating custom widget extensions in \QD, refer to + the \l{taskmenuextension}{Task Menu Extension} and + \l{containerextension}{Container Extension} examples. +*/ + + +/*! + \page designer-ui-file-format.html + \previouspage Creating Custom Widget Extensions + + \title Qt Widgets Designer's UI File Format + + The \c UI file format used by \QD is described by the + \l{http://www.w3.org/XML/Schema}{XML schema} presented below, + which we include for your convenience. Be aware that the format + may change in future Qt releases. + + \quotefile ../../../../data/ui4.xsd +*/ diff --git a/src/designer/src/designer/doc/src/qtdesigner-index.qdoc b/src/designer/src/designer/doc/src/qtdesigner-index.qdoc new file mode 100644 index 0000000..5332279 --- /dev/null +++ b/src/designer/src/designer/doc/src/qtdesigner-index.qdoc @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtdesigner-index.html + \title Qt Widgets Designer + \brief Provides classes to create your own custom widget plugins for + \QD and classes to access \QD components. + + Provides classes to create your own custom widget plugins for + \QD and classes to access \QD components. + + In addition, the QFormBuilder class provides the possibility of + constructing user interfaces from UI files at run-time. + + \note This documentation covers the tool \QD and classes + related to building graphical user interfaces for Qt Widgets. + \l {Qt Design Studio Manual}{Qt Design Studio} is a UI composition tool + that covers all steps of UI design and implementation for Qt Quick user + interfaces. + + \if !defined(qtforpython) + + \section1 Using the Module + + \include {module-use.qdocinc} {using the c++ api} + + \section2 Building with CMake + + \include {module-use.qdocinc} {building with cmake} {Designer} + + \section2 Building with qmake + + \include {module-use.qdocinc} {building_with_qmake} {designer} + \endif + + \section1 Articles and Guides + + These articles contain information about \QD. + + \list + \li \l{Creating and Using Components for Qt Widgets Designer} - Creating and using + custom widget plugins + \li \l{Qt Widgets Designer Manual} - Using Qt Widgets Designer + \endlist + + \section1 API Reference + + These are links to the API reference material. + + \list + \li \l{Qt Widgets Designer C++ Classes}{C++ Classes} + \endlist + +*/ diff --git a/src/designer/src/designer/doc/src/qtdesigner-module.qdoc b/src/designer/src/designer/doc/src/qtdesigner-module.qdoc new file mode 100644 index 0000000..1e545de --- /dev/null +++ b/src/designer/src/designer/doc/src/qtdesigner-module.qdoc @@ -0,0 +1,21 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \module QtDesigner + \title Qt Widgets Designer C++ Classes + \ingroup modules + \qtcmakepackage Designer + \qtvariable designer + + \brief Provides classes to create your own custom widget plugins for + \QD and classes to access \QD components. + + In addition, the QFormBuilder class provides the possibility of + constructing user interfaces from UI files at run-time. +*/ + +/*! + \namespace qdesigner_internal + \internal +*/ diff --git a/src/designer/src/designer/doc/src/qtdesigner-toc.qdoc b/src/designer/src/designer/doc/src/qtdesigner-toc.qdoc new file mode 100644 index 0000000..4fd3a61 --- /dev/null +++ b/src/designer/src/designer/doc/src/qtdesigner-toc.qdoc @@ -0,0 +1,41 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtdesigner-toc.html + \title Qt Widgets Designer module topics + + The following list has links to all the individual topics (HTML files) + in the Qt Widgets Designer module. + + \list + \li \l {Qt Widgets Designer}{Using the module} + \li \l {Getting to Know Qt Widgets Designer} + \li \l {A Quick Start to Qt Widgets Designer} + \li \l {Qt Widgets Designer's Editing Modes} + \list + \li \l {Qt Widgets Designer's Widget Editing Mode}{Widget Editing Mode} + \li \l {Qt Widgets Designer's Signals and Slots Editing Mode} + {Signals and Slots Editing Mode} + \li \l {Qt Widgets Designer's Buddy Editing Mode} + {Buddy Editing Mode} + \li \l {Qt Widgets Designer's Tab Order Editing Mode} + {Tab Order Editing Mode} + \endlist + \li \l {Using Layouts in Qt Widgets Designer}{Using Layouts} + \li \l {Saving, Previewing and Printing Forms in Qt Widgets Designer}{Saving, Previewing and Printing Forms} + \li \l {Using Containers in Qt Widgets Designer}{Using Containers} + \li \l {Creating Main Windows in Qt Widgets Designer}{Creating Main Windows} + \li \l {Editing Resources with Qt Widgets Designer}{Editing Resources} + \li \l {Using Stylesheets with Qt Widgets Designer}{Using Stylesheets} + \li \l {Using a Designer UI File in Your C++ Application} + \li \l {Using a Designer UI File in Your Qt for Python Application} + \li \l {Customizing Qt Widgets Designer Forms} + \li \l {Using Custom Widgets with Qt Widgets Designer}{Using Custom Widgets} + \li \l {Creating Custom Widgets for Qt Widgets Designer} + \li \l {Creating Custom Widget Extensions} + \li \l {Qt Widgets Designer's UI File Format} + \li \l {Creating and Using Components for Qt Widgets Designer} + \endlist + +*/ diff --git a/src/designer/src/designer/images/designer.png b/src/designer/src/designer/images/designer.png new file mode 100644 index 0000000000000000000000000000000000000000..9e6097c7d338c6321b16e32d06c3aad5e01cde49 GIT binary patch literal 4167 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 literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/main.cpp b/src/designer/src/designer/main.cpp new file mode 100644 index 0000000..5bff622 --- /dev/null +++ b/src/designer/src/designer/main.cpp @@ -0,0 +1,29 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner.h" +#include +#include + +#include + +QT_USE_NAMESPACE + +int main(int argc, char *argv[]) +{ + // required for QWebEngineView + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + + QDesigner app(argc, argv); + switch (app.parseCommandLineArguments()) { + case QDesigner::ParseArgumentsSuccess: + break; + case QDesigner::ParseArgumentsError: + return 1; + case QDesigner::ParseArgumentsHelpRequested: + return 0; + } + QGuiApplication::setQuitOnLastWindowClosed(false); + + return QApplication::exec(); +} diff --git a/src/designer/src/designer/mainwindow.cpp b/src/designer/src/designer/mainwindow.cpp new file mode 100644 index 0000000..c0f314a --- /dev/null +++ b/src/designer/src/designer/mainwindow.cpp @@ -0,0 +1,372 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "mainwindow.h" +#include "qdesigner.h" +#include "qdesigner_actions.h" +#include "qdesigner_workbench.h" +#include "qdesigner_formwindow.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_settings.h" +#include "qttoolbardialog_p.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using ActionList = QList; + +// Helpers for creating toolbars and menu + +static void addActionsToToolBar(const ActionList &actions, QToolBar *t) +{ + for (QAction *action : actions) { + if (action->property(QDesignerActions::defaultToolbarPropertyName).toBool()) + t->addAction(action); + } +} + +static QToolBar *createToolBar(const QString &title, const QString &objectName, + const ActionList &actions) +{ + QToolBar *rc = new QToolBar; + rc->setObjectName(objectName); + rc->setWindowTitle(title); + addActionsToToolBar(actions, rc); + return rc; +} + +// ---------------- MainWindowBase + +MainWindowBase::MainWindowBase(QWidget *parent, Qt::WindowFlags flags) : + QMainWindow(parent, flags) +{ +#ifndef Q_OS_MACOS + setWindowIcon(qDesigner->windowIcon()); +#endif +} + +void MainWindowBase::closeEvent(QCloseEvent *e) +{ + switch (m_policy) { + case AcceptCloseEvents: + QMainWindow::closeEvent(e); + break; + case EmitCloseEventSignal: + emit closeEventReceived(e); + break; + } +} + +QList MainWindowBase::createToolBars(const QDesignerActions *actions, bool singleToolBar) +{ + // Note that whenever you want to add a new tool bar here, you also have to update the default + // action groups added to the toolbar manager in the mainwindow constructor + QList rc; + if (singleToolBar) { + //: Not currently used (main tool bar) + QToolBar *main = createToolBar(tr("Main"), u"mainToolBar"_s, + actions->fileActions()->actions()); + addActionsToToolBar(actions->editActions()->actions(), main); + addActionsToToolBar(actions->toolActions()->actions(), main); + addActionsToToolBar(actions->formActions()->actions(), main); + rc.push_back(main); + } else { + rc.append(createToolBar(tr("File"), u"fileToolBar"_s, + actions->fileActions()->actions())); + rc.append(createToolBar(tr("Edit"), u"editToolBar"_s, + actions->editActions()->actions())); + rc.append(createToolBar(tr("Tools"), u"toolsToolBar"_s, + actions->toolActions()->actions())); + rc.append(createToolBar(tr("Form"), u"formToolBar"_s, + actions->formActions()->actions())); + } + return rc; +} + +QString MainWindowBase::mainWindowTitle() +{ + return tr("Qt Widgets Designer"); +} + +// Use the minor Qt version as settings versions to avoid conflicts +int MainWindowBase::settingsVersion() +{ + const int version = QT_VERSION; + return (version & 0x00FF00) >> 8; +} + +// ----------------- DockedMdiArea + +DockedMdiArea::DockedMdiArea(const QString &extension, QWidget *parent) : + QMdiArea(parent), + m_extension(extension) +{ + setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + setLineWidth(1); + setAcceptDrops(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); +} + +QStringList DockedMdiArea::uiFiles(const QMimeData *d) const +{ + // Extract dropped UI files from Mime data. + QStringList rc; + if (!d->hasFormat("text/uri-list"_L1)) + return rc; + const auto urls = d->urls(); + if (urls.isEmpty()) + return rc; + for (const auto &url : urls) { + const QString fileName = url.toLocalFile(); + if (!fileName.isEmpty() && fileName.endsWith(m_extension)) + rc.push_back(fileName); + } + return rc; +} + +bool DockedMdiArea::event(QEvent *event) +{ + // Listen for desktop file manager drop and emit a signal once a file is + // dropped. + switch (event->type()) { + case QEvent::DragEnter: { + QDragEnterEvent *e = static_cast(event); + if (!uiFiles(e->mimeData()).isEmpty()) { + e->acceptProposedAction(); + return true; + } + } + break; + case QEvent::Drop: { + QDropEvent *e = static_cast(event); + const QStringList files = uiFiles(e->mimeData()); + for (const auto &f : files) + emit fileDropped(f); + e->acceptProposedAction(); + return true; + } + break; + default: + break; + } + return QMdiArea::event(event); +} + +// ------------- ToolBarManager: + +static void addActionsToToolBarManager(const ActionList &al, const QString &title, QtToolBarManager *tbm) +{ + for (QAction *action : al) + tbm->addAction(action, title); +} + +ToolBarManager::ToolBarManager(QMainWindow *configureableMainWindow, + QWidget *parent, + QMenu *toolBarMenu, + const QDesignerActions *actions, + const QList &toolbars, + const QList &toolWindows) : + QObject(parent), + m_configureableMainWindow(configureableMainWindow), + m_parent(parent), + m_toolBarMenu(toolBarMenu), + m_manager(new QtToolBarManager(this)), + m_configureAction(new QAction(tr("Configure Toolbars..."), this)), + m_toolbars(toolbars) +{ + m_configureAction->setMenuRole(QAction::NoRole); + m_configureAction->setObjectName(u"__qt_configure_tool_bars_action"_s); + connect(m_configureAction, &QAction::triggered, this, &ToolBarManager::configureToolBars); + + m_manager->setMainWindow(configureableMainWindow); + + for (QToolBar *tb : std::as_const(m_toolbars)) { + const QString title = tb->windowTitle(); + m_manager->addToolBar(tb, title); + addActionsToToolBarManager(tb->actions(), title, m_manager); + } + + addActionsToToolBarManager(actions->windowActions()->actions(), tr("Window"), m_manager); + addActionsToToolBarManager(actions->helpActions()->actions(), tr("Help"), m_manager); + + // Filter out the device profile preview actions which have int data(). + ActionList previewActions = actions->styleActions()->actions(); + auto it = previewActions.begin(); + for ( ; (*it)->isSeparator() || (*it)->data().metaType().id() == QMetaType::Int; ++it) ; + previewActions.erase(previewActions.begin(), it); + addActionsToToolBarManager(previewActions, tr("Style"), m_manager); + + const QString dockTitle = tr("Dock views"); + for (QDesignerToolWindow *tw : toolWindows) { + if (QAction *action = tw->action()) + m_manager->addAction(action, dockTitle); + } + + addActionsToToolBarManager(actions->fileActions()->actions(), tr("File"), m_manager); + addActionsToToolBarManager(actions->editActions()->actions(), tr("Edit"), m_manager); + addActionsToToolBarManager(actions->toolActions()->actions(), tr("Tools"), m_manager); + addActionsToToolBarManager(actions->formActions()->actions(), tr("Form"), m_manager); + + m_manager->addAction(m_configureAction, tr("Toolbars")); + updateToolBarMenu(); +} + +// sort function for sorting tool bars alphabetically by title [non-static since called from template] + +bool toolBarTitleLessThan(const QToolBar *t1, const QToolBar *t2) +{ + return t1->windowTitle() < t2->windowTitle(); +} + +void ToolBarManager::updateToolBarMenu() +{ + // Sort tool bars alphabetically by title + std::stable_sort(m_toolbars.begin(), m_toolbars.end(), toolBarTitleLessThan); + // add to menu + m_toolBarMenu->clear(); + for (QToolBar *tb : std::as_const(m_toolbars)) + m_toolBarMenu->addAction(tb->toggleViewAction()); + m_toolBarMenu->addAction(m_configureAction); +} + +void ToolBarManager::configureToolBars() +{ + QtToolBarDialog dlg(m_parent); + dlg.setToolBarManager(m_manager); + dlg.exec(); + updateToolBarMenu(); +} + +QByteArray ToolBarManager::saveState(int version) const +{ + return m_manager->saveState(version); +} + +bool ToolBarManager::restoreState(const QByteArray &state, int version) +{ + return m_manager->restoreState(state, version); +} + +// ---------- DockedMainWindow + +DockedMainWindow::DockedMainWindow(QDesignerWorkbench *wb, + QMenu *toolBarMenu, + const QList &toolWindows) +{ + setObjectName(u"MDIWindow"_s); + setWindowTitle(mainWindowTitle()); + + const QList toolbars = createToolBars(wb->actionManager(), false); + for (QToolBar *tb : toolbars) + addToolBar(tb); + DockedMdiArea *dma = new DockedMdiArea(wb->actionManager()->uiExtension()); + connect(dma, &DockedMdiArea::fileDropped, + this, &DockedMainWindow::fileDropped); + connect(dma, &QMdiArea::subWindowActivated, + this, &DockedMainWindow::slotSubWindowActivated); + setCentralWidget(dma); + + QStatusBar *sb = statusBar(); + Q_UNUSED(sb); + + m_toolBarManager = new ToolBarManager(this, this, toolBarMenu, wb->actionManager(), toolbars, toolWindows); +} + +QMdiArea *DockedMainWindow::mdiArea() const +{ + return static_cast(centralWidget()); +} + +void DockedMainWindow::slotSubWindowActivated(QMdiSubWindow* subWindow) +{ + if (subWindow) { + QWidget *widget = subWindow->widget(); + if (QDesignerFormWindow *fw = qobject_cast(widget)) { + emit formWindowActivated(fw); + mdiArea()->setActiveSubWindow(subWindow); + } + } +} + +// Create a MDI subwindow for the form. +QMdiSubWindow *DockedMainWindow::createMdiSubWindow(QWidget *fw, Qt::WindowFlags f, const QKeySequence &designerCloseActionShortCut) +{ + QMdiSubWindow *rc = mdiArea()->addSubWindow(fw, f); + // Make action shortcuts respond only if focused to avoid conflicts with + // designer menu actions + if (designerCloseActionShortCut == QKeySequence(QKeySequence::Close)) { + const ActionList systemMenuActions = rc->systemMenu()->actions(); + for (auto *a : systemMenuActions) { + if (a->shortcut() == designerCloseActionShortCut) { + a->setShortcutContext(Qt::WidgetShortcut); + break; + } + } + } + return rc; +} + +DockedMainWindow::DockWidgetList DockedMainWindow::addToolWindows(const DesignerToolWindowList &tls) +{ + DockWidgetList rc; + for (QDesignerToolWindow *tw : tls) { + QDockWidget *dockWidget = new QDockWidget; + dockWidget->setObjectName(tw->objectName() + "_dock"_L1); + dockWidget->setWindowTitle(tw->windowTitle()); + addDockWidget(tw->dockWidgetAreaHint(), dockWidget); + dockWidget->setWidget(tw); + rc.push_back(dockWidget); + } + return rc; +} + +// Settings consist of MainWindow state and tool bar manager state +void DockedMainWindow::restoreSettings(const QDesignerSettings &s, const DockWidgetList &dws, const QRect &desktopArea) +{ + const int version = settingsVersion(); + m_toolBarManager->restoreState(s.toolBarsState(DockedMode), version); + + // If there are no old geometry settings, show the window maximized + s.restoreGeometry(this, QRect(desktopArea.topLeft(), QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX))); + + const QByteArray mainWindowState = s.mainWindowState(DockedMode); + const bool restored = !mainWindowState.isEmpty() && restoreState(mainWindowState, version); + if (!restored) { + // Default: Tabify less relevant windows bottom/right. + tabifyDockWidget(dws.at(QDesignerToolWindow::SignalSlotEditor), + dws.at(QDesignerToolWindow::ActionEditor)); + tabifyDockWidget(dws.at(QDesignerToolWindow::ActionEditor), + dws.at(QDesignerToolWindow::ResourceEditor)); + } +} + +void DockedMainWindow::saveSettings(QDesignerSettings &s) const +{ + const int version = settingsVersion(); + s.setToolBarsState(DockedMode, m_toolBarManager->saveState(version)); + s.saveGeometryFor(this); + s.setMainWindowState(DockedMode, saveState(version)); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/mainwindow.h b/src/designer/src/designer/mainwindow.h new file mode 100644 index 0000000..df41d6f --- /dev/null +++ b/src/designer/src/designer/mainwindow.h @@ -0,0 +1,150 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerActions; +class QDesignerWorkbench; +class QDesignerToolWindow; +class QDesignerFormWindow; +class QDesignerSettings; + +class QtToolBarManager; +class QToolBar; +class QMdiArea; +class QMenu; +class QByteArray; +class QMimeData; + +/* A main window that has a configureable policy on handling close events. If + * enabled, it can forward the close event to external handlers. + * Base class for windows that can switch roles between tool windows + * and main windows. */ + +class MainWindowBase: public QMainWindow +{ + Q_DISABLE_COPY_MOVE(MainWindowBase) + Q_OBJECT +protected: + explicit MainWindowBase(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Window); + +public: + enum CloseEventPolicy { + /* Always accept close events */ + AcceptCloseEvents, + /* Emit a signal with the event, have it handled elsewhere */ + EmitCloseEventSignal }; + + CloseEventPolicy closeEventPolicy() const { return m_policy; } + void setCloseEventPolicy(CloseEventPolicy pol) { m_policy = pol; } + + static QList createToolBars(const QDesignerActions *actions, bool singleToolBar); + static QString mainWindowTitle(); + + // Use the minor Qt version as settings versions to avoid conflicts + static int settingsVersion(); + +signals: + void closeEventReceived(QCloseEvent *e); + +protected: + void closeEvent(QCloseEvent *e) override; +private: + CloseEventPolicy m_policy = AcceptCloseEvents; +}; + +/* An MdiArea that listens for desktop file manager file drop events and emits + * a signal to open a dropped file. */ +class DockedMdiArea : public QMdiArea +{ + Q_DISABLE_COPY_MOVE(DockedMdiArea) + Q_OBJECT +public: + explicit DockedMdiArea(const QString &extension, QWidget *parent = nullptr); + +signals: + void fileDropped(const QString &); + +protected: + bool event (QEvent *event) override; + +private: + QStringList uiFiles(const QMimeData *d) const; + + const QString m_extension; +}; + +// Convenience class that manages a QtToolBarManager and an action to trigger +// it on a mainwindow. +class ToolBarManager : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ToolBarManager) +public: + explicit ToolBarManager(QMainWindow *configureableMainWindow, + QWidget *parent, + QMenu *toolBarMenu, + const QDesignerActions *actions, + const QList &toolbars, + const QList &toolWindows); + + QByteArray saveState(int version = 0) const; + bool restoreState(const QByteArray &state, int version = 0); + +private slots: + void configureToolBars(); + void updateToolBarMenu(); + +private: + QMainWindow *m_configureableMainWindow; + QWidget *m_parent; + QMenu *m_toolBarMenu; + QtToolBarManager *m_manager; + QAction *m_configureAction; + QList m_toolbars; +}; + +/* Main window to be used for docked mode */ +class DockedMainWindow : public MainWindowBase { + Q_OBJECT + Q_DISABLE_COPY_MOVE(DockedMainWindow) +public: + using DesignerToolWindowList = QList; + using DockWidgetList = QList; + + explicit DockedMainWindow(QDesignerWorkbench *wb, + QMenu *toolBarMenu, + const DesignerToolWindowList &toolWindows); + + // Create a MDI subwindow for the form. + QMdiSubWindow *createMdiSubWindow(QWidget *fw, Qt::WindowFlags f, const QKeySequence &designerCloseActionShortCut); + + QMdiArea *mdiArea() const; + + DockWidgetList addToolWindows(const DesignerToolWindowList &toolWindows); + + void restoreSettings(const QDesignerSettings &s, const DockWidgetList &dws, const QRect &desktopArea); + void saveSettings(QDesignerSettings &) const; + +signals: + void fileDropped(const QString &); + void formWindowActivated(QDesignerFormWindow *); + +private slots: + void slotSubWindowActivated(QMdiSubWindow*); + +private: + ToolBarManager *m_toolBarManager = nullptr; +}; + +QT_END_NAMESPACE + +#endif // MAINWINDOW_H diff --git a/src/designer/src/designer/newform.cpp b/src/designer/src/designer/newform.cpp new file mode 100644 index 0000000..3c77bc6 --- /dev/null +++ b/src/designer/src/designer/newform.cpp @@ -0,0 +1,187 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "newform.h" +#include "qdesigner_workbench.h" +#include "qdesigner_actions.h" +#include "qdesigner_formwindow.h" +#include "qdesigner_settings.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +NewForm::NewForm(QDesignerWorkbench *workbench, QWidget *parentWidget, const QString &fileName) + : QDialog(parentWidget, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint), + m_fileName(fileName), + m_newFormWidget(QDesignerNewFormWidgetInterface::createNewFormWidget(workbench->core())), + m_workbench(workbench), + m_chkShowOnStartup(new QCheckBox(tr("Show this Dialog on Startup"))), + m_createButton(new QPushButton(QApplication::translate("NewForm", "C&reate", nullptr))), + m_recentButton(new QPushButton(QApplication::translate("NewForm", "Recent", nullptr))) +{ + setWindowTitle(tr("New Form")); + QDesignerSettings settings(m_workbench->core()); + + QVBoxLayout *vBoxLayout = new QVBoxLayout; + + connect(m_newFormWidget, &QDesignerNewFormWidgetInterface::templateActivated, + this, &NewForm::slotTemplateActivated); + connect(m_newFormWidget, &QDesignerNewFormWidgetInterface::currentTemplateChanged, + this, &NewForm::slotCurrentTemplateChanged); + vBoxLayout->addWidget(m_newFormWidget); + + QFrame *horizontalLine = new QFrame; + horizontalLine->setFrameShape(QFrame::HLine); + horizontalLine->setFrameShadow(QFrame::Sunken); + vBoxLayout->addWidget(horizontalLine); + + m_chkShowOnStartup->setChecked(settings.showNewFormOnStartup()); + vBoxLayout->addWidget(m_chkShowOnStartup); + + m_buttonBox = createButtonBox(); + vBoxLayout->addWidget(m_buttonBox); + setLayout(vBoxLayout); + + resize(500, 400); + slotCurrentTemplateChanged(m_newFormWidget->hasCurrentTemplate()); +} + +QDialogButtonBox *NewForm::createButtonBox() +{ + // Dialog buttons with 'recent files' + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->addButton(QApplication::translate("NewForm", "&Close", nullptr), + QDialogButtonBox::RejectRole); + buttonBox->addButton(m_createButton, QDialogButtonBox::AcceptRole); + buttonBox->addButton(QApplication::translate("NewForm", "&Open...", nullptr), + QDialogButtonBox::ActionRole); + buttonBox->addButton(m_recentButton, QDialogButtonBox::ActionRole); + QDesignerActions *da = m_workbench->actionManager(); + QMenu *recentFilesMenu = new QMenu(tr("&Recent Forms"), m_recentButton); + // Pop the "Recent Files" stuff in here. + const auto recentActions = da->recentFilesActions()->actions(); + for (auto action : recentActions) { + recentFilesMenu->addAction(action); + connect(action, &QAction::triggered, this, &NewForm::recentFileChosen); + } + m_recentButton->setMenu(recentFilesMenu); + connect(buttonBox, &QDialogButtonBox::clicked, this, &NewForm::slotButtonBoxClicked); + return buttonBox; +} + +NewForm::~NewForm() +{ + QDesignerSettings settings (m_workbench->core()); + settings.setShowNewFormOnStartup(m_chkShowOnStartup->isChecked()); +} + +void NewForm::recentFileChosen() +{ + QAction *action = qobject_cast(sender()); + if (action && action->objectName() != "__qt_action_clear_menu_"_L1) + close(); +} + +void NewForm::slotCurrentTemplateChanged(bool templateSelected) +{ + if (templateSelected) { + m_createButton->setEnabled(true); + m_createButton->setDefault(true); + } else { + m_createButton->setEnabled(false); + } +} + +void NewForm::slotTemplateActivated() +{ + m_createButton->animateClick(); +} + +void NewForm::slotButtonBoxClicked(QAbstractButton *btn) +{ + switch (m_buttonBox->buttonRole(btn)) { + case QDialogButtonBox::RejectRole: + reject(); + break; + case QDialogButtonBox::ActionRole: + if (btn != m_recentButton) { + m_fileName.clear(); + if (m_workbench->actionManager()->openForm(this)) + accept(); + } + break; + case QDialogButtonBox::AcceptRole: { + QString errorMessage; + if (openTemplate(&errorMessage)) { + accept(); + } else { + QMessageBox::warning(this, tr("Read error"), errorMessage); + } + } + break; + default: + break; + } +} + +bool NewForm::openTemplate(QString *ptrToErrorMessage) +{ + const QString contents = m_newFormWidget->currentTemplate(ptrToErrorMessage); + if (contents.isEmpty()) + return false; + // Write to temporary file and open + QString tempPattern = QDir::tempPath(); + if (!tempPattern.endsWith(QDir::separator())) // platform-dependant + tempPattern += QDir::separator(); + tempPattern += "XXXXXX.ui"_L1; + QTemporaryFile tempFormFile(tempPattern); + + tempFormFile.setAutoRemove(true); + if (!tempFormFile.open()) { + *ptrToErrorMessage = tr("A temporary form file could not be created in %1: %2") + .arg(QDir::toNativeSeparators(QDir::tempPath()), tempFormFile.errorString()); + return false; + } + const QString tempFormFileName = tempFormFile.fileName(); + tempFormFile.write(contents.toUtf8()); + if (!tempFormFile.flush()) { + *ptrToErrorMessage = tr("The temporary form file %1 could not be written: %2") + .arg(QDir::toNativeSeparators(tempFormFileName), tempFormFile.errorString()); + return false; + } + tempFormFile.close(); + return m_workbench->openTemplate(tempFormFileName, m_fileName, ptrToErrorMessage); +} + +QImage NewForm::grabForm(QDesignerFormEditorInterface *core, + QIODevice &file, + const QString &workingDir, + const qdesigner_internal::DeviceProfile &dp) +{ + return qdesigner_internal::NewFormWidget::grabForm(core, file, workingDir, dp); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/newform.h b/src/designer/src/designer/newform.h new file mode 100644 index 0000000..e13f685 --- /dev/null +++ b/src/designer/src/designer/newform.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NEWFORM_H +#define NEWFORM_H + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + class DeviceProfile; +} + +class QDesignerFormEditorInterface; +class QDesignerNewFormWidgetInterface; +class QDesignerWorkbench; + +class QCheckBox; +class QAbstractButton; +class QPushButton; +class QDialogButtonBox; +class QImage; +class QIODevice; + +class NewForm: public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(NewForm) + +public: + NewForm(QDesignerWorkbench *workbench, + QWidget *parentWidget, + // Use that file name instead of a temporary one + const QString &fileName = QString()); + + ~NewForm() override; + + // Convenience for implementing file dialogs with preview + static QImage grabForm(QDesignerFormEditorInterface *core, + QIODevice &file, + const QString &workingDir, + const qdesigner_internal::DeviceProfile &dp); + +private slots: + void slotButtonBoxClicked(QAbstractButton *btn); + void recentFileChosen(); + void slotCurrentTemplateChanged(bool templateSelected); + void slotTemplateActivated(); + +private: + QDialogButtonBox *createButtonBox(); + bool openTemplate(QString *ptrToErrorMessage); + + QString m_fileName; + QDesignerNewFormWidgetInterface *m_newFormWidget; + QDesignerWorkbench *m_workbench; + QCheckBox *m_chkShowOnStartup; + QPushButton *m_createButton; + QPushButton *m_recentButton; + QDialogButtonBox *m_buttonBox = nullptr; +}; + +QT_END_NAMESPACE + +#endif // NEWFORM_H diff --git a/src/designer/src/designer/preferencesdialog.cpp b/src/designer/src/designer/preferencesdialog.cpp new file mode 100644 index 0000000..b376092 --- /dev/null +++ b/src/designer/src/designer/preferencesdialog.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "preferencesdialog.h" +#include "ui_preferencesdialog.h" +#include "qdesigner_appearanceoptions.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +PreferencesDialog::PreferencesDialog(QDesignerFormEditorInterface *core, QWidget *parentWidget) : + QDialog(parentWidget), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::PreferencesDialog()) +{ + m_ui->setupUi(this); + + m_optionsPages = core->optionsPages(); + + m_ui->m_optionTabWidget->clear(); + for (QDesignerOptionsPageInterface *optionsPage : std::as_const(m_optionsPages)) { + QWidget *page = optionsPage->createPage(this); + m_ui->m_optionTabWidget->addTab(page, optionsPage->name()); + if (QDesignerAppearanceOptionsWidget *appearanceWidget = qobject_cast(page)) + connect(appearanceWidget, &QDesignerAppearanceOptionsWidget::uiModeChanged, + this, &PreferencesDialog::slotUiModeChanged); + } + + connect(m_ui->m_dialogButtonBox, &QDialogButtonBox::rejected, this, &PreferencesDialog::slotRejected); + connect(m_ui->m_dialogButtonBox, &QDialogButtonBox::accepted, this, &PreferencesDialog::slotAccepted); + connect(applyButton(), &QAbstractButton::clicked, this, &PreferencesDialog::slotApply); +} + +PreferencesDialog::~PreferencesDialog() +{ + delete m_ui; +} + +QPushButton *PreferencesDialog::applyButton() const +{ + return m_ui->m_dialogButtonBox->button(QDialogButtonBox::Apply); +} + +void PreferencesDialog::slotApply() +{ + for (QDesignerOptionsPageInterface *optionsPage : std::as_const(m_optionsPages)) + optionsPage->apply(); +} + +void PreferencesDialog::slotAccepted() +{ + slotApply(); + closeOptionPages(); + accept(); +} + +void PreferencesDialog::slotRejected() +{ + closeOptionPages(); + reject(); +} + +void PreferencesDialog::slotUiModeChanged(bool modified) +{ + // Cannot "apply" a ui mode change (destroy the dialogs parent) + applyButton()->setEnabled(!modified); +} + +void PreferencesDialog::closeOptionPages() +{ + for (QDesignerOptionsPageInterface *optionsPage : std::as_const(m_optionsPages)) + optionsPage->finish(); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/preferencesdialog.h b/src/designer/src/designer/preferencesdialog.h new file mode 100644 index 0000000..c07b3e0 --- /dev/null +++ b/src/designer/src/designer/preferencesdialog.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PREFERENCESDIALOG_H +#define PREFERENCESDIALOG_H + +#include + +QT_BEGIN_NAMESPACE + +class QPushButton; +class QDesignerFormEditorInterface; +class QDesignerOptionsPageInterface; + +namespace Ui { + class PreferencesDialog; +} + +class PreferencesDialog: public QDialog +{ + Q_OBJECT +public: + explicit PreferencesDialog(QDesignerFormEditorInterface *core, QWidget *parentWidget = nullptr); + ~PreferencesDialog(); + + +private slots: + void slotAccepted(); + void slotRejected(); + void slotApply(); + void slotUiModeChanged(bool modified); + +private: + QPushButton *applyButton() const; + void closeOptionPages(); + + Ui::PreferencesDialog *m_ui; + QList m_optionsPages; +}; + +QT_END_NAMESPACE + +#endif // PREFERENCESDIALOG_H diff --git a/src/designer/src/designer/preferencesdialog.ui b/src/designer/src/designer/preferencesdialog.ui new file mode 100644 index 0000000..28d14bb --- /dev/null +++ b/src/designer/src/designer/preferencesdialog.ui @@ -0,0 +1,91 @@ + + + PreferencesDialog + + + + 0 + 0 + 474 + 304 + + + + + 0 + 0 + + + + Preferences + + + true + + + + + + + 0 + 0 + + + + 0 + + + + Tab 1 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Apply|QDialogButtonBox::Ok + + + + + + + + + m_dialogButtonBox + accepted() + PreferencesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + m_dialogButtonBox + rejected() + PreferencesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/designer/src/designer/qdesigner.cpp b/src/designer/src/designer/qdesigner.cpp new file mode 100644 index 0000000..ea51a1f --- /dev/null +++ b/src/designer/src/designer/qdesigner.cpp @@ -0,0 +1,330 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// designer +#include "qdesigner.h" +#include "qdesigner_actions.h" +#include "qdesigner_server.h" +#include "qdesigner_settings.h" +#include "qdesigner_workbench.h" +#include "mainwindow.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto designerApplicationName = "Designer"_L1; +static constexpr auto designerDisplayName = "Qt Widgets Designer"_L1; +static constexpr auto designerWarningPrefix = "Designer: "_L1; +static QtMessageHandler previousMessageHandler = nullptr; + +static void designerMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + // Only Designer warnings are displayed as box + QDesigner *designerApp = qDesigner; + if (type != QtWarningMsg || !designerApp || !msg.startsWith(designerWarningPrefix)) { + previousMessageHandler(type, context, msg); + return; + } + designerApp->showErrorMessage(msg); +} + +QDesigner::QDesigner(int &argc, char **argv) + : QApplication(argc, argv) +{ + setOrganizationName(u"QtProject"_s); + QGuiApplication::setApplicationDisplayName(designerDisplayName); + setApplicationName(designerApplicationName); + QDesignerComponents::initializeResources(); + +#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN) + setWindowIcon(QIcon(u":/qt-project.org/designer/images/designer.png"_s)); +#endif +} + +QDesigner::~QDesigner() +{ + delete m_workbench; + delete m_server; + delete m_client; +} + +void QDesigner::showErrorMessage(const QString &message) +{ + // strip the prefix + const QString qMessage = + message.right(message.size() - int(designerWarningPrefix.size())); + // If there is no main window yet, just store the message. + // The QErrorMessage would otherwise be hidden by the main window. + if (m_mainWindow) { + showErrorMessageBox(qMessage); + } else { + const QMessageLogContext emptyContext; + previousMessageHandler(QtWarningMsg, emptyContext, message); // just in case we crash + m_initializationErrors += qMessage; + m_initializationErrors += u'\n'; + } +} + +void QDesigner::showErrorMessageBox(const QString &msg) +{ + // Manually suppress consecutive messages. + // This happens if for example sth is wrong with custom widget creation. + // The same warning will be displayed by Widget box D&D and form Drop + // while trying to create instance. + if (m_errorMessageDialog && m_lastErrorMessage == msg) + return; + + if (!m_errorMessageDialog) { + m_lastErrorMessage.clear(); + m_errorMessageDialog = new QErrorMessage(m_mainWindow); + const QString title = QCoreApplication::translate("QDesigner", "%1 - warning").arg(designerApplicationName); + m_errorMessageDialog->setWindowTitle(title); + m_errorMessageDialog->setMinimumSize(QSize(600, 250)); + } + m_errorMessageDialog->showMessage(msg); + m_lastErrorMessage = msg; +} + +QDesignerWorkbench *QDesigner::workbench() const +{ + return m_workbench; +} + +QDesignerServer *QDesigner::server() const +{ + return m_server; +} + +static void showHelp(QCommandLineParser &parser, const QString &errorMessage = QString()) +{ + QString text; + QTextStream str(&text); + str << ""; + if (!errorMessage.isEmpty()) + str << "

" << errorMessage << "

"; + str << "
" << parser.helpText().toHtmlEscaped() << "
"; + QMessageBox box(errorMessage.isEmpty() ? QMessageBox::Information : QMessageBox::Warning, + QGuiApplication::applicationDisplayName(), text, + QMessageBox::Ok); + box.setTextInteractionFlags(Qt::TextBrowserInteraction); + box.exec(); +} + +struct Options +{ + QStringList files; + QString resourceDir{QLibraryInfo::path(QLibraryInfo::TranslationsPath)}; + QStringList pluginPaths; + std::optional qtVersion; + bool server{false}; + quint16 clientPort{0}; + bool enableInternalDynamicProperties{false}; +}; + +static inline QDesigner::ParseArgumentsResult + parseDesignerCommandLineArguments(QCommandLineParser &parser, Options *options, + QString *errorMessage) +{ + parser.setApplicationDescription(u"Qt Widgets Designer " QT_VERSION_STR "\n\nUI designer for QWidget-based applications."_s); + const QCommandLineOption helpOption = parser.addHelpOption(); + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + const QCommandLineOption serverOption(u"server"_s, + u"Server mode"_s); + parser.addOption(serverOption); + const QCommandLineOption clientOption(u"client"_s, + u"Client mode"_s, + u"port"_s); + parser.addOption(clientOption); + const QCommandLineOption resourceDirOption(u"resourcedir"_s, + u"Resource directory"_s, + u"directory"_s); + parser.addOption(resourceDirOption); + const QCommandLineOption internalDynamicPropertyOption(u"enableinternaldynamicproperties"_s, + u"Enable internal dynamic properties"_s); + parser.addOption(internalDynamicPropertyOption); + const QCommandLineOption pluginPathsOption(u"plugin-path"_s, + u"Default plugin path list"_s, + u"path"_s); + parser.addOption(pluginPathsOption); + + const QCommandLineOption qtVersionOption(u"qt-version"_s, + u"Qt Version for writing .ui files"_s, + u"version"_s); + parser.addOption(qtVersionOption); + + parser.addPositionalArgument(u"files"_s, + u"The UI files to open."_s); + + if (!parser.parse(QCoreApplication::arguments())) { + *errorMessage = parser.errorText(); + return QDesigner::ParseArgumentsError; + } + + if (parser.isSet(helpOption)) + return QDesigner::ParseArgumentsHelpRequested; + // There is no way to retrieve the complete help text from QCommandLineParser, + // so, call process() to display it. + if (parser.isSet(u"help-all"_s)) + parser.process(QCoreApplication::arguments()); // exits + options->server = parser.isSet(serverOption); + if (parser.isSet(clientOption)) { + bool ok; + options->clientPort = parser.value(clientOption).toUShort(&ok); + if (!ok) { + *errorMessage = u"Non-numeric argument specified for -client"_s; + return QDesigner::ParseArgumentsError; + } + } + if (parser.isSet(resourceDirOption)) + options->resourceDir = parser.value(resourceDirOption); + const auto pluginPathValues = parser.values(pluginPathsOption); + for (const auto &pluginPath : pluginPathValues) + options->pluginPaths.append(pluginPath.split(QDir::listSeparator(), Qt::SkipEmptyParts)); + + if (parser.isSet(qtVersionOption)) + options->qtVersion = QVersionNumber::fromString(parser.value(qtVersionOption)); + + options->enableInternalDynamicProperties = parser.isSet(internalDynamicPropertyOption); + options->files = parser.positionalArguments(); + return QDesigner::ParseArgumentsSuccess; +} + +QDesigner::ParseArgumentsResult QDesigner::parseCommandLineArguments() +{ + QString errorMessage; + Options options; + QCommandLineParser parser; + const ParseArgumentsResult result = parseDesignerCommandLineArguments(parser, &options, &errorMessage); + if (result != ParseArgumentsSuccess) { + showHelp(parser, errorMessage); + return result; + } + // initialize the sub components + if (options.clientPort) + m_client = new QDesignerClient(options.clientPort, this); + if (options.server) { + m_server = new QDesignerServer(); + printf("%d\n", m_server->serverPort()); + fflush(stdout); + } + if (options.enableInternalDynamicProperties) + QDesignerPropertySheet::setInternalDynamicPropertiesEnabled(true); + + std::unique_ptr designerTranslator(new QTranslator(this)); + if (designerTranslator->load(QLocale(), u"designer"_s, u"_"_s, options.resourceDir)) { + installTranslator(designerTranslator.release()); + std::unique_ptr qtTranslator(new QTranslator(this)); + if (qtTranslator->load(QLocale(), u"qt"_s, u"_"_s, options.resourceDir)) + installTranslator(qtTranslator.release()); + } + + m_workbench = new QDesignerWorkbench(options.pluginPaths); + + emit initialized(); + previousMessageHandler = qInstallMessageHandler(designerMessageHandler); // Warn when loading faulty forms + Q_ASSERT(previousMessageHandler); + + bool suppressNewFormShow = m_workbench->readInBackup(); + + for (auto fileName : std::as_const(options.files)) { + // Ensure absolute paths for recent file list to be unique + const QFileInfo fi(fileName); + if (fi.exists() && fi.isRelative()) + fileName = fi.absoluteFilePath(); + m_workbench->readInForm(fileName); + } + + if (m_workbench->formWindowCount() > 0) + suppressNewFormShow = true; + + if (options.qtVersion.has_value()) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + m_workbench->core()->integration()->setQtVersion(options.qtVersion.value()); +#else + auto version = QVariant::fromValue(options.qtVersion.value()); + m_workbench->core()->integration()->setProperty("qtVersion", version); +#endif + } + + // Show up error box with parent now if something went wrong + if (m_initializationErrors.isEmpty()) { + if (!isServerOrClientEnabled() && !suppressNewFormShow) + m_workbench->showNewForm(); + } else { + showErrorMessageBox(m_initializationErrors); + m_initializationErrors.clear(); + } + return result; +} + +bool QDesigner::isServerOrClientEnabled() const +{ + return m_server || m_client; +} + +bool QDesigner::event(QEvent *ev) +{ + bool eaten; + switch (ev->type()) { + case QEvent::FileOpen: + m_workbench->readInForm(static_cast(ev)->file()); + m_workbench->requestActivate(); + eaten = true; + break; + case QEvent::Close: { + QCloseEvent *closeEvent = static_cast(ev); + closeEvent->setAccepted(m_workbench->handleClose()); + if (closeEvent->isAccepted()) { + // We're going down, make sure that we don't get our settings saved twice. + if (m_mainWindow) + m_mainWindow->setCloseEventPolicy(MainWindowBase::AcceptCloseEvents); + eaten = QApplication::event(ev); + } + eaten = true; + break; + } + default: + eaten = QApplication::event(ev); + break; + } + return eaten; +} + +void QDesigner::setMainWindow(MainWindowBase *tw) +{ + m_mainWindow = tw; +} + +MainWindowBase *QDesigner::mainWindow() const +{ + return m_mainWindow; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/qdesigner.h b/src/designer/src/designer/qdesigner.h new file mode 100644 index 0000000..9754cc0 --- /dev/null +++ b/src/designer/src/designer/qdesigner.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_H +#define QDESIGNER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +#define qDesigner \ + (static_cast(QCoreApplication::instance())) + +class QDesignerWorkbench; +class QDesignerToolWindow; +class MainWindowBase; +class QDesignerServer; +class QDesignerClient; +class QErrorMessage; +class QCommandLineParser; +struct Options; + +class QDesigner: public QApplication +{ + Q_OBJECT +public: + enum ParseArgumentsResult { + ParseArgumentsSuccess, + ParseArgumentsError, + ParseArgumentsHelpRequested + }; + + QDesigner(int &argc, char **argv); + ~QDesigner() override; + + ParseArgumentsResult parseCommandLineArguments(); + + QDesignerWorkbench *workbench() const; + QDesignerServer *server() const; + bool isServerOrClientEnabled() const; + MainWindowBase *mainWindow() const; + void setMainWindow(MainWindowBase *tw); + +protected: + bool event(QEvent *ev) override; + +signals: + void initialized(); + +public slots: + void showErrorMessage(const QString &message); + +private: + void showErrorMessageBox(const QString &); + + QDesignerServer *m_server = nullptr; + QDesignerClient *m_client = nullptr; + QDesignerWorkbench *m_workbench = nullptr; + QPointer m_mainWindow; + QPointer m_errorMessageDialog; + + QString m_initializationErrors; + QString m_lastErrorMessage; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_H diff --git a/src/designer/src/designer/qdesigner_actions.cpp b/src/designer/src/designer/qdesigner_actions.cpp new file mode 100644 index 0000000..854e6d1 --- /dev/null +++ b/src/designer/src/designer/qdesigner_actions.cpp @@ -0,0 +1,1315 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_actions.h" +#include "designer_enums.h" +#include +#include "qdesigner.h" +#include "qdesigner_workbench.h" +#include "qdesigner_formwindow.h" +#include "mainwindow.h" +#include "newform.h" +#include "versiondialog.h" +#include "saveformastemplate.h" +#include "preferencesdialog.h" +#include "appfontdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +// sdk +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#if defined(QT_PRINTSUPPORT_LIB) // Some platforms may not build QtPrintSupport +# include +# if QT_CONFIG(printer) && QT_CONFIG(printdialog) +# include +# include +# define HAS_PRINTER +# endif +#endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +const char *QDesignerActions::defaultToolbarPropertyName = "__qt_defaultToolBarAction"; + +//#ifdef Q_OS_MACOS +# define NONMODAL_PREVIEW +//#endif + +static QAction *createSeparator(QObject *parent) { + QAction * rc = new QAction(parent); + rc->setSeparator(true); + return rc; +} + +static QActionGroup *createActionGroup(QObject *parent, bool exclusive = false) { + QActionGroup * rc = new QActionGroup(parent); + rc->setExclusive(exclusive); + return rc; +} + +static void fixActionContext(const QList &actions) +{ + for (QAction *a : actions) + a->setShortcutContext(Qt::ApplicationShortcut); +} + +static inline QString savedMessage(const QString &fileName) +{ + return QDesignerActions::tr("Saved %1.").arg(fileName); +} + +static QString fileDialogFilters(const QString &extension) +{ + return QDesignerActions::tr("Designer UI files (*.%1);;All Files (*)").arg(extension); +} + +static QString fixResourceFileBackupPath(const QDesignerFormWindowInterface *fwi, + const QDir& backupDir); + +static QByteArray formWindowContents(const QDesignerFormWindowInterface *fw, + std::optional alternativeDir = {}) +{ + QString contents = alternativeDir.has_value() + ? fixResourceFileBackupPath(fw, alternativeDir.value()) : fw->contents(); + if (auto *fwb = qobject_cast(fw)) { + if (fwb->lineTerminatorMode() == qdesigner_internal::FormWindowBase::CRLFLineTerminator) + contents.replace(u'\n', "\r\n"_L1); + } + return contents.toUtf8(); +} + +QFileDialog *createSaveAsDialog(QWidget *parent, const QString &dir, const QString &extension) +{ + auto result = new QFileDialog(parent, QDesignerActions::tr("Save Form As"), + dir, fileDialogFilters(extension)); + result->setAcceptMode(QFileDialog::AcceptSave); + result->setDefaultSuffix(extension); + return result; +} + +QDesignerActions::QDesignerActions(QDesignerWorkbench *workbench) + : QObject(workbench), + m_workbench(workbench), + m_core(workbench->core()), + m_settings(workbench->core()), + m_backupTimer(new QTimer(this)), + m_fileActions(createActionGroup(this)), + m_recentFilesActions(createActionGroup(this)), + m_editActions(createActionGroup(this)), + m_formActions(createActionGroup(this)), + m_settingsActions(createActionGroup(this)), + m_windowActions(createActionGroup(this)), + m_toolActions(createActionGroup(this, true)), + m_editWidgetsAction(new QAction(tr("Edit Widgets"), this)), + m_newFormAction(new QAction(qdesigner_internal::createIconSet(QIcon::ThemeIcon::DocumentNew, + "filenew.png"_L1), + tr("&New..."), this)), + m_openFormAction(new QAction(qdesigner_internal::createIconSet(QIcon::ThemeIcon::DocumentOpen, + "fileopen.png"_L1), + tr("&Open..."), this)), + m_saveFormAction(new QAction(qdesigner_internal::createIconSet(QIcon::ThemeIcon::DocumentSave, + "filesave.png"_L1), + tr("&Save"), this)), + m_saveFormAsAction(new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSaveAs), + tr("Save &As..."), this)), + m_saveAllFormsAction(new QAction(tr("Save A&ll"), this)), + m_saveFormAsTemplateAction(new QAction(tr("Save As &Template..."), this)), + m_closeFormAction(new QAction(QIcon::fromTheme(QIcon::ThemeIcon::WindowClose), + tr("&Close"), this)), + m_savePreviewImageAction(new QAction(tr("Save &Image..."), this)), + m_printPreviewAction(new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentPrint), + tr("&Print..."), this)), + m_quitAction(new QAction(QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit), + tr("&Quit"), this)), + m_viewCppCodeAction(new QAction(tr("View &C++ Code..."), this)), + m_viewPythonCodeAction(new QAction(tr("View &Python Code..."), this)), + m_minimizeAction(new QAction(tr("&Minimize"), this)), + m_bringAllToFrontSeparator(createSeparator(this)), + m_bringAllToFrontAction(new QAction(tr("Bring All to Front"), this)), + m_windowListSeparatorAction(createSeparator(this)), + m_preferencesAction(new QAction(tr("Preferences..."), this)), + m_appFontAction(new QAction(tr("Additional Fonts..."), this)) +{ + Q_ASSERT(m_core != nullptr); + qdesigner_internal::QDesignerFormWindowManager *ifwm = qobject_cast(m_core->formWindowManager()); + Q_ASSERT(ifwm); + m_previewManager = ifwm->previewManager(); + m_previewFormAction = ifwm->action(QDesignerFormWindowManagerInterface::DefaultPreviewAction); + m_styleActions = ifwm->actionGroup(QDesignerFormWindowManagerInterface::StyledPreviewActionGroup); + connect(ifwm, &QDesignerFormWindowManagerInterface::formWindowSettingsChanged, + this, &QDesignerActions::formWindowSettingsChanged); + + m_editWidgetsAction->setObjectName(u"__qt_edit_widgets_action"_s); + m_newFormAction->setObjectName(u"__qt_new_form_action"_s); + m_openFormAction->setObjectName(u"__qt_open_form_action"_s); + m_saveFormAction->setObjectName(u"__qt_save_form_action"_s); + m_saveFormAsAction->setObjectName(u"__qt_save_form_as_action"_s); + m_saveAllFormsAction->setObjectName(u"__qt_save_all_forms_action"_s); + m_saveFormAsTemplateAction->setObjectName(u"__qt_save_form_as_template_action"_s); + m_closeFormAction->setObjectName(u"__qt_close_form_action"_s); + m_quitAction->setObjectName(u"__qt_quit_action"_s); + m_previewFormAction->setObjectName(u"__qt_preview_form_action"_s); + m_viewCppCodeAction->setObjectName(u"__qt_preview_cpp_code_action"_s); + m_viewPythonCodeAction->setObjectName(u"__qt_preview_python_code_action"_s); + m_minimizeAction->setObjectName(u"__qt_minimize_action"_s); + m_bringAllToFrontAction->setObjectName(u"__qt_bring_all_to_front_action"_s); + m_preferencesAction->setObjectName(u"__qt_preferences_action"_s); + + m_helpActions = createHelpActions(); + + m_newFormAction->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + m_openFormAction->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + m_saveFormAction->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + + QDesignerFormWindowManagerInterface *formWindowManager = m_core->formWindowManager(); + Q_ASSERT(formWindowManager != nullptr); + +// +// file actions +// + m_newFormAction->setShortcut(QKeySequence::New); + connect(m_newFormAction, &QAction::triggered, this, &QDesignerActions::createForm); + m_fileActions->addAction(m_newFormAction); + + m_openFormAction->setShortcut(QKeySequence::Open); + connect(m_openFormAction, &QAction::triggered, this, &QDesignerActions::slotOpenForm); + m_fileActions->addAction(m_openFormAction); + + m_fileActions->addAction(createRecentFilesMenu()); + m_fileActions->addAction(createSeparator(this)); + + m_saveFormAction->setShortcut(QKeySequence::Save); + connect(m_saveFormAction, &QAction::triggered, this, + QOverload<>::of(&QDesignerActions::saveForm)); + m_fileActions->addAction(m_saveFormAction); + + connect(m_saveFormAsAction, &QAction::triggered, this, + QOverload<>::of(&QDesignerActions::saveFormAs)); + m_fileActions->addAction(m_saveFormAsAction); + +#ifdef Q_OS_MACOS + m_saveAllFormsAction->setShortcut(tr("ALT+CTRL+S")); +#else + m_saveAllFormsAction->setShortcut(tr("CTRL+SHIFT+S")); // Commonly "Save As" on Mac +#endif + connect(m_saveAllFormsAction, &QAction::triggered, this, &QDesignerActions::saveAllForms); + m_fileActions->addAction(m_saveAllFormsAction); + + connect(m_saveFormAsTemplateAction, &QAction::triggered, this, &QDesignerActions::saveFormAsTemplate); + m_fileActions->addAction(m_saveFormAsTemplateAction); + + m_fileActions->addAction(createSeparator(this)); + + m_printPreviewAction->setShortcut(QKeySequence::Print); + connect(m_printPreviewAction, &QAction::triggered, this, &QDesignerActions::printPreviewImage); + m_fileActions->addAction(m_printPreviewAction); + m_printPreviewAction->setObjectName(u"__qt_print_action"_s); + + connect(m_savePreviewImageAction, &QAction::triggered, this, &QDesignerActions::savePreviewImage); + m_savePreviewImageAction->setObjectName(u"__qt_saveimage_action"_s); + m_fileActions->addAction(m_savePreviewImageAction); + m_fileActions->addAction(createSeparator(this)); + + m_closeFormAction->setShortcut(QKeySequence::Close); + connect(m_closeFormAction, &QAction::triggered, this, &QDesignerActions::closeForm); + m_fileActions->addAction(m_closeFormAction); + updateCloseAction(); + + m_fileActions->addAction(createSeparator(this)); + + m_quitAction->setShortcuts(QKeySequence::Quit); + m_quitAction->setMenuRole(QAction::QuitRole); + connect(m_quitAction, &QAction::triggered, this, &QDesignerActions::shutdown); + m_fileActions->addAction(m_quitAction); + +// +// edit actions +// + QAction *undoAction = formWindowManager->action(QDesignerFormWindowManagerInterface::UndoAction); + undoAction->setObjectName(u"__qt_undo_action"_s); + undoAction->setShortcut(QKeySequence::Undo); + m_editActions->addAction(undoAction); + + QAction *redoAction = formWindowManager->action(QDesignerFormWindowManagerInterface::RedoAction); + redoAction->setObjectName(u"__qt_redo_action"_s); + redoAction->setShortcut(QKeySequence::Redo); + m_editActions->addAction(redoAction); + + m_editActions->addAction(createSeparator(this)); + +#if QT_CONFIG(clipboard) + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::CutAction)); + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::CopyAction)); + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::PasteAction)); +#endif + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::DeleteAction)); + + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::SelectAllAction)); + + m_editActions->addAction(createSeparator(this)); + + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::LowerAction)); + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::RaiseAction)); + + formWindowManager->action(QDesignerFormWindowManagerInterface::LowerAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::RaiseAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + +// +// edit mode actions +// + + m_editWidgetsAction->setCheckable(true); + QList shortcuts; + shortcuts.append(QKeySequence(Qt::Key_F3)); + shortcuts.append(QKeySequence(Qt::Key_Escape)); + m_editWidgetsAction->setShortcuts(shortcuts); + m_editWidgetsAction->setIcon(qdesigner_internal::createIconSet("widgettool.png"_L1)); + connect(m_editWidgetsAction, &QAction::triggered, this, &QDesignerActions::editWidgetsSlot); + m_editWidgetsAction->setChecked(true); + m_editWidgetsAction->setEnabled(false); + m_editWidgetsAction->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + m_toolActions->addAction(m_editWidgetsAction); + + connect(formWindowManager, &qdesigner_internal::QDesignerFormWindowManager::activeFormWindowChanged, + this, &QDesignerActions::activeFormWindowChanged); + + const QObjectList builtinPlugins = QPluginLoader::staticInstances() + + m_core->pluginManager()->instances(); + for (QObject *plugin : builtinPlugins) { + if (QDesignerFormEditorPluginInterface *formEditorPlugin = qobject_cast(plugin)) { + if (QAction *action = formEditorPlugin->action()) { + m_toolActions->addAction(action); + action->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + action->setCheckable(true); + } + } + } + + connect(m_preferencesAction, &QAction::triggered, this, &QDesignerActions::showPreferencesDialog); + m_preferencesAction->setMenuRole(QAction::PreferencesRole); + m_settingsActions->addAction(m_preferencesAction); + + connect(m_appFontAction, &QAction::triggered, this, &QDesignerActions::showAppFontDialog); + m_settingsActions->addAction(m_appFontAction); +// +// form actions +// + + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::HorizontalLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::VerticalLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::SplitHorizontalAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::SplitVerticalAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::GridLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::FormLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::BreakLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::AdjustSizeAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::SimplifyLayoutAction)); + m_formActions->addAction(createSeparator(this)); + + formWindowManager->action(QDesignerFormWindowManagerInterface::HorizontalLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::VerticalLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::SplitHorizontalAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::SplitVerticalAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::GridLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::FormLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::BreakLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::AdjustSizeAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + + m_previewFormAction->setShortcut(tr("CTRL+R")); + m_formActions->addAction(m_previewFormAction); + connect(m_previewManager, &qdesigner_internal::PreviewManager::firstPreviewOpened, + this, &QDesignerActions::updateCloseAction); + connect(m_previewManager, &qdesigner_internal::PreviewManager::lastPreviewClosed, + this, &QDesignerActions::updateCloseAction); + + connect(m_viewCppCodeAction, &QAction::triggered, this, + [this] () { this->viewCode(qdesigner_internal::UicLanguage::Cpp); }); + connect(m_viewPythonCodeAction, &QAction::triggered, this, + [this] () { this->viewCode(qdesigner_internal::UicLanguage::Python); }); + + // Preview code only in Cpp/Python (uic) + if (qt_extension(m_core->extensionManager(), m_core) == nullptr) { + m_formActions->addAction(m_viewCppCodeAction); + m_formActions->addAction(m_viewPythonCodeAction); + } + + m_formActions->addAction(createSeparator(this)); + + m_formActions->addAction(ifwm->action(QDesignerFormWindowManagerInterface::FormWindowSettingsDialogAction)); +// +// window actions +// + m_minimizeAction->setEnabled(false); + m_minimizeAction->setCheckable(true); + m_minimizeAction->setShortcut(tr("CTRL+M")); + connect(m_minimizeAction, &QAction::triggered, m_workbench, &QDesignerWorkbench::toggleFormMinimizationState); + m_windowActions->addAction(m_minimizeAction); + + m_windowActions->addAction(m_bringAllToFrontSeparator); + connect(m_bringAllToFrontAction, &QAction::triggered, m_workbench, &QDesignerWorkbench::bringAllToFront); + m_windowActions->addAction(m_bringAllToFrontAction); + m_windowActions->addAction(m_windowListSeparatorAction); + + setWindowListSeparatorVisible(false); + +// +// connections +// + fixActionContext(m_fileActions->actions()); + fixActionContext(m_editActions->actions()); + fixActionContext(m_toolActions->actions()); + fixActionContext(m_formActions->actions()); + fixActionContext(m_windowActions->actions()); + fixActionContext(m_helpActions->actions()); + + activeFormWindowChanged(core()->formWindowManager()->activeFormWindow()); + + m_backupTimer->start(180000); // 3min + connect(m_backupTimer, &QTimer::timeout, this, &QDesignerActions::backupForms); + + // Enable application font action + connect(formWindowManager, &QDesignerFormWindowManagerInterface::formWindowAdded, + this, &QDesignerActions::formWindowCountChanged); + connect(formWindowManager, &QDesignerFormWindowManagerInterface::formWindowRemoved, + this, &QDesignerActions::formWindowCountChanged); + formWindowCountChanged(); +} + +QActionGroup *QDesignerActions::createHelpActions() +{ + QActionGroup *helpActions = createActionGroup(this); + +#ifndef QT_JAMBI_BUILD + QAction *mainHelpAction = new QAction(tr("Qt Widgets Designer &Help"), this); + mainHelpAction->setObjectName(u"__qt_designer_help_action"_s); + connect(mainHelpAction, &QAction::triggered, this, &QDesignerActions::showDesignerHelp); + mainHelpAction->setShortcut(Qt::CTRL | Qt::Key_Question); + helpActions->addAction(mainHelpAction); + + helpActions->addAction(createSeparator(this)); + QAction *widgetHelp = new QAction(tr("Current Widget Help"), this); + widgetHelp->setObjectName(u"__qt_current_widget_help_action"_s); + widgetHelp->setShortcut(Qt::Key_F1); + connect(widgetHelp, &QAction::triggered, this, &QDesignerActions::showWidgetSpecificHelp); + helpActions->addAction(widgetHelp); + +#endif + + helpActions->addAction(createSeparator(this)); + QAction *aboutPluginsAction = new QAction(tr("About Plugins"), this); + aboutPluginsAction->setObjectName(u"__qt_about_plugins_action"_s); + aboutPluginsAction->setMenuRole(QAction::ApplicationSpecificRole); + connect(aboutPluginsAction, &QAction::triggered, + m_core->formWindowManager(), &QDesignerFormWindowManagerInterface::showPluginDialog); + helpActions->addAction(aboutPluginsAction); + + QAction *aboutDesignerAction = new QAction(tr("About Qt Widgets Designer"), this); + aboutDesignerAction->setMenuRole(QAction::AboutRole); + aboutDesignerAction->setObjectName(u"__qt_about_designer_action"_s); + connect(aboutDesignerAction, &QAction::triggered, this, &QDesignerActions::aboutDesigner); + helpActions->addAction(aboutDesignerAction); + + QAction *aboutQtAction = new QAction(tr("About Qt"), this); + aboutQtAction->setMenuRole(QAction::AboutQtRole); + aboutQtAction->setObjectName(u"__qt_about_qt_action"_s); + connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt); + helpActions->addAction(aboutQtAction); + return helpActions; +} + +QDesignerActions::~QDesignerActions() +{ +#ifdef HAS_PRINTER + delete m_printer; +#endif +} + +QString QDesignerActions::uiExtension() const +{ + QDesignerLanguageExtension *lang + = qt_extension(m_core->extensionManager(), m_core); + if (lang) + return lang->uiExtension(); + return u"ui"_s; +} + +QAction *QDesignerActions::createRecentFilesMenu() +{ + m_recentMenu.reset(new QMenu); + QAction *act; + // Need to insert this into the QAction. + for (int i = 0; i < MaxRecentFiles; ++i) { + act = new QAction(this); + act->setVisible(false); + connect(act, &QAction::triggered, this, &QDesignerActions::openRecentForm); + m_recentFilesActions->addAction(act); + m_recentMenu->addAction(act); + } + updateRecentFileActions(); + m_recentMenu->addSeparator(); + act = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::EditClear), + tr("Clear &Menu"), this); + act->setObjectName(u"__qt_action_clear_menu_"_s); + connect(act, &QAction::triggered, this, &QDesignerActions::clearRecentFiles); + m_recentFilesActions->addAction(act); + m_recentMenu->addAction(act); + + act = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentOpenRecent), + tr("&Recent Forms"), this); + act->setMenu(m_recentMenu.get()); + return act; +} + +QActionGroup *QDesignerActions::toolActions() const +{ return m_toolActions; } + +QDesignerWorkbench *QDesignerActions::workbench() const +{ return m_workbench; } + +QDesignerFormEditorInterface *QDesignerActions::core() const +{ return m_core; } + +QActionGroup *QDesignerActions::fileActions() const +{ return m_fileActions; } + +QActionGroup *QDesignerActions::editActions() const +{ return m_editActions; } + +QActionGroup *QDesignerActions::formActions() const +{ return m_formActions; } + +QActionGroup *QDesignerActions::settingsActions() const +{ return m_settingsActions; } + +QActionGroup *QDesignerActions::windowActions() const +{ return m_windowActions; } + +QActionGroup *QDesignerActions::helpActions() const +{ return m_helpActions; } + +QActionGroup *QDesignerActions::styleActions() const +{ return m_styleActions; } + +QAction *QDesignerActions::previewFormAction() const +{ return m_previewFormAction; } + +QAction *QDesignerActions::viewCodeAction() const +{ return m_viewCppCodeAction; } + + +void QDesignerActions::editWidgetsSlot() +{ + QDesignerFormWindowManagerInterface *formWindowManager = core()->formWindowManager(); + for (int i=0; iformWindowCount(); ++i) { + QDesignerFormWindowInterface *formWindow = formWindowManager->formWindow(i); + formWindow->editWidgets(); + } +} + +void QDesignerActions::createForm() +{ + showNewFormDialog(QString()); +} + +void QDesignerActions::showNewFormDialog(const QString &fileName) +{ + closePreview(); + NewForm *dlg = new NewForm(workbench(), workbench()->core()->topLevel(), fileName); + + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->setAttribute(Qt::WA_ShowModal); + + dlg->setGeometry(fixDialogRect(dlg->rect())); + dlg->exec(); +} + +void QDesignerActions::slotOpenForm() +{ + openForm(core()->topLevel()); +} + +bool QDesignerActions::openForm(QWidget *parent) +{ + closePreview(); + const QString extension = uiExtension(); + const QStringList fileNames = QFileDialog::getOpenFileNames(parent, tr("Open Form"), + m_openDirectory, fileDialogFilters(extension), nullptr); + + if (fileNames.isEmpty()) + return false; + + bool atLeastOne = false; + for (const QString &fileName : fileNames) { + if (readInForm(fileName) && !atLeastOne) + atLeastOne = true; + } + + return atLeastOne; +} + +bool QDesignerActions::saveFormAs(QDesignerFormWindowInterface *fw) +{ + const QString extension = uiExtension(); + + QString dir = fw->fileName(); + if (dir.isEmpty()) { + do { + // Build untitled name + if (!m_saveDirectory.isEmpty()) { + dir = m_saveDirectory; + break; + } + if (!m_openDirectory.isEmpty()) { + dir = m_openDirectory; + break; + } + dir = QDir::current().absolutePath(); + } while (false); + dir += QDir::separator(); + dir += "untitled."_L1; + dir += extension; + } + + QScopedPointer saveAsDialog(createSaveAsDialog(fw, dir, extension)); + if (saveAsDialog->exec() != QDialog::Accepted) + return false; + + const QString saveFile = saveAsDialog->selectedFiles().constFirst(); + saveAsDialog.reset(); // writeOutForm potentially shows other dialogs + + fw->setFileName(saveFile); + return writeOutForm(fw, saveFile); +} + +void QDesignerActions::saveForm() +{ + if (QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow()) { + if (saveForm(fw)) + showStatusBarMessage(savedMessage(QFileInfo(fw->fileName()).fileName())); + } +} + +void QDesignerActions::saveAllForms() +{ + QString fileNames; + QDesignerFormWindowManagerInterface *formWindowManager = core()->formWindowManager(); + if (const int totalWindows = formWindowManager->formWindowCount()) { + const auto separator = ", "_L1; + for (int i = 0; i < totalWindows; ++i) { + QDesignerFormWindowInterface *fw = formWindowManager->formWindow(i); + if (fw && fw->isDirty()) { + formWindowManager->setActiveFormWindow(fw); + if (saveForm(fw)) { + if (!fileNames.isEmpty()) + fileNames += separator; + fileNames += QFileInfo(fw->fileName()).fileName(); + } else { + break; + } + } + } + } + + if (!fileNames.isEmpty()) { + showStatusBarMessage(savedMessage(fileNames)); + } +} + +bool QDesignerActions::saveForm(QDesignerFormWindowInterface *fw) +{ + bool ret; + if (fw->fileName().isEmpty()) + ret = saveFormAs(fw); + else + ret = writeOutForm(fw, fw->fileName()); + return ret; +} + +void QDesignerActions::closeForm() +{ + if (m_previewManager->previewCount()) { + closePreview(); + return; + } + + if (QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow()) + if (QWidget *parent = fw->parentWidget()) { + if (QMdiSubWindow *mdiSubWindow = qobject_cast(parent->parentWidget())) { + mdiSubWindow->close(); + } else { + parent->close(); + } + } +} + +void QDesignerActions::saveFormAs() +{ + if (QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow()) { + if (saveFormAs(fw)) + showStatusBarMessage(savedMessage(fw->fileName())); + } +} + +void QDesignerActions::saveFormAsTemplate() +{ + if (QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow()) { + SaveFormAsTemplate dlg(core(), fw, fw->window()); + dlg.exec(); + } +} + +void QDesignerActions::notImplementedYet() +{ + QMessageBox::information(core()->topLevel(), tr("Designer"), tr("Feature not implemented yet!")); +} + +void QDesignerActions::closePreview() +{ + m_previewManager->closeAllPreviews(); +} + +void QDesignerActions::viewCode(qdesigner_internal::UicLanguage language) +{ + QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow(); + if (!fw) + return; + QString errorMessage; + if (!qdesigner_internal::CodeDialog::showCodeDialog(fw, language, fw, &errorMessage)) + QMessageBox::warning(fw, tr("Code generation failed"), errorMessage); +} + +bool QDesignerActions::readInForm(const QString &fileName) +{ + QString fn = fileName; + + // First make sure that we don't have this one open already. + QDesignerFormWindowManagerInterface *formWindowManager = core()->formWindowManager(); + const int totalWindows = formWindowManager->formWindowCount(); + for (int i = 0; i < totalWindows; ++i) { + QDesignerFormWindowInterface *w = formWindowManager->formWindow(i); + if (w->fileName() == fn) { + w->raise(); + formWindowManager->setActiveFormWindow(w); + addRecentFile(fn); + return true; + } + } + + // Otherwise load it. + do { + QString errorMessage; + if (workbench()->openForm(fn, &errorMessage)) { + addRecentFile(fn); + m_openDirectory = QFileInfo(fn).absolutePath(); + return true; + } else { + // prompt to reload + QMessageBox box(QMessageBox::Warning, tr("Read error"), + tr("%1\nDo you want to update the file location or generate a new form?").arg(errorMessage), + QMessageBox::Cancel, core()->topLevel()); + + QPushButton *updateButton = box.addButton(tr("&Update"), QMessageBox::ActionRole); + QPushButton *newButton = box.addButton(tr("&New Form"), QMessageBox::ActionRole); + box.exec(); + if (box.clickedButton() == box.button(QMessageBox::Cancel)) + return false; + + if (box.clickedButton() == updateButton) { + const QString extension = uiExtension(); + fn = QFileDialog::getOpenFileName(core()->topLevel(), + tr("Open Form"), m_openDirectory, + fileDialogFilters(extension), nullptr); + + if (fn.isEmpty()) + return false; + } else if (box.clickedButton() == newButton) { + // If the file does not exist, but its directory, is valid, open the template with the editor file name set to it. + // (called from command line). + QString newFormFileName; + const QFileInfo fInfo(fn); + if (!fInfo.exists()) { + // Normalize file name + const QString directory = fInfo.absolutePath(); + if (QDir(directory).exists()) + newFormFileName = directory + u'/' + fInfo.fileName(); + } + showNewFormDialog(newFormFileName); + return false; + } + } + } while (true); + return true; +} + +bool QDesignerActions::writeOutForm(QDesignerFormWindowInterface *fw, const QString &saveFile, bool check) +{ + Q_ASSERT(fw && !saveFile.isEmpty()); + + if (check) { + const QStringList problems = fw->checkContents(); + if (!problems.isEmpty()) + QMessageBox::information(fw->window(), tr("Qt Widgets Designer"), problems.join("
"_L1)); + } + + m_workbench->updateBackup(fw); + + QSaveFile f(saveFile); + while (!f.open(QFile::WriteOnly)) { + QMessageBox box(QMessageBox::Warning, + tr("Save Form?"), + tr("Could not open file"), + QMessageBox::NoButton, fw); + + box.setWindowModality(Qt::WindowModal); + box.setInformativeText(tr("The file %1 could not be opened." + "\nReason: %2" + "\nWould you like to retry or select a different file?") + .arg(f.fileName(), f.errorString())); + QPushButton *retryButton = box.addButton(QMessageBox::Retry); + retryButton->setDefault(true); + QPushButton *switchButton = box.addButton(tr("Select New File"), QMessageBox::AcceptRole); + QPushButton *cancelButton = box.addButton(QMessageBox::Cancel); + box.exec(); + + if (box.clickedButton() == cancelButton) + return false; + if (box.clickedButton() == switchButton) { + QScopedPointer saveAsDialog(createSaveAsDialog(fw, QDir::currentPath(), uiExtension())); + if (saveAsDialog->exec() != QDialog::Accepted) + return false; + + const QString fileName = saveAsDialog->selectedFiles().constFirst(); + f.setFileName(fileName); + fw->setFileName(fileName); + } + // loop back around... + } + f.write(formWindowContents(fw)); + if (!f.commit()) { + QMessageBox box(QMessageBox::Warning, tr("Save Form"), + tr("Could not write file"), + QMessageBox::Cancel, fw); + box.setWindowModality(Qt::WindowModal); + box.setInformativeText(tr("It was not possible to write the file %1 to disk." + "\nReason: %2") + .arg(f.fileName(), f.errorString())); + box.exec(); + return false; + } + addRecentFile(saveFile); + m_saveDirectory = QFileInfo(f.fileName()).absolutePath(); + + fw->setDirty(false); + fw->parentWidget()->setWindowModified(false); + return true; +} + +void QDesignerActions::shutdown() +{ + // Follow the idea from the Mac, i.e. send the Application a close event + // and if it's accepted, quit. + QCloseEvent ev; + QApplication::sendEvent(qDesigner, &ev); + if (ev.isAccepted()) + qDesigner->quit(); +} + +void QDesignerActions::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) +{ + const bool enable = formWindow != nullptr; + m_saveFormAction->setEnabled(enable); + m_saveFormAsAction->setEnabled(enable); + m_saveAllFormsAction->setEnabled(enable); + m_saveFormAsTemplateAction->setEnabled(enable); + m_closeFormAction->setEnabled(enable); + m_savePreviewImageAction->setEnabled(enable); + m_printPreviewAction->setEnabled(enable); + + m_editWidgetsAction->setEnabled(enable); + + m_previewFormAction->setEnabled(enable); + m_viewCppCodeAction->setEnabled(enable); + m_viewPythonCodeAction->setEnabled(enable); + m_styleActions->setEnabled(enable); +} + +void QDesignerActions::formWindowSettingsChanged(QDesignerFormWindowInterface *fw) +{ + if (QDesignerFormWindow *window = m_workbench->findFormWindow(fw)) + window->updateChanged(); +} + +void QDesignerActions::updateRecentFileActions() +{ + QStringList files = m_settings.recentFilesList(); + auto existingEnd = std::remove_if(files.begin(), files.end(), + [] (const QString &f) { return !QFileInfo::exists(f); }); + if (existingEnd != files.end()) { + files.erase(existingEnd, files.end()); + m_settings.setRecentFilesList(files); + } + + const auto recentFilesActs = m_recentFilesActions->actions(); + qsizetype i = 0; + for (QAction *action : recentFilesActs) { + if (i < files.size()) { + const QString &file = files.at(i); + action->setText(QFileInfo(file).fileName()); + action->setIconText(file); + action->setVisible(true); + } else { + action->setVisible(false); + } + ++i; + } +} + +void QDesignerActions::openRecentForm() +{ + if (const QAction *action = qobject_cast(sender())) { + if (!readInForm(action->iconText())) + updateRecentFileActions(); // File doesn't exist, remove it from settings + } +} + +void QDesignerActions::clearRecentFiles() +{ + m_settings.setRecentFilesList(QStringList()); + updateRecentFileActions(); +} + +QActionGroup *QDesignerActions::recentFilesActions() const +{ + return m_recentFilesActions; +} + +void QDesignerActions::addRecentFile(const QString &fileName) +{ + QStringList files = m_settings.recentFilesList(); + files.removeAll(fileName); + files.prepend(fileName); + while (files.size() > MaxRecentFiles) + files.removeLast(); + + m_settings.setRecentFilesList(files); + updateRecentFileActions(); +} + +QAction *QDesignerActions::openFormAction() const +{ + return m_openFormAction; +} + +QAction *QDesignerActions::closeFormAction() const +{ + return m_closeFormAction; +} + +QAction *QDesignerActions::minimizeAction() const +{ + return m_minimizeAction; +} + +void QDesignerActions::showDesignerHelp() +{ + QString url = AssistantClient::designerManualUrl(); + url += "qtdesigner-manual.html"_L1; + showHelp(url); +} + +void QDesignerActions::helpRequested(const QString &manual, const QString &document) +{ + QString url = AssistantClient::documentUrl(manual); + url += document; + showHelp(url); +} + +void QDesignerActions::showHelp(const QString &url) +{ + QString errorMessage; + if (!m_assistantClient.showPage(url, &errorMessage)) + QMessageBox::warning(core()->topLevel(), tr("Assistant"), errorMessage); +} + +void QDesignerActions::aboutDesigner() +{ + VersionDialog mb(core()->topLevel()); + mb.setWindowTitle(tr("About Qt Widgets Designer")); + if (mb.exec()) { + QMessageBox messageBox(QMessageBox::Information, u"Easter Egg"_s, + u"Easter Egg"_s, QMessageBox::Ok, core()->topLevel()); + messageBox.setInformativeText(u"The Easter Egg has been removed."_s); + messageBox.exec(); + } +} + +QAction *QDesignerActions::editWidgets() const +{ + return m_editWidgetsAction; +} + +void QDesignerActions::showWidgetSpecificHelp() +{ + const QString helpId = core()->integration()->contextHelpId(); + + if (helpId.isEmpty()) { + showDesignerHelp(); + return; + } + + QString errorMessage; + const bool rc = m_assistantClient.activateIdentifier(helpId, &errorMessage); + if (!rc) + QMessageBox::warning(core()->topLevel(), tr("Assistant"), errorMessage); +} + +void QDesignerActions::updateCloseAction() +{ + if (m_previewManager->previewCount()) { + m_closeFormAction->setText(tr("&Close Preview")); + } else { + m_closeFormAction->setText(tr("&Close")); + } +} + +void QDesignerActions::backupForms() +{ + const int count = m_workbench->formWindowCount(); + if (!count || !ensureBackupDirectories()) + return; + + + QMap backupMap; + QDir backupDir(m_backupPath); + for (int i = 0; i < count; ++i) { + QDesignerFormWindow *fw = m_workbench->formWindow(i); + QDesignerFormWindowInterface *fwi = fw->editor(); + + QString formBackupName = m_backupPath + "/backup"_L1 + QString::number(i) + ".bak"_L1; + + QString fwn = QDir::toNativeSeparators(fwi->fileName()); + if (fwn.isEmpty()) + fwn = fw->windowTitle(); + + backupMap.insert(fwn, formBackupName); + + bool ok = false; + QSaveFile file(formBackupName); + if (file.open(QFile::WriteOnly)) { + file.write(formWindowContents(fw->editor(), backupDir)); + ok = file.commit(); + } + if (!ok) { + backupMap.remove(fwn); + qdesigner_internal::designerWarning(tr("The backup file %1 could not be written: %2"). + arg(QDir::toNativeSeparators(file.fileName()), + file.errorString())); + } + } + + if (!backupMap.isEmpty()) + m_settings.setBackup(backupMap); +} + +static QString fixResourceFileBackupPath(const QDesignerFormWindowInterface *fwi, + const QDir& backupDir) +{ + const QString content = fwi->contents(); + QDomDocument domDoc(u"backup"_s); + if(!domDoc.setContent(content)) + return content; + + const QDomNodeList list = domDoc.elementsByTagName(u"resources"_s); + if (list.isEmpty()) + return content; + + for (int i = 0; i < list.count(); i++) { + const QDomNode node = list.at(i); + if (!node.isNull()) { + const QDomElement element = node.toElement(); + if (!element.isNull() && element.tagName() == "resources"_L1) { + QDomNode childNode = element.firstChild(); + while (!childNode.isNull()) { + QDomElement childElement = childNode.toElement(); + if (!childElement.isNull() && childElement.tagName() == "include"_L1) { + const QString attr = childElement.attribute(u"location"_s); + const QString path = fwi->absoluteDir().absoluteFilePath(attr); + childElement.setAttribute(u"location"_s, backupDir.relativeFilePath(path)); + } + childNode = childNode.nextSibling(); + } + } + } + } + + + return domDoc.toString(); +} + +QRect QDesignerActions::fixDialogRect(const QRect &rect) const +{ + QRect frameGeometry; + const QRect availableGeometry = core()->topLevel()->screen()->geometry(); + + if (workbench()->mode() == DockedMode) { + frameGeometry = core()->topLevel()->frameGeometry(); + } else + frameGeometry = availableGeometry; + + QRect dlgRect = rect; + dlgRect.moveCenter(frameGeometry.center()); + + // make sure that parts of the dialog are not outside of screen + dlgRect.moveBottom(qMin(dlgRect.bottom(), availableGeometry.bottom())); + dlgRect.moveRight(qMin(dlgRect.right(), availableGeometry.right())); + dlgRect.moveLeft(qMax(dlgRect.left(), availableGeometry.left())); + dlgRect.moveTop(qMax(dlgRect.top(), availableGeometry.top())); + + return dlgRect; +} + +void QDesignerActions::showStatusBarMessage(const QString &message) const +{ + if (workbench()->mode() == DockedMode) { + QStatusBar *bar = qDesigner->mainWindow()->statusBar(); + if (bar && !bar->isHidden()) + bar->showMessage(message, 3000); + } +} + +void QDesignerActions::setBringAllToFrontVisible(bool visible) +{ + m_bringAllToFrontSeparator->setVisible(visible); + m_bringAllToFrontAction->setVisible(visible); +} + +void QDesignerActions::setWindowListSeparatorVisible(bool visible) +{ + m_windowListSeparatorAction->setVisible(visible); +} + +bool QDesignerActions::ensureBackupDirectories() { + + if (m_backupPath.isEmpty()) // create names + m_backupPath = qdesigner_internal::dataDirectory() + u"/backup"_s; + + // ensure directories + const QDir backupDir(m_backupPath); + + if (!backupDir.exists()) { + if (!backupDir.mkpath(m_backupPath)) { + qdesigner_internal::designerWarning(tr("The backup directory %1 could not be created.") + .arg(QDir::toNativeSeparators(m_backupPath))); + return false; + } + } + return true; +} + +void QDesignerActions::showPreferencesDialog() +{ + { + PreferencesDialog preferencesDialog(workbench()->core(), m_core->topLevel()); + preferencesDialog.exec(); + } // Make sure the preference dialog is destroyed before switching UI modes. + m_workbench->applyUiSettings(); +} + +void QDesignerActions::showAppFontDialog() +{ + if (!m_appFontDialog) // Might get deleted when switching ui modes + m_appFontDialog = new AppFontDialog(core()->topLevel()); + m_appFontDialog->show(); + m_appFontDialog->raise(); +} + +QPixmap QDesignerActions::createPreviewPixmap(QDesignerFormWindowInterface *fw) +{ + const QCursor oldCursor = core()->topLevel()->cursor(); + core()->topLevel()->setCursor(Qt::WaitCursor); + + QString errorMessage; + const QPixmap pixmap = m_previewManager->createPreviewPixmap(fw, QString(), &errorMessage); + core()->topLevel()->setCursor(oldCursor); + if (pixmap.isNull()) { + QMessageBox::warning(fw, tr("Preview failed"), errorMessage); + } + return pixmap; +} + +qdesigner_internal::PreviewConfiguration QDesignerActions::previewConfiguration() +{ + qdesigner_internal::PreviewConfiguration pc; + qdesigner_internal::QDesignerSharedSettings settings(core()); + if (settings.isCustomPreviewConfigurationEnabled()) + pc = settings.customPreviewConfiguration(); + return pc; +} + +void QDesignerActions::savePreviewImage() +{ + const char *format = "png"; + + QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow(); + if (!fw) + return; + + QImage image; + const QString extension = QString::fromLatin1(format); + const QString filter = tr("Image files (*.%1)").arg(extension); + + QString suggestion = fw->fileName(); + if (!suggestion.isEmpty()) + suggestion = QFileInfo(suggestion).baseName() + u'.' + extension; + + QFileDialog dialog(fw, tr("Save Image"), suggestion, filter); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDefaultSuffix(extension); + + do { + if (dialog.exec() != QDialog::Accepted) + break; + const QString fileName = dialog.selectedFiles().constFirst(); + + if (image.isNull()) { + const QPixmap pixmap = createPreviewPixmap(fw); + if (pixmap.isNull()) + break; + + image = pixmap.toImage(); + } + + if (image.save(fileName, format)) { + showStatusBarMessage(tr("Saved image %1.").arg(QFileInfo(fileName).fileName())); + break; + } + + QMessageBox box(QMessageBox::Warning, tr("Save Image"), + tr("The file %1 could not be written.").arg( fileName), + QMessageBox::Retry|QMessageBox::Cancel, fw); + if (box.exec() == QMessageBox::Cancel) + break; + } while (true); +} + +void QDesignerActions::formWindowCountChanged() +{ + const bool enabled = m_core->formWindowManager()->formWindowCount() == 0; + /* Disable the application font action if there are form windows open + * as the reordering of the fonts sets font properties to 'changed' + * and overloaded fonts are not updated. */ + static const QString disabledTip = tr("Please close all forms to enable the loading of additional fonts."); + m_appFontAction->setEnabled(enabled); + m_appFontAction->setStatusTip(enabled ? QString() : disabledTip); +} + +void QDesignerActions::printPreviewImage() +{ +#ifdef HAS_PRINTER + QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow(); + if (!fw) + return; + + if (!m_printer) + m_printer = new QPrinter(QPrinter::HighResolution); + + m_printer->setFullPage(false); + + // Grab the image to be able to a suggest suitable orientation + const QPixmap pixmap = createPreviewPixmap(fw); + if (pixmap.isNull()) + return; + + const QSizeF pixmapSize = pixmap.size(); + + m_printer->setPageOrientation(pixmapSize.width() > pixmapSize.height() ? + QPageLayout::Landscape : QPageLayout::Portrait); + + // Printer parameters + QPrintDialog dialog(m_printer, fw); + if (!dialog.exec()) + return; + + const QCursor oldCursor = core()->topLevel()->cursor(); + core()->topLevel()->setCursor(Qt::WaitCursor); + // Estimate of required scaling to make form look the same on screen and printer. + const double suggestedScaling = static_cast(m_printer->physicalDpiX()) / static_cast(fw->physicalDpiX()); + + QPainter painter(m_printer); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + + // Clamp to page + const QRectF page = painter.viewport(); + const double maxScaling = qMin(page.size().width() / pixmapSize.width(), page.size().height() / pixmapSize.height()); + const double scaling = qMin(suggestedScaling, maxScaling); + + const double xOffset = page.left() + qMax(0.0, (page.size().width() - scaling * pixmapSize.width()) / 2.0); + const double yOffset = page.top() + qMax(0.0, (page.size().height() - scaling * pixmapSize.height()) / 2.0); + + // Draw. + painter.translate(xOffset, yOffset); + painter.scale(scaling, scaling); + painter.drawPixmap(0, 0, pixmap); + core()->topLevel()->setCursor(oldCursor); + + showStatusBarMessage(tr("Printed %1.").arg(QFileInfo(fw->fileName()).fileName())); +#endif // HAS_PRINTER +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/qdesigner_actions.h b/src/designer/src/designer/qdesigner_actions.h new file mode 100644 index 0000000..e2efdfb --- /dev/null +++ b/src/designer/src/designer/qdesigner_actions.h @@ -0,0 +1,191 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_ACTIONS_H +#define QDESIGNER_ACTIONS_H + +#include "assistantclient.h" +#include "qdesigner_settings.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerWorkbench; + +class QTimer; +class QAction; +class QActionGroup; +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class AppFontDialog; + +class QRect; +class QWidget; +class QPixmap; +class QPrinter; +class QMenu; + +namespace qdesigner_internal { + class PreviewConfiguration; + class PreviewManager; + enum class UicLanguage; +} + +class QDesignerActions: public QObject +{ + Q_OBJECT +public: + explicit QDesignerActions(QDesignerWorkbench *mainWindow); + ~QDesignerActions() override; + + QDesignerWorkbench *workbench() const; + QDesignerFormEditorInterface *core() const; + + bool saveForm(QDesignerFormWindowInterface *fw); + bool readInForm(const QString &fileName); + bool writeOutForm(QDesignerFormWindowInterface *formWindow, const QString &fileName, bool check = true); + + QActionGroup *fileActions() const; + QActionGroup *recentFilesActions() const; + QActionGroup *editActions() const; + QActionGroup *formActions() const; + QActionGroup *settingsActions() const; + QActionGroup *windowActions() const; + QActionGroup *toolActions() const; + QActionGroup *helpActions() const; + QActionGroup *uiMode() const; + QActionGroup *styleActions() const; + // file actions + QAction *openFormAction() const; + QAction *closeFormAction() const; + // window actions + QAction *minimizeAction() const; + // edit mode actions + QAction *editWidgets() const; + // form actions + QAction *previewFormAction() const; + QAction *viewCodeAction() const; + + void setBringAllToFrontVisible(bool visible); + void setWindowListSeparatorVisible(bool visible); + + bool openForm(QWidget *parent); + + QString uiExtension() const; + + // Boolean dynamic property set on actions to + // show them in the default toolbar layout + static const char *defaultToolbarPropertyName; + +public slots: + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + void createForm(); + void slotOpenForm(); + void helpRequested(const QString &manual, const QString &document); + +signals: + void useBigIcons(bool); + +private slots: + void saveForm(); + void saveFormAs(); + void saveAllForms(); + void saveFormAsTemplate(); + void notImplementedYet(); + void shutdown(); + void editWidgetsSlot(); + void openRecentForm(); + void clearRecentFiles(); + void closeForm(); + void showDesignerHelp(); + void aboutDesigner(); + void showWidgetSpecificHelp(); + void backupForms(); + void showNewFormDialog(const QString &fileName); + void showPreferencesDialog(); + void showAppFontDialog(); + void savePreviewImage(); + void printPreviewImage(); + void updateCloseAction(); + void formWindowCountChanged(); + void formWindowSettingsChanged(QDesignerFormWindowInterface *fw); + +private: + QAction *createRecentFilesMenu(); + bool saveFormAs(QDesignerFormWindowInterface *fw); + void updateRecentFileActions(); + void addRecentFile(const QString &fileName); + void showHelp(const QString &help); + void closePreview(); + QRect fixDialogRect(const QRect &rect) const; + void showStatusBarMessage(const QString &message) const; + QActionGroup *createHelpActions(); + bool ensureBackupDirectories(); + QPixmap createPreviewPixmap(QDesignerFormWindowInterface *fw); + qdesigner_internal::PreviewConfiguration previewConfiguration(); + void viewCode(qdesigner_internal::UicLanguage language); + + enum { MaxRecentFiles = 10 }; + QDesignerWorkbench *m_workbench; + QDesignerFormEditorInterface *m_core; + QDesignerSettings m_settings; + AssistantClient m_assistantClient; + QString m_openDirectory; + QString m_saveDirectory; + + + QString m_backupPath; + QString m_backupTmpPath; + + QTimer* m_backupTimer; + + QActionGroup *m_fileActions; + QActionGroup *m_recentFilesActions; + QActionGroup *m_editActions; + QActionGroup *m_formActions; + QActionGroup *m_settingsActions; + QActionGroup *m_windowActions; + QActionGroup *m_toolActions; + QActionGroup *m_helpActions = nullptr; + QActionGroup *m_styleActions = nullptr; + + QAction *m_editWidgetsAction; + + QAction *m_newFormAction; + QAction *m_openFormAction; + QAction *m_saveFormAction; + QAction *m_saveFormAsAction; + QAction *m_saveAllFormsAction; + QAction *m_saveFormAsTemplateAction; + QAction *m_closeFormAction; + QAction *m_savePreviewImageAction; + QAction *m_printPreviewAction; + + QAction *m_quitAction; + + QAction *m_previewFormAction = nullptr; + QAction *m_viewCppCodeAction; + QAction *m_viewPythonCodeAction; + + QAction *m_minimizeAction; + QAction *m_bringAllToFrontSeparator; + QAction *m_bringAllToFrontAction; + QAction *m_windowListSeparatorAction; + + QAction *m_preferencesAction; + QAction *m_appFontAction; + + QPointer m_appFontDialog; + + QPrinter *m_printer = nullptr; + + qdesigner_internal::PreviewManager *m_previewManager = nullptr; + + std::unique_ptr m_recentMenu; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_ACTIONS_H diff --git a/src/designer/src/designer/qdesigner_appearanceoptions.cpp b/src/designer/src/designer/qdesigner_appearanceoptions.cpp new file mode 100644 index 0000000..a4e0621 --- /dev/null +++ b/src/designer/src/designer/qdesigner_appearanceoptions.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_appearanceoptions.h" +#include "ui_qdesigner_appearanceoptions.h" + +#include "qdesigner_settings.h" +#include "qdesigner_toolwindow.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// ---------------- AppearanceOptions +void AppearanceOptions::toSettings(QDesignerSettings &settings) const +{ + settings.setUiMode(uiMode); + settings.setToolWindowFont(toolWindowFontSettings); +} + +void AppearanceOptions::fromSettings(const QDesignerSettings &settings) +{ + uiMode = settings.uiMode(); + toolWindowFontSettings = settings.toolWindowFont(); +} + +// ---------------- QDesignerAppearanceOptionsWidget +QDesignerAppearanceOptionsWidget::QDesignerAppearanceOptionsWidget(QWidget *parent) : + QWidget(parent), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::AppearanceOptionsWidget) +{ + m_ui->setupUi(this); + + m_ui->m_uiModeCombo->addItem(tr("Docked Window"), QVariant(DockedMode)); + m_ui->m_uiModeCombo->addItem(tr("Multiple Top-Level Windows"), QVariant(TopLevelMode)); + connect(m_ui->m_uiModeCombo, &QComboBox::currentIndexChanged, + this, &QDesignerAppearanceOptionsWidget::slotUiModeComboChanged); + + m_ui->m_fontPanel->setCheckable(true); + m_ui->m_fontPanel->setTitle(tr("Toolwindow Font")); + +} + +QDesignerAppearanceOptionsWidget::~QDesignerAppearanceOptionsWidget() +{ + delete m_ui; +} + +UIMode QDesignerAppearanceOptionsWidget::uiMode() const +{ + return static_cast(m_ui->m_uiModeCombo->itemData(m_ui->m_uiModeCombo->currentIndex()).toInt()); +} + +AppearanceOptions QDesignerAppearanceOptionsWidget::appearanceOptions() const +{ + AppearanceOptions rc; + rc.uiMode = uiMode(); + rc.toolWindowFontSettings.m_font = m_ui->m_fontPanel->selectedFont(); + rc.toolWindowFontSettings.m_useFont = m_ui->m_fontPanel->isChecked(); + rc.toolWindowFontSettings.m_writingSystem = m_ui->m_fontPanel->writingSystem(); + return rc; +} + +void QDesignerAppearanceOptionsWidget::setAppearanceOptions(const AppearanceOptions &ao) +{ + m_initialUIMode = ao.uiMode; + m_ui->m_uiModeCombo->setCurrentIndex(m_ui->m_uiModeCombo->findData(QVariant(ao.uiMode))); + m_ui->m_fontPanel->setWritingSystem(ao.toolWindowFontSettings.m_writingSystem); + m_ui->m_fontPanel->setSelectedFont(ao.toolWindowFontSettings.m_font); + m_ui->m_fontPanel->setChecked(ao.toolWindowFontSettings.m_useFont); +} + +void QDesignerAppearanceOptionsWidget::slotUiModeComboChanged() +{ + emit uiModeChanged(m_initialUIMode != uiMode()); +} + +// ----------- QDesignerAppearanceOptionsPage +QDesignerAppearanceOptionsPage::QDesignerAppearanceOptionsPage(QDesignerFormEditorInterface *core) : + m_core(core) +{ +} + +QString QDesignerAppearanceOptionsPage::name() const +{ + //: Tab in preferences dialog + return QCoreApplication::translate("QDesignerAppearanceOptionsPage", "Appearance"); +} + +QWidget *QDesignerAppearanceOptionsPage::createPage(QWidget *parent) +{ + m_widget = new QDesignerAppearanceOptionsWidget(parent); + m_initialOptions.fromSettings(QDesignerSettings(m_core)); + m_widget->setAppearanceOptions(m_initialOptions); + return m_widget; +} + +void QDesignerAppearanceOptionsPage::apply() +{ + if (m_widget) { + const AppearanceOptions newOptions = m_widget->appearanceOptions(); + if (newOptions != m_initialOptions) { + QDesignerSettings settings(m_core); + newOptions.toSettings(settings); + m_initialOptions = newOptions; + emit settingsChanged(); + } + } +} + +void QDesignerAppearanceOptionsPage::finish() +{ +} + +QT_END_NAMESPACE + diff --git a/src/designer/src/designer/qdesigner_appearanceoptions.h b/src/designer/src/designer/qdesigner_appearanceoptions.h new file mode 100644 index 0000000..1b37750 --- /dev/null +++ b/src/designer/src/designer/qdesigner_appearanceoptions.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_APPEARANCEOPTIONS_H +#define QDESIGNER_APPEARANCEOPTIONS_H + +#include "designer_enums.h" +#include "qdesigner_toolwindow.h" + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerSettings; + +namespace Ui { + class AppearanceOptionsWidget; +} + +/* AppearanceOptions data */ +struct AppearanceOptions +{ + void toSettings(QDesignerSettings &) const; + void fromSettings(const QDesignerSettings &); + + UIMode uiMode{DockedMode}; + ToolWindowFontSettings toolWindowFontSettings; + + friend bool comparesEqual(const AppearanceOptions &lhs, + const AppearanceOptions &rhs) noexcept + { + return lhs.uiMode == rhs.uiMode + && lhs.toolWindowFontSettings == rhs.toolWindowFontSettings; + } + Q_DECLARE_EQUALITY_COMPARABLE(AppearanceOptions) +}; + +/* QDesignerAppearanceOptionsWidget: Let the user edit AppearanceOptions */ +class QDesignerAppearanceOptionsWidget : public QWidget +{ + Q_OBJECT +public: + explicit QDesignerAppearanceOptionsWidget(QWidget *parent = nullptr); + ~QDesignerAppearanceOptionsWidget(); + + AppearanceOptions appearanceOptions() const; + void setAppearanceOptions(const AppearanceOptions &ao); + +signals: + void uiModeChanged(bool modified); + +private slots: + void slotUiModeComboChanged(); + +private: + UIMode uiMode() const; + + Ui::AppearanceOptionsWidget *m_ui; + UIMode m_initialUIMode = NeutralMode; +}; + +/* The options page for appearance options. */ + +class QDesignerAppearanceOptionsPage : public QObject, public QDesignerOptionsPageInterface +{ + Q_OBJECT + +public: + QDesignerAppearanceOptionsPage(QDesignerFormEditorInterface *core); + + QString name() const override; + QWidget *createPage(QWidget *parent) override; + void apply() override; + void finish() override; + +signals: + void settingsChanged(); + +private: + QDesignerFormEditorInterface *m_core; + QPointer m_widget; + AppearanceOptions m_initialOptions; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_APPEARANCEOPTIONS_H diff --git a/src/designer/src/designer/qdesigner_appearanceoptions.ui b/src/designer/src/designer/qdesigner_appearanceoptions.ui new file mode 100644 index 0000000..6db8b88 --- /dev/null +++ b/src/designer/src/designer/qdesigner_appearanceoptions.ui @@ -0,0 +1,57 @@ + + + AppearanceOptionsWidget + + + + 0 + 0 + 325 + 360 + + + + Form + + + + + + User Interface Mode + + + + + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + FontPanel + QGroupBox +
fontpanel_p.h
+ 1 +
+
+ + +
diff --git a/src/designer/src/designer/qdesigner_formwindow.cpp b/src/designer/src/designer/qdesigner_formwindow.cpp new file mode 100644 index 0000000..44db4df --- /dev/null +++ b/src/designer/src/designer/qdesigner_formwindow.cpp @@ -0,0 +1,255 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_formwindow.h" +#include "qdesigner_workbench.h" +#include "formwindowbase_p.h" + +// sdk +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QDesignerFormWindow::QDesignerFormWindow(QDesignerFormWindowInterface *editor, QDesignerWorkbench *workbench, QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags), + m_editor(editor), + m_workbench(workbench), + m_action(new QAction(this)) +{ + Q_ASSERT(workbench); + + setMaximumSize(0xFFF, 0xFFF); + QDesignerFormEditorInterface *core = workbench->core(); + + if (m_editor) { + m_editor->setParent(this); + } else { + m_editor = core->formWindowManager()->createFormWindow(this); + } + + QVBoxLayout *l = new QVBoxLayout(this); + l->setContentsMargins(QMargins()); + l->addWidget(m_editor); + + m_action->setCheckable(true); + + connect(m_editor->commandHistory(), &QUndoStack::indexChanged, this, &QDesignerFormWindow::updateChanged); + connect(m_editor.data(), &QDesignerFormWindowInterface::geometryChanged, + this, &QDesignerFormWindow::slotGeometryChanged); +} + +QDesignerFormWindow::~QDesignerFormWindow() +{ + if (workbench()) + workbench()->removeFormWindow(this); +} + +QAction *QDesignerFormWindow::action() const +{ + return m_action; +} + +void QDesignerFormWindow::changeEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::WindowTitleChange: + m_action->setText(windowTitle().remove("[*]"_L1)); + break; + case QEvent::WindowIconChange: + m_action->setIcon(windowIcon()); + break; + case QEvent::WindowStateChange: { + const QWindowStateChangeEvent *wsce = static_cast(e); + const bool wasMinimized = Qt::WindowMinimized & wsce->oldState(); + const bool isMinimizedNow = isMinimized(); + if (wasMinimized != isMinimizedNow ) + emit minimizationStateChanged(m_editor, isMinimizedNow); + } + break; + default: + break; + } + QWidget::changeEvent(e); +} + +QRect QDesignerFormWindow::geometryHint() const +{ + const QPoint point(0, 0); + // If we have a container, we want to be just as big. + // QMdiSubWindow attempts to resize its children to sizeHint() when switching user interface modes. + if (QWidget *mainContainer = m_editor->mainContainer()) + return QRect(point, mainContainer->size()); + + return QRect(point, sizeHint()); +} + +QDesignerFormWindowInterface *QDesignerFormWindow::editor() const +{ + return m_editor; +} + +QDesignerWorkbench *QDesignerFormWindow::workbench() const +{ + return m_workbench; +} + +void QDesignerFormWindow::firstShow() +{ + // Set up handling of file name changes and set initial title. + if (!m_windowTitleInitialized) { + m_windowTitleInitialized = true; + if (m_editor) { + connect(m_editor.data(), &QDesignerFormWindowInterface::fileNameChanged, + this, &QDesignerFormWindow::updateWindowTitle); + updateWindowTitle(m_editor->fileName()); + updateChanged(); + } + } + show(); +} + +int QDesignerFormWindow::getNumberOfUntitledWindows() const +{ + const int totalWindows = m_workbench->formWindowCount(); + if (!totalWindows) + return 0; + + int maxUntitled = 0; + // Find the number of untitled windows excluding ourselves. + // Do not fall for 'untitled.ui', match with modified place holder. + // This will cause some problems with i18n, but for now I need the string to be "static" + static const QRegularExpression rx(u"untitled( (\\d+))?\\[\\*\\]$"_s); + Q_ASSERT(rx.isValid()); + for (int i = 0; i < totalWindows; ++i) { + QDesignerFormWindow *fw = m_workbench->formWindow(i); + if (fw != this) { + const QString title = m_workbench->formWindow(i)->windowTitle(); + const QRegularExpressionMatch match = rx.match(title); + if (match.hasMatch()) { + if (maxUntitled == 0) + ++maxUntitled; + if (match.lastCapturedIndex() >= 2) { + const auto numberCapture = match.capturedView(2); + if (!numberCapture.isEmpty()) + maxUntitled = qMax(numberCapture.toInt(), maxUntitled); + } + } + } + } + return maxUntitled; +} + +void QDesignerFormWindow::updateWindowTitle(const QString &fileName) +{ + if (!m_windowTitleInitialized) { + m_windowTitleInitialized = true; + if (m_editor) + connect(m_editor.data(), &QDesignerFormWindowInterface::fileNameChanged, + this, &QDesignerFormWindow::updateWindowTitle); + } + + QString fileNameTitle; + if (fileName.isEmpty()) { + fileNameTitle += "untitled"_L1; + if (const int maxUntitled = getNumberOfUntitledWindows()) { + fileNameTitle += u' ' + QString::number(maxUntitled + 1); + } + } else { + fileNameTitle = QFileInfo(fileName).fileName(); + } + + if (const QWidget *mc = m_editor->mainContainer()) { + setWindowIcon(mc->windowIcon()); + setWindowTitle(tr("%1 - %2[*]").arg(mc->windowTitle(), fileNameTitle)); + } else { + setWindowTitle(fileNameTitle); + } +} + +void QDesignerFormWindow::closeEvent(QCloseEvent *ev) +{ + if (m_editor->isDirty()) { + raise(); + QMessageBox box(QMessageBox::Information, tr("Save Form?"), + tr("Do you want to save the changes to this document before closing?"), + QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save, m_editor); + box.setInformativeText(tr("If you don't save, your changes will be lost.")); + box.setWindowModality(Qt::WindowModal); + static_cast(box.button(QMessageBox::Save))->setDefault(true); + + switch (box.exec()) { + case QMessageBox::Save: { + bool ok = workbench()->saveForm(m_editor); + ev->setAccepted(ok); + m_editor->setDirty(!ok); + break; + } + case QMessageBox::Discard: + m_editor->setDirty(false); // Not really necessary, but stops problems if we get close again. + ev->accept(); + break; + case QMessageBox::Cancel: + ev->ignore(); + break; + } + } +} + +void QDesignerFormWindow::updateChanged() +{ + // Sometimes called after form window destruction. + if (m_editor) { + setWindowModified(m_editor->isDirty()); + updateWindowTitle(m_editor->fileName()); + } +} + +void QDesignerFormWindow::resizeEvent(QResizeEvent *rev) +{ + if(m_initialized) { + m_editor->setDirty(true); + setWindowModified(true); + } + + m_initialized = true; + QWidget::resizeEvent(rev); +} + +void QDesignerFormWindow::slotGeometryChanged() +{ + // If the form window changes, re-update the geometry of the current widget in the property editor. + // Note that in the case of layouts, non-maincontainer widgets must also be updated, + // so, do not do it for the main container only + const QDesignerFormEditorInterface *core = m_editor->core(); + QObject *object = core->propertyEditor()->object(); + if (object == nullptr || !object->isWidgetType()) + return; + static const QString geometryProperty = u"geometry"_s; + const QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), object); + const int geometryIndex = sheet->indexOf(geometryProperty); + if (geometryIndex == -1) + return; + core->propertyEditor()->setPropertyValue(geometryProperty, sheet->property(geometryIndex)); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/qdesigner_formwindow.h b/src/designer/src/designer/qdesigner_formwindow.h new file mode 100644 index 0000000..6e509fc --- /dev/null +++ b/src/designer/src/designer/qdesigner_formwindow.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_FORMWINDOW_H +#define QDESIGNER_FORMWINDOW_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerWorkbench; +class QDesignerFormWindowInterface; + +class QDesignerFormWindow: public QWidget +{ + Q_OBJECT +public: + QDesignerFormWindow(QDesignerFormWindowInterface *formWindow, QDesignerWorkbench *workbench, + QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + + void firstShow(); + + ~QDesignerFormWindow() override; + + QAction *action() const; + QDesignerWorkbench *workbench() const; + QDesignerFormWindowInterface *editor() const; + + QRect geometryHint() const; + +public slots: + void updateChanged(); + +private slots: + void updateWindowTitle(const QString &fileName); + void slotGeometryChanged(); + +signals: + void minimizationStateChanged(QDesignerFormWindowInterface *formWindow, bool minimized); + void triggerAction(); + +protected: + void changeEvent(QEvent *e) override; + void closeEvent(QCloseEvent *ev) override; + void resizeEvent(QResizeEvent* rev) override; + +private: + int getNumberOfUntitledWindows() const; + QPointer m_editor; + QPointer m_workbench; + QAction *m_action; + bool m_initialized = false; + bool m_windowTitleInitialized = false; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_FORMWINDOW_H diff --git a/src/designer/src/designer/qdesigner_pch.h b/src/designer/src/designer/qdesigner_pch.h new file mode 100644 index 0000000..da4e03d --- /dev/null +++ b/src/designer/src/designer/qdesigner_pch.h @@ -0,0 +1,21 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#if defined __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qdesigner.h" +#include "qdesigner_formwindow.h" +#include "qdesigner_settings.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_workbench.h" +#endif diff --git a/src/designer/src/designer/qdesigner_server.cpp b/src/designer/src/designer/qdesigner_server.cpp new file mode 100644 index 0000000..5fd5c63 --- /dev/null +++ b/src/designer/src/designer/qdesigner_server.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include + +#include +#include +#include + +#include "qdesigner.h" +#include "qdesigner_server.h" + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// ### review + +QDesignerServer::QDesignerServer(QObject *parent) + : QObject(parent) +{ + m_server = new QTcpServer(this); + if (m_server->listen(QHostAddress::LocalHost, 0)) { + connect(m_server, &QTcpServer::newConnection, + this, &QDesignerServer::handleNewConnection); + } +} + +QDesignerServer::~QDesignerServer() = default; + +quint16 QDesignerServer::serverPort() const +{ + return m_server ? m_server->serverPort() : 0; +} + +void QDesignerServer::sendOpenRequest(int port, const QStringList &files) +{ + QTcpSocket *sSocket = new QTcpSocket(); + sSocket->connectToHost(QHostAddress::LocalHost, port); + if(sSocket->waitForConnected(3000)) + { + for (const QString &file : files) { + QFileInfo fi(file); + sSocket->write(fi.absoluteFilePath().toUtf8() + '\n'); + } + sSocket->waitForBytesWritten(3000); + sSocket->close(); + } + delete sSocket; +} + +void QDesignerServer::readFromClient() +{ + while (m_socket->canReadLine()) { + QString file = QString::fromUtf8(m_socket->readLine()); + if (!file.isNull()) { + file.remove(u'\n'); + file.remove(u'\r'); + qDesigner->postEvent(qDesigner, new QFileOpenEvent(file)); + } + } +} + +void QDesignerServer::socketClosed() +{ + m_socket = nullptr; +} + +void QDesignerServer::handleNewConnection() +{ + // no need for more than one connection + if (m_socket == nullptr) { + m_socket = m_server->nextPendingConnection(); + connect(m_socket, &QTcpSocket::readyRead, + this, &QDesignerServer::readFromClient); + connect(m_socket, &QTcpSocket::disconnected, + this, &QDesignerServer::socketClosed); + } +} + + +QDesignerClient::QDesignerClient(quint16 port, QObject *parent) +: QObject(parent) +{ + m_socket = new QTcpSocket(this); + m_socket->connectToHost(QHostAddress::LocalHost, port); + connect(m_socket, &QTcpSocket::readyRead, + this, &QDesignerClient::readFromSocket); + +} + +QDesignerClient::~QDesignerClient() +{ + m_socket->close(); + m_socket->flush(); +} + +void QDesignerClient::readFromSocket() +{ + while (m_socket->canReadLine()) { + QString file = QString::fromUtf8(m_socket->readLine()); + if (!file.isNull()) { + file.remove(u'\n'); + file.remove(u'\r'); + if (QFile::exists(file)) + qDesigner->postEvent(qDesigner, new QFileOpenEvent(file)); + } + } +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/qdesigner_server.h b/src/designer/src/designer/qdesigner_server.h new file mode 100644 index 0000000..d453455 --- /dev/null +++ b/src/designer/src/designer/qdesigner_server.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_SERVER_H +#define QDESIGNER_SERVER_H + +#include + +QT_BEGIN_NAMESPACE + +class QTcpServer; +class QTcpSocket; + +class QDesignerServer: public QObject +{ + Q_OBJECT +public: + explicit QDesignerServer(QObject *parent = nullptr); + ~QDesignerServer() override; + + quint16 serverPort() const; + + static void sendOpenRequest(int port, const QStringList &files); + +private slots: + void handleNewConnection(); + void readFromClient(); + void socketClosed(); + +private: + QTcpServer *m_server; + QTcpSocket *m_socket = nullptr; +}; + +class QDesignerClient: public QObject +{ + Q_OBJECT +public: + explicit QDesignerClient(quint16 port, QObject *parent = nullptr); + ~QDesignerClient() override; + +private slots: + void readFromSocket(); + +private: + QTcpSocket *m_socket; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_SERVER_H diff --git a/src/designer/src/designer/qdesigner_settings.cpp b/src/designer/src/designer/qdesigner_settings.cpp new file mode 100644 index 0000000..87c497e --- /dev/null +++ b/src/designer/src/designer/qdesigner_settings.cpp @@ -0,0 +1,210 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner.h" +#include "qdesigner_settings.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_workbench.h" + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +enum { debugSettings = 0 }; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto newFormShowKey = "newFormDialog/ShowOnStartup"_L1; + +// Change the version whenever the arrangement changes significantly. +static constexpr auto mainWindowStateKey = "MainWindowState45"_L1; +static constexpr auto toolBarsStateKey = "ToolBarsState45"_L1; + +static constexpr auto backupOrgListKey = "backup/fileListOrg"_L1; +static constexpr auto backupBakListKey = "backup/fileListBak"_L1; +static constexpr auto recentFilesListKey = "recentFilesList"_L1; + +QDesignerSettings::QDesignerSettings(QDesignerFormEditorInterface *core) : + qdesigner_internal::QDesignerSharedSettings(core) +{ +} + +void QDesignerSettings::setValue(const QString &key, const QVariant &value) +{ + settings()->setValue(key, value); +} + +QVariant QDesignerSettings::value(const QString &key, const QVariant &defaultValue) const +{ + return settings()->value(key, defaultValue); +} + +static inline QChar modeChar(UIMode mode) +{ + return QLatin1Char(static_cast(mode) + '0'); +} + +void QDesignerSettings::saveGeometryFor(const QWidget *w) +{ + Q_ASSERT(w && !w->objectName().isEmpty()); + QDesignerSettingsInterface *s = settings(); + const bool visible = w->isVisible(); + if (debugSettings) + qDebug() << Q_FUNC_INFO << w << "visible=" << visible; + s->beginGroup(w->objectName()); + s->setValue(u"visible"_s, visible); + s->setValue(u"geometry"_s, w->saveGeometry()); + s->endGroup(); +} + +void QDesignerSettings::restoreGeometry(QWidget *w, QRect fallBack) const +{ + Q_ASSERT(w && !w->objectName().isEmpty()); + const QString key = w->objectName(); + const QByteArray ba(settings()->value(key + "/geometry"_L1).toByteArray()); + const bool visible = settings()->value(key + "/visible"_L1, true).toBool(); + + if (debugSettings) + qDebug() << Q_FUNC_INFO << w << fallBack << "visible=" << visible; + if (ba.isEmpty()) { + /// Apply default geometry, check for null and maximal size + if (fallBack.isNull()) + fallBack = QRect(QPoint(0, 0), w->sizeHint()); + if (fallBack.size() == QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) { + w->setWindowState(w->windowState() | Qt::WindowMaximized); + } else { + w->move(fallBack.topLeft()); + w->resize(fallBack.size()); + } + } else { + w->restoreGeometry(ba); + } + + if (visible) + w->show(); +} + +QStringList QDesignerSettings::recentFilesList() const +{ + return settings()->value(recentFilesListKey).toStringList(); +} + +void QDesignerSettings::setRecentFilesList(const QStringList &sl) +{ + settings()->setValue(recentFilesListKey, sl); +} + +void QDesignerSettings::setShowNewFormOnStartup(bool showIt) +{ + settings()->setValue(newFormShowKey, showIt); +} + +bool QDesignerSettings::showNewFormOnStartup() const +{ + return settings()->value(newFormShowKey, true).toBool(); +} + +QByteArray QDesignerSettings::mainWindowState(UIMode mode) const +{ + return settings()->value(mainWindowStateKey + modeChar(mode)).toByteArray(); +} + +void QDesignerSettings::setMainWindowState(UIMode mode, const QByteArray &mainWindowState) +{ + settings()->setValue(mainWindowStateKey + modeChar(mode), mainWindowState); +} + +QByteArray QDesignerSettings::toolBarsState(UIMode mode) const +{ + QString key = toolBarsStateKey; + key += modeChar(mode); + return settings()->value(key).toByteArray(); +} + +void QDesignerSettings::setToolBarsState(UIMode mode, const QByteArray &toolBarsState) +{ + QString key = toolBarsStateKey; + key += modeChar(mode); + settings()->setValue(key, toolBarsState); +} + +void QDesignerSettings::clearBackup() +{ + QDesignerSettingsInterface *s = settings(); + s->remove(backupOrgListKey); + s->remove(backupBakListKey); +} + +void QDesignerSettings::setBackup(const QMap &map) +{ + const QStringList org = map.keys(); + const QStringList bak = map.values(); + + QDesignerSettingsInterface *s = settings(); + s->setValue(backupOrgListKey, org); + s->setValue(backupBakListKey, bak); +} + +QMap QDesignerSettings::backup() const +{ + const QStringList org = settings()->value(backupOrgListKey, QStringList()).toStringList(); + const QStringList bak = settings()->value(backupBakListKey, QStringList()).toStringList(); + + QMap map; + const qsizetype orgCount = org.size(); + for (qsizetype i = 0; i < orgCount; ++i) + map.insert(org.at(i), bak.at(i)); + + return map; +} + +void QDesignerSettings::setUiMode(UIMode mode) +{ + QDesignerSettingsInterface *s = settings(); + s->beginGroup(u"UI"_s); + s->setValue(u"currentMode"_s, mode); + s->endGroup(); +} + +UIMode QDesignerSettings::uiMode() const +{ + constexpr UIMode defaultMode = DockedMode; + UIMode uiMode = static_cast(value(u"UI/currentMode"_s, defaultMode).toInt()); + return uiMode; +} + +void QDesignerSettings::setToolWindowFont(const ToolWindowFontSettings &fontSettings) +{ + QDesignerSettingsInterface *s = settings(); + s->beginGroup(u"UI"_s); + s->setValue(u"font"_s, fontSettings.m_font); + s->setValue(u"useFont"_s, fontSettings.m_useFont); + s->setValue(u"writingSystem"_s, fontSettings.m_writingSystem); + s->endGroup(); +} + +ToolWindowFontSettings QDesignerSettings::toolWindowFont() const +{ + ToolWindowFontSettings fontSettings; + fontSettings.m_writingSystem = + static_cast(value(u"UI/writingSystem"_s, + QFontDatabase::Any).toInt()); + fontSettings.m_font = qvariant_cast(value(u"UI/font"_s)); + fontSettings.m_useFont = + settings()->value(u"UI/useFont"_s, QVariant(false)).toBool(); + return fontSettings; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/qdesigner_settings.h b/src/designer/src/designer/qdesigner_settings.h new file mode 100644 index 0000000..72895a5 --- /dev/null +++ b/src/designer/src/designer/qdesigner_settings.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_SETTINGS_H +#define QDESIGNER_SETTINGS_H + +#include "designer_enums.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerSettingsInterface; +struct ToolWindowFontSettings; + +class QDesignerSettings : public qdesigner_internal::QDesignerSharedSettings +{ +public: + QDesignerSettings(QDesignerFormEditorInterface *core); + + void setValue(const QString &key, const QVariant &value); + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + + void restoreGeometry(QWidget *w, QRect fallBack = QRect()) const; + void saveGeometryFor(const QWidget *w); + + QStringList recentFilesList() const; + void setRecentFilesList(const QStringList &list); + + void setShowNewFormOnStartup(bool showIt); + bool showNewFormOnStartup() const; + + void setUiMode(UIMode mode); + UIMode uiMode() const; + + void setToolWindowFont(const ToolWindowFontSettings &fontSettings); + ToolWindowFontSettings toolWindowFont() const; + + QByteArray mainWindowState(UIMode mode) const; + void setMainWindowState(UIMode mode, const QByteArray &mainWindowState); + + QByteArray toolBarsState(UIMode mode) const; + void setToolBarsState(UIMode mode, const QByteArray &mainWindowState); + + void clearBackup(); + void setBackup(const QMap &map); + QMap backup() const; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_SETTINGS_H diff --git a/src/designer/src/designer/qdesigner_toolwindow.cpp b/src/designer/src/designer/qdesigner_toolwindow.cpp new file mode 100644 index 0000000..3583a55 --- /dev/null +++ b/src/designer/src/designer/qdesigner_toolwindow.cpp @@ -0,0 +1,364 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_settings.h" +#include "qdesigner_workbench.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static constexpr bool debugToolWindow = false; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr int margin = 20; + +// ---------------- QDesignerToolWindow +QDesignerToolWindow::QDesignerToolWindow(QDesignerWorkbench *workbench, + QWidget *w, + const QString &objectName, + const QString &title, + const QString &actionObjectName, + Qt::DockWidgetArea dockAreaHint, + QWidget *parent, + Qt::WindowFlags flags) : + MainWindowBase(parent, flags), + m_dockAreaHint(dockAreaHint), + m_workbench(workbench), + m_action(new QAction(this)) +{ + setObjectName(objectName); + setCentralWidget(w); + + setWindowTitle(title); + + m_action->setObjectName(actionObjectName); + m_action->setShortcutContext(Qt::ApplicationShortcut); + m_action->setText(title); + m_action->setCheckable(true); + connect(m_action, &QAction::triggered, this, &QDesignerToolWindow::showMe); +} + +void QDesignerToolWindow::showMe(bool v) +{ + // Access the QMdiSubWindow in MDI mode. + if (QWidget *target = m_workbench->mode() == DockedMode ? parentWidget() : this) { + if (v) + target->setWindowState(target->windowState() & ~Qt::WindowMinimized); + target->setVisible(v); + } +} + +void QDesignerToolWindow::showEvent(QShowEvent *e) +{ + Q_UNUSED(e); + + bool blocked = m_action->blockSignals(true); + m_action->setChecked(true); + m_action->blockSignals(blocked); +} + +void QDesignerToolWindow::hideEvent(QHideEvent *e) +{ + Q_UNUSED(e); + + bool blocked = m_action->blockSignals(true); + m_action->setChecked(false); + m_action->blockSignals(blocked); +} + +QAction *QDesignerToolWindow::action() const +{ + return m_action; +} + +void QDesignerToolWindow::changeEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::WindowTitleChange: + m_action->setText(windowTitle()); + break; + case QEvent::WindowIconChange: + m_action->setIcon(windowIcon()); + break; + default: + break; + } + QMainWindow::changeEvent(e); +} + +QDesignerWorkbench *QDesignerToolWindow::workbench() const +{ + return m_workbench; +} + +// ---------------------- PropertyEditorToolWindow + +static inline QWidget *createPropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr) +{ + QDesignerPropertyEditorInterface *widget = QDesignerComponents::createPropertyEditor(core, parent); + core->setPropertyEditor(widget); + return widget; +} + +class PropertyEditorToolWindow : public QDesignerToolWindow +{ +public: + explicit PropertyEditorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &) const override; + +protected: + void showEvent(QShowEvent *event) override; +}; + +PropertyEditorToolWindow::PropertyEditorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + createPropertyEditor(workbench->core()), + u"qt_designer_propertyeditor"_s, + QDesignerToolWindow::tr("Property Editor"), + u"__qt_property_editor_action"_s, + Qt::RightDockWidgetArea) +{ + action()->setShortcut(Qt::CTRL | Qt::Key_I); + +} + +QRect PropertyEditorToolWindow::geometryHint(const QRect &g) const +{ + const int spacing = 40; + const QSize sz(g.width() * 1/4, g.height() * 4/6); + + const QRect rc = QRect((g.right() + 1 - sz.width() - margin), + (g.top() + margin + g.height() * 1/6) + spacing, + sz.width(), sz.height()); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << rc; + return rc; +} + +void PropertyEditorToolWindow::showEvent(QShowEvent *event) +{ + if (QDesignerPropertyEditorInterface *e = workbench()->core()->propertyEditor()) { + // workaround to update the propertyeditor when it is not visible! + e->setObject(e->object()); // ### remove me + } + + QDesignerToolWindow::showEvent(event); +} + +// ---------------------- ActionEditorToolWindow + +static inline QWidget *createActionEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr) +{ + QDesignerActionEditorInterface *widget = QDesignerComponents::createActionEditor(core, parent); + core->setActionEditor(widget); + return widget; +} + +class ActionEditorToolWindow: public QDesignerToolWindow +{ +public: + explicit ActionEditorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +ActionEditorToolWindow::ActionEditorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + createActionEditor(workbench->core()), + u"qt_designer_actioneditor"_s, + QDesignerToolWindow::tr("Action Editor"), + u"__qt_action_editor_tool_action"_s, + Qt::RightDockWidgetArea) +{ +} + +QRect ActionEditorToolWindow::geometryHint(const QRect &g) const +{ + const QSize sz(g.width() * 1/4, g.height() * 1/6); + + const QRect rc = QRect((g.right() + 1 - sz.width() - margin), + g.top() + margin, + sz.width(), sz.height()); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << rc; + return rc; +} + +// ---------------------- ObjectInspectorToolWindow + +static inline QWidget *createObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent = nullptr) +{ + QDesignerObjectInspectorInterface *widget = QDesignerComponents::createObjectInspector(core, parent); + core->setObjectInspector(widget); + return widget; +} + +class ObjectInspectorToolWindow: public QDesignerToolWindow +{ +public: + explicit ObjectInspectorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +ObjectInspectorToolWindow::ObjectInspectorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + createObjectInspector(workbench->core()), + u"qt_designer_objectinspector"_s, + QDesignerToolWindow::tr("Object Inspector"), + u"__qt_object_inspector_tool_action"_s, + Qt::RightDockWidgetArea) +{ +} + +QRect ObjectInspectorToolWindow::geometryHint(const QRect &g) const +{ + const QSize sz(g.width() * 1/4, g.height() * 1/6); + + const QRect rc = QRect((g.right() + 1 - sz.width() - margin), + g.top() + margin, + sz.width(), sz.height()); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << rc; + return rc; +} + +// ---------------------- ResourceEditorToolWindow + +class ResourceEditorToolWindow: public QDesignerToolWindow +{ +public: + explicit ResourceEditorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +ResourceEditorToolWindow::ResourceEditorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + QDesignerComponents::createResourceEditor(workbench->core(), nullptr), + u"qt_designer_resourceeditor"_s, + QDesignerToolWindow::tr("Resource Browser"), + u"__qt_resource_editor_tool_action"_s, + Qt::RightDockWidgetArea) +{ +} + +QRect ResourceEditorToolWindow::geometryHint(const QRect &g) const +{ + const QSize sz(g.width() * 1/3, g.height() * 1/6); + QRect r(QPoint(0, 0), sz); + r.moveCenter(g.center()); + r.moveBottom(g.bottom() - margin); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << r; + return r; +} + +// ---------------------- SignalSlotEditorToolWindow + +class SignalSlotEditorToolWindow: public QDesignerToolWindow +{ +public: + explicit SignalSlotEditorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +SignalSlotEditorToolWindow::SignalSlotEditorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + QDesignerComponents::createSignalSlotEditor(workbench->core(), nullptr), + u"qt_designer_signalsloteditor"_s, + QDesignerToolWindow::tr("Signal/Slot Editor"), + u"__qt_signal_slot_editor_tool_action"_s, + Qt::RightDockWidgetArea) +{ +} + +QRect SignalSlotEditorToolWindow::geometryHint(const QRect &g) const +{ + const QSize sz(g.width() * 1/3, g.height() * 1/6); + QRect r(QPoint(0, 0), sz); + r.moveCenter(g.center()); + r.moveTop(margin + g.top()); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << r; + return r; +} + +// ---------------------- WidgetBoxToolWindow + +static inline QWidget *createWidgetBox(QDesignerFormEditorInterface *core, QWidget *parent = nullptr) +{ + QDesignerWidgetBoxInterface *widget = QDesignerComponents::createWidgetBox(core, parent); + core->setWidgetBox(widget); + return widget; +} + +class WidgetBoxToolWindow: public QDesignerToolWindow +{ +public: + explicit WidgetBoxToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +WidgetBoxToolWindow::WidgetBoxToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + createWidgetBox(workbench->core()), + u"qt_designer_widgetbox"_s, + QDesignerToolWindow::tr("Widget Box"), + u"__qt_widget_box_tool_action"_s, + Qt::LeftDockWidgetArea) +{ +} + +QRect WidgetBoxToolWindow::geometryHint(const QRect &g) const +{ + const QRect rc = QRect(g.left() + margin, + g.top() + margin, + g.width() * 1/4, g.height() * 5/6); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << rc; + return rc; +} + +// -- Factory +QDesignerToolWindow *QDesignerToolWindow::createStandardToolWindow(StandardToolWindow which, + QDesignerWorkbench *workbench) +{ + switch (which) { + case ActionEditor: + return new ActionEditorToolWindow(workbench); + case ResourceEditor: + return new ResourceEditorToolWindow(workbench); + case SignalSlotEditor: + return new SignalSlotEditorToolWindow(workbench); + case PropertyEditor: + return new PropertyEditorToolWindow(workbench); + case ObjectInspector: + return new ObjectInspectorToolWindow(workbench); + case WidgetBox: + return new WidgetBoxToolWindow(workbench); + default: + break; + } + return nullptr; +} + + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/qdesigner_toolwindow.h b/src/designer/src/designer/qdesigner_toolwindow.h new file mode 100644 index 0000000..7fec139 --- /dev/null +++ b/src/designer/src/designer/qdesigner_toolwindow.h @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_TOOLWINDOW_H +#define QDESIGNER_TOOLWINDOW_H + +#include "mainwindow.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct ToolWindowFontSettings +{ + QFont m_font; + QFontDatabase::WritingSystem m_writingSystem{QFontDatabase::Any}; + bool m_useFont{false}; + + friend bool comparesEqual(const ToolWindowFontSettings &lhs, + const ToolWindowFontSettings &rhs) noexcept + { + return lhs.m_useFont == rhs.m_useFont + && lhs.m_writingSystem == rhs.m_writingSystem + && lhs.m_font == rhs.m_font; + } + Q_DECLARE_EQUALITY_COMPARABLE(ToolWindowFontSettings) +}; + +class QDesignerWorkbench; + +/* A tool window with an action that activates it. Note that in toplevel mode, + * the Widget box is a tool window as well as the applications' main window, + * So, we need to inherit from MainWindowBase. */ + +class QDesignerToolWindow : public MainWindowBase +{ + Q_OBJECT +protected: + explicit QDesignerToolWindow(QDesignerWorkbench *workbench, + QWidget *w, + const QString &objectName, + const QString &title, + const QString &actionObjectName, + Qt::DockWidgetArea dockAreaHint, + QWidget *parent = nullptr, + Qt::WindowFlags flags = Qt::Window); + +public: + // Note: The order influences the dock widget position. + enum StandardToolWindow { WidgetBox, ObjectInspector, PropertyEditor, + ResourceEditor, ActionEditor, SignalSlotEditor, + StandardToolWindowCount }; + + static QDesignerToolWindow *createStandardToolWindow(StandardToolWindow which, QDesignerWorkbench *workbench); + + QDesignerWorkbench *workbench() const; + QAction *action() const; + + Qt::DockWidgetArea dockWidgetAreaHint() const { return m_dockAreaHint; } + virtual QRect geometryHint(const QRect &availableGeometry) const = 0; + +private slots: + void showMe(bool); + +protected: + void showEvent(QShowEvent *e) override; + void hideEvent(QHideEvent *e) override; + void changeEvent(QEvent *e) override; + +private: + const Qt::DockWidgetArea m_dockAreaHint; + QDesignerWorkbench *m_workbench; + QAction *m_action; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_TOOLWINDOW_H diff --git a/src/designer/src/designer/qdesigner_workbench.cpp b/src/designer/src/designer/qdesigner_workbench.cpp new file mode 100644 index 0000000..b0fda8d --- /dev/null +++ b/src/designer/src/designer/qdesigner_workbench.cpp @@ -0,0 +1,1108 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_workbench.h" +#include "qdesigner.h" +#include "qdesigner_actions.h" +#include "qdesigner_appearanceoptions.h" +#include "qdesigner_settings.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_formwindow.h" +#include "appfontdialog.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto appFontPrefixC = "AppFonts"_L1; + +using ActionList = QList; + +static QMdiSubWindow *mdiSubWindowOf(const QWidget *w) +{ + auto *rc = qobject_cast(w->parentWidget()); + Q_ASSERT(rc); + return rc; +} + +static QDockWidget *dockWidgetOf(const QWidget *w) +{ + for (QWidget *parentWidget = w->parentWidget(); parentWidget ; parentWidget = parentWidget->parentWidget()) { + if (auto *dw = qobject_cast(parentWidget)) { + return dw; + } + } + Q_ASSERT("Dock widget not found"); + return nullptr; +} + +// ------------ QDesignerWorkbench::Position +QDesignerWorkbench::Position::Position(const QMdiSubWindow *mdiSubWindow) : + m_minimized(mdiSubWindow->isShaded()), + m_position(mdiSubWindow->pos() + mdiSubWindow->mdiArea()->pos()) +{ +} + +QDesignerWorkbench::Position::Position(const QDockWidget *dockWidget) : + m_minimized(dockWidget->isMinimized()), + m_position(dockWidget->pos()) +{ +} + +QDesignerWorkbench::Position::Position(const QWidget *topLevelWindow) +{ + const QWidget *window = topLevelWindow->window(); + Q_ASSERT(window); + m_minimized = window->isMinimized(); + m_position = window->pos() - window->screen()->availableGeometry().topLeft(); +} + +void QDesignerWorkbench::Position::applyTo(QMdiSubWindow *mdiSubWindow, + const QPoint &mdiAreaOffset) const +{ + // QMdiSubWindow attempts to resize its children to sizeHint() when switching user interface modes. + // Restore old size + const QPoint mdiAreaPos = QPoint(qMax(0, m_position.x() - mdiAreaOffset.x()), + qMax(0, m_position.y() - mdiAreaOffset.y())); + mdiSubWindow->move(mdiAreaPos); + const QSize decorationSize = mdiSubWindow->size() - mdiSubWindow->contentsRect().size(); + mdiSubWindow->resize(mdiSubWindow->widget()->size() + decorationSize); + mdiSubWindow->show(); + if (m_minimized) { + mdiSubWindow->showShaded(); + } +} + +void QDesignerWorkbench::Position::applyTo(QWidget *topLevelWindow, const QPoint &desktopTopLeft) const +{ + QWidget *window = topLevelWindow->window (); + const QPoint newPos = m_position + desktopTopLeft; + window->move(newPos); + if ( m_minimized) { + topLevelWindow->showMinimized(); + } else { + topLevelWindow->show(); + } +} + +void QDesignerWorkbench::Position::applyTo(QDockWidget *dockWidget) const +{ + dockWidget->widget()->setVisible(true); + dockWidget->setVisible(!m_minimized); +} + +static inline void addActionsToMenu(QMenu *m, const ActionList &al) +{ + for (auto *a : al) + m->addAction(a); +} + +static inline QMenu *addMenu(QMenuBar *mb, const QString &title, const ActionList &al) +{ + QMenu *rc = mb->addMenu(title); + addActionsToMenu(rc, al); + return rc; +} + +// -------- QDesignerWorkbench + +QDesignerWorkbench::QDesignerWorkbench(const QStringList &pluginPaths) : + m_core(QDesignerComponents::createFormEditorWithPluginPaths(pluginPaths, this)), + m_windowActions(new QActionGroup(this)), + m_globalMenuBar(new QMenuBar) +{ + QDesignerSettings settings(m_core); + + (void) QDesignerComponents::createTaskMenu(core(), this); + + initializeCorePlugins(); + QDesignerComponents::initializePlugins(core()); + m_actionManager = new QDesignerActions(this); // accesses plugin components + + m_windowActions->setExclusive(true); + connect(m_windowActions, &QActionGroup::triggered, + this, &QDesignerWorkbench::formWindowActionTriggered); + + // Build main menu bar + addMenu(m_globalMenuBar, tr("&File"), m_actionManager->fileActions()->actions()); + + QMenu *editMenu = addMenu(m_globalMenuBar, tr("&Edit"), m_actionManager->editActions()->actions()); + editMenu->addSeparator(); + addActionsToMenu(editMenu, m_actionManager->toolActions()->actions()); + + QMenu *formMenu = addMenu(m_globalMenuBar, tr("F&orm"), m_actionManager->formActions()->actions()); + auto *previewSubMenu = new QMenu(tr("Preview in"), formMenu); + formMenu->insertMenu(m_actionManager->previewFormAction(), previewSubMenu); + addActionsToMenu(previewSubMenu, m_actionManager->styleActions()->actions()); + + QMenu *viewMenu = m_globalMenuBar->addMenu(tr("&View")); + + addMenu(m_globalMenuBar, tr("&Settings"), m_actionManager->settingsActions()->actions()); + + m_windowMenu = addMenu(m_globalMenuBar, tr("&Window"), m_actionManager->windowActions()->actions()); + + addMenu(m_globalMenuBar, tr("&Help"), m_actionManager->helpActions()->actions()); + + // Add the tools in view menu order + auto *viewActions = new QActionGroup(this); + viewActions->setExclusive(false); + + for (int i = 0; i < QDesignerToolWindow::StandardToolWindowCount; i++) { + QDesignerToolWindow *toolWindow = QDesignerToolWindow::createStandardToolWindow(static_cast< QDesignerToolWindow::StandardToolWindow>(i), this); + m_toolWindows.push_back(toolWindow); + if (QAction *action = toolWindow->action()) { + viewMenu->addAction(action); + viewActions->addAction(action); + } + // The widget box becomes the main window in top level mode + if (i == QDesignerToolWindow::WidgetBox) { + connect(toolWindow, &QDesignerToolWindow::closeEventReceived, + this, &QDesignerWorkbench::handleCloseEvent); + } + } + // Integration + m_integration = new QDesignerIntegration(m_core, this); + connect(m_integration, &QDesignerIntegration::helpRequested, + m_actionManager, &QDesignerActions::helpRequested); + + // remaining view options (config toolbars) + viewMenu->addSeparator(); + m_toolbarMenu = viewMenu->addMenu(tr("Toolbars")); + + emit initialized(); + + connect(m_core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &QDesignerWorkbench::updateWindowMenu); + + + { // Add application specific options pages + QDesignerAppearanceOptionsPage *appearanceOptions = new QDesignerAppearanceOptionsPage(m_core); + connect(appearanceOptions, &QDesignerAppearanceOptionsPage::settingsChanged, this, &QDesignerWorkbench::notifyUISettingsChanged); + auto optionsPages = m_core->optionsPages(); + optionsPages.push_front(appearanceOptions); + m_core->setOptionsPages(optionsPages); + } + + restoreUISettings(); + AppFontWidget::restore(m_core->settingsManager(), appFontPrefixC); + m_state = StateUp; +} + +QDesignerWorkbench::~QDesignerWorkbench() +{ + switch (m_mode) { + case NeutralMode: + case DockedMode: + qDeleteAll(m_toolWindows); + break; + case TopLevelMode: // Everything parented here + delete widgetBoxToolWindow(); + break; + } + delete m_globalMenuBar; + m_windowMenu = nullptr; + delete m_dockedMainWindow; +} + +void QDesignerWorkbench::saveGeometriesForModeChange() +{ + m_Positions.clear(); + switch (m_mode) { + case NeutralMode: + break; + case TopLevelMode: { + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + m_Positions.insert(tw, Position(tw)); + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) + m_Positions.insert(fw, Position(fw)); + } + break; + case DockedMode: { + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + m_Positions.insert(tw, Position(dockWidgetOf(tw))); + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) + m_Positions.insert(fw, Position(mdiSubWindowOf(fw))); + } + break; + } +} + +UIMode QDesignerWorkbench::mode() const +{ + return m_mode; +} + +void QDesignerWorkbench::addFormWindow(QDesignerFormWindow *formWindow) +{ + // ### Q_ASSERT(formWindow->windowTitle().isEmpty() == false); + + m_formWindows.append(formWindow); + + + m_actionManager->setWindowListSeparatorVisible(true); + + if (QAction *action = formWindow->action()) { + m_windowActions->addAction(action); + m_windowMenu->addAction(action); + action->setChecked(true); + } + + m_actionManager->minimizeAction()->setEnabled(true); + m_actionManager->minimizeAction()->setChecked(false); + connect(formWindow, &QDesignerFormWindow::minimizationStateChanged, + this, &QDesignerWorkbench::minimizationStateChanged); + + m_actionManager->editWidgets()->trigger(); +} + +Qt::WindowFlags QDesignerWorkbench::magicalWindowFlags(const QWidget *widgetForFlags) const +{ + switch (m_mode) { + case TopLevelMode: { +#ifdef Q_OS_MACOS + if (qobject_cast(widgetForFlags)) + return Qt::Tool; +#else + Q_UNUSED(widgetForFlags); +#endif + return Qt::Window; + } + case DockedMode: + return Qt::Window | Qt::WindowShadeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowTitleHint; + case NeutralMode: + return Qt::Window; + default: + Q_ASSERT(0); + return {}; + } +} + +QWidget *QDesignerWorkbench::magicalParent(const QWidget *w) const +{ + switch (m_mode) { + case TopLevelMode: { + // Use widget box as parent for all windows except self. This will + // result in having just one entry in the MS Windows task bar. + QWidget *widgetBoxWrapper = widgetBoxToolWindow(); + return w == widgetBoxWrapper ? nullptr : widgetBoxWrapper; + } + case DockedMode: + return m_dockedMainWindow->mdiArea(); + case NeutralMode: + break; + default: + Q_ASSERT(false); + break; + } + return nullptr; +} + +void QDesignerWorkbench::switchToNeutralMode() +{ + QDesignerSettings settings(m_core); + saveGeometries(settings); + saveGeometriesForModeChange(); + + if (m_mode == TopLevelMode) { + delete m_topLevelData.toolbarManager; + m_topLevelData.toolbarManager = nullptr; + qDeleteAll(m_topLevelData.toolbars); + m_topLevelData.toolbars.clear(); + } + + m_mode = NeutralMode; + + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) { + tw->setCloseEventPolicy(MainWindowBase::AcceptCloseEvents); + tw->setParent(nullptr); + // Prevent unneeded native children when switching to docked + if (auto *handle = tw->windowHandle()) + handle->destroy(); + } + + if (m_dockedMainWindow != nullptr) // Prevent assert + m_dockedMainWindow->mdiArea()->setActiveSubWindow(nullptr); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) { + fw->setParent(nullptr); + fw->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + // Prevent unneeded native children when switching to docked + if (auto *handle = fw->windowHandle()) + handle->destroy(); + } + +#ifndef Q_OS_MACOS + m_globalMenuBar->setParent(nullptr); +#endif + + m_core->setTopLevel(nullptr); + qDesigner->setMainWindow(nullptr); + + delete m_dockedMainWindow; +} + +void QDesignerWorkbench::switchToDockedMode() +{ + if (m_mode == DockedMode) + return; + + switchToNeutralMode(); + +#if !defined(Q_OS_MACOS) +# if defined(Q_OS_UNIX) + QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, false); +# endif // Q_OS_UNIX + QDesignerToolWindow *widgetBoxWrapper = widgetBoxToolWindow(); + widgetBoxWrapper->action()->setVisible(true); + widgetBoxWrapper->setWindowTitle(tr("Widget Box")); +#endif // !Q_OS_MACOS + + m_mode = DockedMode; + const QDesignerSettings settings(m_core); + m_dockedMainWindow = new DockedMainWindow(this, m_toolbarMenu, m_toolWindows); + m_dockedMainWindow->setUnifiedTitleAndToolBarOnMac(true); + m_dockedMainWindow->setCloseEventPolicy(MainWindowBase::EmitCloseEventSignal); + connect(m_dockedMainWindow, &DockedMainWindow::closeEventReceived, + this, &QDesignerWorkbench::handleCloseEvent); + connect(m_dockedMainWindow, &DockedMainWindow::fileDropped, + this, &QDesignerWorkbench::slotFileDropped); + connect(m_dockedMainWindow, &DockedMainWindow::formWindowActivated, + this, &QDesignerWorkbench::slotFormWindowActivated); + m_dockedMainWindow->restoreSettings(settings, + m_dockedMainWindow->addToolWindows(m_toolWindows), + screen()->availableGeometry()); + + m_core->setTopLevel(m_dockedMainWindow); + +#ifndef Q_OS_MACOS + m_dockedMainWindow->setMenuBar(m_globalMenuBar); + m_globalMenuBar->show(); +#endif + qDesigner->setMainWindow(m_dockedMainWindow); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) { + QMdiSubWindow *subwin = m_dockedMainWindow->createMdiSubWindow(fw, magicalWindowFlags(fw), + m_actionManager->closeFormAction()->shortcut()); + subwin->hide(); + if (QWidget *mainContainer = fw->editor()->mainContainer()) + resizeForm(fw, mainContainer); + } + + m_actionManager->setBringAllToFrontVisible(false); + m_dockedMainWindow->show(); + // Trigger adjustMDIFormPositions() delayed as viewport size is not yet known. + + if (m_state != StateInitializing) + QMetaObject::invokeMethod(this, "adjustMDIFormPositions", Qt::QueuedConnection); +} + +void QDesignerWorkbench::adjustMDIFormPositions() +{ + const QPoint mdiAreaOffset = m_dockedMainWindow->mdiArea()->pos(); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) { + const auto pit = m_Positions.constFind(fw); + if (pit != m_Positions.constEnd()) + pit->applyTo(mdiSubWindowOf(fw), mdiAreaOffset); + } +} + +static QScreen *screenUnderMouse() +{ + const auto &screens = QGuiApplication::screens(); + const auto pos = QCursor::pos(); + auto pred = [pos](const QScreen *s) { return s->geometry().contains(pos); }; + auto it = std::find_if(screens.cbegin(), screens.cend(), pred); + return it != screens.cend() ? *it : QGuiApplication::primaryScreen(); +} + +void QDesignerWorkbench::switchToTopLevelMode() +{ + if (m_mode == TopLevelMode) + return; + + // make sure that the widgetbox is visible if it is different from neutral. + QDesignerToolWindow *widgetBoxWrapper = widgetBoxToolWindow(); + Q_ASSERT(widgetBoxWrapper); + + switchToNeutralMode(); + m_mode = TopLevelMode; // Set new mode before calling screen() + const QDesignerSettings settings(m_core); + const QByteArray mainWindowState = settings.mainWindowState(m_mode); + // Open on screen where the mouse is when no settings exist + const auto *currentScreen = mainWindowState.isEmpty() ? screenUnderMouse() : screen(); + const QRect availableGeometry = currentScreen->availableGeometry(); + const QPoint desktopOffset = availableGeometry.topLeft(); + + // The widget box is special, it gets the menubar and gets to be the main widget. + + m_core->setTopLevel(widgetBoxWrapper); +#if !defined(Q_OS_MACOS) +# if defined(Q_OS_UNIX) + // For now the appmenu protocol does not make it possible to associate a + // menubar with all application windows. This means in top level mode you + // can only reach the menubar when the widgetbox window is active. Since + // this is quite inconvenient, better not use the native menubar in this + // configuration and keep the menubar in the widgetbox window. + QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true); +# endif // Q_OS_UNIX + widgetBoxWrapper->setMenuBar(m_globalMenuBar); + widgetBoxWrapper->action()->setVisible(false); + widgetBoxWrapper->setCloseEventPolicy(MainWindowBase::EmitCloseEventSignal); + qDesigner->setMainWindow(widgetBoxWrapper); + widgetBoxWrapper->setWindowTitle(MainWindowBase::mainWindowTitle()); +#endif // !Q_OS_MACOS + + m_topLevelData.toolbars = MainWindowBase::createToolBars(m_actionManager, false); + m_topLevelData.toolbarManager = new ToolBarManager(widgetBoxWrapper, widgetBoxWrapper, + m_toolbarMenu, m_actionManager, + m_topLevelData.toolbars, m_toolWindows); + const qsizetype toolBarCount = m_topLevelData.toolbars.size(); + for (qsizetype i = 0; i < toolBarCount; ++i) { + widgetBoxWrapper->addToolBar(m_topLevelData.toolbars.at(i)); + if (i == 3) + widgetBoxWrapper->insertToolBarBreak(m_topLevelData.toolbars.at(i)); + } + m_topLevelData.toolbarManager->restoreState(settings.toolBarsState(m_mode), MainWindowBase::settingsVersion()); + widgetBoxWrapper->restoreState(mainWindowState, MainWindowBase::settingsVersion()); + + bool found_visible_window = false; + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) { + tw->setParent(magicalParent(tw), magicalWindowFlags(tw)); + settings.restoreGeometry(tw, tw->geometryHint(availableGeometry)); + tw->action()->setChecked(tw->isVisible()); + found_visible_window |= tw->isVisible(); + } + + if (!m_toolWindows.isEmpty() && !found_visible_window) + m_toolWindows.constFirst()->show(); + + m_actionManager->setBringAllToFrontVisible(true); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) { + fw->setParent(magicalParent(fw), magicalWindowFlags(fw)); + fw->setAttribute(Qt::WA_DeleteOnClose, true); + const auto pit = m_Positions.constFind(fw); + if (pit != m_Positions.constEnd()) pit->applyTo(fw, desktopOffset); + // Force an activate in order to refresh minimumSize, otherwise it will not be respected + if (QLayout *layout = fw->layout()) + layout->invalidate(); + if (QWidget *mainContainer = fw->editor()->mainContainer()) + resizeForm(fw, mainContainer); + } +} + +QDesignerFormWindowManagerInterface *QDesignerWorkbench::formWindowManager() const +{ + return m_core->formWindowManager(); +} + +QDesignerFormEditorInterface *QDesignerWorkbench::core() const +{ + return m_core; +} + +int QDesignerWorkbench::toolWindowCount() const +{ + return m_toolWindows.size(); +} + +QDesignerToolWindow *QDesignerWorkbench::toolWindow(int index) const +{ + return m_toolWindows.at(index); +} + +int QDesignerWorkbench::formWindowCount() const +{ + return m_formWindows.size(); +} + +QDesignerFormWindow *QDesignerWorkbench::formWindow(int index) const +{ + return m_formWindows.at(index); +} + +QScreen *QDesignerWorkbench::screen() const +{ + auto *widget = m_mode == DockedMode + ? static_cast(m_dockedMainWindow.data()) + : static_cast(widgetBoxToolWindow()); + return widget != nullptr + ? widget->screen() : QGuiApplication::primaryScreen(); +} + +QRect QDesignerWorkbench::availableFormGeometry() const +{ + // Return available geometry for forms + return m_mode == DockedMode + ? m_dockedMainWindow->mdiArea()->geometry() : screen()->availableGeometry(); +} + +void QDesignerWorkbench::slotFormWindowActivated(QDesignerFormWindow* fw) +{ + core()->formWindowManager()->setActiveFormWindow(fw->editor()); +} + +void QDesignerWorkbench::removeFormWindow(QDesignerFormWindow *formWindow) +{ + QDesignerFormWindowInterface *editor = formWindow->editor(); + const bool loadOk = editor->mainContainer(); + updateBackup(editor); + const int index = m_formWindows.indexOf(formWindow); + if (index != -1) { + m_formWindows.removeAt(index); + } + + if (QAction *action = formWindow->action()) { + m_windowActions->removeAction(action); + if (m_windowMenu) + m_windowMenu->removeAction(action); + } + + if (m_formWindows.isEmpty()) { + m_actionManager->setWindowListSeparatorVisible(false); + // Show up new form dialog unless closing + if (loadOk && m_state == StateUp) + showNewForm(); + } +} + +void QDesignerWorkbench::showNewForm() +{ + if (!m_suppressNewFormShow && QDesignerSettings(m_core).showNewFormOnStartup()) + QTimer::singleShot(100, m_actionManager, &QDesignerActions::createForm); +} + +void QDesignerWorkbench::initializeCorePlugins() +{ + QObjectList plugins = QPluginLoader::staticInstances(); + plugins += core()->pluginManager()->instances(); + + for (QObject *plugin : std::as_const(plugins)) { + if (QDesignerFormEditorPluginInterface *formEditorPlugin = qobject_cast(plugin)) { + if (!formEditorPlugin->isInitialized()) + formEditorPlugin->initialize(core()); + } + } +} + +void QDesignerWorkbench::saveSettings() const +{ + QDesignerSettings settings(m_core); + settings.clearBackup(); + saveGeometries(settings); + AppFontWidget::save(m_core->settingsManager(), appFontPrefixC); +} + +void QDesignerWorkbench::saveGeometries(QDesignerSettings &settings) const +{ + switch (m_mode) { + case DockedMode: + m_dockedMainWindow->saveSettings(settings); + break; + case TopLevelMode: + settings.setToolBarsState(m_mode, m_topLevelData.toolbarManager->saveState(MainWindowBase::settingsVersion())); + settings.setMainWindowState(m_mode, widgetBoxToolWindow()->saveState(MainWindowBase::settingsVersion())); + for (const QDesignerToolWindow *tw : m_toolWindows) + settings.saveGeometryFor(tw); + break; + case NeutralMode: + break; + } +} + +void QDesignerWorkbench::slotFileDropped(const QString &f) +{ + readInForm(f); +} + +bool QDesignerWorkbench::readInForm(const QString &fileName) const +{ + return m_actionManager->readInForm(fileName); +} + +bool QDesignerWorkbench::writeOutForm(QDesignerFormWindowInterface *formWindow, const QString &fileName) const +{ + return m_actionManager->writeOutForm(formWindow, fileName); +} + +bool QDesignerWorkbench::saveForm(QDesignerFormWindowInterface *frm) +{ + return m_actionManager->saveForm(frm); +} + +QDesignerFormWindow *QDesignerWorkbench::findFormWindow(QWidget *widget) const +{ + for (QDesignerFormWindow *formWindow : m_formWindows) { + if (formWindow->editor() == widget) + return formWindow; + } + + return nullptr; +} + +bool QDesignerWorkbench::handleClose() +{ + m_state = StateClosing; + QList dirtyForms; + for (QDesignerFormWindow *w : std::as_const(m_formWindows)) { + if (w->editor()->isDirty()) + dirtyForms << w; + } + + const auto count = dirtyForms.size(); + if (count == 1) { + if (!dirtyForms.at(0)->close()) { + m_state = StateUp; + return false; + } + } else if (count > 1) { + QMessageBox box(QMessageBox::Warning, tr("Save Forms?"), + tr("There are %n forms with unsaved changes." + " Do you want to review these changes before quitting?", "", count), + QMessageBox::Cancel | QMessageBox::Discard | QMessageBox::Save); + box.setInformativeText(tr("If you do not review your documents, all your changes will be lost.")); + box.button(QMessageBox::Discard)->setText(tr("Discard Changes")); + auto *save = static_cast(box.button(QMessageBox::Save)); + save->setText(tr("Review Changes")); + box.setDefaultButton(save); + switch (box.exec()) { + case QMessageBox::Cancel: + m_state = StateUp; + return false; + case QMessageBox::Save: + for (QDesignerFormWindow *fw : std::as_const(dirtyForms)) { + fw->show(); + fw->raise(); + if (!fw->close()) { + m_state = StateUp; + return false; + } + } + break; + case QMessageBox::Discard: + for (QDesignerFormWindow *fw : std::as_const(dirtyForms)) { + fw->editor()->setDirty(false); + fw->setWindowModified(false); + } + break; + } + } + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) + fw->close(); + + saveSettings(); + return true; +} + +QDesignerActions *QDesignerWorkbench::actionManager() const +{ + return m_actionManager; +} + +void QDesignerWorkbench::updateWindowMenu(QDesignerFormWindowInterface *fwi) +{ + bool minimizeChecked = false; + bool minimizeEnabled = false; + QDesignerFormWindow *activeFormWindow = nullptr; + do { + if (!fwi) + break; + activeFormWindow = qobject_cast(fwi->parentWidget()); + if (!activeFormWindow) + break; + + minimizeEnabled = true; + minimizeChecked = isFormWindowMinimized(activeFormWindow); + } while (false) ; + + m_actionManager->minimizeAction()->setEnabled(minimizeEnabled); + m_actionManager->minimizeAction()->setChecked(minimizeChecked); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) + fw->action()->setChecked(fw == activeFormWindow); +} + +void QDesignerWorkbench::formWindowActionTriggered(QAction *a) +{ + auto *fw = qobject_cast(a->parent()); + Q_ASSERT(fw); + + if (isFormWindowMinimized(fw)) + setFormWindowMinimized(fw, false); + + if (m_mode == DockedMode) { + if (auto *subWindow = qobject_cast(fw->parent())) { + m_dockedMainWindow->mdiArea()->setActiveSubWindow(subWindow); + } + } else { + fw->activateWindow(); + fw->raise(); + } +} + +void QDesignerWorkbench::closeAllToolWindows() +{ + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + tw->hide(); +} + +bool QDesignerWorkbench::readInBackup() +{ + const QMap backupFileMap = QDesignerSettings(m_core).backup(); + if (backupFileMap.isEmpty()) + return false; + + const QMessageBox::StandardButton answer = + QMessageBox::question(nullptr, tr("Backup Information"), + tr("The last session of Designer was not terminated correctly. " + "Backup files were left behind. Do you want to load them?"), + QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes); + if (answer == QMessageBox::No) + return false; + + const auto modifiedPlaceHolder = "[*]"_L1; + for (auto it = backupFileMap.cbegin(), end = backupFileMap.cend(); it != end; ++it) { + QString fileName = it.key(); + fileName.remove(modifiedPlaceHolder); + + if(m_actionManager->readInForm(it.value())) + formWindowManager()->activeFormWindow()->setFileName(fileName); + + } + return true; +} + +void QDesignerWorkbench::updateBackup(QDesignerFormWindowInterface* fwi) +{ + QString fwn = QDir::toNativeSeparators(fwi->fileName()); + if (fwn.isEmpty()) + fwn = fwi->parentWidget()->windowTitle(); + + QDesignerSettings settings(m_core); + QMap map = settings.backup(); + map.remove(fwn); + settings.setBackup(map); +} + +namespace { + void raiseWindow(QWidget *w) { + if (w->isMinimized()) + w->setWindowState(w->windowState() & ~Qt::WindowMinimized); + w->raise(); + } +} + +void QDesignerWorkbench::bringAllToFront() +{ + if (m_mode != TopLevelMode) + return; + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + raiseWindow(tw); + for (QDesignerFormWindow *dfw : std::as_const(m_formWindows)) + raiseWindow(dfw); +} + +void QDesignerWorkbench::requestActivate() +{ + switch (m_mode) { + case NeutralMode: + break; + case TopLevelMode: + bringAllToFront(); + widgetBoxToolWindow()->windowHandle()->requestActivate(); + break; + case DockedMode: + raiseWindow(m_dockedMainWindow); + m_dockedMainWindow->windowHandle()->requestActivate(); + break; + } +} + +// Resize a form window taking MDI decorations into account +// Apply maximum size as there is no layout connection between +// the form's main container and the integration's outer +// container due to the tool widget stack. + +void QDesignerWorkbench::resizeForm(QDesignerFormWindow *fw, const QWidget *mainContainer) const +{ + const QSize containerSize = mainContainer->size(); + const QSize containerMaximumSize = mainContainer->maximumSize(); + if (m_mode != DockedMode) { + fw->resize(containerSize); + fw->setMaximumSize(containerMaximumSize); + return; + } + // get decorations and resize MDI + auto *mdiSubWindow = qobject_cast(fw->parent()); + Q_ASSERT(mdiSubWindow); + const QSize decorationSize = mdiSubWindow->geometry().size() - mdiSubWindow->contentsRect().size(); + mdiSubWindow->resize(containerSize + decorationSize); + // In Qt::RightToLeft mode, the window can grow to be partially hidden by the right border. + const int mdiAreaWidth = m_dockedMainWindow->mdiArea()->width(); + if (qApp->layoutDirection() == Qt::RightToLeft && mdiSubWindow->geometry().right() >= mdiAreaWidth) + mdiSubWindow->move(mdiAreaWidth - mdiSubWindow->width(), mdiSubWindow->pos().y()); + + if (containerMaximumSize == QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) { + mdiSubWindow->setMaximumSize(containerMaximumSize); + } else { + mdiSubWindow->setMaximumSize(containerMaximumSize + decorationSize); + } +} + + +// Load a form or return 0 and do cleanup. file name and editor file +// name in case of loading a template file. + +QDesignerFormWindow * QDesignerWorkbench::loadForm(const QString &fileName, + bool detectLineTermiantorMode, + QString *errorMessage) +{ + QFile file(fileName); + + qdesigner_internal::FormWindowBase::LineTerminatorMode mode = qdesigner_internal::FormWindowBase::NativeLineTerminator; + + if (detectLineTermiantorMode) { + if (file.open(QFile::ReadOnly)) { + const QString text = QString::fromUtf8(file.readLine()); + file.close(); + + const auto lf = text.indexOf(u'\n'); + if (lf > 0 && text.at(lf - 1) == u'\r') { + mode = qdesigner_internal::FormWindowBase::CRLFLineTerminator; + } else if (lf >= 0) { + mode = qdesigner_internal::FormWindowBase::LFLineTerminator; + } + } + } + + if (!file.open(QFile::ReadOnly|QFile::Text)) { + *errorMessage = tr("The file %1 could not be opened: %2").arg(file.fileName(), file.errorString()); + return nullptr; + } + + // Create a form + QDesignerFormWindowManagerInterface *formWindowManager = m_core->formWindowManager(); + + auto *formWindow = new QDesignerFormWindow(/*formWindow=*/ nullptr, this); + addFormWindow(formWindow); + QDesignerFormWindowInterface *editor = formWindow->editor(); + Q_ASSERT(editor); + + // Temporarily set the file name. It is needed when converting a UIC 3 file. + // In this case, the file name will we be cleared on return to force a save box. + editor->setFileName(fileName); + + if (!editor->setContents(&file, errorMessage)) { + removeFormWindow(formWindow); + formWindowManager->removeFormWindow(editor); + m_core->metaDataBase()->remove(editor); + return nullptr; + } + + if (qdesigner_internal::FormWindowBase *fwb = qobject_cast(editor)) + fwb->setLineTerminatorMode(mode); + + switch (m_mode) { + case DockedMode: { + // below code must be after above call to setContents(), because setContents() may popup warning dialogs which would cause + // mdi sub window activation (because of dialogs internal call to processEvent or such) + // That activation could have worse consequences, e.g. NULL resource set for active form) before the form is loaded + QMdiSubWindow *subWin = m_dockedMainWindow->createMdiSubWindow(formWindow, magicalWindowFlags(formWindow), m_actionManager->closeFormAction()->shortcut()); + m_dockedMainWindow->mdiArea()->setActiveSubWindow(subWin); + } + break; + case TopLevelMode: { + const QRect formWindowGeometryHint = formWindow->geometryHint(); + formWindow->setAttribute(Qt::WA_DeleteOnClose, true); + formWindow->setParent(magicalParent(formWindow), magicalWindowFlags(formWindow)); + formWindow->resize(formWindowGeometryHint.size()); + formWindow->move(availableFormGeometry().center() - formWindowGeometryHint.center()); + } + break; + case NeutralMode: + break; + } + + // Did user specify another (missing) resource path -> set dirty. + const bool dirty = editor->property("_q_resourcepathchanged").toBool(); + editor->setDirty(dirty); + resizeForm(formWindow, editor->mainContainer()); + formWindowManager->setActiveFormWindow(editor); + return formWindow; +} + + +QDesignerFormWindow * QDesignerWorkbench::openForm(const QString &fileName, QString *errorMessage) +{ + QDesignerFormWindow *rc = loadForm(fileName, true, errorMessage); + if (!rc) + return nullptr; + rc->editor()->setFileName(fileName); + rc->firstShow(); + return rc; +} + +QDesignerFormWindow * QDesignerWorkbench::openTemplate(const QString &templateFileName, + const QString &editorFileName, + QString *errorMessage) +{ + QDesignerFormWindow *rc = loadForm(templateFileName, false, errorMessage); + if (!rc) + return nullptr; + + rc->editor()->setFileName(editorFileName); + rc->firstShow(); + return rc; +} + +void QDesignerWorkbench::minimizationStateChanged(QDesignerFormWindowInterface *formWindow, bool minimized) +{ + // refresh the minimize action state + if (core()->formWindowManager()->activeFormWindow() == formWindow) { + m_actionManager->minimizeAction()->setChecked(minimized); + } +} + +void QDesignerWorkbench::toggleFormMinimizationState() +{ + QDesignerFormWindowInterface *fwi = core()->formWindowManager()->activeFormWindow(); + if (!fwi || m_mode == NeutralMode) + return; + QDesignerFormWindow *fw = qobject_cast(fwi->parentWidget()); + Q_ASSERT(fw); + setFormWindowMinimized(fw, !isFormWindowMinimized(fw)); +} + +bool QDesignerWorkbench::isFormWindowMinimized(const QDesignerFormWindow *fw) +{ + switch (m_mode) { + case DockedMode: + return mdiSubWindowOf(fw)->isShaded(); + case TopLevelMode: + return fw->window()->isMinimized(); + default: + break; + } + return fw->isMinimized(); +} + +void QDesignerWorkbench::setFormWindowMinimized(QDesignerFormWindow *fw, bool minimized) +{ + switch (m_mode) { + case DockedMode: { + QMdiSubWindow *mdiSubWindow = mdiSubWindowOf(fw); + if (minimized) { + mdiSubWindow->showShaded(); + } else { + mdiSubWindow->setWindowState(mdiSubWindow->windowState() & ~Qt::WindowMinimized); + } + } + break; + case TopLevelMode: { + QWidget *window = fw->window(); + if (window->isMinimized()) { + window->setWindowState(window->windowState() & ~Qt::WindowMinimized); + } else { + window->showMinimized(); + } + } + break; + default: + break; + } +} + +/* Applies UI mode changes using Timer-0 delayed signal + * signal to make sure the preferences dialog is closed and destroyed + * before a possible switch from docked mode to top-level mode happens. + * (The switch deletes the main window, which the preference dialog is + * a child of -> BOOM) */ + +void QDesignerWorkbench::applyUiSettings() +{ + if (m_uiSettingsChanged) { + m_uiSettingsChanged = false; + QTimer::singleShot(0, this, &QDesignerWorkbench::restoreUISettings); + } +} + +void QDesignerWorkbench::notifyUISettingsChanged() +{ + m_uiSettingsChanged = true; +} + +void QDesignerWorkbench::restoreUISettings() +{ + UIMode mode = QDesignerSettings(m_core).uiMode(); + switch (mode) { + case TopLevelMode: + switchToTopLevelMode(); + break; + case DockedMode: + switchToDockedMode(); + break; + + default: Q_ASSERT(0); + } + + ToolWindowFontSettings fontSettings = QDesignerSettings(m_core).toolWindowFont(); + const QFont &font = fontSettings.m_useFont ? fontSettings.m_font : qApp->font(); + + if (font == m_toolWindows.constFirst()->font()) + return; + + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + tw->setFont(font); +} + +void QDesignerWorkbench::handleCloseEvent(QCloseEvent *ev) +{ + ev->setAccepted(handleClose()); + if (ev->isAccepted()) + QMetaObject::invokeMethod(qDesigner, "quit", Qt::QueuedConnection); // We're going down! +} + +QDesignerToolWindow *QDesignerWorkbench::widgetBoxToolWindow() const +{ + return m_toolWindows.at(QDesignerToolWindow::WidgetBox); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/qdesigner_workbench.h b/src/designer/src/designer/qdesigner_workbench.h new file mode 100644 index 0000000..dfba06d --- /dev/null +++ b/src/designer/src/designer/qdesigner_workbench.h @@ -0,0 +1,178 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_WORKBENCH_H +#define QDESIGNER_WORKBENCH_H + +#include "designer_enums.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerActions; +class QDesignerToolWindow; +class QDesignerFormWindow; +class DockedMainWindow; +class QDesignerSettings; + +class QAction; +class QActionGroup; +class QDockWidget; +class QMenu; +class QMenuBar; +class QToolBar; +class QMdiSubWindow; +class QCloseEvent; +class QScreen; +class ToolBarManager; + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QDesignerFormWindowManagerInterface; +class QDesignerIntegration; + +class QDesignerWorkbench: public QObject +{ + Q_OBJECT + +public: + explicit QDesignerWorkbench(const QStringList &pluginPaths); + ~QDesignerWorkbench() override; + + UIMode mode() const; + + QDesignerFormEditorInterface *core() const; + QDesignerFormWindow *findFormWindow(QWidget *widget) const; + + QDesignerFormWindow *openForm(const QString &fileName, QString *errorMessage); + QDesignerFormWindow *openTemplate(const QString &templateFileName, + const QString &editorFileName, + QString *errorMessage); + + int toolWindowCount() const; + QDesignerToolWindow *toolWindow(int index) const; + + int formWindowCount() const; + QDesignerFormWindow *formWindow(int index) const; + + QDesignerActions *actionManager() const; + + QActionGroup *modeActionGroup() const; + + bool readInForm(const QString &fileName) const; + bool writeOutForm(QDesignerFormWindowInterface *formWindow, const QString &fileName) const; + bool saveForm(QDesignerFormWindowInterface *fw); + bool handleClose(); + bool readInBackup(); + void updateBackup(QDesignerFormWindowInterface* fwi); + void applyUiSettings(); + + bool suppressNewFormShow() const { return m_suppressNewFormShow; } + void setSuppressNewFormShow(bool v) { m_suppressNewFormShow = v; } + +signals: + void modeChanged(UIMode mode); + void initialized(); + +public slots: + void addFormWindow(QDesignerFormWindow *formWindow); + void removeFormWindow(QDesignerFormWindow *formWindow); + void bringAllToFront(); + void toggleFormMinimizationState(); + void showNewForm(); + void requestActivate(); + +private slots: + void switchToNeutralMode(); + void switchToDockedMode(); + void switchToTopLevelMode(); + void initializeCorePlugins(); + void handleCloseEvent(QCloseEvent *); + void slotFormWindowActivated(QDesignerFormWindow* fw); + void updateWindowMenu(QDesignerFormWindowInterface *fw); + void formWindowActionTriggered(QAction *a); + void adjustMDIFormPositions(); + void minimizationStateChanged(QDesignerFormWindowInterface *formWindow, bool minimized); + + void restoreUISettings(); + void notifyUISettingsChanged(); + void slotFileDropped(const QString &f); + +private: + QScreen *screen() const; + QRect availableFormGeometry() const; + QWidget *magicalParent(const QWidget *w) const; + Qt::WindowFlags magicalWindowFlags(const QWidget *widgetForFlags) const; + QDesignerFormWindowManagerInterface *formWindowManager() const; + void closeAllToolWindows(); + QDesignerToolWindow *widgetBoxToolWindow() const; + QDesignerFormWindow *loadForm(const QString &fileName, bool detectLineTermiantorMode, QString *errorMessage); + void resizeForm(QDesignerFormWindow *fw, const QWidget *mainContainer) const; + void saveGeometriesForModeChange(); + void saveGeometries(QDesignerSettings &settings) const; + + bool isFormWindowMinimized(const QDesignerFormWindow *fw); + void setFormWindowMinimized(QDesignerFormWindow *fw, bool minimized); + void saveSettings() const; + + QDesignerFormEditorInterface *m_core; + QDesignerIntegration *m_integration; + + QDesignerActions *m_actionManager; + QActionGroup *m_windowActions; + + QMenu *m_windowMenu; + + QPointer m_globalMenuBar; + + struct TopLevelData { + ToolBarManager *toolbarManager; + QList toolbars; + }; + TopLevelData m_topLevelData; + + UIMode m_mode = NeutralMode; + QPointer m_dockedMainWindow; + + QList m_toolWindows; + QList m_formWindows; + + QMenu *m_toolbarMenu; + + // Helper class to remember the position of a window while switching user + // interface modes. + class Position { + public: + explicit Position(const QDockWidget *dockWidget); + explicit Position(const QMdiSubWindow *mdiSubWindow); + explicit Position(const QWidget *topLevelWindow); + + void applyTo(QMdiSubWindow *mdiSubWindow, const QPoint &mdiAreaOffset) const; + void applyTo(QWidget *topLevelWindow, const QPoint &desktopTopLeft) const; + void applyTo(QDockWidget *dockWidget) const; + + QPoint position() const { return m_position; } + private: + bool m_minimized; + // Position referring to top-left corner (desktop in top-level mode or + // main window in MDI mode) + QPoint m_position; + }; + using PositionMap = QHash; + PositionMap m_Positions; + + enum State { StateInitializing, StateUp, StateClosing }; + State m_state = StateInitializing; + bool m_uiSettingsChanged = false; // UI mode changed in preference dialog, trigger delayed slot. + bool m_suppressNewFormShow = false; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_WORKBENCH_H diff --git a/src/designer/src/designer/saveformastemplate.cpp b/src/designer/src/designer/saveformastemplate.cpp new file mode 100644 index 0000000..e8bc644 --- /dev/null +++ b/src/designer/src/designer/saveformastemplate.cpp @@ -0,0 +1,135 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "saveformastemplate.h" +#include "qdesigner_settings.h" + +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +SaveFormAsTemplate::SaveFormAsTemplate(QDesignerFormEditorInterface *core, + QDesignerFormWindowInterface *formWindow, + QWidget *parent) + : QDialog(parent, Qt::Sheet), + m_core(core), + m_formWindow(formWindow) +{ + ui.setupUi(this); + + ui.templateNameEdit->setText(formWindow->mainContainer()->objectName()); + ui.templateNameEdit->selectAll(); + + ui.templateNameEdit->setFocus(); + + QStringList paths = QDesignerSettings(m_core).formTemplatePaths(); + ui.categoryCombo->addItems(paths); + ui.categoryCombo->addItem(tr("Add path...")); + m_addPathIndex = ui.categoryCombo->count() - 1; + connect(ui.templateNameEdit, &QLineEdit::textChanged, + this, &SaveFormAsTemplate::updateOKButton); + connect(ui.categoryCombo, &QComboBox::activated, + this, &SaveFormAsTemplate::checkToAddPath); +} + +SaveFormAsTemplate::~SaveFormAsTemplate() = default; + +void SaveFormAsTemplate::accept() +{ + const QString name = ui.templateNameEdit->text(); + QString templateFileName = ui.categoryCombo->currentText() + u'/' + name; + const auto extension = ".ui"_L1; + if (!templateFileName.endsWith(extension)) + templateFileName.append(extension); + QFile file(templateFileName); + + if (file.exists()) { + QMessageBox msgBox(QMessageBox::Information, tr("Template Exists"), + tr("A template with the name %1 already exists.\n" + "Do you want overwrite the template?").arg(name), QMessageBox::Cancel, m_formWindow); + msgBox.setDefaultButton(QMessageBox::Cancel); + QPushButton *overwriteButton = msgBox.addButton(tr("Overwrite Template"), QMessageBox::AcceptRole); + msgBox.exec(); + if (msgBox.clickedButton() != overwriteButton) + return; + } + + while (!file.open(QFile::WriteOnly)) { + if (QMessageBox::information(m_formWindow, tr("Open Error"), + tr("There was an error opening template %1 for writing. Reason: %2") + .arg(name, file.errorString()), + QMessageBox::Retry|QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Cancel) { + return; + } + } + + const QString origName = m_formWindow->fileName(); + // ensure m_formWindow->contents() will convert properly resource paths to relative paths + // (relative to template location, not to the current form location) + m_formWindow->setFileName(templateFileName); + QByteArray ba = m_formWindow->contents().toUtf8(); + m_formWindow->setFileName(origName); + while (file.write(ba) != ba.size()) { + if (QMessageBox::information(m_formWindow, tr("Write Error"), + tr("There was an error writing the template %1 to disk. Reason: %2") + .arg(name, file.errorString()), + QMessageBox::Retry|QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Cancel) { + file.close(); + file.remove(); + return; + } + file.reset(); + } + // update the list of places too... + QStringList sl; + for (int i = 0; i < m_addPathIndex; ++i) + sl << ui.categoryCombo->itemText(i); + + QDesignerSettings(m_core).setFormTemplatePaths(sl); + + QDialog::accept(); +} + +void SaveFormAsTemplate::updateOKButton(const QString &str) +{ + QPushButton *okButton = ui.buttonBox->button(QDialogButtonBox::Ok); + okButton->setEnabled(!str.isEmpty()); +} + +QString SaveFormAsTemplate::chooseTemplatePath(QWidget *parent) +{ + QString rc = QFileDialog::getExistingDirectory(parent, + tr("Pick a directory to save templates in")); + if (rc.isEmpty()) + return rc; + + if (rc.endsWith(QDir::separator())) + rc.remove(rc.size() - 1, 1); + return rc; +} + +void SaveFormAsTemplate::checkToAddPath(int itemIndex) +{ + if (itemIndex != m_addPathIndex) + return; + + const QString dir = chooseTemplatePath(this); + if (dir.isEmpty()) { + ui.categoryCombo->setCurrentIndex(0); + return; + } + + ui.categoryCombo->insertItem(m_addPathIndex, dir); + ui.categoryCombo->setCurrentIndex(m_addPathIndex); + ++m_addPathIndex; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/designer/saveformastemplate.h b/src/designer/src/designer/saveformastemplate.h new file mode 100644 index 0000000..2a76306 --- /dev/null +++ b/src/designer/src/designer/saveformastemplate.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SAVEFORMASTEMPLATE_H +#define SAVEFORMASTEMPLATE_H + +#include "ui_saveformastemplate.h" + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class SaveFormAsTemplate: public QDialog +{ + Q_OBJECT +public: + explicit SaveFormAsTemplate(QDesignerFormEditorInterface *m_core, + QDesignerFormWindowInterface *formWindow, + QWidget *parent = nullptr); + ~SaveFormAsTemplate() override; + +private slots: + void accept() override; + void updateOKButton(const QString &str); + void checkToAddPath(int itemIndex); + +private: + static QString chooseTemplatePath(QWidget *parent); + + Ui::SaveFormAsTemplate ui; + QDesignerFormEditorInterface *m_core; + QDesignerFormWindowInterface *m_formWindow; + int m_addPathIndex; +}; + +QT_END_NAMESPACE + +#endif // SAVEFORMASTEMPLATE_H diff --git a/src/designer/src/designer/saveformastemplate.ui b/src/designer/src/designer/saveformastemplate.ui new file mode 100644 index 0000000..e77e4c2 --- /dev/null +++ b/src/designer/src/designer/saveformastemplate.ui @@ -0,0 +1,130 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + SaveFormAsTemplate + + + Save Form As Template + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + &Name: + + + Qt::AutoText + + + templateNameEdit + + + + + + + + 222 + 0 + + + + + + + QLineEdit::Normal + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + &Category: + + + Qt::AutoText + + + categoryCombo + + + + + + + + + + + + QFrame::HLine + + + QFrame::Sunken + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SaveFormAsTemplate + accept() + + + 256 + 124 + + + 113 + 143 + + + + + buttonBox + rejected() + SaveFormAsTemplate + reject() + + + 332 + 127 + + + 372 + 147 + + + + + diff --git a/src/designer/src/designer/uifile.icns b/src/designer/src/designer/uifile.icns new file mode 100644 index 0000000000000000000000000000000000000000..f9d84d506ad40ef019d513002a642cdaa7d3f6f5 GIT binary patch literal 157015 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

    MXr&TzFPK5u16U;jVigRt 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 arn(}sVaJ3G9!GH3_TM_ry*!8a^Zx*6q&!yu literal 0 HcmV?d00001 diff --git a/src/designer/src/designer/versiondialog.cpp b/src/designer/src/designer/versiondialog.cpp new file mode 100644 index 0000000..c9a77e1 --- /dev/null +++ b/src/designer/src/designer/versiondialog.cpp @@ -0,0 +1,159 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "versiondialog.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class VersionLabel : public QLabel +{ + Q_OBJECT +public: + VersionLabel(QWidget *parent = nullptr); + +signals: + void triggered(); + +protected: + void mousePressEvent(QMouseEvent *me) override; + void mouseMoveEvent(QMouseEvent *me) override; + void mouseReleaseEvent(QMouseEvent *me) override; + void paintEvent(QPaintEvent *pe) override; +private: + QList hitPoints; + QList missPoints; + QPainterPath m_path; + bool secondStage = false; + bool m_pushed = false; +}; + +VersionLabel::VersionLabel(QWidget *parent) + : QLabel(parent) +{ + QPixmap pixmap(u":/qt-project.org/designer/images/designer.png"_s); + pixmap.setDevicePixelRatio(devicePixelRatioF()); + setPixmap(pixmap); + hitPoints.append(QPoint(56, 25)); + hitPoints.append(QPoint(29, 55)); + hitPoints.append(QPoint(56, 87)); + hitPoints.append(QPoint(82, 55)); + hitPoints.append(QPoint(58, 56)); + + secondStage = false; + m_pushed = false; +} + +void VersionLabel::mousePressEvent(QMouseEvent *me) +{ + if (me->button() == Qt::LeftButton) { + if (!secondStage) { + m_path = QPainterPath(me->pos()); + } else { + m_pushed = true; + update(); + } + } +} + +void VersionLabel::mouseMoveEvent(QMouseEvent *me) +{ + if (me->buttons() & Qt::LeftButton) + if (!secondStage) + m_path.lineTo(me->pos()); +} + +void VersionLabel::mouseReleaseEvent(QMouseEvent *me) +{ + if (me->button() == Qt::LeftButton) { + if (!secondStage) { + m_path.lineTo(me->pos()); + bool gotIt = true; + for (const QPoint &pt : std::as_const(hitPoints)) { + if (!m_path.contains(pt)) { + gotIt = false; + break; + } + } + if (gotIt) { + for (const QPoint &pt : std::as_const(missPoints)) { + if (m_path.contains(pt)) { + gotIt = false; + break; + } + } + } + if (gotIt && !secondStage) { + secondStage = true; + m_path = QPainterPath(); + update(); + } + } else { + m_pushed = false; + update(); + emit triggered(); + } + } +} + +void VersionLabel::paintEvent(QPaintEvent *pe) +{ + if (secondStage) { + QPainter p(this); + QStyleOptionButton opt; + opt.initFrom(this); + if (!m_pushed) + opt.state |= QStyle::State_Raised; + else + opt.state |= QStyle::State_Sunken; + opt.state &= ~QStyle::State_HasFocus; + style()->drawControl(QStyle::CE_PushButtonBevel, &opt, &p, this); + } + QLabel::paintEvent(pe); +} + +VersionDialog::VersionDialog(QWidget *parent) + : QDialog(parent +#ifdef Q_OS_MACOS + , Qt::Tool +#endif + ) +{ + setWindowFlag(Qt::MSWindowsFixedSizeDialogHint, true); + QGridLayout *layout = new QGridLayout(this); + VersionLabel *label = new VersionLabel(this); + QLabel *lbl = new QLabel(this); + QString version = tr("

    %1



    Version %2"); + version = version.arg(tr("Qt Widgets Designer")).arg(QLatin1StringView(QT_VERSION_STR)); + version.append(tr("
    Qt Widgets Designer is a graphical user interface designer for Qt applications.
    ")); + + lbl->setText( + tr("%1
    Copyright (C) The Qt Company Ltd. and other contributors.").arg(version)); + + lbl->setWordWrap(true); + lbl->setOpenExternalLinks(true); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this); + connect(buttonBox , &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(label, &VersionLabel::triggered, this, &QDialog::accept); + layout->addWidget(label, 0, 0, 1, 1); + layout->addWidget(lbl, 0, 1, 4, 4); + layout->addWidget(buttonBox, 4, 2, 1, 1); +} + +QT_END_NAMESPACE + +#include "versiondialog.moc" diff --git a/src/designer/src/designer/versiondialog.h b/src/designer/src/designer/versiondialog.h new file mode 100644 index 0000000..4f7f8e4 --- /dev/null +++ b/src/designer/src/designer/versiondialog.h @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef VERSIONDIALOG_H +#define VERSIONDIALOG_H + +#include + +QT_BEGIN_NAMESPACE + +class VersionDialog : public QDialog +{ + Q_OBJECT +public: + explicit VersionDialog(QWidget *parent); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/lib/CMakeLists.txt b/src/designer/src/lib/CMakeLists.txt new file mode 100644 index 0000000..5513f2b --- /dev/null +++ b/src/designer/src/lib/CMakeLists.txt @@ -0,0 +1,485 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +##################################################################### +## Designer Module: +##################################################################### + +qt_internal_add_module(Designer + PLUGIN_TYPES designer + SOURCES + ../../../shared/deviceskin/deviceskin.cpp ../../../shared/deviceskin/deviceskin_p.h + ../../../shared/findwidget/abstractfindwidget.cpp ../../../shared/findwidget/abstractfindwidget_p.h + ../../../shared/findwidget/itemviewfindwidget.cpp ../../../shared/findwidget/itemviewfindwidget_p.h + ../../../shared/findwidget/texteditfindwidget.cpp ../../../shared/findwidget/texteditfindwidget_p.h + ../../../shared/qtgradienteditor/qtcolorbutton.cpp ../../../shared/qtgradienteditor/qtcolorbutton_p.h + ../../../shared/qtgradienteditor/qtcolorline.cpp ../../../shared/qtgradienteditor/qtcolorline_p.h + ../../../shared/qtgradienteditor/qtgradientdialog.cpp ../../../shared/qtgradienteditor/qtgradientdialog_p.h + ../../../shared/qtgradienteditor/qtgradienteditor.cpp ../../../shared/qtgradienteditor/qtgradienteditor_p.h + ../../../shared/qtgradienteditor/qtgradientmanager.cpp ../../../shared/qtgradienteditor/qtgradientmanager_p.h + ../../../shared/qtgradienteditor/qtgradientstopscontroller.cpp ../../../shared/qtgradienteditor/qtgradientstopscontroller_p.h + ../../../shared/qtgradienteditor/qtgradientstopsmodel.cpp ../../../shared/qtgradienteditor/qtgradientstopsmodel_p.h + ../../../shared/qtgradienteditor/qtgradientstopswidget.cpp ../../../shared/qtgradienteditor/qtgradientstopswidget_p.h + ../../../shared/qtgradienteditor/qtgradientutils.cpp ../../../shared/qtgradienteditor/qtgradientutils_p.h + ../../../shared/qtgradienteditor/qtgradientview.cpp ../../../shared/qtgradienteditor/qtgradientview_p.h + ../../../shared/qtgradienteditor/qtgradientviewdialog.cpp ../../../shared/qtgradienteditor/qtgradientviewdialog_p.h + ../../../shared/qtgradienteditor/qtgradientwidget.cpp ../../../shared/qtgradienteditor/qtgradientwidget_p.h + extension/default_extensionfactory.cpp extension/default_extensionfactory.h + extension/extension_global.h + extension/extension.cpp extension/extension.h + extension/qextensionmanager.cpp extension/qextensionmanager.h + sdk/abstractactioneditor.cpp sdk/abstractactioneditor.h + sdk/abstractdialoggui.cpp sdk/abstractdialoggui_p.h + sdk/abstractdnditem.h + sdk/abstractformeditor.cpp sdk/abstractformeditor.h + sdk/abstractformeditorplugin.cpp sdk/abstractformeditorplugin.h + sdk/abstractformwindow.cpp sdk/abstractformwindow.h + sdk/abstractformwindowcursor.cpp sdk/abstractformwindowcursor.h + sdk/abstractformwindowmanager.cpp sdk/abstractformwindowmanager.h + sdk/abstractformwindowtool.cpp sdk/abstractformwindowtool.h + sdk/abstractintegration.cpp sdk/abstractintegration.h + sdk/abstractintrospection.cpp sdk/abstractintrospection_p.h + sdk/abstractlanguage.h + sdk/abstractmetadatabase.cpp sdk/abstractmetadatabase.h + sdk/abstractnewformwidget.cpp sdk/abstractnewformwidget.h + sdk/abstractobjectinspector.cpp sdk/abstractobjectinspector.h + sdk/abstractoptionspage.h + sdk/abstractpromotioninterface.cpp sdk/abstractpromotioninterface.h + sdk/abstractpropertyeditor.cpp sdk/abstractpropertyeditor.h + sdk/abstractresourcebrowser.cpp sdk/abstractresourcebrowser.h + sdk/abstractsettings.h + sdk/abstractwidgetbox.cpp sdk/abstractwidgetbox.h + sdk/abstractwidgetdatabase.cpp sdk/abstractwidgetdatabase.h + sdk/abstractwidgetfactory.cpp sdk/abstractwidgetfactory.h + sdk/container.h + sdk/dynamicpropertysheet.h + sdk/extrainfo.cpp sdk/extrainfo.h + sdk/layoutdecoration.h + sdk/membersheet.h + sdk/propertysheet.h + sdk/sdk_global.h + sdk/taskmenu.cpp sdk/taskmenu.h + shared/actioneditor.cpp shared/actioneditor_p.h + shared/actionprovider_p.h + shared/actionrepository.cpp shared/actionrepository_p.h + shared/codedialog.cpp shared/codedialog_p.h + shared/connectionedit.cpp shared/connectionedit_p.h + shared/csshighlighter.cpp shared/csshighlighter_p.h + shared/deviceprofile.cpp shared/deviceprofile_p.h + shared/dialoggui.cpp shared/dialoggui_p.h + shared/extensionfactory_p.h + shared/formlayoutmenu.cpp shared/formlayoutmenu_p.h + shared/formwindowbase.cpp shared/formwindowbase_p.h + shared/grid.cpp shared/grid_p.h + shared/gridpanel.cpp shared/gridpanel_p.h + shared/htmlhighlighter.cpp shared/htmlhighlighter_p.h + shared/iconloader.cpp shared/iconloader_p.h + shared/iconselector.cpp shared/iconselector_p.h + shared/invisible_widget.cpp shared/invisible_widget_p.h + shared/layout.cpp shared/layout_p.h + shared/layoutinfo.cpp shared/layoutinfo_p.h + shared/metadatabase.cpp shared/metadatabase_p.h + shared/morphmenu.cpp shared/morphmenu_p.h + shared/newactiondialog.cpp shared/newactiondialog_p.h + shared/newformwidget.cpp shared/newformwidget_p.h + shared/orderdialog.cpp shared/orderdialog_p.h + shared/plaintexteditor.cpp shared/plaintexteditor_p.h + shared/plugindialog.cpp shared/plugindialog_p.h + shared/pluginmanager.cpp shared/pluginmanager_p.h + shared/previewconfigurationwidget.cpp shared/previewconfigurationwidget_p.h + shared/previewmanager.cpp shared/previewmanager_p.h + shared/promotionmodel.cpp shared/promotionmodel_p.h + shared/promotiontaskmenu.cpp shared/promotiontaskmenu_p.h + shared/propertylineedit.cpp shared/propertylineedit_p.h + shared/qdesigner_command.cpp shared/qdesigner_command_p.h + shared/qdesigner_command2.cpp shared/qdesigner_command2_p.h + shared/qdesigner_dnditem.cpp shared/qdesigner_dnditem_p.h + shared/qdesigner_dockwidget.cpp shared/qdesigner_dockwidget_p.h + shared/qdesigner_formbuilder.cpp shared/qdesigner_formbuilder_p.h + shared/qdesigner_formeditorcommand.cpp shared/qdesigner_formeditorcommand_p.h + shared/qdesigner_formwindowcommand.cpp shared/qdesigner_formwindowcommand_p.h + shared/qdesigner_formwindowmanager.cpp shared/qdesigner_formwindowmanager_p.h + shared/qdesigner_introspection.cpp shared/qdesigner_introspection_p.h + shared/qdesigner_membersheet.cpp shared/qdesigner_membersheet_p.h + shared/qdesigner_menu.cpp shared/qdesigner_menu_p.h + shared/qdesigner_menubar.cpp shared/qdesigner_menubar_p.h + shared/qdesigner_objectinspector.cpp shared/qdesigner_objectinspector_p.h + shared/qdesigner_promotion.cpp shared/qdesigner_promotion_p.h + shared/qdesigner_promotiondialog.cpp shared/qdesigner_promotiondialog_p.h + shared/qdesigner_propertycommand.cpp + shared/qdesigner_propertyeditor.cpp shared/qdesigner_propertyeditor_p.h + shared/qdesigner_propertysheet.cpp shared/qdesigner_propertysheet_p.h + shared/qdesigner_qsettings.cpp shared/qdesigner_qsettings_p.h + shared/qdesigner_stackedbox.cpp shared/qdesigner_stackedbox_p.h + shared/qdesigner_tabwidget.cpp shared/qdesigner_tabwidget_p.h + shared/qdesigner_taskmenu.cpp shared/qdesigner_taskmenu_p.h + shared/qdesigner_toolbar.cpp shared/qdesigner_toolbar_p.h + shared/qdesigner_toolbox.cpp shared/qdesigner_toolbox_p.h + shared/qdesigner_utils.cpp shared/qdesigner_utils_p.h + shared/qdesigner_widget.cpp shared/qdesigner_widget_p.h + shared/qdesigner_widgetbox.cpp shared/qdesigner_widgetbox_p.h + shared/qdesigner_widgetitem.cpp shared/qdesigner_widgetitem_p.h + shared/qlayout_widget.cpp shared/qlayout_widget_p.h + shared/qsimpleresource.cpp shared/qsimpleresource_p.h + shared/qtresourceeditordialog.cpp shared/qtresourceeditordialog_p.h + shared/qtresourcemodel.cpp shared/qtresourcemodel_p.h + shared/qtresourceview.cpp shared/qtresourceview_p.h + shared/rcc.cpp shared/rcc_p.h + shared/richtexteditor.cpp shared/richtexteditor_p.h + shared/selectsignaldialog.cpp shared/selectsignaldialog_p.h + shared/shared_enums_p.h + shared/shared_global_p.h + shared/shared_settings.cpp shared/shared_settings_p.h + shared/sheet_delegate.cpp shared/sheet_delegate_p.h + shared/signalslotdialog.cpp shared/signalslotdialog_p.h + shared/spacer_widget.cpp shared/spacer_widget_p.h + shared/stylesheeteditor.cpp shared/stylesheeteditor_p.h + shared/textpropertyeditor.cpp shared/textpropertyeditor_p.h + shared/widgetdatabase.cpp shared/widgetdatabase_p.h + shared/widgetfactory.cpp shared/widgetfactory_p.h + shared/zoomwidget.cpp shared/zoomwidget_p.h + uilib/uilib_global.h + uilib/abstractformbuilder.cpp uilib/abstractformbuilder.h + uilib/formbuilder.cpp uilib/formbuilder.h + uilib/formbuilderextra.cpp uilib/formbuilderextra_p.h + uilib/properties.cpp uilib/properties_p.h + uilib/resourcebuilder.cpp uilib/resourcebuilder_p.h + uilib/textbuilder.cpp uilib/textbuilder_p.h + uilib/ui4.cpp uilib/ui4_p.h + components/qdesigner_components.h + components/qdesigner_components_global.h + NO_UNITY_BUILD_SOURCES + shared/qdesigner_command.cpp # redefinition of 'QMetaTypeId>' (from morphmenu.cpp) + # and recursiveUpdate (from formwindowbase.cpp) + uilib/abstractformbuilder.cpp # using namespace QFormInternal/redefinition of 'QMetaTypeId>' (from morphmenu.cpp) + uilib/ui4.cpp # using namespace QFormInternal + DEFINES + QDESIGNER_EXTENSION_LIBRARY + QDESIGNER_SDK_LIBRARY + QDESIGNER_SHARED_LIBRARY + QDESIGNER_UILIB_LIBRARY + QT_DESIGNER + QT_USE_QSTRINGBUILDER + INCLUDE_DIRECTORIES + ../../../shared/deviceskin + ../../../shared/findwidget + ../../../shared/qtgradienteditor + extension + sdk + shared + uilib + LIBRARIES + Qt::CorePrivate + Qt::GuiPrivate + Qt::UiPlugin + Qt::WidgetsPrivate + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui + Qt::UiPlugin + Qt::Widgets + Qt::Xml + PRIVATE_MODULE_INTERFACE + Qt::CorePrivate + Qt::GuiPrivate + Qt::WidgetsPrivate + ENABLE_AUTOGEN_TOOLS + uic + PRECOMPILED_HEADER + "lib_pch.h" + NO_GENERATE_CPP_EXPORTS +) + +set(ui_sources + ../../../shared/qtgradienteditor/qtgradientdialog.ui + ../../../shared/qtgradienteditor/qtgradienteditor.ui + ../../../shared/qtgradienteditor/qtgradientview.ui + ../../../shared/qtgradienteditor/qtgradientviewdialog.ui + shared/addlinkdialog.ui + shared/formlayoutrowdialog.ui + shared/gridpanel.ui + shared/previewconfigurationwidget.ui + shared/newactiondialog.ui + shared/newformwidget.ui + shared/orderdialog.ui + shared/plugindialog.ui + shared/qtresourceeditordialog.ui + shared/selectsignaldialog.ui + shared/signalslotdialog.ui +) + +# Work around QTBUG-95305 +if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" AND CMAKE_CROSS_CONFIGS) + qt6_wrap_ui(ui_sources_processed ${ui_sources}) +else() + set(ui_sources_processed ${ui_sources}) +endif() +target_sources(Designer PRIVATE ${ui_sources_processed}) + +# Resources: +set(ClamshellPhone_resource_files + "../../../shared/deviceskin/skins/ClamshellPhone.skin" +) + +qt_internal_add_resource(Designer "ClamshellPhone" + PREFIX + "/skins" + BASE + "../../../shared/deviceskin/skins" + FILES + ${ClamshellPhone_resource_files} +) +set(SmartPhone2_resource_files + "../../../shared/deviceskin/skins/SmartPhone2.skin" +) + +qt_internal_add_resource(Designer "SmartPhone2" + PREFIX + "/skins" + BASE + "../../../shared/deviceskin/skins" + FILES + ${SmartPhone2_resource_files} +) +set(SmartPhone_resource_files + "../../../shared/deviceskin/skins/SmartPhone.skin" +) + +qt_internal_add_resource(Designer "SmartPhone" + PREFIX + "/skins" + BASE + "../../../shared/deviceskin/skins" + FILES + ${SmartPhone_resource_files} +) +set(SmartPhoneWithButtons_resource_files + "../../../shared/deviceskin/skins/SmartPhoneWithButtons.skin" +) + +qt_internal_add_resource(Designer "SmartPhoneWithButtons" + PREFIX + "/skins" + BASE + "../../../shared/deviceskin/skins" + FILES + ${SmartPhoneWithButtons_resource_files} +) +set(TouchscreenPhone_resource_files + "../../../shared/deviceskin/skins/TouchscreenPhone.skin" +) + +qt_internal_add_resource(Designer "TouchscreenPhone" + PREFIX + "/skins" + BASE + "../../../shared/deviceskin/skins" + FILES + ${TouchscreenPhone_resource_files} +) +set(PortableMedia_resource_files + "../../../shared/deviceskin/skins/PortableMedia.skin" +) + +qt_internal_add_resource(Designer "PortableMedia" + PREFIX + "/skins" + BASE + "../../../shared/deviceskin/skins" + FILES + ${PortableMedia_resource_files} +) +set(S60-QVGA-Candybar_resource_files + "../../../shared/deviceskin/skins/S60-QVGA-Candybar.skin" +) + +qt_internal_add_resource(Designer "S60-QVGA-Candybar" + PREFIX + "/skins" + BASE + "../../../shared/deviceskin/skins" + FILES + ${S60-QVGA-Candybar_resource_files} +) +set(S60-nHD-Touchscreen_resource_files + "../../../shared/deviceskin/skins/S60-nHD-Touchscreen.skin" +) + +qt_internal_add_resource(Designer "S60-nHD-Touchscreen" + PREFIX + "/skins" + BASE + "../../../shared/deviceskin/skins" + FILES + ${S60-nHD-Touchscreen_resource_files} +) +set(findwidget_resource_files + "../../../shared/findwidget/images/mac/closetab.png" + "../../../shared/findwidget/images/mac/next.png" + "../../../shared/findwidget/images/mac/previous.png" + "../../../shared/findwidget/images/mac/searchfind.png" + "../../../shared/findwidget/images/win/closetab.png" + "../../../shared/findwidget/images/win/next.png" + "../../../shared/findwidget/images/win/previous.png" + "../../../shared/findwidget/images/win/searchfind.png" + "../../../shared/findwidget/images/wrap.png" +) + +qt_internal_add_resource(Designer "findwidget" + PREFIX + "/qt-project.org/shared" + BASE + "../../../shared/findwidget" + FILES + ${findwidget_resource_files} +) +set(qtgradienteditor_resource_files + "../../../shared/qtgradienteditor/images/down.png" + "../../../shared/qtgradienteditor/images/edit.png" + "../../../shared/qtgradienteditor/images/editdelete.png" + "../../../shared/qtgradienteditor/images/minus.png" + "../../../shared/qtgradienteditor/images/plus.png" + "../../../shared/qtgradienteditor/images/spreadpad.png" + "../../../shared/qtgradienteditor/images/spreadreflect.png" + "../../../shared/qtgradienteditor/images/spreadrepeat.png" + "../../../shared/qtgradienteditor/images/typeconical.png" + "../../../shared/qtgradienteditor/images/typelinear.png" + "../../../shared/qtgradienteditor/images/typeradial.png" + "../../../shared/qtgradienteditor/images/up.png" + "../../../shared/qtgradienteditor/images/zoomin.png" + "../../../shared/qtgradienteditor/images/zoomout.png" +) + +qt_internal_add_resource(Designer "qtgradienteditor" + PREFIX + "/qt-project.org/qtgradienteditor" + BASE + "../../../shared/qtgradienteditor" + FILES + ${qtgradienteditor_resource_files} +) +set(shared_resource_files + "shared/defaultgradients.xml" + "shared/icon-naming-spec.txt" + "shared/templates/forms/240x320/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/240x320/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/320x240/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/320x240/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/480x640/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/480x640/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/640x480/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/640x480/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/Dialog_without_Buttons.ui" + "shared/templates/forms/Main_Window.ui" + "shared/templates/forms/Widget.ui" +) + +qt_internal_add_resource(Designer "shared" + PREFIX + "/qt-project.org/designer" + BASE + "shared" + FILES + ${shared_resource_files} +) + +# MODULE = "designer" + +## Scopes: +##################################################################### + +qt_internal_extend_target(Designer CONDITION TARGET Qt::OpenGLWidgets + PUBLIC_LIBRARIES + Qt::OpenGLWidgets +) + +qt_internal_extend_target(Designer CONDITION NOT QT_BUILD_SHARED_LIBS + DEFINES + QT_DESIGNER_STATIC + SOURCES + ../../../shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp ../../../shared/qtpropertybrowser/qtbuttonpropertybrowser_p.h + ../../../shared/qtpropertybrowser/qteditorfactory.cpp ../../../shared/qtpropertybrowser/qteditorfactory_p.h + ../../../shared/qtpropertybrowser/qtgroupboxpropertybrowser.cpp ../../../shared/qtpropertybrowser/qtgroupboxpropertybrowser_p.h + ../../../shared/qtpropertybrowser/qtpropertybrowser.cpp ../../../shared/qtpropertybrowser/qtpropertybrowser_p.h + ../../../shared/qtpropertybrowser/qtpropertybrowserutils.cpp ../../../shared/qtpropertybrowser/qtpropertybrowserutils_p.h + ../../../shared/qtpropertybrowser/qtpropertymanager.cpp ../../../shared/qtpropertybrowser/qtpropertymanager_p.h + ../../../shared/qtpropertybrowser/qttreepropertybrowser.cpp ../../../shared/qtpropertybrowser/qttreepropertybrowser_p.h + ../../../shared/qtpropertybrowser/qtvariantproperty.cpp ../../../shared/qtpropertybrowser/qtvariantproperty_p.h + INCLUDE_DIRECTORIES + ../../../shared/qtpropertybrowser +) + +if(TARGET zstd::libzstd) + qt_internal_disable_find_package_global_promotion(zstd::libzstd) +endif() +if(TARGET zstd::libzstd_shared) + qt_internal_disable_find_package_global_promotion(zstd::libzstd_shared) +endif() +if(TARGET zstd::libzstd_static) + qt_internal_disable_find_package_global_promotion(zstd::libzstd_static) +endif() +if(NOT TARGET WrapZSTD::WrapZSTD) + qt_find_package(WrapZSTD 1.3 + PROVIDED_TARGETS + WrapZSTD::WrapZSTD + zstd::libzstd + zstd::libzstd_static + zstd::libzstd_shared + ) +endif() + +qt_internal_extend_target(Designer CONDITION QT_FEATURE_zstd + LIBRARIES + WrapZSTD::WrapZSTD +) + +if(NOT QT_BUILD_SHARED_LIBS) + # Resources: + set(qtpropertybrowser_resource_files + "../../../shared/qtpropertybrowser/images/cursor-arrow.png" + "../../../shared/qtpropertybrowser/images/cursor-busy.png" + "../../../shared/qtpropertybrowser/images/cursor-closedhand.png" + "../../../shared/qtpropertybrowser/images/cursor-cross.png" + "../../../shared/qtpropertybrowser/images/cursor-forbidden.png" + "../../../shared/qtpropertybrowser/images/cursor-hand.png" + "../../../shared/qtpropertybrowser/images/cursor-hsplit.png" + "../../../shared/qtpropertybrowser/images/cursor-ibeam.png" + "../../../shared/qtpropertybrowser/images/cursor-openhand.png" + "../../../shared/qtpropertybrowser/images/cursor-sizeall.png" + "../../../shared/qtpropertybrowser/images/cursor-sizeb.png" + "../../../shared/qtpropertybrowser/images/cursor-sizef.png" + "../../../shared/qtpropertybrowser/images/cursor-sizeh.png" + "../../../shared/qtpropertybrowser/images/cursor-sizev.png" + "../../../shared/qtpropertybrowser/images/cursor-uparrow.png" + "../../../shared/qtpropertybrowser/images/cursor-vsplit.png" + "../../../shared/qtpropertybrowser/images/cursor-wait.png" + "../../../shared/qtpropertybrowser/images/cursor-whatsthis.png" + ) + + qt_internal_add_resource(Designer "qtpropertybrowser" + PREFIX + "/qt-project.org/qtpropertybrowser" + BASE + "../../../shared/qtpropertybrowser" + FILES + ${qtpropertybrowser_resource_files} + ) +endif() + +qt_internal_extend_target(Designer CONDITION QT_BUILD_SHARED_LIBS + SOURCES + ../../../shared/qtpropertybrowser/qtpropertybrowserutils.cpp ../../../shared/qtpropertybrowser/qtpropertybrowserutils_p.h + INCLUDE_DIRECTORIES + ../../../shared/qtpropertybrowser +) + +qt_internal_extend_target(Designer CONDITION QT_FEATURE_opengl + LIBRARIES + Qt::OpenGL +) + +# UiPlugin module generates deprecated header files for Designer. +qt_internal_add_sync_header_dependencies(Designer UiPlugin) diff --git a/src/designer/src/lib/components/qdesigner_components.h b/src/designer/src/lib/components/qdesigner_components.h new file mode 100644 index 0000000..33ee90d --- /dev/null +++ b/src/designer/src/lib/components/qdesigner_components.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_COMPONENTS_H +#define QDESIGNER_COMPONENTS_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QObject; +class QWidget; + +class QDesignerFormEditorInterface; +class QDesignerWidgetBoxInterface; +class QDesignerPropertyEditorInterface; +class QDesignerObjectInspectorInterface; +class QDesignerActionEditorInterface; + +class QDESIGNER_COMPONENTS_EXPORT QDesignerComponents +{ +public: + static void initializeResources(); + static void initializePlugins(QDesignerFormEditorInterface *core); + + static QDesignerFormEditorInterface *createFormEditor(QObject *parent); + static QDesignerFormEditorInterface * + createFormEditorWithPluginPaths(const QStringList &pluginPaths, + QObject *parent); + static QDesignerWidgetBoxInterface *createWidgetBox(QDesignerFormEditorInterface *core, QWidget *parent); + static QDesignerPropertyEditorInterface *createPropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent); + static QDesignerObjectInspectorInterface *createObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent); + static QDesignerActionEditorInterface *createActionEditor(QDesignerFormEditorInterface *core, QWidget *parent); + + static QObject *createTaskMenu(QDesignerFormEditorInterface *core, QObject *parent); + static QWidget *createResourceEditor(QDesignerFormEditorInterface *core, QWidget *parent); + static QWidget *createSignalSlotEditor(QDesignerFormEditorInterface *core, QWidget *parent); + + static QStringList defaultPluginPaths(); +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_COMPONENTS_H diff --git a/src/designer/src/lib/components/qdesigner_components_global.h b/src/designer/src/lib/components/qdesigner_components_global.h new file mode 100644 index 0000000..ced3bd1 --- /dev/null +++ b/src/designer/src/lib/components/qdesigner_components_global.h @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_COMPONENTS_GLOBAL_H +#define QDESIGNER_COMPONENTS_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#define QDESIGNER_COMPONENTS_EXTERN Q_DECL_EXPORT +#define QDESIGNER_COMPONENTS_IMPORT Q_DECL_IMPORT + +#ifdef QT_DESIGNER_STATIC +# define QDESIGNER_COMPONENTS_EXPORT +#elif defined(QDESIGNER_COMPONENTS_LIBRARY) +# define QDESIGNER_COMPONENTS_EXPORT QDESIGNER_COMPONENTS_EXTERN +#else +# define QDESIGNER_COMPONENTS_EXPORT QDESIGNER_COMPONENTS_IMPORT +#endif + + +QT_END_NAMESPACE +#endif // QDESIGNER_COMPONENTS_GLOBAL_H diff --git a/src/designer/src/lib/extension/default_extensionfactory.cpp b/src/designer/src/lib/extension/default_extensionfactory.cpp new file mode 100644 index 0000000..87a8d15 --- /dev/null +++ b/src/designer/src/lib/extension/default_extensionfactory.cpp @@ -0,0 +1,137 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "default_extensionfactory.h" +#include "qextensionmanager.h" +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QExtensionFactory + + \brief The QExtensionFactory class allows you to create a factory + that is able to make instances of custom extensions in Qt + Designer. + + \inmodule QtDesigner + + In \QD the extensions are not created until they are required. For + that reason, when implementing a custom extension, you must also + create a QExtensionFactory, i.e. a class that is able to make an + instance of your extension, and register it using \QD's \l + {QExtensionManager}{extension manager}. + + The QExtensionManager class provides extension management + facilities for \QD. When an extension is required, Qt + Designer's \l {QExtensionManager}{extension manager} will run + through all its registered factories calling + QExtensionFactory::createExtension() for each until the first one + that is able to create a requested extension for the selected + object, is found. This factory will then make an instance of the + extension. + + There are four available types of extensions in \QD: + QDesignerContainerExtension , QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and QDesignerTaskMenuExtension. Qt + Designer's behavior is the same whether the requested extension is + associated with a multi page container, a member sheet, a property + sheet or a task menu. + + You can either create a new QExtensionFactory and reimplement the + QExtensionFactory::createExtension() function. For example: + + \snippet lib/tools_designer_src_lib_extension_default_extensionfactory.cpp 0 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create your extension as well. For example: + + \snippet lib/tools_designer_src_lib_extension_default_extensionfactory.cpp 1 + + For a complete example using the QExtensionFactory class, see the + \l {taskmenuextension}{Task Menu Extension example}. The + example shows how to create a custom widget plugin for Qt + Designer, and how to use the QDesignerTaskMenuExtension class + to add custom items to \QD's task menu. + + \sa QExtensionManager, QAbstractExtensionFactory +*/ + +/*! + Constructs an extension factory with the given \a parent. +*/ +QExtensionFactory::QExtensionFactory(QExtensionManager *parent) + : QObject(parent) +{ +} + +/*! + Returns the extension specified by \a iid for the given \a object. + + \sa createExtension() +*/ + +QObject *QExtensionFactory::extension(QObject *object, const QString &iid) const +{ + if (!object) + return nullptr; + const auto key = std::make_pair(iid, object); + + auto it = m_extensions.find(key); + if (it == m_extensions.end()) { + if (QObject *ext = createExtension(object, iid, const_cast(this))) { + connect(ext, &QObject::destroyed, this, &QExtensionFactory::objectDestroyed); + it = m_extensions.insert(key, ext); + } + } + + if (!m_extended.contains(object)) { + connect(object, &QObject::destroyed, this, &QExtensionFactory::objectDestroyed); + m_extended.insert(object, true); + } + + if (it == m_extensions.end()) + return nullptr; + + return it.value(); +} + +void QExtensionFactory::objectDestroyed(QObject *object) +{ + for (auto it = m_extensions.begin(); it != m_extensions.end(); ) { + if (it.key().second == object || object == it.value()) + it = m_extensions.erase(it); + else + ++it; + } + + m_extended.remove(object); +} + +/*! + Creates an extension specified by \a iid for the given \a object. + The extension object is created as a child of the specified \a + parent. + + \sa extension() +*/ +QObject *QExtensionFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const +{ + Q_UNUSED(object); + Q_UNUSED(iid); + Q_UNUSED(parent); + + return nullptr; +} + +/*! + Returns the extension manager for the extension factory. +*/ +QExtensionManager *QExtensionFactory::extensionManager() const +{ + return static_cast(parent()); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/extension/default_extensionfactory.h b/src/designer/src/lib/extension/default_extensionfactory.h new file mode 100644 index 0000000..825b9d5 --- /dev/null +++ b/src/designer/src/lib/extension/default_extensionfactory.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEFAULT_EXTENSIONFACTORY_H +#define DEFAULT_EXTENSIONFACTORY_H + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QExtensionManager; + +class QDESIGNER_EXTENSION_EXPORT QExtensionFactory : public QObject, public QAbstractExtensionFactory +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionFactory) +public: + explicit QExtensionFactory(QExtensionManager *parent = nullptr); + + QObject *extension(QObject *object, const QString &iid) const override; + QExtensionManager *extensionManager() const; + +private Q_SLOTS: + void objectDestroyed(QObject *object); + +protected: + virtual QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const; + +private: + mutable QMap, QObject *> m_extensions; + // ### FIXME Qt 7: Use QSet, add out of line destructor. + mutable QHash m_extended; +}; + +QT_END_NAMESPACE + +#endif // DEFAULT_EXTENSIONFACTORY_H diff --git a/src/designer/src/lib/extension/extension.cpp b/src/designer/src/lib/extension/extension.cpp new file mode 100644 index 0000000..d51b391 --- /dev/null +++ b/src/designer/src/lib/extension/extension.cpp @@ -0,0 +1,148 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractExtensionFactory + + \brief The QAbstractExtensionFactory class provides an interface + for extension factories in \QD. + + \inmodule QtDesigner + + QAbstractExtensionFactory is not intended to be instantiated + directly; use the QExtensionFactory instead. + + In \QD, extension factories are used to look up and create named + extensions as they are required. For that reason, when + implementing a custom extension, you must also create a + QExtensionFactory, i.e a class that is able to make an instance of + your extension, and register it using \QD's \l + {QExtensionManager}{extension manager}. + + When an extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create the requested + extension for the selected object, is found. This factory will + then make an instance of the extension. + + \sa QExtensionFactory, QExtensionManager +*/ + +/*! + Destroys the extension factory. +*/ +QAbstractExtensionFactory::~QAbstractExtensionFactory() + = default; + +/*! + \fn QObject *QAbstractExtensionFactory::extension(QObject *object, const QString &iid) const + + Returns the extension specified by \a iid for the given \a object. +*/ + + +/*! + \class QAbstractExtensionManager + + \brief The QAbstractExtensionManager class provides an interface + for extension managers in \QD. + + \inmodule QtDesigner + + QAbstractExtensionManager is not intended to be instantiated + directly; use the QExtensionManager instead. + + In \QD, extension are not created until they are required. For + that reason, when implementing a custom extension, you must also + create a QExtensionFactory, i.e a class that is able to make an + instance of your extension, and register it using \QD's \l + {QExtensionManager}{extension manager}. + + When an extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create the requested + extension for the selected object, is found. This factory will + then make an instance of the extension. + + \sa QExtensionManager, QExtensionFactory +*/ + +/*! + Destroys the extension manager. +*/ +QAbstractExtensionManager::~QAbstractExtensionManager() + = default; + +/*! + \fn void QAbstractExtensionManager::registerExtensions(QAbstractExtensionFactory *factory, const QString &iid) + + Register the given extension \a factory with the extension + specified by \a iid. +*/ + +/*! + \fn void QAbstractExtensionManager::unregisterExtensions(QAbstractExtensionFactory *factory, const QString &iid) + + Unregister the given \a factory with the extension specified by \a + iid. +*/ + +/*! + \fn QObject *QAbstractExtensionManager::extension(QObject *object, const QString &iid) const + + Returns the extension, specified by \a iid, for the given \a + object. +*/ + +/*! + \fn template T qt_extension(QAbstractExtensionManager* manager, QObject *object) + + \relates QExtensionManager + + Returns the extension of the given \a object cast to type T if the + object is of type T (or of a subclass); otherwise returns 0. The + extension is retrieved using the given extension \a manager. + + \snippet lib/tools_designer_src_lib_extension_extension.cpp 0 + + When implementing a custom widget plugin, a pointer to \QD's + current QDesignerFormEditorInterface object (\c formEditor) is + provided by the QDesignerCustomWidgetInterface::initialize() + function's parameter. + + If the widget in the example above doesn't have a defined + QDesignerPropertySheetExtension, \c propertySheet will be a null + pointer. + +*/ + +/*! + \macro Q_DECLARE_EXTENSION_INTERFACE(ExtensionName, Identifier) + + \relates QExtensionManager + + Associates the given \a Identifier (a string literal) to the + extension class called \a ExtensionName. The \a Identifier must be + unique. For example: + + \snippet lib/tools_designer_src_lib_extension_extension.cpp 1 + + Using the company and product names is a good way to ensure + uniqueness of the identifier. + + When implementing a custom extension class, you must use + Q_DECLARE_EXTENSION_INTERFACE() to enable usage of the + qt_extension() function. The macro is normally located right after the + class definition for \a ExtensionName, in the associated header + file. + + \sa {Q_DECLARE_INTERFACE}{Q_DECLARE_INTERFACE()} +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/extension/extension.h b/src/designer/src/lib/extension/extension.h new file mode 100644 index 0000000..e9c9e24 --- /dev/null +++ b/src/designer/src/lib/extension/extension.h @@ -0,0 +1,49 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EXTENSION_H +#define EXTENSION_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +#define Q_TYPEID(IFace) QLatin1StringView(IFace##_iid) + +class QDESIGNER_EXTENSION_EXPORT QAbstractExtensionFactory +{ +public: + virtual ~QAbstractExtensionFactory(); + + virtual QObject *extension(QObject *object, const QString &iid) const = 0; +}; +Q_DECLARE_INTERFACE(QAbstractExtensionFactory, "org.qt-project.Qt.QAbstractExtensionFactory") + +class QDESIGNER_EXTENSION_EXPORT QAbstractExtensionManager +{ +public: + virtual ~QAbstractExtensionManager(); + + virtual void registerExtensions(QAbstractExtensionFactory *factory, const QString &iid) = 0; + virtual void unregisterExtensions(QAbstractExtensionFactory *factory, const QString &iid) = 0; + + virtual QObject *extension(QObject *object, const QString &iid) const = 0; +}; +Q_DECLARE_INTERFACE(QAbstractExtensionManager, "org.qt-project.Qt.QAbstractExtensionManager") + +template +inline T qt_extension(QAbstractExtensionManager *, QObject *) +{ return nullptr; } + +#define Q_DECLARE_EXTENSION_INTERFACE(IFace, IId) \ +const char * const IFace##_iid = IId; \ +Q_DECLARE_INTERFACE(IFace, IId) \ +template <> inline IFace *qt_extension(QAbstractExtensionManager *manager, QObject *object) \ +{ QObject *extension = manager->extension(object, Q_TYPEID(IFace)); return extension ? static_cast(extension->qt_metacast(IFace##_iid)) : static_cast(nullptr); } + +QT_END_NAMESPACE + +#endif // EXTENSION_H diff --git a/src/designer/src/lib/extension/extension_global.h b/src/designer/src/lib/extension/extension_global.h new file mode 100644 index 0000000..781974f --- /dev/null +++ b/src/designer/src/lib/extension/extension_global.h @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EXTENSION_GLOBAL_H +#define EXTENSION_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#define QDESIGNER_EXTENSION_EXTERN Q_DECL_EXPORT +#define QDESIGNER_EXTENSION_IMPORT Q_DECL_IMPORT + +#ifdef QT_DESIGNER_STATIC +# define QDESIGNER_EXTENSION_EXPORT +#elif defined(QDESIGNER_EXTENSION_LIBRARY) +# define QDESIGNER_EXTENSION_EXPORT QDESIGNER_EXTENSION_EXTERN +#else +# define QDESIGNER_EXTENSION_EXPORT QDESIGNER_EXTENSION_IMPORT +#endif + +QT_END_NAMESPACE + +#endif // EXTENSION_GLOBAL_H diff --git a/src/designer/src/lib/extension/qextensionmanager.cpp b/src/designer/src/lib/extension/qextensionmanager.cpp new file mode 100644 index 0000000..2d974c6 --- /dev/null +++ b/src/designer/src/lib/extension/qextensionmanager.cpp @@ -0,0 +1,135 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qextensionmanager.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QExtensionManager + + \brief The QExtensionManager class provides extension management + facilities for \QD. + + \inmodule QtDesigner + + In \QD the extensions are not created until they are required. For + that reason, when implementing an extension, you must also create + a QExtensionFactory, i.e a class that is able to make an instance + of your extension, and register it using \QD's extension manager. + + The registration of an extension factory is typically made in the + QDesignerCustomWidgetInterface::initialize() function: + + \snippet lib/tools_designer_src_lib_extension_qextensionmanager.cpp 0 + + The QExtensionManager is not intended to be instantiated + directly. You can retrieve an interface to \QD's extension manager + using the QDesignerFormEditorInterface::extensionManager() + function. A pointer to \QD's current QDesignerFormEditorInterface + object (\c formEditor in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + Then, when an extension is required, \QD's extension manager will + run through all its registered factories calling + QExtensionFactory::createExtension() for each until the first one + that is able to create the requested extension for the selected + object, is found. This factory will then make an instance of the + extension. + + There are four available types of extensions in \QD: + QDesignerContainerExtension , QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and + QDesignerTaskMenuExtension. \QD's behavior is the same whether the + requested extension is associated with a container, a member + sheet, a property sheet or a task menu. + + For a complete example using the QExtensionManager class, see the + \l {taskmenuextension}{Task Menu Extension example}. The + example shows how to create a custom widget plugin for Qt + Designer, and how to use the QDesignerTaskMenuExtension class + to add custom items to \QD's task menu. + + \sa QExtensionFactory, QAbstractExtensionManager +*/ + +/*! + Constructs an extension manager with the given \a parent. +*/ +QExtensionManager::QExtensionManager(QObject *parent) + : QObject(parent) +{ +} + + +/*! + Destroys the extension manager +*/ +QExtensionManager::~QExtensionManager() = default; + +/*! + Register the extension specified by the given \a factory and + extension identifier \a iid. +*/ +void QExtensionManager::registerExtensions(QAbstractExtensionFactory *factory, const QString &iid) +{ + if (iid.isEmpty()) { + m_globalExtension.prepend(factory); + return; + } + + auto it = m_extensions.find(iid); + if (it == m_extensions.end()) + it = m_extensions.insert(iid, FactoryList()); + + it.value().prepend(factory); +} + +/*! + Unregister the extension specified by the given \a factory and + extension identifier \a iid. +*/ +void QExtensionManager::unregisterExtensions(QAbstractExtensionFactory *factory, const QString &iid) +{ + if (iid.isEmpty()) { + m_globalExtension.removeAll(factory); + return; + } + + const auto it = m_extensions.find(iid); + if (it == m_extensions.end()) + return; + + FactoryList &factories = it.value(); + factories.removeAll(factory); + + if (factories.isEmpty()) + m_extensions.erase(it); +} + +/*! + Returns the extension specified by \a iid, for the given \a + object. +*/ +QObject *QExtensionManager::extension(QObject *object, const QString &iid) const +{ + const auto it = m_extensions.constFind(iid); + if (it != m_extensions.constEnd()) { + for (const auto &f : it.value()) { + if (QObject *ext = f->extension(object, iid)) + return ext; + } + } + + for (const auto &gf : m_globalExtension) { + if (QObject *ext = gf->extension(object, iid)) + return ext; + } + + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/extension/qextensionmanager.h b/src/designer/src/lib/extension/qextensionmanager.h new file mode 100644 index 0000000..0fb2bcd --- /dev/null +++ b/src/designer/src/lib/extension/qextensionmanager.h @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QEXTENSIONMANAGER_H +#define QEXTENSIONMANAGER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QObject; // Fool syncqt + +class QDESIGNER_EXTENSION_EXPORT QExtensionManager: public QObject, public QAbstractExtensionManager +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionManager) +public: + explicit QExtensionManager(QObject *parent = nullptr); + ~QExtensionManager(); + + void registerExtensions(QAbstractExtensionFactory *factory, const QString &iid = QString()) override; + void unregisterExtensions(QAbstractExtensionFactory *factory, const QString &iid = QString()) override; + + QObject *extension(QObject *object, const QString &iid) const override; + +private: + using FactoryList = QList; + QHash m_extensions; + FactoryList m_globalExtension; +}; + +QT_END_NAMESPACE + +#endif // QEXTENSIONMANAGER_H diff --git a/src/designer/src/lib/lib_pch.h b/src/designer/src/lib/lib_pch.h new file mode 100644 index 0000000..f8dfc99 --- /dev/null +++ b/src/designer/src/lib/lib_pch.h @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifdef __cplusplus +#include "shared_global_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qdesigner_widget_p.h" +#include +#include +#include +#include +#include "layout_p.h" +#endif diff --git a/src/designer/src/lib/sdk/abstractactioneditor.cpp b/src/designer/src/lib/sdk/abstractactioneditor.cpp new file mode 100644 index 0000000..5b50a94 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractactioneditor.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractactioneditor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerActionEditorInterface + + \brief The QDesignerActionEditorInterface class allows you to + change the focus of \QD's action editor. + + \inmodule QtDesigner + + The QDesignerActionEditorInterface class is not intended to be + instantiated directly. You can retrieve an interface to \QD's + action editor using the + QDesignerFormEditorInterface::actionEditor() function. + + You can control which actions that are available in the action + editor's window using the manageAction() and unmanageAction() + functions. An action that is managed by \QD is available in the + action editor while an unmanaged action is ignored. + + QDesignerActionEditorInterface also provides the core() function + that you can use to retrieve a pointer to \QD's current + QDesignerFormEditorInterface object, and the setFormWindow() + function that enables you to change the currently selected form + window. + + \sa QDesignerFormEditorInterface, QDesignerFormWindowInterface +*/ + +/*! + Constructs an action editor interface with the given \a parent and + the specified window \a flags. +*/ +QDesignerActionEditorInterface::QDesignerActionEditorInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the action editor interface. +*/ +QDesignerActionEditorInterface::~QDesignerActionEditorInterface() = default; + +/*! + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ +QDesignerFormEditorInterface *QDesignerActionEditorInterface::core() const +{ + return nullptr; +} + +/*! + \fn void QDesignerActionEditorInterface::setFormWindow(QDesignerFormWindowInterface *formWindow) + + Sets the currently selected form window to \a formWindow. + +*/ + +/*! + \fn void QDesignerActionEditorInterface::manageAction(QAction *action) + + Instructs \QD to manage the specified \a action. An action that is + managed by \QD is available in the action editor. + + \sa unmanageAction() +*/ + +/*! + \fn void QDesignerActionEditorInterface::unmanageAction(QAction *action) + + Instructs \QD to ignore the specified \a action. An unmanaged + action is not available in the action editor. + + \sa manageAction() +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractactioneditor.h b/src/designer/src/lib/sdk/abstractactioneditor.h new file mode 100644 index 0000000..5ca6b2c --- /dev/null +++ b/src/designer/src/lib/sdk/abstractactioneditor.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTACTIONEDITOR_H +#define ABSTRACTACTIONEDITOR_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class QDESIGNER_SDK_EXPORT QDesignerActionEditorInterface: public QWidget +{ + Q_OBJECT +public: + explicit QDesignerActionEditorInterface(QWidget *parent, Qt::WindowFlags flags = {}); + virtual ~QDesignerActionEditorInterface(); + + virtual QDesignerFormEditorInterface *core() const; + + virtual void manageAction(QAction *action) = 0; + virtual void unmanageAction(QAction *action) = 0; + +public Q_SLOTS: + virtual void setFormWindow(QDesignerFormWindowInterface *formWindow) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTACTIONEDITOR_H diff --git a/src/designer/src/lib/sdk/abstractdialoggui.cpp b/src/designer/src/lib/sdk/abstractdialoggui.cpp new file mode 100644 index 0000000..6c1eb6d --- /dev/null +++ b/src/designer/src/lib/sdk/abstractdialoggui.cpp @@ -0,0 +1,119 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractdialoggui_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerDialogGuiInterface + \since 4.4 + \internal + + \brief The QDesignerDialogGuiInterface allows integrations of \QD to replace the + message boxes displayed by \QD by custom dialogs. + + \inmodule QtDesigner + + QDesignerDialogGuiInterface provides virtual functions that can be overwritten + to display message boxes and file dialogs. + \sa QMessageBox, QFileDialog +*/ + +/*! + \enum QDesignerDialogGuiInterface::Message + + This enum specifies the context from within the message box is called. + + \value FormLoadFailureMessage Loading of a form failed + \value UiVersionMismatchMessage Attempt to load a file created with an old version of Designer + \value ResourceLoadFailureMessage Resources specified in a file could not be found + \value TopLevelSpacerMessage Spacer items detected on a container without layout + \value PropertyEditorMessage Messages of the propert yeditor + \value SignalSlotEditorMessage Messages of the signal / slot editor + \value FormEditorMessage Messages of the form editor + \value PreviewFailureMessage A preview could not be created + \value PromotionErrorMessage Messages related to promotion of a widget + \value ResourceEditorMessage Messages of the resource editor + \value ScriptDialogMessage Messages of the script dialog + \value SignalSlotDialogMessage Messages of the signal slot dialog + \value OtherMessage Unspecified context +*/ + +/*! + Constructs a QDesignerDialogGuiInterface object. +*/ + +QDesignerDialogGuiInterface::QDesignerDialogGuiInterface() = default; + +/*! + Destroys the QDesignerDialogGuiInterface object. +*/ +QDesignerDialogGuiInterface::~QDesignerDialogGuiInterface() = default; + +/*! + \fn QMessageBox::StandardButton QDesignerDialogGuiInterface::message(QWidget *parent, Message context, QMessageBox::Icon icon, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) + + Opens a message box as child of \a parent within the context \a context, using \a icon, \a title, \a text, \a buttons and \a defaultButton + and returns the button chosen by the user. +*/ + +/*! + \fn QString QDesignerDialogGuiInterface::getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options) + + Opens a file dialog as child of \a parent using the parameters \a caption, \a dir and \a options that prompts the + user for an existing directory. Returns a directory selected by the user. +*/ + +/*! + \fn QString QDesignerDialogGuiInterface::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options) + + Opens a file dialog as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for an existing file. Returns a file selected by the user. +*/ + +/*! + \fn QStringList QDesignerDialogGuiInterface::getOpenFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options) + + Opens a file dialog as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for a set of existing files. Returns one or more existing files selected by the user. +*/ + +/*! + Opens a file dialog with image browsing capabilities as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for an existing file. Returns a file selected by the user. + + The default implementation simply calls getOpenFileName(). On platforms that do not support an image preview in the QFileDialog, + the function can be reimplemented to provide an image browser. + + \since 4.5 +*/ + +QString QDesignerDialogGuiInterface::getOpenImageFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return getOpenFileName(parent, caption, dir, filter, selectedFilter, options); +} + +/*! + Opens a file dialog with image browsing capabilities as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for a set of existing files. Returns one or more existing files selected by the user. + + The default implementation simply calls getOpenFileNames(). On platforms that do not support an image preview in the QFileDialog, + the function can be reimplemented to provide an image browser. + + \since 4.5 +*/ + +QStringList QDesignerDialogGuiInterface::getOpenImageFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return getOpenFileNames(parent, caption, dir, filter, selectedFilter, options); +} + +/*! + \fn QString QDesignerDialogGuiInterface::getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options) + + Opens a file dialog as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for a file. Returns a file selected by the user. The file does not have to exist. +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractdialoggui_p.h b/src/designer/src/lib/sdk/abstractdialoggui_p.h new file mode 100644 index 0000000..b230a5b --- /dev/null +++ b/src/designer/src/lib/sdk/abstractdialoggui_p.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ABSTRACTDIALOGGUI_H +#define ABSTRACTDIALOGGUI_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QWidget; + +class QDESIGNER_SDK_EXPORT QDesignerDialogGuiInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerDialogGuiInterface) + + QDesignerDialogGuiInterface(); + virtual ~QDesignerDialogGuiInterface(); + + enum Message { FormLoadFailureMessage, UiVersionMismatchMessage, ResourceLoadFailureMessage, + TopLevelSpacerMessage, PropertyEditorMessage, SignalSlotEditorMessage, FormEditorMessage, + PreviewFailureMessage, PromotionErrorMessage, ResourceEditorMessage, + ScriptDialogMessage, SignalSlotDialogMessage, OtherMessage, FileChangedMessage }; + + virtual QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) = 0; + + virtual QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) = 0; + + virtual QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, const QString &detailedText, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) = 0; + + virtual QString getExistingDirectory(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), QFileDialog::Options options = QFileDialog::ShowDirsOnly)= 0; + virtual QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {})= 0; + virtual QString getOpenImageFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}); + virtual QStringList getOpenFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {})= 0; + virtual QStringList getOpenImageFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}); + virtual QString getSaveFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {})= 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTDIALOGGUI_H diff --git a/src/designer/src/lib/sdk/abstractdnditem.h b/src/designer/src/lib/sdk/abstractdnditem.h new file mode 100644 index 0000000..44d1da1 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractdnditem.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTDNDITEM_H +#define ABSTRACTDNDITEM_H + +#include + +QT_BEGIN_NAMESPACE + +class DomUI; +class QWidget; +class QPoint; + +class QDESIGNER_SDK_EXPORT QDesignerDnDItemInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerDnDItemInterface) + + enum DropType { MoveDrop, CopyDrop }; + + QDesignerDnDItemInterface() = default; + virtual ~QDesignerDnDItemInterface() = default; + + virtual DomUI *domUi() const = 0; + virtual QWidget *widget() const = 0; + virtual QWidget *decoration() const = 0; + virtual QPoint hotSpot() const = 0; + virtual DropType type() const = 0; + virtual QWidget *source() const = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTDNDITEM_H diff --git a/src/designer/src/lib/sdk/abstractdnditem.qdoc b/src/designer/src/lib/sdk/abstractdnditem.qdoc new file mode 100644 index 0000000..b3dd3d3 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractdnditem.qdoc @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerDnDItemInterface + \brief The QDesignerDnDItemInterface class provides an interface that is used to manage items + during a drag and drop operation. + \inmodule QtDesigner + \internal +*/ + +/*! + \enum QDesignerDnDItemInterface::DropType + + This enum describes the result of a drag and drop operation. + + \value MoveDrop The item was moved. + \value CopyDrop The item was copied. +*/ + +/*! + \fn QDesignerDnDItemInterface::QDesignerDnDItemInterface() + + Constructs a new interface to a drag and drop item. +*/ + +/*! + \fn QDesignerDnDItemInterface::~QDesignerDnDItemInterface() + + Destroys the interface to the item. +*/ + +/*! + \fn DomUI *QDesignerDnDItemInterface::domUi() const + + Returns a user interface object for the item. +*/ + +/*! + \fn QWidget *QDesignerDnDItemInterface::widget() const + + Returns the widget being copied or moved in the drag and drop operation. + + \sa source() +*/ + +/*! + \fn QWidget *QDesignerDnDItemInterface::decoration() const + + Returns the widget used to represent the item. +*/ + +/*! + \fn QPoint QDesignerDnDItemInterface::hotSpot() const + + Returns the cursor's hotspot. + + \sa QDrag::hotSpot() +*/ + +/*! + \fn DropType QDesignerDnDItemInterface::type() const + + Returns the type of drag and drop operation in progress. +*/ + +/*! + \fn QWidget *QDesignerDnDItemInterface::source() const + + Returns the widget that is the source of the drag and drop operation; i.e. the original + container of the widget being dragged. + + \sa widget() +*/ diff --git a/src/designer/src/lib/sdk/abstractformeditor.cpp b/src/designer/src/lib/sdk/abstractformeditor.cpp new file mode 100644 index 0000000..9a75805 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformeditor.cpp @@ -0,0 +1,571 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformeditor.h" +#include "abstractdialoggui_p.h" +#include "abstractintrospection_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Must be done outside of the Qt namespace +static void initResources() +{ + Q_INIT_RESOURCE(shared); + Q_INIT_RESOURCE(ClamshellPhone); + Q_INIT_RESOURCE(PortableMedia); + Q_INIT_RESOURCE(S60_nHD_Touchscreen); + Q_INIT_RESOURCE(S60_QVGA_Candybar); + Q_INIT_RESOURCE(SmartPhone2); + Q_INIT_RESOURCE(SmartPhone); + Q_INIT_RESOURCE(SmartPhoneWithButtons); + Q_INIT_RESOURCE(TouchscreenPhone); +} + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QDesignerFormEditorInterfacePrivate { +public: + QDesignerFormEditorInterfacePrivate(); + ~QDesignerFormEditorInterfacePrivate(); + + + QPointer m_topLevel; + QPointer m_widgetBox; + QPointer m_propertyEditor; + QPointer m_formWindowManager; + QPointer m_extensionManager; + QPointer m_metaDataBase; + QPointer m_widgetDataBase; + QPointer m_widgetFactory; + QPointer m_objectInspector; + QPointer m_integration; + QPointer m_actionEditor; + QDesignerSettingsInterface *m_settingsManager = nullptr; + QDesignerPluginManager *m_pluginManager = nullptr; + QDesignerPromotionInterface *m_promotion = nullptr; + QDesignerIntrospectionInterface *m_introspection = nullptr; + QDesignerDialogGuiInterface *m_dialogGui = nullptr; + QPointer m_resourceModel; + QPointer m_gradientManager; // instantiated and deleted by designer_integration + QList m_optionsPages; +}; + +QDesignerFormEditorInterfacePrivate::QDesignerFormEditorInterfacePrivate() = default; + +QDesignerFormEditorInterfacePrivate::~QDesignerFormEditorInterfacePrivate() +{ + delete m_settingsManager; + delete m_formWindowManager; + delete m_promotion; + delete m_introspection; + delete m_dialogGui; + delete m_resourceModel; + qDeleteAll(m_optionsPages); +} + +/*! + \class QDesignerFormEditorInterface + + \brief The QDesignerFormEditorInterface class allows you to access + Qt Widgets Designer's various components. + + \inmodule QtDesigner + + \QD's current QDesignerFormEditorInterface object holds + information about all \QD's components: The action editor, the + object inspector, the property editor, the widget box, and the + extension and form window managers. QDesignerFormEditorInterface + contains a collection of functions that provides interfaces to all + these components. They are typically used to query (and + manipulate) the respective component. For example: + + \snippet lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp 0 + + QDesignerFormEditorInterface is not intended to be instantiated + directly. A pointer to \QD's current QDesignerFormEditorInterface + object (\c formEditor in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + QDesignerFormEditorInterface also provides functions that can set + the action editor, property editor, object inspector and widget + box. These are only useful if you want to provide your own custom + components. + + If designer is embedded in another program, one could to provide its + own settings manager. The manager is used by the components of \QD + to store/retrieve persistent configuration settings. The default + manager uses QSettings as the backend. + + Finally, QDesignerFormEditorInterface provides the topLevel() + function that returns \QD's top-level widget. + + \sa QDesignerCustomWidgetInterface +*/ + +/*! + Constructs a QDesignerFormEditorInterface object with the given \a + parent. +*/ + +QDesignerFormEditorInterface::QDesignerFormEditorInterface(QObject *parent) + : QObject(parent), + d(new QDesignerFormEditorInterfacePrivate()) +{ + initResources(); +} + +/*! + Destroys the QDesignerFormEditorInterface object. +*/ +QDesignerFormEditorInterface::~QDesignerFormEditorInterface() = default; + +/*! + Returns an interface to \QD's widget box. + + \sa setWidgetBox() +*/ +QDesignerWidgetBoxInterface *QDesignerFormEditorInterface::widgetBox() const +{ + return d->m_widgetBox; +} + +/*! + Sets \QD's widget box to be the specified \a widgetBox. + + \sa widgetBox() +*/ +void QDesignerFormEditorInterface::setWidgetBox(QDesignerWidgetBoxInterface *widgetBox) +{ + d->m_widgetBox = widgetBox; +} + +/*! + Returns an interface to \QD's property editor. + + \sa setPropertyEditor() +*/ +QDesignerPropertyEditorInterface *QDesignerFormEditorInterface::propertyEditor() const +{ + return d->m_propertyEditor; +} + +/*! + Sets \QD's property editor to be the specified \a propertyEditor. + + \sa propertyEditor() +*/ +void QDesignerFormEditorInterface::setPropertyEditor(QDesignerPropertyEditorInterface *propertyEditor) +{ + d->m_propertyEditor = propertyEditor; +} + +/*! + Returns an interface to \QD's action editor. + + \sa setActionEditor() +*/ +QDesignerActionEditorInterface *QDesignerFormEditorInterface::actionEditor() const +{ + return d->m_actionEditor; +} + +/*! + Sets \QD's action editor to be the specified \a actionEditor. + + \sa actionEditor() +*/ +void QDesignerFormEditorInterface::setActionEditor(QDesignerActionEditorInterface *actionEditor) +{ + d->m_actionEditor = actionEditor; +} + +/*! + Returns \QD's top-level widget. +*/ +QWidget *QDesignerFormEditorInterface::topLevel() const +{ + return d->m_topLevel; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setTopLevel(QWidget *topLevel) +{ + d->m_topLevel = topLevel; +} + +/*! + Returns an interface to \QD's form window manager. +*/ +QDesignerFormWindowManagerInterface *QDesignerFormEditorInterface::formWindowManager() const +{ + return d->m_formWindowManager; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setFormManager(QDesignerFormWindowManagerInterface *formWindowManager) +{ + d->m_formWindowManager = formWindowManager; +} + +/*! + Returns an interface to \QD's extension manager. +*/ +QExtensionManager *QDesignerFormEditorInterface::extensionManager() const +{ + return d->m_extensionManager; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setExtensionManager(QExtensionManager *extensionManager) +{ + d->m_extensionManager = extensionManager; +} + +/*! + \internal + + Returns an interface to the meta database used by the form editor. +*/ +QDesignerMetaDataBaseInterface *QDesignerFormEditorInterface::metaDataBase() const +{ + return d->m_metaDataBase; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setMetaDataBase(QDesignerMetaDataBaseInterface *metaDataBase) +{ + d->m_metaDataBase = metaDataBase; +} + +/*! + \internal + + Returns an interface to the widget database used by the form editor. +*/ +QDesignerWidgetDataBaseInterface *QDesignerFormEditorInterface::widgetDataBase() const +{ + return d->m_widgetDataBase; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setWidgetDataBase(QDesignerWidgetDataBaseInterface *widgetDataBase) +{ + d->m_widgetDataBase = widgetDataBase; +} + +/*! + \internal + + Returns an interface to the designer promotion handler. +*/ + +QDesignerPromotionInterface *QDesignerFormEditorInterface::promotion() const +{ + return d->m_promotion; +} + +/*! + \internal + + Sets the designer promotion handler. +*/ + +void QDesignerFormEditorInterface::setPromotion(QDesignerPromotionInterface *promotion) +{ + delete d->m_promotion; + d->m_promotion = promotion; +} + +/*! + \internal + + Returns an interface to the widget factory used by the form editor + to create widgets for the form. +*/ +QDesignerWidgetFactoryInterface *QDesignerFormEditorInterface::widgetFactory() const +{ + return d->m_widgetFactory; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setWidgetFactory(QDesignerWidgetFactoryInterface *widgetFactory) +{ + d->m_widgetFactory = widgetFactory; +} + +/*! + Returns an interface to \QD's object inspector. +*/ +QDesignerObjectInspectorInterface *QDesignerFormEditorInterface::objectInspector() const +{ + return d->m_objectInspector; +} + +/*! + Sets \QD's object inspector to be the specified \a + objectInspector. + + \sa objectInspector() +*/ +void QDesignerFormEditorInterface::setObjectInspector(QDesignerObjectInspectorInterface *objectInspector) +{ + d->m_objectInspector = objectInspector; +} + +/*! + \internal + + Returns an interface to the integration. +*/ +QDesignerIntegrationInterface *QDesignerFormEditorInterface::integration() const +{ + return d->m_integration; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setIntegration(QDesignerIntegrationInterface *integration) +{ + d->m_integration = integration; +} + +/*! + \internal + \since 4.5 + Returns the list of options pages that allow the user to configure \QD components. +*/ +QList QDesignerFormEditorInterface::optionsPages() const +{ + return d->m_optionsPages; +} + +/*! + \internal + \since 4.5 + Sets the list of options pages that allow the user to configure \QD components. +*/ +void QDesignerFormEditorInterface::setOptionsPages(const QList &optionsPages) +{ + d->m_optionsPages = optionsPages; +} + + +/*! + \internal + + Returns the plugin manager used by the form editor. +*/ +QDesignerPluginManager *QDesignerFormEditorInterface::pluginManager() const +{ + return d->m_pluginManager; +} + +/*! + \internal + + Sets the plugin manager used by the form editor to the specified + \a pluginManager. +*/ +void QDesignerFormEditorInterface::setPluginManager(QDesignerPluginManager *pluginManager) +{ + d->m_pluginManager = pluginManager; +} + +/*! + \internal + \since 4.4 + Returns the resource model used by the form editor. +*/ +QtResourceModel *QDesignerFormEditorInterface::resourceModel() const +{ + return d->m_resourceModel; +} + +/*! + \internal + + Sets the resource model used by the form editor to the specified + \a resourceModel. +*/ +void QDesignerFormEditorInterface::setResourceModel(QtResourceModel *resourceModel) +{ + d->m_resourceModel = resourceModel; +} + +/*! + \internal + \since 4.4 + Returns the gradient manager used by the style sheet editor. +*/ +QtGradientManager *QDesignerFormEditorInterface::gradientManager() const +{ + return d->m_gradientManager; +} + +/*! + \internal + + Sets the gradient manager used by the style sheet editor to the specified + \a gradientManager. +*/ +void QDesignerFormEditorInterface::setGradientManager(QtGradientManager *gradientManager) +{ + d->m_gradientManager = gradientManager; +} + +/*! + \internal + \since 4.5 + Returns the settings manager used by the components to store persistent settings. +*/ +QDesignerSettingsInterface *QDesignerFormEditorInterface::settingsManager() const +{ + return d->m_settingsManager; +} + +/*! + \internal + \since 4.5 + Sets the settings manager used to store/retrieve the persistent settings of the components. +*/ +void QDesignerFormEditorInterface::setSettingsManager(QDesignerSettingsInterface *settingsManager) +{ + if (d->m_settingsManager) + delete d->m_settingsManager; + d->m_settingsManager = settingsManager; + + // This is a (hopefully) safe place to perform settings-dependent + // initializations. + const qdesigner_internal::QDesignerSharedSettings settings(this); + qdesigner_internal::FormWindowBase::setDefaultDesignerGrid(settings.defaultGrid()); + qdesigner_internal::ActionEditor::setObjectNamingMode(settings.objectNamingMode()); +} + +/*! + \internal + \since 4.4 + Returns the introspection used by the form editor. +*/ +QDesignerIntrospectionInterface *QDesignerFormEditorInterface::introspection() const +{ + return d->m_introspection; +} + +/*! + \internal + \since 4.4 + + Sets the introspection used by the form editor to the specified \a introspection. +*/ +void QDesignerFormEditorInterface::setIntrospection(QDesignerIntrospectionInterface *introspection) +{ + delete d->m_introspection; + d->m_introspection = introspection; +} + +/*! + \internal + + Returns the path to the resources used by the form editor. +*/ +QString QDesignerFormEditorInterface::resourceLocation() const +{ +#ifdef Q_OS_MACOS + return u":/qt-project.org/formeditor/images/mac"_s; +#else + return u":/qt-project.org/formeditor/images/win"_s; +#endif +} + +/*! + \internal + + Returns the dialog GUI used by the form editor. +*/ + +QDesignerDialogGuiInterface *QDesignerFormEditorInterface::dialogGui() const +{ + return d->m_dialogGui; +} + +/*! + \internal + + Sets the dialog GUI used by the form editor to the specified \a dialogGui. +*/ + +void QDesignerFormEditorInterface::setDialogGui(QDesignerDialogGuiInterface *dialogGui) +{ + delete d->m_dialogGui; + d->m_dialogGui = dialogGui; +} + +/*! + \internal + + \since 5.0 + + Returns the plugin instances of QDesignerPluginManager. +*/ + +QObjectList QDesignerFormEditorInterface::pluginInstances() const +{ + return d->m_pluginManager->instances(); +} + +/*! + \internal + + \since 5.0 + + Return icons for actions of \QD. +*/ + +QIcon QDesignerFormEditorInterface::createIcon(const QString &name) +{ + return qdesigner_internal::createIconSet(name); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractformeditor.h b/src/designer/src/lib/sdk/abstractformeditor.h new file mode 100644 index 0000000..6512f7c --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformeditor.h @@ -0,0 +1,100 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMEDITOR_H +#define ABSTRACTFORMEDITOR_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerWidgetBoxInterface; +class QDesignerPropertyEditorInterface; +class QDesignerFormWindowManagerInterface; +class QDesignerWidgetDataBaseInterface; +class QDesignerMetaDataBaseInterface; +class QDesignerWidgetFactoryInterface; +class QDesignerObjectInspectorInterface; +class QDesignerPromotionInterface; +class QDesignerActionEditorInterface; +class QDesignerIntegrationInterface; +class QDesignerPluginManager; +class QDesignerIntrospectionInterface; +class QDesignerDialogGuiInterface; +class QDesignerSettingsInterface; +class QDesignerOptionsPageInterface; +class QtResourceModel; +class QtGradientManager; + +class QWidget; +class QIcon; + +class QExtensionManager; + +class QDesignerFormEditorInterfacePrivate; + +class QDESIGNER_SDK_EXPORT QDesignerFormEditorInterface : public QObject +{ + Q_OBJECT +public: + explicit QDesignerFormEditorInterface(QObject *parent = nullptr); + virtual ~QDesignerFormEditorInterface(); + + QExtensionManager *extensionManager() const; + + QWidget *topLevel() const; + QDesignerWidgetBoxInterface *widgetBox() const; + QDesignerPropertyEditorInterface *propertyEditor() const; + QDesignerObjectInspectorInterface *objectInspector() const; + QDesignerFormWindowManagerInterface *formWindowManager() const; + QDesignerWidgetDataBaseInterface *widgetDataBase() const; + QDesignerMetaDataBaseInterface *metaDataBase() const; + QDesignerPromotionInterface *promotion() const; + QDesignerWidgetFactoryInterface *widgetFactory() const; + QDesignerActionEditorInterface *actionEditor() const; + QDesignerIntegrationInterface *integration() const; + QDesignerPluginManager *pluginManager() const; + QDesignerIntrospectionInterface *introspection() const; + QDesignerDialogGuiInterface *dialogGui() const; + QDesignerSettingsInterface *settingsManager() const; + QString resourceLocation() const; + QtResourceModel *resourceModel() const; + QtGradientManager *gradientManager() const; + QList optionsPages() const; + + void setTopLevel(QWidget *topLevel); + void setWidgetBox(QDesignerWidgetBoxInterface *widgetBox); + void setPropertyEditor(QDesignerPropertyEditorInterface *propertyEditor); + void setObjectInspector(QDesignerObjectInspectorInterface *objectInspector); + void setPluginManager(QDesignerPluginManager *pluginManager); + void setActionEditor(QDesignerActionEditorInterface *actionEditor); + void setIntegration(QDesignerIntegrationInterface *integration); + void setIntrospection(QDesignerIntrospectionInterface *introspection); + void setDialogGui(QDesignerDialogGuiInterface *dialogGui); + void setSettingsManager(QDesignerSettingsInterface *settingsManager); + void setResourceModel(QtResourceModel *model); + void setGradientManager(QtGradientManager *manager); + void setOptionsPages(const QList &optionsPages); + + QObjectList pluginInstances() const; + + static QIcon createIcon(const QString &name); + +protected: + void setFormManager(QDesignerFormWindowManagerInterface *formWindowManager); + void setMetaDataBase(QDesignerMetaDataBaseInterface *metaDataBase); + void setWidgetDataBase(QDesignerWidgetDataBaseInterface *widgetDataBase); + void setPromotion(QDesignerPromotionInterface *promotion); + void setWidgetFactory(QDesignerWidgetFactoryInterface *widgetFactory); + void setExtensionManager(QExtensionManager *extensionManager); + +private: + QScopedPointer d; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMEDITOR_H diff --git a/src/designer/src/lib/sdk/abstractformeditorplugin.cpp b/src/designer/src/lib/sdk/abstractformeditorplugin.cpp new file mode 100644 index 0000000..af4f49e --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformeditorplugin.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QDesignerFormEditorPluginInterface + \brief The QDesignerFormEditorPluginInterface class provides an interface that is used to + manage plugins for \QD's form editor component. + \inmodule QtDesigner + + \sa QDesignerFormEditorInterface +*/ + +/*! + \fn virtual QDesignerFormEditorPluginInterface::~QDesignerFormEditorPluginInterface() + + Destroys the plugin interface. +*/ + +/*! + \fn virtual bool QDesignerFormEditorPluginInterface::isInitialized() const = 0 + + Returns true if the plugin interface is initialized; otherwise returns false. +*/ + +/*! + \fn virtual void QDesignerFormEditorPluginInterface::initialize(QDesignerFormEditorInterface *core) = 0 + + Initializes the plugin interface for the specified \a core interface. +*/ + +/*! + \fn virtual QAction *QDesignerFormEditorPluginInterface::action() const = 0 + + Returns the action associated with this interface. +*/ + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerFormEditorPluginInterface::core() const = 0 + + Returns the core form editor interface associated with this component. +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractformeditorplugin.h b/src/designer/src/lib/sdk/abstractformeditorplugin.h new file mode 100644 index 0000000..eab4676 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformeditorplugin.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMEDITORPLUGIN_H +#define ABSTRACTFORMEDITORPLUGIN_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QAction; + +class QDESIGNER_SDK_EXPORT QDesignerFormEditorPluginInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerFormEditorPluginInterface) + + QDesignerFormEditorPluginInterface() = default; + virtual ~QDesignerFormEditorPluginInterface() = default; + + virtual bool isInitialized() const = 0; + virtual void initialize(QDesignerFormEditorInterface *core) = 0; + virtual QAction *action() const = 0; + + virtual QDesignerFormEditorInterface *core() const = 0; +}; +Q_DECLARE_INTERFACE(QDesignerFormEditorPluginInterface, "org.qt-project.Qt.Designer.QDesignerFormEditorPluginInterface") + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMEDITORPLUGIN_H diff --git a/src/designer/src/lib/sdk/abstractformwindow.cpp b/src/designer/src/lib/sdk/abstractformwindow.cpp new file mode 100644 index 0000000..1835f81 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformwindow.cpp @@ -0,0 +1,867 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindow.h" +#include "qtresourcemodel_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerFormWindowInterface + + \brief The QDesignerFormWindowInterface class allows you to query + and manipulate form windows appearing in \QD's workspace. + + \inmodule QtDesigner + + QDesignerFormWindowInterface provides information about + the associated form window as well as allowing its properties to be + altered. The interface is not intended to be instantiated + directly, but to provide access to \QD's current form windows + controlled by \QD's \l {QDesignerFormWindowManagerInterface}{form + window manager}. + + If you are looking for the form window containing a specific + widget, you can use the static + QDesignerFormWindowInterface::findFormWindow() function: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindow.cpp 0 + + But in addition, you can access any of the current form windows + through \QD's form window manager: Use the + QDesignerFormEditorInterface::formWindowManager() function to + retrieve an interface to the manager. Once you have this + interface, you have access to all of \QD's current form windows + through the QDesignerFormWindowManagerInterface::formWindow() + function. For example: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindow.cpp 1 + + The pointer to \QD's current QDesignerFormEditorInterface object + (\c formEditor in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface class to expose your + plugin to \QD. + + Once you have the form window, you can query its properties. For + example, a plain custom widget plugin is managed by \QD only at + its top level, i.e. none of its child widgets can be resized in + \QD's workspace. But QDesignerFormWindowInterface provides you + with functions that enables you to control whether a widget should + be managed by \QD, or not: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindow.cpp 2 + + The complete list of functions concerning widget management is: + isManaged(), manageWidget() and unmanageWidget(). There is also + several associated signals: widgetManaged(), widgetRemoved(), + aboutToUnmanageWidget() and widgetUnmanaged(). + + In addition to controlling the management of widgets, you can + control the current selection in the form window using the + selectWidget(), clearSelection() and emitSelectionChanged() + functions, and the selectionChanged() signal. + + You can also retrieve information about where the form is stored + using absoluteDir(), its include files using includeHints(), and + its layout and pixmap functions using layoutDefault(), + layoutFunction() and pixmapFunction(). You can find out whether + the form window has been modified (but not saved) or not, using + the isDirty() function. You can retrieve its author(), its + contents(), its fileName(), associated comment() and + exportMacro(), its mainContainer(), its features(), its grid() and + its resourceFiles(). + + The interface provides you with functions and slots allowing you + to alter most of this information as well. The exception is the + directory storing the form window. Finally, there is several + signals associated with changes to the information mentioned above + and to the form window in general. + + \sa QDesignerFormWindowCursorInterface, + QDesignerFormEditorInterface, QDesignerFormWindowManagerInterface +*/ + +/*! + \enum QDesignerFormWindowInterface::FeatureFlag + + This enum describes the features that are available and can be + controlled by the form window interface. These values are used + when querying the form window to determine which features it + supports: + + \value EditFeature Form editing + \value GridFeature Grid display and snap-to-grid facilities for editing + \value TabOrderFeature Tab order management + \value DefaultFeature Support for default features (form editing and grid) + + \sa hasFeature(), features() +*/ + +/*! + \enum QDesignerFormWindowInterface::ResourceFileSaveMode + \since 5.0 + + This enum describes how resource files are saved. + + + \value SaveAllResourceFiles Save all resource files. + \value SaveOnlyUsedResourceFiles Save resource files used by form. + \value DontSaveResourceFiles Do not save resource files. +*/ + +/*! + Constructs a form window interface with the given \a parent and + the specified window \a flags. +*/ +QDesignerFormWindowInterface::QDesignerFormWindowInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the form window interface. +*/ +QDesignerFormWindowInterface::~QDesignerFormWindowInterface() = default; + +/*! + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ +QDesignerFormEditorInterface *QDesignerFormWindowInterface::core() const +{ + return nullptr; +} + +/*! + \fn QDesignerFormWindowInterface *QDesignerFormWindowInterface::findFormWindow(QWidget *widget) + + Returns the form window interface for the given \a widget. +*/ + +static inline bool stopFindAtTopLevel(const QObject *w, bool stopAtMenu) +{ + // Do we need to go beyond top levels when looking for the form window? + // 1) A dialog has a window attribute at the moment it is created + // before it is properly embedded into a form window. The property + // sheet queries the layout attributes precisely at this moment. + // 2) In the case of floating docks and toolbars, we also need to go beyond the top level window. + // 3) In the case of menu editing, we don't want to block events from the + // Designer menu, so, we say stop. + // Note that there must be no false positives for dialogs parented on + // the form (for example, the "change object name" dialog), else, its + // events will be blocked. + + if (stopAtMenu && w->inherits("QDesignerMenu")) + return true; + return !qdesigner_internal::WidgetFactory::isFormEditorObject(w); +} + +QDesignerFormWindowInterface *QDesignerFormWindowInterface::findFormWindow(QWidget *w) +{ + while (w != nullptr) { + if (QDesignerFormWindowInterface *fw = qobject_cast(w)) + return fw; + if (w->isWindow() && stopFindAtTopLevel(w, true)) + break; + + w = w->parentWidget(); + } + + return nullptr; +} + +/*! + \fn QDesignerFormWindowInterface *QDesignerFormWindowInterface::findFormWindow(QObject *object) + + Returns the form window interface for the given \a object. + + \since 4.4 +*/ + +QDesignerFormWindowInterface *QDesignerFormWindowInterface::findFormWindow(QObject *object) +{ + while (object != nullptr) { + if (QDesignerFormWindowInterface *fw = qobject_cast(object)) + return fw; + + QWidget *w = qobject_cast(object); + // QDesignerMenu is a window, so stopFindAtTopLevel(w) returns 0. + // However, we want to find the form window for QActions of a menu. + // If this check is inside stopFindAtTopLevel(w), it will break designer + // menu editing (e.g. when clicking on items inside an opened menu) + if (w && w->isWindow() && stopFindAtTopLevel(w, false)) + break; + + object = object->parent(); + } + + return nullptr; +} + +/*! + \fn virtual QtResourceSet *QDesignerFormWindowInterface::resourceSet() const + + Returns the resource set used by the form window interface. + + \since 5.0 + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setResourceSet(QtResourceSet *resourceSet) + + Sets the resource set used by the form window interface to \a resourceSet. + + \since 5.0 + \internal +*/ + +/*! + Returns the active resource (.qrc) file paths currently loaded in \QD. + \since 5.0 + \sa activateResourceFilePaths() +*/ + +QStringList QDesignerFormWindowInterface::activeResourceFilePaths() const +{ + return resourceSet()->activeResourceFilePaths(); +} + +/*! + Activates the resource (.qrc) file paths \a paths, returning the count of errors in \a errorCount and + error message in \a errorMessages. \QD loads the resources using the QResource class, + making them available for form editing. + + In IDE integrations, a list of the project's resource (.qrc) file can be activated, making + them available to \QD. + + \since 5.0 + \sa activeResourceFilePaths() + \sa QResource +*/ + +void QDesignerFormWindowInterface::activateResourceFilePaths(const QStringList &paths, int *errorCount, QString *errorMessages) +{ + resourceSet()->activateResourceFilePaths(paths, errorCount, errorMessages); +} + +/*! + \fn virtual QString QDesignerFormWindowInterface::fileName() const + + Returns the file name of the UI file that describes the form + currently being shown. + + \sa setFileName() +*/ + +/*! + \fn virtual QDir QDesignerFormWindowInterface::absoluteDir() const + + Returns the absolute location of the directory containing the form + shown in the form window. +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::contents() const + + Returns details of the contents of the form currently being + displayed in the window. +*/ + +/*! + \fn virtual QStringList QDesignerFormWindowInterface::checkContents() const + + Performs checks on the current form and returns a list of richtext warnings + about potential issues (for example, top level spacers on unlaid-out forms). + + IDE integrations can call this before handling starting a save operation. + + \since 5.0 +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::setContents(QIODevice *device, QString *errorMessage = 0) + + Sets the form's contents using data obtained from the given \a device and returns whether + loading succeeded. If it fails, the error message is returned in \a errorMessage. + + Data can be read from QFile objects or any other subclass of QIODevice. +*/ + +/*! + \fn virtual Feature QDesignerFormWindowInterface::features() const + + Returns a combination of the features provided by the form window + associated with the interface. The value returned can be tested + against the \l Feature enum values to determine which features are + supported by the window. + + \sa setFeatures(), hasFeature() +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::hasFeature(Feature feature) const + + Returns true if the form window offers the specified \a feature; + otherwise returns false. + + \sa features() +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::author() const + + Returns details of the author or creator of the form currently + being displayed in the window. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setAuthor(const QString &author) + + Sets the details for the author or creator of the form to the \a + author specified. +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::comment() const + + Returns comments about the form currently being displayed in the window. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setComment(const QString &comment) + + Sets the information about the form to the \a comment + specified. This information should be a human-readable comment + about the form. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::layoutDefault(int *margin, int *spacing) + + Fills in the default margin and spacing for the form's default + layout in the \a margin and \a spacing variables specified. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setLayoutDefault(int margin, int spacing) + + Sets the default \a margin and \a spacing for the form's layout. + + \sa layoutDefault() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::layoutFunction(QString *margin, QString *spacing) + + Fills in the current margin and spacing for the form's layout in + the \a margin and \a spacing variables specified. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setLayoutFunction(const QString &margin, const QString &spacing) + + Sets the \a margin and \a spacing for the form's layout. + + The default layout properties will be replaced by the + corresponding layout functions when \c uic generates code for the + form, that is, if the functions are specified. This is useful when + different environments requires different layouts for the same + form. + + \sa layoutFunction() +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::pixmapFunction() const + + Returns the name of the function used to load pixmaps into the + form window. + + \sa setPixmapFunction() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setPixmapFunction(const QString &pixmapFunction) + + Sets the function used to load pixmaps into the form window + to the given \a pixmapFunction. + + \sa pixmapFunction() +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::exportMacro() const + + Returns the export macro associated with the form currently being + displayed in the window. The export macro is used when the form + is compiled to create a widget plugin. + + \sa {Creating Custom Widgets for Qt Widgets Designer} +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setExportMacro(const QString &exportMacro) + + Sets the form window's export macro to \a exportMacro. The export + macro is used when building a widget plugin to export the form's + interface to other components. +*/ + +/*! + \fn virtual QStringList QDesignerFormWindowInterface::includeHints() const + + Returns a list of the header files that will be included in the + form window's associated UI file. + + Header files may be local, i.e. relative to the project's + directory, \c "mywidget.h", or global, i.e. part of Qt or the + compilers standard libraries: \c . + + \sa setIncludeHints() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setIncludeHints(const QStringList &includeHints) + + Sets the header files that will be included in the form window's + associated UI file to the specified \a includeHints. + + Header files may be local, i.e. relative to the project's + directory, \c "mywidget.h", or global, i.e. part of Qt or the + compilers standard libraries: \c . + + \sa includeHints() +*/ + +/*! + \fn virtual QDesignerFormWindowCursorInterface *QDesignerFormWindowInterface::cursor() const + + Returns the cursor interface used by the form window. +*/ + +/*! + \fn virtual int QDesignerFormWindowInterface::toolCount() const + + Returns the number of tools available. + + \internal +*/ + +/*! + \fn virtual int QDesignerFormWindowInterface::currentTool() const + + Returns the index of the current tool in use. + + \sa setCurrentTool() + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setCurrentTool(int index) + + Sets the current tool to be the one with the given \a index. + + \sa currentTool() + + \internal +*/ + +/*! + \fn virtual QDesignerFormWindowToolInterface *QDesignerFormWindowInterface::tool(int index) const + + Returns an interface to the tool with the given \a index. + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::registerTool(QDesignerFormWindowToolInterface *tool) + + Registers the given \a tool with the form window. + + \internal +*/ + +/*! + \fn virtual QPoint QDesignerFormWindowInterface::grid() const = 0 + + Returns the grid spacing used by the form window. + + \sa setGrid() +*/ + +/*! + \fn virtual QWidget *QDesignerFormWindowInterface::mainContainer() const + + Returns the main container widget for the form window. + + \sa setMainContainer() + \internal +*/ + +/*! + \fn virtual QWidget *QDesignerFormWindowInterface::formContainer() const + + Returns the form the widget containing the main container widget. + + \since 5.0 +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setMainContainer(QWidget *mainContainer) + + Sets the main container widget on the form to the specified \a + mainContainer. + + \sa mainContainerChanged() +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::isManaged(QWidget *widget) const + + Returns true if the specified \a widget is managed by the form + window; otherwise returns false. + + \sa manageWidget() +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::isDirty() const + + Returns true if the form window is "dirty" (modified but not + saved); otherwise returns false. + + \sa setDirty() +*/ + +/*! + \fn virtual QUndoStack *QDesignerFormWindowInterface::commandHistory() const + + Returns an object that can be used to obtain the commands used so + far in the construction of the form. + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::beginCommand(const QString &description) + + Begins execution of a command with the given \a + description. Commands are executed between beginCommand() and + endCommand() function calls to ensure that they are recorded on + the undo stack. + + \sa endCommand() + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::endCommand() + + Ends execution of the current command. + + \sa beginCommand() + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::simplifySelection(QList *widgets) const + + Simplifies the selection of widgets specified by \a widgets. + + \sa selectionChanged() + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::emitSelectionChanged() + + Emits the selectionChanged() signal. + + \sa selectWidget(), clearSelection() +*/ + +/*! + \fn virtual QStringList QDesignerFormWindowInterface::resourceFiles() const + + Returns a list of paths to resource files that are currently being + used by the form window. + + \sa addResourceFile(), removeResourceFile() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::addResourceFile(const QString &path) + + Adds the resource file at the given \a path to those used by the form. + + \sa resourceFiles(), resourceFilesChanged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::removeResourceFile(const QString &path) + + Removes the resource file at the specified \a path from the list + of those used by the form. + + \sa resourceFiles(), resourceFilesChanged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::ensureUniqueObjectName(QObject *object) + + Ensures that the specified \a object has a unique name amongst the + other objects on the form. + + \internal +*/ + +// Slots + +/*! + \fn virtual void QDesignerFormWindowInterface::manageWidget(QWidget *widget) + + Instructs the form window to manage the specified \a widget. + + \sa isManaged(), unmanageWidget(), widgetManaged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::unmanageWidget(QWidget *widget) + + Instructs the form window not to manage the specified \a widget. + + \sa aboutToUnmanageWidget(), widgetUnmanaged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setFeatures(Feature features) + + Enables the specified \a features for the form window. + + \sa features(), featureChanged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setDirty(bool dirty) + + If \a dirty is true, the form window is marked as dirty, meaning + that it is modified but not saved. If \a dirty is false, the form + window is considered to be unmodified. + + \sa isDirty() +*/ + +/*! +\fn virtual void QDesignerFormWindowInterface::clearSelection(bool update) + + Clears the current selection in the form window. If \a update is + true, the emitSelectionChanged() function is called, emitting the + selectionChanged() signal. + + \sa selectWidget() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::selectWidget(QWidget *widget, bool select) + + If \a select is true, the given \a widget is selected; otherwise + the \a widget is deselected. + + \sa clearSelection(), selectionChanged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setGrid(const QPoint &grid) + + Sets the grid size for the form window to the point specified by + \a grid. In this function, the coordinates in the QPoint are used + to specify the dimensions of a rectangle in the grid. + + \sa grid() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setFileName(const QString &fileName) + + Sets the file name for the form to the given \a fileName. + + \sa fileName(), fileNameChanged() +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::setContents(const QString &contents) + + Sets the contents of the form using data read from the specified + \a contents string and returns whether the operation succeeded. + + \sa contents() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::editWidgets() + + Switches the form window into editing mode. + + \sa {Qt Widgets Designer's Form Editing Mode} + + \internal +*/ + +// Signals + +/*! + \fn void QDesignerFormWindowInterface::mainContainerChanged(QWidget *mainContainer) + + This signal is emitted whenever the main container changes. + The new container is specified by \a mainContainer. + + \sa setMainContainer() +*/ + +/*! + \fn void QDesignerFormWindowInterface::toolChanged(int toolIndex) + + This signal is emitted whenever the current tool changes. + The specified \a toolIndex is the index of the new tool in the list of + tools in the widget box. + + \internal +*/ + +/*! + \fn void QDesignerFormWindowInterface::fileNameChanged(const QString &fileName) + + This signal is emitted whenever the file name of the form changes. + The new file name is specified by \a fileName. + + \sa setFileName() +*/ + +/*! + \fn void QDesignerFormWindowInterface::featureChanged(Feature feature) + + This signal is emitted whenever a feature changes in the form. + The new feature is specified by \a feature. + + \sa setFeatures() +*/ + +/*! + \fn void QDesignerFormWindowInterface::selectionChanged() + + This signal is emitted whenever the selection in the form changes. + + \sa selectWidget(), clearSelection() +*/ + +/*! + \fn void QDesignerFormWindowInterface::geometryChanged() + + This signal is emitted whenever the form's geometry changes. +*/ + +/*! + \fn void QDesignerFormWindowInterface::resourceFilesChanged() + + This signal is emitted whenever the list of resource files used by the + form changes. + + \sa resourceFiles() +*/ + +/*! + \fn ResourceFileSaveMode QDesignerFormWindowInterface::resourceFileSaveMode() const + + Returns the resource file save mode behavior. + + \sa setResourceFileSaveMode() +*/ + +/*! + \fn void QDesignerFormWindowInterface::setResourceFileSaveMode(ResourceFileSaveMode behavior) + + Sets the resource file save mode \a behavior. + + \sa resourceFileSaveMode() +*/ + +/*! + \fn void QDesignerFormWindowInterface::widgetManaged(QWidget *widget) + + This signal is emitted whenever a widget on the form becomes managed. + The newly managed widget is specified by \a widget. + + \sa manageWidget() +*/ + +/*! + \fn void QDesignerFormWindowInterface::widgetUnmanaged(QWidget *widget) + + This signal is emitted whenever a widget on the form becomes unmanaged. + The newly released widget is specified by \a widget. + + \sa unmanageWidget(), aboutToUnmanageWidget() +*/ + +/*! + \fn void QDesignerFormWindowInterface::aboutToUnmanageWidget(QWidget *widget) + + This signal is emitted whenever a widget on the form is about to + become unmanaged. When this signal is emitted, the specified \a + widget is still managed, and a widgetUnmanaged() signal will + follow, indicating when it is no longer managed. + + \sa unmanageWidget(), isManaged() +*/ + +/*! + \fn void QDesignerFormWindowInterface::activated(QWidget *widget) + + This signal is emitted whenever a widget is activated on the form. + The activated widget is specified by \a widget. +*/ + +/*! + \fn void QDesignerFormWindowInterface::changed() + + This signal is emitted whenever a form is changed. +*/ + +/*! + \fn void QDesignerFormWindowInterface::widgetRemoved(QWidget *widget) + + This signal is emitted whenever a widget is removed from the form. + The widget that was removed is specified by \a widget. +*/ + +/*! + \fn void QDesignerFormWindowInterface::objectRemoved(QObject *object) + + This signal is emitted whenever an object (such as + an action or a QButtonGroup) is removed from the form. + The object that was removed is specified by \a object. + + \since 4.5 +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractformwindow.h b/src/designer/src/lib/sdk/abstractformwindow.h new file mode 100644 index 0000000..441ae3a --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformwindow.h @@ -0,0 +1,159 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMWINDOW_H +#define ABSTRACTFORMWINDOW_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowCursorInterface; +class QDesignerFormWindowToolInterface; +class QUndoStack; +class QDir; +class QtResourceSet; + +class QDESIGNER_SDK_EXPORT QDesignerFormWindowInterface: public QWidget +{ + Q_OBJECT +public: + enum FeatureFlag + { + EditFeature = 0x01, + GridFeature = 0x02, + TabOrderFeature = 0x04, + DefaultFeature = EditFeature | GridFeature + }; + Q_DECLARE_FLAGS(Feature, FeatureFlag) + + enum ResourceFileSaveMode + { + SaveAllResourceFiles, + SaveOnlyUsedResourceFiles, + DontSaveResourceFiles + }; + Q_ENUM(ResourceFileSaveMode) + + explicit QDesignerFormWindowInterface(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + virtual ~QDesignerFormWindowInterface(); + + virtual QString fileName() const = 0; + virtual QDir absoluteDir() const = 0; + + virtual QString contents() const = 0; + virtual QStringList checkContents() const = 0; + virtual bool setContents(QIODevice *dev, QString *errorMessage = nullptr) = 0; + + virtual Feature features() const = 0; + virtual bool hasFeature(Feature f) const = 0; + + virtual QString author() const = 0; + virtual void setAuthor(const QString &author) = 0; + + virtual QString comment() const = 0; + virtual void setComment(const QString &comment) = 0; + + virtual void layoutDefault(int *margin, int *spacing) = 0; + virtual void setLayoutDefault(int margin, int spacing) = 0; + + virtual void layoutFunction(QString *margin, QString *spacing) = 0; + virtual void setLayoutFunction(const QString &margin, const QString &spacing) = 0; + + virtual QString pixmapFunction() const = 0; + virtual void setPixmapFunction(const QString &pixmapFunction) = 0; + + virtual QString exportMacro() const = 0; + virtual void setExportMacro(const QString &exportMacro) = 0; + + virtual QStringList includeHints() const = 0; + virtual void setIncludeHints(const QStringList &includeHints) = 0; + + virtual ResourceFileSaveMode resourceFileSaveMode() const = 0; + virtual void setResourceFileSaveMode(ResourceFileSaveMode behaviour) = 0; + + virtual QtResourceSet *resourceSet() const = 0; + virtual void setResourceSet(QtResourceSet *resourceSet) = 0; + + QStringList activeResourceFilePaths() const; + + virtual QDesignerFormEditorInterface *core() const; + virtual QDesignerFormWindowCursorInterface *cursor() const = 0; + + virtual int toolCount() const = 0; + + virtual int currentTool() const = 0; + virtual void setCurrentTool(int index) = 0; + + virtual QDesignerFormWindowToolInterface *tool(int index) const = 0; + virtual void registerTool(QDesignerFormWindowToolInterface *tool) = 0; + + virtual QPoint grid() const = 0; + + virtual QWidget *mainContainer() const = 0; + virtual void setMainContainer(QWidget *mainContainer) = 0; + virtual QWidget *formContainer() const = 0; + + virtual bool isManaged(QWidget *widget) const = 0; + + virtual bool isDirty() const = 0; + + static QDesignerFormWindowInterface *findFormWindow(QWidget *w); + static QDesignerFormWindowInterface *findFormWindow(QObject *obj); + + virtual QUndoStack *commandHistory() const = 0; + virtual void beginCommand(const QString &description) = 0; + virtual void endCommand() = 0; + + virtual void simplifySelection(QList *widgets) const = 0; + + // notifications + virtual void emitSelectionChanged() = 0; + + virtual QStringList resourceFiles() const = 0; + virtual void addResourceFile(const QString &path) = 0; + virtual void removeResourceFile(const QString &path) = 0; + + virtual void ensureUniqueObjectName(QObject *object) = 0; + +public Q_SLOTS: + virtual void manageWidget(QWidget *widget) = 0; + virtual void unmanageWidget(QWidget *widget) = 0; + + virtual void setFeatures(Feature f) = 0; + virtual void setDirty(bool dirty) = 0; + virtual void clearSelection(bool changePropertyDisplay = true) = 0; + virtual void selectWidget(QWidget *w, bool select = true) = 0; + virtual void setGrid(const QPoint &grid) = 0; + virtual void setFileName(const QString &fileName) = 0; + virtual bool setContents(const QString &contents) = 0; + + virtual void editWidgets() = 0; + void activateResourceFilePaths(const QStringList &paths, int *errorCount = nullptr, QString *errorMessages = nullptr); + +Q_SIGNALS: + void mainContainerChanged(QWidget *mainContainer); + void toolChanged(int toolIndex); + void fileNameChanged(const QString &fileName); + void featureChanged(Feature f); + void selectionChanged(); + void geometryChanged(); + + void resourceFilesChanged(); + + void widgetManaged(QWidget *widget); + void widgetUnmanaged(QWidget *widget); + void aboutToUnmanageWidget(QWidget *widget); + void activated(QWidget *widget); + + void changed(); + void widgetRemoved(QWidget *w); + void objectRemoved(QObject *o); +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMWINDOW_H diff --git a/src/designer/src/lib/sdk/abstractformwindowcursor.cpp b/src/designer/src/lib/sdk/abstractformwindowcursor.cpp new file mode 100644 index 0000000..9c4c135 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformwindowcursor.cpp @@ -0,0 +1,214 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindowcursor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerFormWindowCursorInterface + + \brief The QDesignerFormWindowCursorInterface class allows you to + query and modify a form window's widget selection, and in addition + modify the properties of all the form's widgets. + + \inmodule QtDesigner + + QDesignerFormWindowCursorInterface is a convenience class that + provides an interface to the associated form window's text cursor; + it provides a collection of functions that enables you to query a + given form window's selection and change the selection's focus + according to defined modes (MoveMode) and movements + (MoveOperation). You can also use the interface to query the + form's widgets and change their properties. + + The interface is not intended to be instantiated directly, but to + provide access to the selections and widgets of \QD's current form + windows. QDesignerFormWindowInterface always provides an + associated cursor interface. The form window for a given widget + can be retrieved using the static + QDesignerFormWindowInterface::findFormWindow() functions. For + example: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp 0 + + You can retrieve any of \QD's current form windows through + \QD's \l {QDesignerFormWindowManagerInterface}{form window + manager}. + + Once you have a form window's cursor interface, you can check if + the form window has a selection at all using the hasSelection() + function. You can query the form window for its total + widgetCount() and selectedWidgetCount(). You can retrieve the + currently selected widget (or widgets) using the current() or + selectedWidget() functions. + + You can retrieve any of the form window's widgets using the + widget() function, and check if a widget is selected using the + isWidgetSelected() function. You can use the setProperty() + function to set the selected widget's properties, and the + setWidgetProperty() or resetWidgetProperty() functions to modify + the properties of any given widget. + + Finally, you can change the selection by changing the text + cursor's position() using the setPosition() and movePosition() + functions. + + \sa QDesignerFormWindowInterface, QDesignerFormWindowManagerInterface +*/ + +/*! + \enum QDesignerFormWindowCursorInterface::MoveOperation + + This enum describes the types of text cursor operation that can occur in a form window. + + \value NoMove The cursor does not move. + \value Start Moves the cursor to the start of the focus chain. + \value End Moves the cursor to the end of the focus chain. + \value Next Moves the cursor to the next widget in the focus chain. + \value Prev Moves the cursor to the previous widget in the focus chain. + \value Left The cursor moves to the left. + \value Right The cursor moves to the right. + \value Up The cursor moves upwards. + \value Down The cursor moves downwards. +*/ + +/*! + \enum QDesignerFormWindowCursorInterface::MoveMode + + This enum describes the different modes that are used when the text cursor moves. + + \value MoveAnchor The anchor moves with the cursor to its new location. + \value KeepAnchor The anchor remains at the cursor's old location. +*/ + +/*! + Returns true if the specified \a widget is selected; otherwise + returns false. +*/ +bool QDesignerFormWindowCursorInterface::isWidgetSelected(QWidget *widget) const +{ + for (int index=0; index + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QWidget; +class QVariant; +class QString; + +class QDESIGNER_SDK_EXPORT QDesignerFormWindowCursorInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerFormWindowCursorInterface) + + enum MoveOperation + { + NoMove, + + Start, + End, + Next, + Prev, + Left, + Right, + Up, + Down + }; + + enum MoveMode + { + MoveAnchor, + KeepAnchor + }; + + QDesignerFormWindowCursorInterface() = default; + virtual ~QDesignerFormWindowCursorInterface() = default; + + virtual QDesignerFormWindowInterface *formWindow() const = 0; + + virtual bool movePosition(MoveOperation op, MoveMode mode = MoveAnchor) = 0; + + virtual int position() const = 0; + virtual void setPosition(int pos, MoveMode mode = MoveAnchor) = 0; + + virtual QWidget *current() const = 0; + + virtual int widgetCount() const = 0; + virtual QWidget *widget(int index) const = 0; + + virtual bool hasSelection() const = 0; + virtual int selectedWidgetCount() const = 0; + virtual QWidget *selectedWidget(int index) const = 0; + + virtual void setProperty(const QString &name, const QVariant &value) = 0; + virtual void setWidgetProperty(QWidget *widget, const QString &name, const QVariant &value) = 0; + virtual void resetWidgetProperty(QWidget *widget, const QString &name) = 0; + + bool isWidgetSelected(QWidget *widget) const; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMWINDOWCURSOR_H diff --git a/src/designer/src/lib/sdk/abstractformwindowmanager.cpp b/src/designer/src/lib/sdk/abstractformwindowmanager.cpp new file mode 100644 index 0000000..9e8494c --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformwindowmanager.cpp @@ -0,0 +1,538 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindowmanager.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerFormWindowManagerInterface + + \brief The QDesignerFormWindowManagerInterface class allows you to + manipulate the collection of form windows in \QD, and + control \QD's form editing actions. + + \inmodule QtDesigner + + QDesignerFormWindowManagerInterface is not intended to be + instantiated directly. \QD uses the form window manager to + control the various form windows in its workspace. You can + retrieve an interface to \QD's form window manager using + the QDesignerFormEditorInterface::formWindowManager() + function. For example: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp 0 + + When implementing a custom widget plugin, a pointer to \QD's + current QDesignerFormEditorInterface object (\c formEditor in the + example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's parameter. + You must subclass the QDesignerCustomWidgetInterface to expose + your plugin to \QD. + + The form window manager interface provides the createFormWindow() + function that enables you to create a new form window which you + can add to the collection of form windows that the manager + maintains, using the addFormWindow() slot. It also provides the + formWindowCount() function returning the number of form windows + currently under the manager's control, the formWindow() function + returning the form window associated with a given index, and the + activeFormWindow() function returning the currently selected form + window. The removeFormWindow() slot allows you to reduce the + number of form windows the manager must maintain, and the + setActiveFormWindow() slot allows you to change the form window + focus in \QD's workspace. + + In addition, QDesignerFormWindowManagerInterface contains a + collection of functions that enables you to intervene and control + \QD's form editing actions. All these functions return the + original action, making it possible to propagate the function + further after intervention. + + Finally, the interface provides three signals which are emitted + when a form window is added, when the currently selected form + window changes, or when a form window is removed, respectively. All + the signals carry the form window in question as their parameter. + + \sa QDesignerFormEditorInterface, QDesignerFormWindowInterface +*/ + +/*! + \enum QDesignerFormWindowManagerInterface::Action + + Specifies an action of \QD. + + \sa action() + + \since 5.0 + \value CutAction Clipboard Cut + \value CopyAction Clipboard Copy + \value PasteAction Clipboard Paste + \value DeleteAction Clipboard Delete + \value SelectAllAction Select All + \value LowerAction Lower current widget + \value RaiseAction Raise current widget + \value UndoAction Undo + \value RedoAction Redo + \value HorizontalLayoutAction Lay out using QHBoxLayout + \value VerticalLayoutAction Lay out using QVBoxLayout + \value SplitHorizontalAction Lay out in horizontal QSplitter + \value SplitVerticalAction Lay out in vertical QSplitter + \value GridLayoutAction Lay out using QGridLayout + \value FormLayoutAction Lay out using QFormLayout + \value BreakLayoutAction Break existing layout + \value AdjustSizeAction Adjust size + \value SimplifyLayoutAction Simplify QGridLayout or QFormLayout + \value DefaultPreviewAction Create a preview in default style + \value FormWindowSettingsDialogAction Show dialog with form settings +*/ + +/*! + \enum QDesignerFormWindowManagerInterface::ActionGroup + + Specifies an action group of \QD. + + \sa actionGroup() + \since 5.0 + \value StyledPreviewActionGroup Action group containing styled preview actions +*/ + +/*! + Constructs an interface with the given \a parent for the form window + manager. +*/ +QDesignerFormWindowManagerInterface::QDesignerFormWindowManagerInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the interface for the form window manager. +*/ +QDesignerFormWindowManagerInterface::~QDesignerFormWindowManagerInterface() = default; + +/*! + Allows you to intervene and control \QD's "cut" action. The function + returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +#if QT_CONFIG(clipboard) +QAction *QDesignerFormWindowManagerInterface::actionCut() const +{ + return action(CutAction); +} +#endif + +/*! + Allows you to intervene and control \QD's "copy" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +#if QT_CONFIG(clipboard) +QAction *QDesignerFormWindowManagerInterface::actionCopy() const +{ + return action(CopyAction); +} +#endif + +/*! + Allows you to intervene and control \QD's "paste" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +#if QT_CONFIG(clipboard) +QAction *QDesignerFormWindowManagerInterface::actionPaste() const +{ + return action(PasteAction); +} +#endif + +/*! + Allows you to intervene and control \QD's "delete" action. The function + returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionDelete() const +{ + return action(DeleteAction); +} + +/*! + Allows you to intervene and control \QD's "select all" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionSelectAll() const +{ + return action(SelectAllAction); +} + +/*! + Allows you to intervene and control the action of lowering a form + window in \QD's workspace. The function returns the original + action. + + \sa QAction + \deprecated + + Use action() instead. +*/ + +QAction *QDesignerFormWindowManagerInterface::actionLower() const +{ + return action(LowerAction); +} + +/*! + Allows you to intervene and control the action of raising of a + form window in \QD's workspace. The function returns the original + action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionRaise() const +{ + return action(RaiseAction); +} + +/*! + Allows you to intervene and control a request for horizontal + layout for a form window in \QD's workspace. The function returns + the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionHorizontalLayout() const +{ + return action(HorizontalLayoutAction); +} + +/*! + Allows you to intervene and control a request for vertical layout + for a form window in \QD's workspace. The function returns the + original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionVerticalLayout() const +{ + return action(VerticalLayoutAction); +} + +/*! + Allows you to intervene and control \QD's "split horizontal" + action. The function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionSplitHorizontal() const +{ + return action(SplitHorizontalAction); +} + +/*! + Allows you to intervene and control \QD's "split vertical" + action. The function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionSplitVertical() const +{ + return action(SplitVerticalAction); +} + +/*! + Allows you to intervene and control a request for grid layout for + a form window in \QD's workspace. The function returns the + original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionGridLayout() const +{ + return action(GridLayoutAction); +} + +/*! + Allows you to intervene and control \QD's "form layout" action. The + function returns the original action. + + \sa QAction + \since 4.4 + \deprecated + + Use action() instead. +*/ + +QAction *QDesignerFormWindowManagerInterface::actionFormLayout() const +{ + return action(FormLayoutAction); +} + +/*! + Allows you to intervene and control \QD's "break layout" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionBreakLayout() const +{ + return action(BreakLayoutAction); +} + +/*! + Allows you to intervene and control \QD's "adjust size" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionAdjustSize() const +{ + return action(AdjustSizeAction); +} + +/*! + Allows you to intervene and control \QD's "simplify layout" action. The + function returns the original action. + + \sa QAction + \since 4.4 + \deprecated + + Use action() instead. +*/ + +QAction *QDesignerFormWindowManagerInterface::actionSimplifyLayout() const +{ + return action(SimplifyLayoutAction); +} + +/*! + \fn virtual QDesignerFormWindowInterface *QDesignerFormWindowManagerInterface::activeFormWindow() const + Returns the currently active form window in \QD's workspace. + + \sa setActiveFormWindow(), removeFormWindow() +*/ + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerFormWindowManagerInterface::core() const + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::addFormWindow(QDesignerFormWindowInterface *formWindow) + Adds the given \a formWindow to the collection of windows that + \QD's form window manager maintains. + + \sa formWindowAdded() +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::removeFormWindow(QDesignerFormWindowInterface *formWindow) + Removes the given \a formWindow from the collection of windows that + \QD's form window manager maintains. + + \sa formWindow(), formWindowRemoved() +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::setActiveFormWindow(QDesignerFormWindowInterface *formWindow) + Sets the given \a formWindow to be the currently active form window in + \QD's workspace. + + \sa activeFormWindow(), activeFormWindowChanged() +*/ + +/*! + \fn int QDesignerFormWindowManagerInterface::formWindowCount() const + Returns the number of form windows maintained by \QD's form window + manager. +*/ + +/*! + \fn QDesignerFormWindowInterface *QDesignerFormWindowManagerInterface::formWindow(int index) const + Returns the form window at the given \a index. + + \sa setActiveFormWindow(), removeFormWindow() +*/ + +/*! + \fn QDesignerFormWindowInterface *QDesignerFormWindowManagerInterface::createFormWindow(QWidget *parent, Qt::WindowFlags flags) + + Creates a form window with the given \a parent and the given window + \a flags. + + \sa addFormWindow() +*/ + +/*! + \fn QPixmap QDesignerFormWindowManagerInterface::createPreviewPixmap() const + + Creates a pixmap representing the preview of the currently active form. +*/ + +/*! + Allows you to intervene and control \QD's "undo" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionUndo() const +{ + return action(UndoAction); +} + +/*! + Allows you to intervene and control \QD's "redo" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionRedo() const +{ + return action(RedoAction); +} + +/*! + \fn void QDesignerFormWindowManagerInterface::formWindowAdded(QDesignerFormWindowInterface *formWindow) + + This signal is emitted when a new form window is added to the + collection of windows that \QD's form window manager maintains. A + pointer to the new \a formWindow is passed as an argument. + + \sa addFormWindow(), setActiveFormWindow() +*/ + +/*! + \fn void QDesignerFormWindowManagerInterface::formWindowSettingsChanged(QDesignerFormWindowInterface *formWindow) + + This signal is emitted when the settings of the form window change. It can be used to update + window titles, etc. accordingly. A pointer to the \a formWindow is passed as an argument. + + \sa FormWindowSettingsDialogAction +*/ + +/*! + \fn void QDesignerFormWindowManagerInterface::formWindowRemoved(QDesignerFormWindowInterface *formWindow) + + This signal is emitted when a form window is removed from the + collection of windows that \QD's form window manager maintains. A + pointer to the removed \a formWindow is passed as an argument. + + \sa removeFormWindow() +*/ + +/*! + \fn void QDesignerFormWindowManagerInterface::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) + + This signal is emitted when the contents of the currently active + form window in \QD's workspace changed. A pointer to the currently + active \a formWindow is passed as an argument. + + \sa activeFormWindow() +*/ + +/*! + \fn void QDesignerFormWindowManagerInterface::dragItems(const QList &item_list) + + \internal +*/ + +/*! + \fn virtual QAction QDesignerFormWindowManagerInterface::action(Action action) const + + Returns the action specified by the enumeration value \a action. + + Obsoletes the action accessors of Qt 4.X. + + \since 5.0 +*/ + +/*! + \fn virtual QActionGroup *QDesignerFormWindowManagerInterface::actionGroup(ActionGroup actionGroup) const + + Returns the action group specified by the enumeration value \a actionGroup. + + \since 5.0 +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::showPreview() + + Show a preview of the current form using the default parameters. + + \since 5.0 + \sa closeAllPreviews() +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::closeAllPreviews() + + Close all currently open previews. + + \since 5.0 + \sa showPreview() +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::showPluginDialog() + + Opens a dialog showing the plugins loaded by \QD's and its plugin load failures. + + \since 5.0 +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractformwindowmanager.h b/src/designer/src/lib/sdk/abstractformwindowmanager.h new file mode 100644 index 0000000..08e07f0 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformwindowmanager.h @@ -0,0 +1,123 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMWINDOWMANAGER_H +#define ABSTRACTFORMWINDOWMANAGER_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerDnDItemInterface; + +class QWidget; +class QPixmap; +class QAction; +class QActionGroup; + +class QDESIGNER_SDK_EXPORT QDesignerFormWindowManagerInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerFormWindowManagerInterface(QObject *parent = nullptr); + virtual ~QDesignerFormWindowManagerInterface(); + + enum Action + { +#if QT_CONFIG(clipboard) + CutAction = 100, + CopyAction, + PasteAction, +#endif + DeleteAction = 103, + SelectAllAction, + + LowerAction = 200, + RaiseAction, + + UndoAction = 300, + RedoAction, + + HorizontalLayoutAction = 400, + VerticalLayoutAction, + SplitHorizontalAction, + SplitVerticalAction, + GridLayoutAction, + FormLayoutAction, + BreakLayoutAction, + AdjustSizeAction, + SimplifyLayoutAction, + + DefaultPreviewAction = 500, + + FormWindowSettingsDialogAction = 600 + }; + Q_ENUM(Action) + + enum ActionGroup + { + StyledPreviewActionGroup = 100 + }; + Q_ENUM(ActionGroup) + + virtual QAction *action(Action action) const = 0; + virtual QActionGroup *actionGroup(ActionGroup actionGroup) const = 0; + +#if QT_CONFIG(clipboard) + QAction *actionCut() const; + QAction *actionCopy() const; + QAction *actionPaste() const; +#endif + QAction *actionDelete() const; + QAction *actionSelectAll() const; + QAction *actionLower() const; + QAction *actionRaise() const; + QAction *actionUndo() const; + QAction *actionRedo() const; + + QAction *actionHorizontalLayout() const; + QAction *actionVerticalLayout() const; + QAction *actionSplitHorizontal() const; + QAction *actionSplitVertical() const; + QAction *actionGridLayout() const; + QAction *actionFormLayout() const; + QAction *actionBreakLayout() const; + QAction *actionAdjustSize() const; + QAction *actionSimplifyLayout() const; + + virtual QDesignerFormWindowInterface *activeFormWindow() const = 0; + + virtual int formWindowCount() const = 0; + virtual QDesignerFormWindowInterface *formWindow(int index) const = 0; + + virtual QDesignerFormWindowInterface *createFormWindow(QWidget *parentWidget = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()) = 0; + + virtual QDesignerFormEditorInterface *core() const = 0; + + virtual void dragItems(const QList &item_list) = 0; + + virtual QPixmap createPreviewPixmap() const = 0; + +Q_SIGNALS: + void formWindowAdded(QDesignerFormWindowInterface *formWindow); + void formWindowRemoved(QDesignerFormWindowInterface *formWindow); + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + void formWindowSettingsChanged(QDesignerFormWindowInterface *fw); + +public Q_SLOTS: + virtual void addFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void removeFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void setActiveFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void showPreview() = 0; + virtual void closeAllPreviews() = 0; + virtual void showPluginDialog() = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMWINDOWMANAGER_H diff --git a/src/designer/src/lib/sdk/abstractformwindowtool.cpp b/src/designer/src/lib/sdk/abstractformwindowtool.cpp new file mode 100644 index 0000000..4bb7db8 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformwindowtool.cpp @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindowtool.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerFormWindowToolInterface + + \brief The QDesignerFormWindowToolInterface class provides an + interface that enables tools to be used on items in a form window. + + \inmodule QtDesigner + + \internal +*/ + +/*! +*/ +QDesignerFormWindowToolInterface::QDesignerFormWindowToolInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! +*/ +QDesignerFormWindowToolInterface::~QDesignerFormWindowToolInterface() = default; + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerFormWindowToolInterface::core() const = 0 +*/ + +/*! + \fn virtual QDesignerFormWindowInterface *QDesignerFormWindowToolInterface::formWindow() const = 0 +*/ + +/*! + \fn virtual QWidget *QDesignerFormWindowToolInterface::editor() const = 0 +*/ + +/*! + \fn virtual QAction *QDesignerFormWindowToolInterface::action() const = 0 +*/ + +/*! + \fn virtual void QDesignerFormWindowToolInterface::activated() = 0 +*/ + +/*! + \fn virtual void QDesignerFormWindowToolInterface::deactivated() = 0 +*/ + +/*! + \fn virtual void QDesignerFormWindowToolInterface::saveToDom(DomUI*, QWidget*) { +*/ + +/*! + \fn virtual void QDesignerFormWindowToolInterface::loadFromDom(DomUI*, QWidget*) { +*/ + +/*! + \fn virtual bool QDesignerFormWindowToolInterface::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) = 0 +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractformwindowtool.h b/src/designer/src/lib/sdk/abstractformwindowtool.h new file mode 100644 index 0000000..93dee15 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractformwindowtool.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMWINDOWTOOL_H +#define ABSTRACTFORMWINDOWTOOL_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QWidget; +class QAction; +class DomUI; + +class QDESIGNER_SDK_EXPORT QDesignerFormWindowToolInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerFormWindowToolInterface(QObject *parent = nullptr); + virtual ~QDesignerFormWindowToolInterface(); + + virtual QDesignerFormEditorInterface *core() const = 0; + virtual QDesignerFormWindowInterface *formWindow() const = 0; + virtual QWidget *editor() const = 0; + + virtual QAction *action() const = 0; + + virtual void activated() = 0; + virtual void deactivated() = 0; + + virtual void saveToDom(DomUI*, QWidget*) {} + virtual void loadFromDom(DomUI*, QWidget*) {} + + virtual bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMWINDOWTOOL_H diff --git a/src/designer/src/lib/sdk/abstractintegration.cpp b/src/designer/src/lib/sdk/abstractintegration.cpp new file mode 100644 index 0000000..aa91e5e --- /dev/null +++ b/src/designer/src/lib/sdk/abstractintegration.cpp @@ -0,0 +1,766 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractintegration.h" +#include "abstractformwindow.h" +#include "abstractformwindowmanager.h" +#include "abstractformwindowcursor.h" +#include "abstractformeditor.h" +#include "abstractactioneditor.h" +#include "abstractwidgetbox.h" +#include "abstractresourcebrowser.h" +#include "propertysheet.h" +#include "abstractpropertyeditor.h" +#include "qextensionmanager.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +/*! + \class QDesignerIntegrationInterface + + \brief The QDesignerIntegrationInterface glues together parts of \QD + and allows for overwriting functionality for IDE integration. + + \internal + + \inmodule QtDesigner + + \sa QDesignerFormEditorInterface +*/ + +/*! + \enum QDesignerIntegrationInterface::ResourceFileWatcherBehaviour + + \internal + + This enum describes if and how resource files are watched. + + \value NoResourceFileWatcher Do not watch for changes + \value ReloadResourceFileSilently Reload files silently. + \value PromptToReloadResourceFile Prompt the user to reload. +*/ + +/*! + \enum QDesignerIntegrationInterface::FeatureFlag + + \internal + + This enum describes the features that are available and can be + controlled by the integration. + + \value ResourceEditorFeature The resource editor is enabled. + \value SlotNavigationFeature Provide context menu entry offering 'Go to slot'. + \value DefaultWidgetActionFeature Trigger the preferred action of + QDesignerTaskMenuExtension when widget is activated. + \value DefaultFeature Default for \QD + + \sa hasFeature(), features() +*/ + +/*! + \property QDesignerIntegrationInterface::headerSuffix + + Returns the suffix of the header of promoted widgets. +*/ + +/*! + \property QDesignerIntegrationInterface::headerLowercase + + Returns whether headers of promoted widgets should be lower-cased (as the user types the class name). +*/ + +/*! + \property QDesignerIntegrationInterface::qtVersion + + Determines the Qt version used when writing out forms. + + \since 6.9 +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateSelection() + + \brief Sets the selected widget of the form window in various components. + + \internal + + In IDE integrations, the method can be overwritten to move the selection handles + of the form's main window, should it be selected. +*/ + +/*! + \fn virtual QWidget *QDesignerIntegrationInterface::containerWindow(QWidget *widget) const + + \brief Returns the outer window containing a form for applying main container geometries. + + \internal + + Should be implemented by IDE integrations. +*/ + +/*! + \fn virtual QDesignerResourceBrowserInterface *QDesignerIntegrationInterface::createResourceBrowser(QWidget *parent = 0) + + \brief Creates a resource browser depending on IDE integration. + + \internal + + \note Language integration takes precedence. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) + + \brief Triggered by the property editor to update a property value. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateProperty(const QString &name, const QVariant &value) + + \brief Triggered by the property editor to update a property value without subproperty handling. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::resetProperty(const QString &name) + + \brief Triggered by the property editor to reset a property value. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::addDynamicProperty(const QString &name, const QVariant &value) + + \brief Triggered by the property editor to add a dynamic property value. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::removeDynamicProperty(const QString &name) + + \brief Triggered by the property editor to remove a dynamic property. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateActiveFormWindow(QDesignerFormWindowInterface *formWindow) + + \brief Sets up the active form window. + + \internal +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::setupFormWindow(QDesignerFormWindowInterface *formWindow) + + \brief Sets up the new form window. + + \internal +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateCustomWidgetPlugins() + + \brief Triggers a reload of the custom widget plugins. + + \internal +*/ + +class QDesignerIntegrationInterfacePrivate +{ +public: + QDesignerIntegrationInterfacePrivate(QDesignerFormEditorInterface *core) : + m_core(core) {} + + QDesignerFormEditorInterface *m_core; + QVersionNumber qtVersion{QLibraryInfo::version()}; +}; + +QDesignerIntegrationInterface::QDesignerIntegrationInterface(QDesignerFormEditorInterface *core, QObject *parent) + : QObject(parent), d(new QDesignerIntegrationInterfacePrivate(core)) +{ + core->setIntegration(this); +} + +QDesignerIntegrationInterface::~QDesignerIntegrationInterface() = default; + +QDesignerFormEditorInterface *QDesignerIntegrationInterface::core() const +{ + return d->m_core; +} + +bool QDesignerIntegrationInterface::hasFeature(Feature f) const +{ + return (features() & f) != 0; +} + +QVersionNumber QDesignerIntegrationInterface::qtVersion() const +{ + return d->qtVersion; +} + +void QDesignerIntegrationInterface::setQtVersion(const QVersionNumber &qtVersion) +{ + d->qtVersion = qtVersion; +} + +void QDesignerIntegrationInterface::emitObjectNameChanged(QDesignerFormWindowInterface *formWindow, QObject *object, const QString &newName, const QString &oldName) +{ + emit objectNameChanged(formWindow, object, newName, oldName); +} + +void QDesignerIntegrationInterface::emitNavigateToSlot(const QString &objectName, + const QString &signalSignature, + const QStringList ¶meterNames) +{ + emit navigateToSlot(objectName, signalSignature, parameterNames); +} + +void QDesignerIntegrationInterface::emitNavigateToSlot(const QString &slotSignature) +{ + emit navigateToSlot(slotSignature); +} + +void QDesignerIntegrationInterface::emitHelpRequested(const QString &manual, const QString &document) +{ + emit helpRequested(manual, document); +} + +/*! + \class QDesignerIntegration + + \brief The QDesignerIntegration class is \QD's implementation of + QDesignerIntegrationInterface. + + \internal + + \inmodule QtDesigner + + IDE integrations should register classes derived from QDesignerIntegration + with QDesignerFormEditorInterface. +*/ + +namespace qdesigner_internal { + +class QDesignerIntegrationPrivate { +public: + explicit QDesignerIntegrationPrivate(QDesignerIntegration *qq); + + QWidget *containerWindow(QWidget *widget) const; + + // Load plugins into widget database and factory. + static void initializePlugins(QDesignerFormEditorInterface *formEditor); + + QString contextHelpId() const; + + void updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling); + void resetProperty(const QString &name); + void addDynamicProperty(const QString &name, const QVariant &value); + void removeDynamicProperty(const QString &name); + + void setupFormWindow(QDesignerFormWindowInterface *formWindow); + void updateSelection(); + void updateCustomWidgetPlugins(); + + void initialize(); + void getSelection(qdesigner_internal::Selection &s); + QObject *propertyEditorObject(); + + QDesignerIntegration *q; + QString headerSuffix; + bool headerLowercase; + QDesignerIntegrationInterface::Feature m_features; + QDesignerIntegrationInterface::ResourceFileWatcherBehaviour m_resourceFileWatcherBehaviour; + QString m_gradientsPath; + QtGradientManager *m_gradientManager; +}; + +QDesignerIntegrationPrivate::QDesignerIntegrationPrivate(QDesignerIntegration *qq) : + q(qq), + headerSuffix(u".h"_s), + headerLowercase(true), + m_features(QDesignerIntegrationInterface::DefaultFeature), + m_resourceFileWatcherBehaviour(QDesignerIntegrationInterface::PromptToReloadResourceFile), + m_gradientManager(nullptr) +{ +} + +void QDesignerIntegrationPrivate::initialize() +{ + // + // integrate the `Form Editor component' + // + + // Extensions + QDesignerFormEditorInterface *core = q->core(); + if (QDesignerPropertyEditor *designerPropertyEditor= qobject_cast(core->propertyEditor())) { + QObject::connect(designerPropertyEditor, &QDesignerPropertyEditor::propertyValueChanged, + q, QOverload::of(&QDesignerIntegration::updateProperty)); + QObject::connect(designerPropertyEditor, &QDesignerPropertyEditor::resetProperty, + q, &QDesignerIntegration::resetProperty); + QObject::connect(designerPropertyEditor, &QDesignerPropertyEditor::addDynamicProperty, + q, &QDesignerIntegration::addDynamicProperty); + QObject::connect(designerPropertyEditor, &QDesignerPropertyEditor::removeDynamicProperty, + q, &QDesignerIntegration::removeDynamicProperty); + } + + QObject::connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowAdded, + q, &QDesignerIntegrationInterface::setupFormWindow); + + QObject::connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + q, &QDesignerIntegrationInterface::updateActiveFormWindow); + + m_gradientManager = new QtGradientManager(q); + core->setGradientManager(m_gradientManager); + + const QString gradientsFile = u"/gradients.xml"_s; + m_gradientsPath = dataDirectory() + gradientsFile; + + // Migrate from legacy to standard data directory in Qt 7 + // ### FIXME Qt 8: Remove (QTBUG-96005) +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + const QString source = QFileInfo::exists(m_gradientsPath) + ? m_gradientsPath : legacyDataDirectory() + gradientsFile; +#else + const QString source = m_gradientsPath; +#endif + + QFile f(source); + if (f.open(QIODevice::ReadOnly)) { + QtGradientUtils::restoreState(m_gradientManager, QString::fromLatin1(f.readAll())); + f.close(); + } else { + QFile defaultGradients(u":/qt-project.org/designer/defaultgradients.xml"_s); + if (defaultGradients.open(QIODevice::ReadOnly)) { + QtGradientUtils::restoreState(m_gradientManager, QString::fromLatin1(defaultGradients.readAll())); + defaultGradients.close(); + } + } + + if (WidgetDataBase *widgetDataBase = qobject_cast(core->widgetDataBase())) + widgetDataBase->grabStandardWidgetBoxIcons(); +} + +void QDesignerIntegrationPrivate::updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) +{ + QDesignerFormWindowInterface *formWindow = q->core()->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + Selection selection; + getSelection(selection); + if (selection.empty()) + return; + + SetPropertyCommand *cmd = new SetPropertyCommand(formWindow); + // find a reference object to compare to and to find the right group + if (cmd->init(selection.selection(), name, value, propertyEditorObject(), enableSubPropertyHandling)) { + formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "Unable to set property " << name << '.'; + } +} + +void QDesignerIntegrationPrivate::resetProperty(const QString &name) +{ + QDesignerFormWindowInterface *formWindow = q->core()->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + Selection selection; + getSelection(selection); + if (selection.empty()) + return; + + ResetPropertyCommand *cmd = new ResetPropertyCommand(formWindow); + // find a reference object to find the right group + if (cmd->init(selection.selection(), name, propertyEditorObject())) { + formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "** WARNING Unable to reset property " << name << '.'; + } +} + +void QDesignerIntegrationPrivate::addDynamicProperty(const QString &name, const QVariant &value) +{ + QDesignerFormWindowInterface *formWindow = q->core()->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + Selection selection; + getSelection(selection); + if (selection.empty()) + return; + + AddDynamicPropertyCommand *cmd = new AddDynamicPropertyCommand(formWindow); + if (cmd->init(selection.selection(), propertyEditorObject(), name, value)) { + formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "** WARNING Unable to add dynamic property " << name << '.'; + } +} + +void QDesignerIntegrationPrivate::removeDynamicProperty(const QString &name) +{ + QDesignerFormWindowInterface *formWindow = q->core()->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + Selection selection; + getSelection(selection); + if (selection.empty()) + return; + + RemoveDynamicPropertyCommand *cmd = new RemoveDynamicPropertyCommand(formWindow); + if (cmd->init(selection.selection(), propertyEditorObject(), name)) { + formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "** WARNING Unable to remove dynamic property " << name << '.'; + } + +} + +void QDesignerIntegrationPrivate::setupFormWindow(QDesignerFormWindowInterface *formWindow) +{ + QObject::connect(formWindow, &QDesignerFormWindowInterface::selectionChanged, + q, &QDesignerIntegrationInterface::updateSelection); +} + +void QDesignerIntegrationPrivate::updateSelection() +{ + QDesignerFormEditorInterface *core = q->core(); + QDesignerFormWindowInterface *formWindow = core->formWindowManager()->activeFormWindow(); + QWidget *selection = nullptr; + + if (formWindow) { + selection = formWindow->cursor()->current(); + } + + if (QDesignerActionEditorInterface *actionEditor = core->actionEditor()) + actionEditor->setFormWindow(formWindow); + + if (QDesignerPropertyEditorInterface *propertyEditor = core->propertyEditor()) + propertyEditor->setObject(selection); + + if (QDesignerObjectInspectorInterface *objectInspector = core->objectInspector()) + objectInspector->setFormWindow(formWindow); + +} + +QWidget *QDesignerIntegrationPrivate::containerWindow(QWidget *widget) const +{ + // Find the parent window to apply a geometry to. + while (widget) { + if (widget->isWindow()) + break; + if (!qstrcmp(widget->metaObject()->className(), "QMdiSubWindow")) + break; + + widget = widget->parentWidget(); + } + + return widget; +} + +void QDesignerIntegrationPrivate::getSelection(Selection &s) +{ + QDesignerFormEditorInterface *core = q->core(); + // Get multiselection from object inspector + if (QDesignerObjectInspector *designerObjectInspector = qobject_cast(core->objectInspector())) { + designerObjectInspector->getSelection(s); + // Action editor puts actions that are not on the form yet + // into the property editor only. + if (s.empty()) + if (QObject *object = core->propertyEditor()->object()) + s.objects.push_back(object); + + } else { + // Just in case someone plugs in an old-style object inspector: Emulate selection + s.clear(); + QDesignerFormWindowInterface *formWindow = core->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + QObject *object = core->propertyEditor()->object(); + if (object->isWidgetType()) { + QWidget *widget = static_cast(object); + QDesignerFormWindowCursorInterface *cursor = formWindow->cursor(); + if (cursor->isWidgetSelected(widget)) { + s.managed.push_back(widget); + } else { + s.unmanaged.push_back(widget); + } + } else { + s.objects.push_back(object); + } + } +} + +QObject *QDesignerIntegrationPrivate::propertyEditorObject() +{ + if (QDesignerPropertyEditorInterface *propertyEditor = q->core()->propertyEditor()) + return propertyEditor->object(); + return nullptr; +} + +// Load plugins into widget database and factory. +void QDesignerIntegrationPrivate::initializePlugins(QDesignerFormEditorInterface *formEditor) +{ + // load the plugins + qdesigner_internal::WidgetDataBase *widgetDataBase = qobject_cast(formEditor->widgetDataBase()); + if (widgetDataBase) { + widgetDataBase->loadPlugins(); + } + + if (qdesigner_internal::WidgetFactory *widgetFactory = qobject_cast(formEditor->widgetFactory())) { + widgetFactory->loadPlugins(); + } + + if (widgetDataBase) { + widgetDataBase->grabDefaultPropertyValues(); + } +} + +void QDesignerIntegrationPrivate::updateCustomWidgetPlugins() +{ + QDesignerFormEditorInterface *formEditor = q->core(); + if (QDesignerPluginManager *pm = formEditor->pluginManager()) + pm->registerNewPlugins(); + + initializePlugins(formEditor); + + // Do not just reload the last file as the WidgetBox merges the compiled-in resources + // and $HOME/.designer/widgetbox.xml. This would also double the scratchpad. + if (QDesignerWidgetBox *wb = qobject_cast(formEditor->widgetBox())) { + const QDesignerWidgetBox::LoadMode oldLoadMode = wb->loadMode(); + wb->setLoadMode(QDesignerWidgetBox::LoadCustomWidgetsOnly); + wb->load(); + wb->setLoadMode(oldLoadMode); + } +} + +static QString fixHelpClassName(const QString &className) +{ + // ### generalize using the Widget Data Base + if (className == "Line"_L1) + return u"QFrame"_s; + if (className == "Spacer"_L1) + return u"QSpacerItem"_s; + if (className == "QLayoutWidget"_L1) + return u"QLayout"_s; + return className; +} + +// Return class in which the property is defined +static QString classForProperty(QDesignerFormEditorInterface *core, + QObject *object, + const QString &property) +{ + if (const QDesignerPropertySheetExtension *ps = qt_extension(core->extensionManager(), object)) { + const int index = ps->indexOf(property); + if (index >= 0) + return ps->propertyGroup(index); + } + return QString(); +} + +QString QDesignerIntegrationPrivate::contextHelpId() const +{ + QDesignerFormEditorInterface *core = q->core(); + QObject *currentObject = core->propertyEditor()->object(); + if (!currentObject) + return QString(); + // Return a help index id consisting of "class::property" + QString className; + QString currentPropertyName = core->propertyEditor()->currentPropertyName(); + if (!currentPropertyName.isEmpty()) + className = classForProperty(core, currentObject, currentPropertyName); + if (className.isEmpty()) { + currentPropertyName.clear(); // We hit on some fake property. + className = qdesigner_internal::WidgetFactory::classNameOf(core, currentObject); + } + QString helpId = fixHelpClassName(className); + if (!currentPropertyName.isEmpty()) { + helpId += "::"_L1; + helpId += currentPropertyName; + } + return helpId; +} + +} // namespace qdesigner_internal + +// -------------- QDesignerIntegration +// As of 4.4, the header will be distributed with the Eclipse plugin. + +QDesignerIntegration::QDesignerIntegration(QDesignerFormEditorInterface *core, QObject *parent) : + QDesignerIntegrationInterface(core, parent), + d(new qdesigner_internal::QDesignerIntegrationPrivate(this)) +{ + d->initialize(); +} + +QDesignerIntegration::~QDesignerIntegration() +{ + QFile f(d->m_gradientsPath); + if (f.open(QIODevice::WriteOnly)) { + f.write(QtGradientUtils::saveState(d->m_gradientManager).toUtf8()); + f.close(); + } +} + +QString QDesignerIntegration::headerSuffix() const +{ + return d->headerSuffix; +} + +void QDesignerIntegration::setHeaderSuffix(const QString &headerSuffix) +{ + d->headerSuffix = headerSuffix; +} + +bool QDesignerIntegration::isHeaderLowercase() const +{ + return d->headerLowercase; +} + +void QDesignerIntegration::setHeaderLowercase(bool headerLowercase) +{ + d->headerLowercase = headerLowercase; +} + +QDesignerIntegrationInterface::Feature QDesignerIntegration::features() const +{ + return d->m_features; +} + +void QDesignerIntegration::setFeatures(Feature f) +{ + d->m_features = f; +} + +QDesignerIntegrationInterface::ResourceFileWatcherBehaviour QDesignerIntegration::resourceFileWatcherBehaviour() const +{ + return d->m_resourceFileWatcherBehaviour; +} + +void QDesignerIntegration::setResourceFileWatcherBehaviour(ResourceFileWatcherBehaviour behaviour) +{ + if (d->m_resourceFileWatcherBehaviour != behaviour) { + d->m_resourceFileWatcherBehaviour = behaviour; + core()->resourceModel()->setWatcherEnabled(behaviour != QDesignerIntegrationInterface::NoResourceFileWatcher); + } +} + +void QDesignerIntegration::updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) +{ + d->updateProperty(name, value, enableSubPropertyHandling); + emit propertyChanged(core()->formWindowManager()->activeFormWindow(), name, value); +} + +void QDesignerIntegration::updateProperty(const QString &name, const QVariant &value) +{ + updateProperty(name, value, true); +} + +void QDesignerIntegration::resetProperty(const QString &name) +{ + d->resetProperty(name); +} + +void QDesignerIntegration::addDynamicProperty(const QString &name, const QVariant &value) +{ + d->addDynamicProperty(name, value); +} + +void QDesignerIntegration::removeDynamicProperty(const QString &name) +{ + d->removeDynamicProperty(name); +} + +void QDesignerIntegration::updateActiveFormWindow(QDesignerFormWindowInterface *) +{ + d->updateSelection(); +} + +void QDesignerIntegration::setupFormWindow(QDesignerFormWindowInterface *formWindow) +{ + d->setupFormWindow(formWindow); + connect(formWindow, &QDesignerFormWindowInterface::selectionChanged, + this, &QDesignerIntegrationInterface::updateSelection); +} + +void QDesignerIntegration::updateSelection() +{ + d->updateSelection(); +} + +QWidget *QDesignerIntegration::containerWindow(QWidget *widget) const +{ + return d->containerWindow(widget); +} + +// Load plugins into widget database and factory. +void QDesignerIntegration::initializePlugins(QDesignerFormEditorInterface *formEditor) +{ + qdesigner_internal::QDesignerIntegrationPrivate::initializePlugins(formEditor); +} + +void QDesignerIntegration::updateCustomWidgetPlugins() +{ + d->updateCustomWidgetPlugins(); +} + +QDesignerResourceBrowserInterface *QDesignerIntegration::createResourceBrowser(QWidget *) +{ + return nullptr; +} + +QString QDesignerIntegration::contextHelpId() const +{ + return d->contextHelpId(); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractintegration.h b/src/designer/src/lib/sdk/abstractintegration.h new file mode 100644 index 0000000..d674564 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractintegration.h @@ -0,0 +1,158 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTINTEGRATION_H +#define ABSTRACTINTEGRATION_H + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QDesignerIntegrationInterfacePrivate; +class QDesignerResourceBrowserInterface; +class QVariant; +class QWidget; + +namespace qdesigner_internal { +class QDesignerIntegrationPrivate; +} + +class QDESIGNER_SDK_EXPORT QDesignerIntegrationInterface: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString headerSuffix READ headerSuffix WRITE setHeaderSuffix) + Q_PROPERTY(bool headerLowercase READ isHeaderLowercase WRITE setHeaderLowercase) + Q_PROPERTY(QVersionNumber qtVersion READ qtVersion WRITE setQtVersion) + +public: + enum ResourceFileWatcherBehaviour + { + NoResourceFileWatcher, + ReloadResourceFileSilently, + PromptToReloadResourceFile // Default + }; + Q_ENUM(ResourceFileWatcherBehaviour) + + enum FeatureFlag + { + ResourceEditorFeature = 0x1, + SlotNavigationFeature = 0x2, + DefaultWidgetActionFeature = 0x4, + DefaultFeature = ResourceEditorFeature | DefaultWidgetActionFeature + }; + Q_DECLARE_FLAGS(Feature, FeatureFlag) + + explicit QDesignerIntegrationInterface(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + virtual ~QDesignerIntegrationInterface(); + + QDesignerFormEditorInterface *core() const; + + virtual QWidget *containerWindow(QWidget *widget) const = 0; + + // Create a resource browser specific to integration. Language integration takes precedence + virtual QDesignerResourceBrowserInterface *createResourceBrowser(QWidget *parent = nullptr) = 0; + virtual QString headerSuffix() const = 0; + virtual void setHeaderSuffix(const QString &headerSuffix) = 0; + + virtual bool isHeaderLowercase() const = 0; + virtual void setHeaderLowercase(bool headerLowerCase) = 0; + + QVersionNumber qtVersion() const; + void setQtVersion(const QVersionNumber &qtVersion); + + virtual Feature features() const = 0; + bool hasFeature(Feature f) const; + + virtual ResourceFileWatcherBehaviour resourceFileWatcherBehaviour() const = 0; + virtual void setResourceFileWatcherBehaviour(ResourceFileWatcherBehaviour behaviour) = 0; + + virtual QString contextHelpId() const = 0; + + void emitObjectNameChanged(QDesignerFormWindowInterface *formWindow, QObject *object, + const QString &newName, const QString &oldName); + void emitNavigateToSlot(const QString &objectName, const QString &signalSignature, const QStringList ¶meterNames); + void emitNavigateToSlot(const QString &slotSignature); + void emitHelpRequested(const QString &manual, const QString &document); + +Q_SIGNALS: + void propertyChanged(QDesignerFormWindowInterface *formWindow, const QString &name, const QVariant &value); + void objectNameChanged(QDesignerFormWindowInterface *formWindow, QObject *object, const QString &newName, const QString &oldName); + void helpRequested(const QString &manual, const QString &document); + + void navigateToSlot(const QString &objectName, const QString &signalSignature, const QStringList ¶meterNames); + void navigateToSlot(const QString &slotSignature); + +public Q_SLOTS: + virtual void setFeatures(Feature f) = 0; + virtual void updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) = 0; + virtual void updateProperty(const QString &name, const QVariant &value) = 0; + // Additional signals of designer property editor + virtual void resetProperty(const QString &name) = 0; + virtual void addDynamicProperty(const QString &name, const QVariant &value) = 0; + virtual void removeDynamicProperty(const QString &name) = 0; + + virtual void updateActiveFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void setupFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void updateSelection() = 0; + virtual void updateCustomWidgetPlugins() = 0; + +private: + QScopedPointer d; +}; + +class QDESIGNER_SDK_EXPORT QDesignerIntegration: public QDesignerIntegrationInterface +{ + Q_OBJECT +public: + explicit QDesignerIntegration(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + virtual ~QDesignerIntegration(); + + QString headerSuffix() const override; + void setHeaderSuffix(const QString &headerSuffix) override; + + bool isHeaderLowercase() const override; + void setHeaderLowercase(bool headerLowerCase) override; + + Feature features() const override; + virtual void setFeatures(Feature f) override; + + ResourceFileWatcherBehaviour resourceFileWatcherBehaviour() const override; + void setResourceFileWatcherBehaviour(ResourceFileWatcherBehaviour behaviour) override; + + virtual QWidget *containerWindow(QWidget *widget) const override; + + // Load plugins into widget database and factory. + static void initializePlugins(QDesignerFormEditorInterface *formEditor); + + // Create a resource browser specific to integration. Language integration takes precedence + QDesignerResourceBrowserInterface *createResourceBrowser(QWidget *parent = nullptr) override; + + QString contextHelpId() const override; + + void updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) override; + void updateProperty(const QString &name, const QVariant &value) override; + // Additional signals of designer property editor + void resetProperty(const QString &name) override; + void addDynamicProperty(const QString &name, const QVariant &value) override; + void removeDynamicProperty(const QString &name) override; + + void updateActiveFormWindow(QDesignerFormWindowInterface *formWindow) override; + void setupFormWindow(QDesignerFormWindowInterface *formWindow) override; + void updateSelection() override; + void updateCustomWidgetPlugins() override; + +private: + QScopedPointer d; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTINTEGRATION_H diff --git a/src/designer/src/lib/sdk/abstractintrospection.cpp b/src/designer/src/lib/sdk/abstractintrospection.cpp new file mode 100644 index 0000000..6fc112f --- /dev/null +++ b/src/designer/src/lib/sdk/abstractintrospection.cpp @@ -0,0 +1,496 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractintrospection_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerMetaEnumInterface + \internal + \since 4.4 + + \brief QDesignerMetaEnumInterface is part of \QD's introspection interface and represents an enumeration. + + \inmodule QtDesigner + + The QDesignerMetaEnumInterface class provides meta-data about an enumerator. + + \sa QDesignerMetaObjectInterface +*/ + +/*! + Constructs a QDesignerMetaEnumInterface object. +*/ + +QDesignerMetaEnumInterface::QDesignerMetaEnumInterface() = default; + +/*! + Destroys the QDesignerMetaEnumInterface object. +*/ +QDesignerMetaEnumInterface::~QDesignerMetaEnumInterface() = default; + +/*! + \fn bool QDesignerMetaEnumInterface::isFlag() const + + Returns true if this enumerator is used as a flag. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::key(int index) const + + Returns the key with the given \a index. +*/ + +/*! + \fn int QDesignerMetaEnumInterface::keyCount() const + + Returns the number of keys. +*/ + +/*! + \fn int QDesignerMetaEnumInterface::keyToValue(const QString &key) const + + Returns the integer value of the given enumeration \a key, or -1 if \a key is not defined. +*/ + +/*! + \fn int QDesignerMetaEnumInterface::keysToValue(const QString &keys) const + + Returns the value derived from combining together the values of the \a keys using the OR operator, or -1 if keys is not defined. Note that the strings in \a keys must be '|'-separated. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::name() const + + Returns the name of the enumerator (without the scope). +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::scope() const + + Returns the scope this enumerator was declared in. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::separator() const + + Returns the separator to be used when building enumeration names. +*/ + +/*! + \fn int QDesignerMetaEnumInterface::value(int index) const + + Returns the value with the given \a index; or returns -1 if there is no such value. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::valueToKey(int value) const + + Returns the string that is used as the name of the given enumeration \a value, or QString::null if value is not defined. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::valueToKeys(int value) const + + Returns a byte array of '|'-separated keys that represents the given \a value. +*/ + +/*! + \class QDesignerMetaPropertyInterface + \internal + \since 4.4 + + \brief QDesignerMetaPropertyInterface is part of \QD's introspection interface and represents a property. + + \inmodule QtDesigner + + The QDesignerMetaPropertyInterface class provides meta-data about a property. + + \sa QDesignerMetaObjectInterface +*/ + +/*! + Constructs a QDesignerMetaPropertyInterface object. +*/ + +QDesignerMetaPropertyInterface::QDesignerMetaPropertyInterface() = default; + +/*! + Destroys the QDesignerMetaPropertyInterface object. +*/ + +QDesignerMetaPropertyInterface::~QDesignerMetaPropertyInterface() = default; + +/*! + \enum QDesignerMetaPropertyInterface::Kind + + This enum indicates whether the property is of a special type. + + \value EnumKind The property is of an enumeration type + \value FlagKind The property is of an flag type + \value OtherKind The property is of another type + */ + +/*! + \enum QDesignerMetaPropertyInterface::AccessFlag + + These flags specify the access the property provides. + + \value ReadAccess Property can be read + \value WriteAccess Property can be written + \value ResetAccess Property can be reset to a default value + */ + +/*! + \enum QDesignerMetaPropertyInterface::Attribute + + Various attributes of the property. + + \value DesignableAttribute Property is designable (visible in \QD) + \value ScriptableAttribute Property is scriptable + \value StoredAttribute Property is stored, that is, not calculated + \value UserAttribute Property is the property that the user can edit for the QObject + */ + +/*! + \fn const QDesignerMetaEnumInterface *QDesignerMetaPropertyInterface::enumerator() const + + Returns the enumerator if this property's type is an enumerator type; +*/ + +/*! + \fn Kind QDesignerMetaPropertyInterface::kind() const + + Returns the type of the property. +*/ + +/*! + \fn AccessFlags QDesignerMetaPropertyInterface::accessFlags() const + + Returns a combination of access flags. +*/ + +/*! + \fn Attributes QDesignerMetaPropertyInterface::attributes() const + + Returns the attributes of the property. +*/ + +/*! + \fn int QDesignerMetaPropertyInterface::type() const + + Returns the type of the property. + + \sa QMetaType::Type +*/ + +/*! + \fn QString QDesignerMetaPropertyInterface::name() const + + Returns the name of the property. +*/ + +/*! + \fn QString QDesignerMetaPropertyInterface::typeName() const + + Returns the name of this property's type. +*/ + + +/*! + \fn int QDesignerMetaPropertyInterface::userType() const + + Returns this property's user type. +*/ + +/*! + \fn bool QDesignerMetaPropertyInterface::hasSetter() const + + Returns whether getter and setter methods exist for this property. +*/ + +/*! + \fn QVariant QDesignerMetaPropertyInterface::read(const QObject *object) const + + Reads the property's value from the given \a object. Returns the value if it was able to read it; otherwise returns an invalid variant. +*/ + +/*! + \fn bool QDesignerMetaPropertyInterface::reset(QObject *object) const + + Resets the property for the given \a object with a reset method. Returns true if the reset worked; otherwise returns false. +*/ + +/*! + \fn bool QDesignerMetaPropertyInterface::write(QObject *object, const QVariant &value) const + + Writes \a value as the property's value to the given \a object. Returns true if the write succeeded; otherwise returns false. +*/ + +/*! + \class QDesignerMetaMethodInterface + \internal + \since 4.4 + + \brief QDesignerMetaMethodInterface is part of \QD's introspection interface and represents a member function. + + \inmodule QtDesigner + + The QDesignerMetaMethodInterface class provides meta-data about a member function. + + \sa QDesignerMetaObjectInterface +*/ + +/*! + Constructs a QDesignerMetaMethodInterface object. +*/ + +QDesignerMetaMethodInterface::QDesignerMetaMethodInterface() = default; + +/*! + Destroys the QDesignerMetaMethodInterface object. +*/ + +QDesignerMetaMethodInterface::~QDesignerMetaMethodInterface() = default; + +/*! + \enum QDesignerMetaMethodInterface::MethodType + + This enum specifies the type of the method + + \value Method The function is a plain member function. + \value Signal The function is a signal. + \value Slot The function is a slot. + \value Constructor The function is a constructor. + +*/ + +/*! + \enum QDesignerMetaMethodInterface::Access + + This enum represents the access specification of the method + + \value Private A private member function + \value Protected A protected member function + \value Public A public member function +*/ + +/*! + \fn QDesignerMetaMethodInterface::Access QDesignerMetaMethodInterface::access() const + + Returns the access specification of this method. +*/ + + +/*! + \fn QDesignerMetaMethodInterface::MethodType QDesignerMetaMethodInterface::methodType() const + + Returns the type of this method. +*/ + +/*! + \fn QStringList QDesignerMetaMethodInterface::parameterNames() const + + Returns a list of parameter names. +*/ + +/*! + \fn QStringList QDesignerMetaMethodInterface::parameterTypes() const + + Returns a list of parameter types. +*/ + +/*! + \fn QString QDesignerMetaMethodInterface::signature() const + + Returns the signature of this method. +*/ + +/*! + \fn QString QDesignerMetaMethodInterface::normalizedSignature() const + + Returns the normalized signature of this method (suitable as signal/slot specification). +*/ + + +/*! + \fn QString QDesignerMetaMethodInterface::tag() const + + Returns the tag associated with this method. +*/ + +/*! + \fn QString QDesignerMetaMethodInterface::typeName() const + + Returns the return type of this method, or an empty string if the return type is void. +*/ + +/*! + \class QDesignerMetaObjectInterface + \internal + \since 4.4 + + \brief QDesignerMetaObjectInterface is part of \QD's introspection interface and provides meta-information about Qt objects + + \inmodule QtDesigner + + The QDesignerMetaObjectInterface class provides meta-data about Qt objects. For a given object, it can be obtained + by querying QDesignerIntrospectionInterface. + + \sa QDesignerIntrospectionInterface +*/ + +/*! + Constructs a QDesignerMetaObjectInterface object. +*/ + +QDesignerMetaObjectInterface::QDesignerMetaObjectInterface() = default; + +/*! + Destroys the QDesignerMetaObjectInterface object. +*/ + +QDesignerMetaObjectInterface::~QDesignerMetaObjectInterface() = default; + +/*! + \fn QString QDesignerMetaObjectInterface::className() const + + Returns the class name. +*/ + +/*! + \fn const QDesignerMetaEnumInterface *QDesignerMetaObjectInterface::enumerator(int index) const + + Returns the meta-data for the enumerator with the given \a index. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::enumeratorCount() const + + Returns the number of enumerators in this class. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::enumeratorOffset() const + + Returns the enumerator offset for this class; i.e. the index position of this class's first enumerator. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfEnumerator(const QString &name) const + + Finds enumerator \a name and returns its index; otherwise returns -1. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfMethod(const QString &method) const + + Finds \a method and returns its index; otherwise returns -1. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfProperty(const QString &name) const + + Finds property \a name and returns its index; otherwise returns -1. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfSignal(const QString &signal) const + + Finds \a signal and returns its index; otherwise returns -1. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfSlot(const QString &slot) const + + Finds \a slot and returns its index; otherwise returns -1. +*/ + +/*! + \fn const QDesignerMetaMethodInterface *QDesignerMetaObjectInterface::method(int index) const + + Returns the meta-data for the method with the given \a index. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::methodCount() const + + Returns the number of methods in this class. These include ordinary methods, signals, and slots. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::methodOffset() const + + Returns the method offset for this class; i.e. the index position of this class's first member function. +*/ + +/*! + \fn const QDesignerMetaPropertyInterface *QDesignerMetaObjectInterface::property(int index) const + + Returns the meta-data for the property with the given \a index. +*/ +/*! + \fn int QDesignerMetaObjectInterface::propertyCount() const + + Returns the number of properties in this class. +*/ +/*! + \fn int QDesignerMetaObjectInterface::propertyOffset() const + + Returns the property offset for this class; i.e. the index position of this class's first property. +*/ + +/*! + \fn const QDesignerMetaObjectInterface *QDesignerMetaObjectInterface::superClass() const + + Returns the meta-object of the superclass, or 0 if there is no such object. +*/ + +/*! + \fn const QDesignerMetaPropertyInterface *QDesignerMetaObjectInterface::userProperty() const + + Returns the property that has the USER flag set to true. +*/ + +/*! + \class QDesignerIntrospectionInterface + \internal + \since 4.4 + + \brief QDesignerIntrospectionInterface provides access to a QDesignerMetaObjectInterface for a given Qt object. + + \inmodule QtDesigner + + QDesignerIntrospectionInterface is the main class of \QD's introspection interface. These + interfaces provide a layer of abstraction around QMetaObject and related classes to allow for the integration + of other programming languages. + + An instance of QDesignerIntrospectionInterface can be obtained from the core. + + \sa QDesignerMetaObjectInterface +*/ + +/*! + Constructs a QDesignerIntrospectionInterface object. +*/ + +QDesignerIntrospectionInterface::QDesignerIntrospectionInterface() +{ +} + +/*! + Destroys the QDesignerIntrospectionInterface object. +*/ + +QDesignerIntrospectionInterface::~QDesignerIntrospectionInterface() +{ +} + +/*! + \fn const QDesignerMetaObjectInterface* QDesignerIntrospectionInterface::metaObject(const QObject *object) const + + Returns the meta object of this \a object. +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractintrospection_p.h b/src/designer/src/lib/sdk/abstractintrospection_p.h new file mode 100644 index 0000000..8add3a4 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractintrospection_p.h @@ -0,0 +1,145 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ABSTRACTMETAOBJECT_H +#define ABSTRACTMETAOBJECT_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDESIGNER_SDK_EXPORT QDesignerMetaEnumInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaEnumInterface) + + QDesignerMetaEnumInterface(); + virtual ~QDesignerMetaEnumInterface(); + virtual bool isFlag() const = 0; + virtual QString key(int index) const = 0; + virtual int keyCount() const = 0; + virtual int keyToValue(const QString &key) const = 0; + virtual int keysToValue(const QString &keys) const = 0; + virtual QString name() const = 0; + virtual QString enumName() const = 0; + virtual QString scope() const = 0; + virtual QString separator() const = 0; + virtual int value(int index) const = 0; + virtual QString valueToKey(int value) const = 0; + virtual QString valueToKeys(int value) const = 0; +}; + +class QDESIGNER_SDK_EXPORT QDesignerMetaPropertyInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaPropertyInterface) + + enum Kind { EnumKind, FlagKind, OtherKind }; + enum AccessFlag { ReadAccess = 0x0001, WriteAccess = 0x0002, ResetAccess = 0x0004 }; + enum Attribute { DesignableAttribute = 0x0001, ScriptableAttribute = 0x0002, StoredAttribute = 0x0004, UserAttribute = 0x0008}; + Q_DECLARE_FLAGS(Attributes, Attribute) + Q_DECLARE_FLAGS(AccessFlags, AccessFlag) + + QDesignerMetaPropertyInterface(); + virtual ~QDesignerMetaPropertyInterface(); + + virtual const QDesignerMetaEnumInterface *enumerator() const = 0; + + virtual Kind kind() const = 0; + virtual AccessFlags accessFlags() const = 0; + virtual Attributes attributes() const = 0; + + virtual int type() const = 0; + virtual QString name() const = 0; + virtual QString typeName() const = 0; + virtual int userType() const = 0; + virtual bool hasSetter() const = 0; + + virtual QVariant read(const QObject *object) const = 0; + virtual bool reset(QObject *object) const = 0; + virtual bool write(QObject *object, const QVariant &value) const = 0; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDesignerMetaPropertyInterface::AccessFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QDesignerMetaPropertyInterface::Attributes) + +class QDESIGNER_SDK_EXPORT QDesignerMetaMethodInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaMethodInterface) + + QDesignerMetaMethodInterface(); + virtual ~QDesignerMetaMethodInterface(); + + enum MethodType { Method, Signal, Slot, Constructor }; + enum Access { Private, Protected, Public }; + + virtual Access access() const = 0; + virtual MethodType methodType() const = 0; + virtual QStringList parameterNames() const = 0; + virtual QStringList parameterTypes() const = 0; + virtual QString signature() const = 0; + virtual QString normalizedSignature() const = 0; + virtual QString tag() const = 0; + virtual QString typeName() const = 0; +}; + +class QDESIGNER_SDK_EXPORT QDesignerMetaObjectInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaObjectInterface) + + QDesignerMetaObjectInterface(); + virtual ~QDesignerMetaObjectInterface(); + + virtual QString className() const = 0; + virtual const QDesignerMetaEnumInterface *enumerator(int index) const = 0; + virtual int enumeratorCount() const = 0; + virtual int enumeratorOffset() const = 0; + + virtual int indexOfEnumerator(const QString &name) const = 0; + virtual int indexOfMethod(const QString &method) const = 0; + virtual int indexOfProperty(const QString &name) const = 0; + virtual int indexOfSignal(const QString &signal) const = 0; + virtual int indexOfSlot(const QString &slot) const = 0; + + virtual const QDesignerMetaMethodInterface *method(int index) const = 0; + virtual int methodCount() const = 0; + virtual int methodOffset() const = 0; + + virtual const QDesignerMetaPropertyInterface *property(int index) const = 0; + virtual int propertyCount() const = 0; + virtual int propertyOffset() const = 0; + + virtual const QDesignerMetaObjectInterface *superClass() const = 0; + virtual const QDesignerMetaPropertyInterface *userProperty() const = 0; +}; + +// To be obtained from core +class QDESIGNER_SDK_EXPORT QDesignerIntrospectionInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerIntrospectionInterface) + + QDesignerIntrospectionInterface(); + virtual ~QDesignerIntrospectionInterface(); + + virtual const QDesignerMetaObjectInterface* metaObject(const QObject *object) const = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTMETAOBJECT_H diff --git a/src/designer/src/lib/sdk/abstractlanguage.h b/src/designer/src/lib/sdk/abstractlanguage.h new file mode 100644 index 0000000..0e0c4f6 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractlanguage.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_ABTRACT_LANGUAGE_H +#define QDESIGNER_ABTRACT_LANGUAGE_H + +#include + +QT_BEGIN_NAMESPACE + +class QDialog; +class QWidget; +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QDesignerResourceBrowserInterface; + +class QDesignerLanguageExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerLanguageExtension) + + QDesignerLanguageExtension() = default; + virtual ~QDesignerLanguageExtension() = default; + + /*! + Returns the name to be matched against the "language" attribute of the element. + + \since 5.0 + */ + + virtual QString name() const = 0; + + virtual QDialog *createFormWindowSettingsDialog(QDesignerFormWindowInterface *formWindow, QWidget *parentWidget) = 0; + virtual QDesignerResourceBrowserInterface *createResourceBrowser(QWidget *parentWidget) = 0; + + virtual QDialog *createPromotionDialog(QDesignerFormEditorInterface *formEditor, QWidget *parentWidget = nullptr) = 0; + + virtual QDialog *createPromotionDialog(QDesignerFormEditorInterface *formEditor, + const QString &promotableWidgetClassName, + QString *promoteToClassName, + QWidget *parentWidget = nullptr) = 0; + + virtual bool isLanguageResource(const QString &path) const = 0; + + virtual QString classNameOf(QObject *object) const = 0; + + virtual bool signalMatchesSlot(const QString &signal, const QString &slot) const = 0; + + virtual QString widgetBoxContents() const = 0; + + virtual QString uiExtension() const = 0; +}; + +Q_DECLARE_EXTENSION_INTERFACE(QDesignerLanguageExtension, "org.qt-project.Qt.Designer.Language.3") + +QT_END_NAMESPACE + +#endif // QDESIGNER_ABTRACT_LANGUAGE_H diff --git a/src/designer/src/lib/sdk/abstractmetadatabase.cpp b/src/designer/src/lib/sdk/abstractmetadatabase.cpp new file mode 100644 index 0000000..46b0d96 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractmetadatabase.cpp @@ -0,0 +1,130 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// sdk +#include "abstractmetadatabase.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerMetaDataBaseInterface + \brief The QDesignerMetaDataBaseInterface class provides an interface to Qt Widgets Designer's + object meta database. + \inmodule QtDesigner + \internal +*/ + +/*! + Constructs an interface to the meta database with the given \a parent. +*/ +QDesignerMetaDataBaseInterface::QDesignerMetaDataBaseInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the interface to the meta database. +*/ +QDesignerMetaDataBaseInterface::~QDesignerMetaDataBaseInterface() = default; + +/*! + \fn QDesignerMetaDataBaseItemInterface *QDesignerMetaDataBaseInterface::item(QObject *object) const + + Returns the item in the meta database associated with the given \a object. +*/ + +/*! + \fn void QDesignerMetaDataBaseInterface::add(QObject *object) + + Adds the specified \a object to the meta database. +*/ + +/*! + \fn void QDesignerMetaDataBaseInterface::remove(QObject *object) + + Removes the specified \a object from the meta database. +*/ + +/*! + \fn QList QDesignerMetaDataBaseInterface::objects() const + + Returns the list of objects that have corresponding items in the meta database. +*/ + +/*! + \fn QDesignerFormEditorInterface *QDesignerMetaDataBaseInterface::core() const + + Returns the core interface that is associated with the meta database. +*/ + + +// Doc: Interface only + +/*! + \class QDesignerMetaDataBaseItemInterface + \brief The QDesignerMetaDataBaseItemInterface class provides an interface to individual + items in \QD's meta database. + \inmodule QtDesigner + \internal + + This class allows individual items in \QD's meta-data database to be accessed and modified. + Use the QDesignerMetaDataBaseInterface class to change the properties of the database itself. +*/ + +/*! + \fn QDesignerMetaDataBaseItemInterface::~QDesignerMetaDataBaseItemInterface() + + Destroys the item interface to the meta-data database. +*/ + +/*! + \fn QString QDesignerMetaDataBaseItemInterface::name() const + + Returns the name of the item in the database. + + \sa setName() +*/ + +/*! + \fn void QDesignerMetaDataBaseItemInterface::setName(const QString &name) + + Sets the name of the item to the given \a name. + + \sa name() +*/ + +/*! + \fn QList QDesignerMetaDataBaseItemInterface::tabOrder() const + + Returns a list of widgets in the order defined by the form's tab order. + + \sa setTabOrder() +*/ + + +/*! + \fn void QDesignerMetaDataBaseItemInterface::setTabOrder(const QList &tabOrder) + + Sets the tab order in the form using the list of widgets defined by \a tabOrder. + + \sa tabOrder() +*/ + + +/*! + \fn bool QDesignerMetaDataBaseItemInterface::enabled() const + + Returns whether the item is enabled. + + \sa setEnabled() +*/ + +/*! + \fn void QDesignerMetaDataBaseItemInterface::setEnabled(bool enabled) + + If \a enabled is true, the item is enabled; otherwise it is disabled. + + \sa enabled() +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractmetadatabase.h b/src/designer/src/lib/sdk/abstractmetadatabase.h new file mode 100644 index 0000000..46728ea --- /dev/null +++ b/src/designer/src/lib/sdk/abstractmetadatabase.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETADATABASE_H +#define ABSTRACTMETADATABASE_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QCursor; +class QWidget; + +class QDesignerFormEditorInterface; + +class QDesignerMetaDataBaseItemInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaDataBaseItemInterface) + + QDesignerMetaDataBaseItemInterface() = default; + virtual ~QDesignerMetaDataBaseItemInterface() = default; + + virtual QString name() const = 0; + virtual void setName(const QString &name) = 0; + + virtual QList tabOrder() const = 0; + virtual void setTabOrder(const QList &tabOrder) = 0; + + virtual bool enabled() const = 0; + virtual void setEnabled(bool b) = 0; +}; + + +class QDESIGNER_SDK_EXPORT QDesignerMetaDataBaseInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerMetaDataBaseInterface(QObject *parent = nullptr); + virtual ~QDesignerMetaDataBaseInterface(); + + virtual QDesignerMetaDataBaseItemInterface *item(QObject *object) const = 0; + virtual void add(QObject *object) = 0; + virtual void remove(QObject *object) = 0; + + virtual QList objects() const = 0; + + virtual QDesignerFormEditorInterface *core() const = 0; + +Q_SIGNALS: + void changed(); +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTMETADATABASE_H diff --git a/src/designer/src/lib/sdk/abstractnewformwidget.cpp b/src/designer/src/lib/sdk/abstractnewformwidget.cpp new file mode 100644 index 0000000..e4b0e7d --- /dev/null +++ b/src/designer/src/lib/sdk/abstractnewformwidget.cpp @@ -0,0 +1,77 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractnewformwidget.h" +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerNewFormWidgetInterface + \since 4.5 + \internal + + \brief QDesignerNewFormWidgetInterface provides an interface for chooser + widgets that can be used within "New Form" dialogs and wizards. + It presents the user with a list of choices taken from built-in + templates, pre-defined template paths and suitable custom widgets. + It provides a static creation function that returns \QD's + implementation. + + \inmodule QtDesigner +*/ + +/*! + Constructs a QDesignerNewFormWidgetInterface object. +*/ + +QDesignerNewFormWidgetInterface::QDesignerNewFormWidgetInterface(QWidget *parent) : + QWidget(parent) +{ +} + +/*! + Destroys the QDesignerNewFormWidgetInterface object. +*/ + +QDesignerNewFormWidgetInterface::~QDesignerNewFormWidgetInterface() = default; + +/*! + Creates an instance of the QDesignerNewFormWidgetInterface as a child + of \a parent using \a core. +*/ + +QDesignerNewFormWidgetInterface *QDesignerNewFormWidgetInterface::createNewFormWidget(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::NewFormWidget(core, parent); +} + +/*! + \fn bool QDesignerNewFormWidgetInterface::hasCurrentTemplate() const + + Returns whether a form template is currently selected. +*/ + +/*! + \fn QString QDesignerNewFormWidgetInterface::currentTemplate(QString *errorMessage = 0) + + Returns the contents of the currently selected template. If the method fails, + an empty string is returned and \a errorMessage receives an error message. +*/ + +// Signals + +/*! + \fn void QDesignerNewFormWidgetInterface::templateActivated() + + This signal is emitted whenever the user activates a template by double-clicking. +*/ + +/*! + \fn void QDesignerNewFormWidgetInterface::currentTemplateChanged(bool templateSelected) + + This signal is emitted whenever the user changes the current template. + \a templateSelected indicates whether a template is currently selected. +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractnewformwidget.h b/src/designer/src/lib/sdk/abstractnewformwidget.h new file mode 100644 index 0000000..c63bcf3 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractnewformwidget.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTNEWFORMWIDGET_H +#define ABSTRACTNEWFORMWIDGET_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +class QDESIGNER_SDK_EXPORT QDesignerNewFormWidgetInterface : public QWidget +{ + Q_OBJECT +public: + explicit QDesignerNewFormWidgetInterface(QWidget *parent = nullptr); + virtual ~QDesignerNewFormWidgetInterface(); + + virtual bool hasCurrentTemplate() const = 0; + virtual QString currentTemplate(QString *errorMessage = nullptr) = 0; + + static QDesignerNewFormWidgetInterface *createNewFormWidget(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + +Q_SIGNALS: + void templateActivated(); + void currentTemplateChanged(bool templateSelected); +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTNEWFORMWIDGET_H diff --git a/src/designer/src/lib/sdk/abstractobjectinspector.cpp b/src/designer/src/lib/sdk/abstractobjectinspector.cpp new file mode 100644 index 0000000..fed91fe --- /dev/null +++ b/src/designer/src/lib/sdk/abstractobjectinspector.cpp @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractobjectinspector.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerObjectInspectorInterface + + \brief The QDesignerObjectInspectorInterface class allows you to + change the focus of \QD's object inspector. + + \inmodule QtDesigner + + You can use the QDesignerObjectInspectorInterface to change the + current form window selection. For example, when implementing a + custom widget plugin: + + \snippet lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp 0 + + The QDesignerObjectInspectorInterface class is not intended to be + instantiated directly. You can retrieve an interface to \QD's + object inspector using the + QDesignerFormEditorInterface::objectInspector() function. A + pointer to \QD's current QDesignerFormEditorInterface object (\c + formEditor in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + The interface provides the core() function that you can use to + retrieve a pointer to \QD's current QDesignerFormEditorInterface + object, and the setFormWindow() function that enables you to + change the current form window selection. + + \sa QDesignerFormEditorInterface, QDesignerFormWindowInterface +*/ + +/*! + Constructs an object inspector interface with the given \a parent + and the specified window \a flags. +*/ +QDesignerObjectInspectorInterface::QDesignerObjectInspectorInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the object inspector interface. +*/ +QDesignerObjectInspectorInterface::~QDesignerObjectInspectorInterface() = default; + +/*! + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ +QDesignerFormEditorInterface *QDesignerObjectInspectorInterface::core() const +{ + return nullptr; +} + +/*! + \fn void QDesignerObjectInspectorInterface::setFormWindow(QDesignerFormWindowInterface *formWindow) + + Sets the currently selected form window to \a formWindow. +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractobjectinspector.h b/src/designer/src/lib/sdk/abstractobjectinspector.h new file mode 100644 index 0000000..1d7748c --- /dev/null +++ b/src/designer/src/lib/sdk/abstractobjectinspector.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTOBJECTINSPECTOR_H +#define ABSTRACTOBJECTINSPECTOR_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class QDESIGNER_SDK_EXPORT QDesignerObjectInspectorInterface: public QWidget +{ + Q_OBJECT +public: + explicit QDesignerObjectInspectorInterface(QWidget *parent, Qt::WindowFlags flags = {}); + virtual ~QDesignerObjectInspectorInterface(); + + virtual QDesignerFormEditorInterface *core() const; + +public Q_SLOTS: + virtual void setFormWindow(QDesignerFormWindowInterface *formWindow) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTOBJECTINSPECTOR_H diff --git a/src/designer/src/lib/sdk/abstractoptionspage.h b/src/designer/src/lib/sdk/abstractoptionspage.h new file mode 100644 index 0000000..f0ff532 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractoptionspage.h @@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTOPTIONSPAGE_P_H +#define ABSTRACTOPTIONSPAGE_P_H + +#include + +QT_BEGIN_NAMESPACE + +class QString; +class QWidget; + +class QDESIGNER_SDK_EXPORT QDesignerOptionsPageInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerOptionsPageInterface) + + QDesignerOptionsPageInterface() = default; + virtual ~QDesignerOptionsPageInterface() = default; + + virtual QString name() const = 0; + virtual QWidget *createPage(QWidget *parent) = 0; + virtual void apply() = 0; + virtual void finish() = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTOPTIONSPAGE_P_H diff --git a/src/designer/src/lib/sdk/abstractoptionspage.qdoc b/src/designer/src/lib/sdk/abstractoptionspage.qdoc new file mode 100644 index 0000000..75214ed --- /dev/null +++ b/src/designer/src/lib/sdk/abstractoptionspage.qdoc @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerOptionsPageInterface + + \brief The QDesignerOptionsPageInterface provides an interface for integrating \QD's + options pages into IDE option dialogs. + + Plugin-based IDE's typically have options dialogs for which the plugins can provide + widgets to be shown for example in a tab-widget. The widgets are created on + demand when the user activates a page. + + In order to do this for \QD, a list of QDesignerOptionsPageInterface objects + can be obtained from QDesignerFormEditorInterface and registered with the option + dialog. When the respective tab is activated, createPage() is invoked to + create the widget. To apply the modified settings, apply() is called. + finish() is called when the dialog closes. + + \sa QDesignerFormEditorInterface::optionsPages(), QDesignerFormEditorInterface::setOptionsPages() + + \internal + \inmodule QtDesigner + \since 5.0 +*/ + +/*! + \fn QDesignerOptionsPageInterface::~QDesignerOptionsPageInterface() + + Destroys the QDesignerOptionsPageInterface object. +*/ + +/*! + \fn QString QDesignerOptionsPageInterface::name() const + + Returns the name of the page, which can for example be used as a tab title. +*/ + +/*! + \fn QWidget *QDesignerOptionsPageInterface::createPage(QWidget *parent) + + Creates the widget of the page parented on \a parent. +*/ + +/*! + \fn QDesignerOptionsPageInterface::apply() + + This function should be called when the user clicks \gui{OK} or \gui{Apply} to + apply the modified settings. +*/ + +/*! + \fn QDesignerOptionsPageInterface::finish() + + This function should be called when the option dialog is closed. +*/ diff --git a/src/designer/src/lib/sdk/abstractpromotioninterface.cpp b/src/designer/src/lib/sdk/abstractpromotioninterface.cpp new file mode 100644 index 0000000..d279145 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractpromotioninterface.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractpromotioninterface.h" + +QT_BEGIN_NAMESPACE +/*! + \class QDesignerPromotionInterface + + \brief The QDesignerPromotionInterface provides functions for modifying + the promoted classes in Designer. + \inmodule QtDesigner + \internal + \since 4.3 +*/ + +/*! + \class QDesignerPromotionInterface::PromotedClass + \inmodule QtDesigner + A pair of database items containing the base class and the promoted class. +*/ + +/*! + \typedef QDesignerPromotionInterface::PromotedClasses + A list of PromotedClass items. +*/ + +/*! + \fn QDesignerPromotionInterface::PromotedClasses QDesignerPromotionInterface::promotedClasses() const + + Returns a list of promoted classes along with their base classes in alphabetical order. + It can be used to populate tree models for editing promoted widgets. +*/ + +/*! + \fn virtual QSet QDesignerPromotionInterface::referencedPromotedClassNames() const + + Returns a set of promoted classed that are referenced by the currently opened forms. +*/ + +/*! + \fn virtual bool QDesignerPromotionInterface::addPromotedClass(const QString &baseClass, const QString &className, const QString &includeFile, QString *errorMessage) + + Add a promoted class named \a with the base class \a and include file \a includeFile. Returns \c true on success or \c false along + with an error message in \a errorMessage on failure. +*/ + +/*! + \fn virtual bool QDesignerPromotionInterface::removePromotedClass(const QString &className, QString *errorMessage) + + Remove the promoted class named \a className unless it is referenced by a form. Returns \c true on success or \c false along + with an error message in \a errorMessage on failure. +*/ + +/*! + \fn virtual bool QDesignerPromotionInterface::changePromotedClassName(const QString &oldClassName, const QString &newClassName, QString *errorMessage) + + Change the class name of a promoted class from \a oldClassName to \a newClassName. Returns \c true on success or \c false along + with an error message in \a errorMessage on failure. +*/ + +/*! + \fn virtual bool QDesignerPromotionInterface::setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) + + Change the include file of a promoted class named \a className to be \a includeFile. Returns \c true on success or \c false along + with an error message in \a errorMessage on failure. +*/ + +/*! \fn virtual QList QDesignerPromotionInterface::promotionBaseClasses() const + + Return a list of base classes that are suitable for promotion. +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractpromotioninterface.h b/src/designer/src/lib/sdk/abstractpromotioninterface.h new file mode 100644 index 0000000..752d36b --- /dev/null +++ b/src/designer/src/lib/sdk/abstractpromotioninterface.h @@ -0,0 +1,53 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTPROMOTIONINTERFACE_H +#define ABSTRACTPROMOTIONINTERFACE_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerWidgetDataBaseItemInterface; + +class QDESIGNER_SDK_EXPORT QDesignerPromotionInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerPromotionInterface) + + QDesignerPromotionInterface() = default; + virtual ~QDesignerPromotionInterface() = default; + + struct PromotedClass + { + QDesignerWidgetDataBaseItemInterface *baseItem; + QDesignerWidgetDataBaseItemInterface *promotedItem; + }; + + using PromotedClasses = QList; + + virtual PromotedClasses promotedClasses() const = 0; + + virtual QSet referencedPromotedClassNames() const = 0; + + virtual bool addPromotedClass(const QString &baseClass, + const QString &className, + const QString &includeFile, + QString *errorMessage) = 0; + + virtual bool removePromotedClass(const QString &className, QString *errorMessage) = 0; + + virtual bool changePromotedClassName(const QString &oldClassName, const QString &newClassName, QString *errorMessage) = 0; + + virtual bool setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) = 0; + + virtual QList promotionBaseClasses() const = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTPROMOTIONINTERFACE_H diff --git a/src/designer/src/lib/sdk/abstractpropertyeditor.cpp b/src/designer/src/lib/sdk/abstractpropertyeditor.cpp new file mode 100644 index 0000000..c9e011e --- /dev/null +++ b/src/designer/src/lib/sdk/abstractpropertyeditor.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractpropertyeditor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerPropertyEditorInterface + + \brief The QDesignerPropertyEditorInterface class allows you to + query and manipulate the current state of Qt Widgets Designer's property + editor. + + \inmodule QtDesigner + + QDesignerPropertyEditorInterface contains a collection of + functions that is typically used to query the property editor for + its current state, and several slots manipulating it's state. The + interface also provide a signal, propertyChanged(), which is + emitted whenever a property changes in the property editor. The + signal's arguments are the property that changed and its new + value. + + For example, when implementing a custom widget plugin, you can + connect the signal to a custom slot: + + \snippet lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp 0 + + Then the custom slot can check if the new value is within the + range we want when a specified property, belonging to a particular + widget, changes: + + \snippet lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp 1 + + The QDesignerPropertyEditorInterface class is not intended to be + instantiated directly. You can retrieve an interface to \QD's + property editor using the + QDesignerFormEditorInterface::propertyEditor() function. A pointer + to \QD's current QDesignerFormEditorInterface object (\c + formEditor in the examples above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + The functions accessing the property editor are the core() + function that you can use to retrieve an interface to the form + editor, the currentPropertyName() function that returns the name + of the currently selected property in the property editor, the + object() function that returns the currently selected object in + \QD's workspace, and the isReadOnly() function that returns true + if the property editor is write proteced (otherwise false). + + The slots manipulating the property editor's state are the + setObject() slot that you can use to change the currently selected + object in \QD's workspace, the setPropertyValue() slot that + changes the value of a given property and the setReadOnly() slot + that control the write protection of the property editor. + + \sa QDesignerFormEditorInterface +*/ + +/*! + Constructs a property editor interface with the given \a parent and + the specified window \a flags. +*/ +QDesignerPropertyEditorInterface::QDesignerPropertyEditorInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the property editor interface. +*/ +QDesignerPropertyEditorInterface::~QDesignerPropertyEditorInterface() = default; + +/*! + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ +QDesignerFormEditorInterface *QDesignerPropertyEditorInterface::core() const +{ + return nullptr; +} + +/*! + \fn bool QDesignerPropertyEditorInterface::isReadOnly() const + + Returns true if the property editor is write protected; otherwise + false. + + \sa setReadOnly() +*/ + +/*! + \fn QObject *QDesignerPropertyEditorInterface::object() const + + Returns the currently selected object in \QD's workspace. + + \sa setObject() +*/ + +/*! + \fn QString QDesignerPropertyEditorInterface::currentPropertyName() const + + Returns the name of the currently selected property in the + property editor. + + \sa setPropertyValue() +*/ + +/*! + \fn void QDesignerPropertyEditorInterface::propertyChanged(const QString &name, const QVariant &value) + + This signal is emitted whenever a property changes in the property + editor. The property that changed and its new value are specified + by \a name and \a value respectively. + + \sa setPropertyValue() +*/ + +/*! + \fn void QDesignerPropertyEditorInterface::setObject(QObject *object) + + Changes the currently selected object in \QD's workspace, to \a + object. + + \sa object() +*/ + +/*! + \fn void QDesignerPropertyEditorInterface::setPropertyValue(const QString &name, const QVariant &value, bool changed = true) + + Sets the value of the property specified by \a name to \a + value. + + In addition, the property is marked as \a changed in the property + editor, i.e. its value is different from the default value. + + \sa currentPropertyName(), propertyChanged() +*/ + +/*! + \fn void QDesignerPropertyEditorInterface::setReadOnly(bool readOnly) + + If \a readOnly is true, the property editor is made write + protected; otherwise the write protection is removed. + + \sa isReadOnly() +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractpropertyeditor.h b/src/designer/src/lib/sdk/abstractpropertyeditor.h new file mode 100644 index 0000000..8fe5d1c --- /dev/null +++ b/src/designer/src/lib/sdk/abstractpropertyeditor.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTPROPERTYEDITOR_H +#define ABSTRACTPROPERTYEDITOR_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QString; +class QVariant; + +class QDESIGNER_SDK_EXPORT QDesignerPropertyEditorInterface: public QWidget +{ + Q_OBJECT +public: + explicit QDesignerPropertyEditorInterface(QWidget *parent, Qt::WindowFlags flags = {}); + virtual ~QDesignerPropertyEditorInterface(); + + virtual QDesignerFormEditorInterface *core() const; + + virtual bool isReadOnly() const = 0; + virtual QObject *object() const = 0; + + virtual QString currentPropertyName() const = 0; + +Q_SIGNALS: + void propertyChanged(const QString &name, const QVariant &value); + +public Q_SLOTS: + virtual void setObject(QObject *object) = 0; + virtual void setPropertyValue(const QString &name, const QVariant &value, bool changed = true) = 0; + virtual void setReadOnly(bool readOnly) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTPROPERTYEDITOR_H diff --git a/src/designer/src/lib/sdk/abstractresourcebrowser.cpp b/src/designer/src/lib/sdk/abstractresourcebrowser.cpp new file mode 100644 index 0000000..516cc90 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractresourcebrowser.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractresourcebrowser.h" + +QT_BEGIN_NAMESPACE + +QDesignerResourceBrowserInterface::QDesignerResourceBrowserInterface(QWidget *parent) + : QWidget(parent) +{ + +} + +QDesignerResourceBrowserInterface::~QDesignerResourceBrowserInterface() = default; + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractresourcebrowser.h b/src/designer/src/lib/sdk/abstractresourcebrowser.h new file mode 100644 index 0000000..a0c9f62 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractresourcebrowser.h @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTRESOURCEBROWSER_H +#define ABSTRACTRESOURCEBROWSER_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QWidget; // FIXME: fool syncqt + +class QDESIGNER_SDK_EXPORT QDesignerResourceBrowserInterface : public QWidget +{ + Q_OBJECT +public: + explicit QDesignerResourceBrowserInterface(QWidget *parent = nullptr); + virtual ~QDesignerResourceBrowserInterface(); + + virtual void setCurrentPath(const QString &filePath) = 0; + virtual QString currentPath() const = 0; + +Q_SIGNALS: + void currentPathChanged(const QString &filePath); + void pathActivated(const QString &filePath); +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMEDITOR_H + diff --git a/src/designer/src/lib/sdk/abstractsettings.h b/src/designer/src/lib/sdk/abstractsettings.h new file mode 100644 index 0000000..3f5b92a --- /dev/null +++ b/src/designer/src/lib/sdk/abstractsettings.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTSETTINGS_P_H +#define ABSTRACTSETTINGS_P_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QString; + +class QDESIGNER_SDK_EXPORT QDesignerSettingsInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerSettingsInterface) + + QDesignerSettingsInterface() = default; + virtual ~QDesignerSettingsInterface() = default; + + virtual void beginGroup(const QString &prefix) = 0; + virtual void endGroup() = 0; + + virtual bool contains(const QString &key) const = 0; + virtual void setValue(const QString &key, const QVariant &value) = 0; + virtual QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const = 0; + virtual void remove(const QString &key) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTSETTINGS_P_H diff --git a/src/designer/src/lib/sdk/abstractsettings.qdoc b/src/designer/src/lib/sdk/abstractsettings.qdoc new file mode 100644 index 0000000..6a0ac2b --- /dev/null +++ b/src/designer/src/lib/sdk/abstractsettings.qdoc @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerSettingsInterface + + \brief The QDesignerSettingsInterface provides an abstraction of QSettings to be used in \QD. + + \QD uses the QDesignerSettingsInterface to save and retrieve its settings. + The default implementation in \QD is based on QSettings, using a file in .ini-format. + IDE integrations can provide their own implementations that for + example store the settings in a database instead of a file. + + The virtual functions are equivalent to the functions of the same name in the + QSettings class. + + \sa QDesignerFormEditorInterface::setSettingsManager(), QDesignerFormEditorInterface::settingsManager() + \sa QSettings + + \internal + \inmodule QtDesigner + \since 5.0 +*/ diff --git a/src/designer/src/lib/sdk/abstractwidgetbox.cpp b/src/designer/src/lib/sdk/abstractwidgetbox.cpp new file mode 100644 index 0000000..db559bd --- /dev/null +++ b/src/designer/src/lib/sdk/abstractwidgetbox.cpp @@ -0,0 +1,300 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractwidgetbox.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerWidgetBoxInterface + + \brief The QDesignerWidgetBoxInterface class allows you to control + the contents of \QD's widget box. + + \inmodule QtDesigner + + QDesignerWidgetBoxInterface contains a collection of functions + that is typically used to manipulate the contents of \QD's widget + box. + + \QD uses an XML file to populate its widget box. The name of that + file is one of the widget box's properties, and you can retrieve + it using the fileName() function. + + QDesignerWidgetBoxInterface also provides the save() function that + saves the contents of the widget box in the file specified by the + widget box's file name property. If you have made changes to the + widget box, for example by dropping a widget into the widget box, + without calling the save() function, the original content can be + restored by a simple invocation of the load() function: + + \snippet lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp 0 + + The QDesignerWidgetBoxInterface class is not intended to be + instantiated directly. You can retrieve an interface to Qt + Designer's widget box using the + QDesignerFormEditorInterface::widgetBox() function. A pointer to + \QD's current QDesignerFormEditorInterface object (\c formEditor + in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + If you want to save your changes, and at the same time preserve + the original contents, you can use the save() function combined + with the setFileName() function to save your changes into another + file. Remember to store the name of the original file first: + + \snippet lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp 1 + + Then you can restore the original contents of the widget box by + resetting the file name to the original file and calling load(): + + \snippet lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp 2 + + In a similar way, you can later use your customized XML file: + + \snippet lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp 3 + + + \sa QDesignerFormEditorInterface +*/ + +/*! + Constructs a widget box interface with the given \a parent and + the specified window \a flags. +*/ +QDesignerWidgetBoxInterface::QDesignerWidgetBoxInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the widget box interface. +*/ +QDesignerWidgetBoxInterface::~QDesignerWidgetBoxInterface() = default; + +/*! + \internal +*/ +int QDesignerWidgetBoxInterface::findOrInsertCategory(const QString &categoryName) +{ + int count = categoryCount(); + for (int index=0; index &item_list, const QPoint &global_mouse_pos) + +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::setFileName(const QString &fileName) + + Sets the XML file that \QD will use to populate its widget box, to + \a fileName. You must call load() to update the widget box with + the new XML file. + + \sa fileName(), load() +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::fileName() const + + Returns the name of the XML file \QD is currently using to + populate its widget box. + + \sa setFileName() +*/ + +/*! + \fn bool QDesignerWidgetBoxInterface::load() + + Populates \QD's widget box by loading (or reloading) the currently + specified XML file. Returns true if the file is successfully + loaded; otherwise false. + + \sa setFileName() +*/ + +/*! + \fn bool QDesignerWidgetBoxInterface::save() + + Saves the contents of \QD's widget box in the file specified by + the fileName() function. Returns true if the content is + successfully saved; otherwise false. + + \sa fileName(), setFileName() +*/ + + +/*! + \internal + + \class QDesignerWidgetBoxInterface::Widget + + \brief The Widget class specified a widget in \QD's widget + box component. +*/ + +/*! + \enum QDesignerWidgetBoxInterface::Widget::Type + + \value Default + \value Custom +*/ + +/*! + \fn QDesignerWidgetBoxInterface::Widget::Widget(const QString &aname, const QString &xml, const QString &icon_name, Type atype) +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::Widget::name() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Widget::setName(const QString &aname) +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::Widget::domXml() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Widget::setDomXml(const QString &xml) +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::Widget::iconName() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Widget::setIconName(const QString &icon_name) +*/ + +/*! + \fn Type QDesignerWidgetBoxInterface::Widget::type() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Widget::setType(Type atype) +*/ + +/*! + \fn bool QDesignerWidgetBoxInterface::Widget::isNull() const +*/ + + +/*! + \class QDesignerWidgetBoxInterface::Category + \brief The Category class specifies a category in \QD's widget box component. + \internal +*/ + +/*! + \enum QDesignerWidgetBoxInterface::Category::Type + + \value Default + \value Scratchpad +*/ + +/*! + \fn QDesignerWidgetBoxInterface::Category::Category(const QString &aname, Type atype) +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::Category::name() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Category::setName(const QString &aname) +*/ + +/*! + \fn int QDesignerWidgetBoxInterface::Category::widgetCount() const +*/ + +/*! + \fn Widget QDesignerWidgetBoxInterface::Category::widget(int idx) const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Category::removeWidget(int idx) +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Category::addWidget(const Widget &awidget) +*/ + +/*! + \fn Type QDesignerWidgetBoxInterface::Category::type() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Category::setType(Type atype) +*/ + +/*! + \fn bool QDesignerWidgetBoxInterface::Category::isNull() const +*/ + +/*! + \typedef QDesignerWidgetBoxInterface::CategoryList + \internal +*/ + +/*! + \typedef QDesignerWidgetBoxInterface::WidgetList + \internal +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractwidgetbox.h b/src/designer/src/lib/sdk/abstractwidgetbox.h new file mode 100644 index 0000000..6f6d9f4 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractwidgetbox.h @@ -0,0 +1,107 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTWIDGETBOX_H +#define ABSTRACTWIDGETBOX_H + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class DomUI; +class QDesignerDnDItemInterface; + +class QDesignerWidgetBoxWidgetData; + +class QDESIGNER_SDK_EXPORT QDesignerWidgetBoxInterface : public QWidget +{ + Q_OBJECT +public: + class QDESIGNER_SDK_EXPORT Widget + { + public: + enum Type { Default, Custom }; + Widget(const QString &aname = QString(), const QString &xml = QString(), + const QString &icon_name = QString(), Type atype = Default); + ~Widget(); + Widget(const Widget &w); + Widget &operator=(const Widget &w); + + QString name() const; + void setName(const QString &aname); + QString domXml() const; + void setDomXml(const QString &xml); + QString iconName() const; + void setIconName(const QString &icon_name); + Type type() const; + void setType(Type atype); + + bool isNull() const; + + private: + QSharedDataPointer m_data; + }; + + using WidgetList = QList; + + class Category + { + public: + enum Type { Default, Scratchpad }; + + Category(const QString &aname = QString(), Type atype = Default) + : m_name(aname), m_type(atype) {} + + QString name() const { return m_name; } + void setName(const QString &aname) { m_name = aname; } + int widgetCount() const { return int(m_widget_list.size()); } + Widget widget(int idx) const { return m_widget_list.at(idx); } + void removeWidget(int idx) { m_widget_list.removeAt(idx); } + void addWidget(const Widget &awidget) { m_widget_list.append(awidget); } + Type type() const { return m_type; } + void setType(Type atype) { m_type = atype; } + + bool isNull() const { return m_name.isEmpty(); } + + private: + QString m_name; + Type m_type; + WidgetList m_widget_list; + }; + + using CategoryList = QList; + + explicit QDesignerWidgetBoxInterface(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + virtual ~QDesignerWidgetBoxInterface(); + + virtual int categoryCount() const = 0; + virtual Category category(int cat_idx) const = 0; + virtual void addCategory(const Category &cat) = 0; + virtual void removeCategory(int cat_idx) = 0; + + virtual int widgetCount(int cat_idx) const = 0; + virtual Widget widget(int cat_idx, int wgt_idx) const = 0; + virtual void addWidget(int cat_idx, const Widget &wgt) = 0; + virtual void removeWidget(int cat_idx, int wgt_idx) = 0; + + int findOrInsertCategory(const QString &categoryName); + + virtual void dropWidgets(const QList &item_list, + const QPoint &global_mouse_pos) = 0; + + virtual void setFileName(const QString &file_name) = 0; + virtual QString fileName() const = 0; + virtual bool load() = 0; + virtual bool save() = 0; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QT_PREPEND_NAMESPACE(QDesignerWidgetBoxInterface::Widget)) + +#endif // ABSTRACTWIDGETBOX_H diff --git a/src/designer/src/lib/sdk/abstractwidgetdatabase.cpp b/src/designer/src/lib/sdk/abstractwidgetdatabase.cpp new file mode 100644 index 0000000..2147afe --- /dev/null +++ b/src/designer/src/lib/sdk/abstractwidgetdatabase.cpp @@ -0,0 +1,320 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractwidgetdatabase.h" +#include +#include + +QT_BEGIN_NAMESPACE + +enum { debugAbstractWidgetDataBase = 0 }; + +/*! + \class QDesignerWidgetDataBaseInterface + \brief The QDesignerWidgetDataBaseInterface class provides an interface that is used to + access and modify \QD's widget database. + \inmodule QtDesigner + \internal +*/ + +/*! + Constructs an interface to the widget database with the given \a parent. +*/ +QDesignerWidgetDataBaseInterface::QDesignerWidgetDataBaseInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the interface to the widget database. +*/ +QDesignerWidgetDataBaseInterface::~QDesignerWidgetDataBaseInterface() +{ + qDeleteAll(m_items); +} + +/*! + +*/ +int QDesignerWidgetDataBaseInterface::count() const +{ + return m_items.size(); +} + +/*! +*/ +QDesignerWidgetDataBaseItemInterface *QDesignerWidgetDataBaseInterface::item(int index) const +{ + return index != -1 ? m_items.at(index) : 0; +} + +/*! +*/ +int QDesignerWidgetDataBaseInterface::indexOf(QDesignerWidgetDataBaseItemInterface *item) const +{ + return m_items.indexOf(item); +} + +/*! +*/ +void QDesignerWidgetDataBaseInterface::insert(int index, QDesignerWidgetDataBaseItemInterface *item) +{ + if (debugAbstractWidgetDataBase) + qDebug() << "insert at " << index << ' ' << item->name() << " derived from " << item->extends(); + + m_items.insert(index, item); +} + +/*! +*/ +void QDesignerWidgetDataBaseInterface::append(QDesignerWidgetDataBaseItemInterface *item) +{ + if (debugAbstractWidgetDataBase) + qDebug() << "append " << item->name() << " derived from " << item->extends(); + m_items.append(item); +} + +/*! +*/ +QDesignerFormEditorInterface *QDesignerWidgetDataBaseInterface::core() const +{ + return nullptr; +} + +/*! +*/ +int QDesignerWidgetDataBaseInterface::indexOfClassName(const QString &name, bool) const +{ + const int itemCount = count(); + for (int i=0; iname() == name) + return i; + } + + return -1; +} + +/*! +*/ +int QDesignerWidgetDataBaseInterface::indexOfObject(QObject *object, bool) const +{ + if (!object) + return -1; + + const QString className = QString::fromUtf8(object->metaObject()->className()); + return indexOfClassName(className); +} + +/*! +*/ +bool QDesignerWidgetDataBaseInterface::isContainer(QObject *object, bool resolveName) const +{ + if (const QDesignerWidgetDataBaseItemInterface *i = item(indexOfObject(object, resolveName))) + return i->isContainer(); + return false; +} + +/*! +*/ +bool QDesignerWidgetDataBaseInterface::isCustom(QObject *object, bool resolveName) const +{ + if (const QDesignerWidgetDataBaseItemInterface *i = item(indexOfObject(object, resolveName))) + return i->isCustom(); + return false; +} + +/*! + \fn void QDesignerWidgetDataBaseInterface::changed() + + This signal is emitted ... +*/ + + +// Doc: No implementation - an abstract class + +/*! + \class QDesignerWidgetDataBaseItemInterface + \brief The QDesignerWidgetDataBaseItemInterface class provides an interface that is used to + access individual items in \QD's widget database. + \inmodule QtDesigner + \internal + + This class enables individual items in the widget database to be accessed and modified. + Changes to the widget database itself are made through the QDesignerWidgetDataBaseInterface + class. +*/ + +/*! + \fn virtual QDesignerWidgetDataBaseItemInterface::~QDesignerWidgetDataBaseItemInterface() + + Destroys the interface. +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::name() const = 0 + + Returns the name of the widget. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setName(const QString &name) = 0 +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::group() const = 0 + + Returns the name of the group that the widget belongs to. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setGroup(const QString &group) = 0 +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::toolTip() const = 0 + + Returns the tool tip to be used by the widget. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setToolTip(const QString &toolTip) = 0 +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::whatsThis() const = 0 + + Returns the "What's This?" help for the widget. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setWhatsThis(const QString &whatsThis) = 0 +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::includeFile() const = 0 + + Returns the name of the include file that the widget needs when being built from source. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setIncludeFile(const QString &includeFile) = 0 +*/ + +/*! + \fn virtual QIcon QDesignerWidgetDataBaseItemInterface::icon() const = 0 + + Returns the icon used to represent the item. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setIcon(const QIcon &icon) = 0 +*/ + +/*! + \fn virtual bool QDesignerWidgetDataBaseItemInterface::isCompat() const = 0 + + Returns true if this type of widget is provided for compatibility purposes (e.g. Qt3Support + widgets); otherwise returns false. + + \sa setCompat() +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setCompat(bool compat) = 0 + + If \a compat is true, the widget is handled as a compatibility widget; otherwise it is + handled normally by \QD. + + \sa isCompat() +*/ + +/*! + \fn virtual bool QDesignerWidgetDataBaseItemInterface::isContainer() const = 0 + + Returns true if this widget is intended to be used to hold other widgets; otherwise returns + false. + + \sa setContainer() +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setContainer(bool container) = 0 + + If \a container is true, the widget can be used to hold other widgets in \QD; otherwise + \QD will refuse to let the user place other widgets inside it. + + \sa isContainer() +*/ + +/*! + \fn virtual bool QDesignerWidgetDataBaseItemInterface::isCustom() const = 0 + + Returns true if the widget is a custom widget; otherwise return false if it is a standard + Qt widget. + + \sa setCustom() +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setCustom(bool custom) = 0 + + If \a custom is true, the widget is handled specially by \QD; otherwise it is handled as + a standard Qt widget. + + \sa isCustom() +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::pluginPath() const = 0 + + Returns the path to use for the widget plugin. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setPluginPath(const QString &path) = 0 +*/ + +/*! + \fn virtual bool QDesignerWidgetDataBaseItemInterface::isPromoted() const = 0 + + Returns true if the widget is promoted; otherwise returns false. + + Promoted widgets are those that represent custom widgets, but which are represented in + \QD by either standard Qt widgets or readily-available custom widgets. + + \sa setPromoted() +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setPromoted(bool promoted) = 0 + + If \a promoted is true, the widget is handled as a promoted widget by \QD and will use + a placeholder widget to represent it; otherwise it is handled as a standard widget. + + \sa isPromoted() +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::extends() const = 0 + + Returns the name of the widget that the item extends. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setExtends(const QString &s) = 0 +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setDefaultPropertyValues(const QList &list) = 0 + + Sets the default property values for the widget to the given \a list. +*/ + +/*! + \fn virtual QList QDesignerWidgetDataBaseItemInterface::defaultPropertyValues() const = 0 + + Returns a list of default values to be used as properties for the item. +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractwidgetdatabase.h b/src/designer/src/lib/sdk/abstractwidgetdatabase.h new file mode 100644 index 0000000..fbf88e7 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractwidgetdatabase.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTWIDGETDATABASE_H +#define ABSTRACTWIDGETDATABASE_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QIcon; +class QString; +class QDesignerFormEditorInterface; +class QDebug; + +class QDesignerWidgetDataBaseItemInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerWidgetDataBaseItemInterface) + + QDesignerWidgetDataBaseItemInterface() = default; + virtual ~QDesignerWidgetDataBaseItemInterface() = default; + + virtual QString name() const = 0; + virtual void setName(const QString &name) = 0; + + virtual QString group() const = 0; + virtual void setGroup(const QString &group) = 0; + + virtual QString toolTip() const = 0; + virtual void setToolTip(const QString &toolTip) = 0; + + virtual QString whatsThis() const = 0; + virtual void setWhatsThis(const QString &whatsThis) = 0; + + virtual QString includeFile() const = 0; + virtual void setIncludeFile(const QString &includeFile) = 0; + + virtual QIcon icon() const = 0; + virtual void setIcon(const QIcon &icon) = 0; + + virtual bool isCompat() const = 0; + virtual void setCompat(bool compat) = 0; + + virtual bool isContainer() const = 0; + virtual void setContainer(bool container) = 0; + + virtual bool isCustom() const = 0; + virtual void setCustom(bool custom) = 0; + + virtual QString pluginPath() const = 0; + virtual void setPluginPath(const QString &path) = 0; + + virtual bool isPromoted() const = 0; + virtual void setPromoted(bool b) = 0; + + virtual QString extends() const = 0; + virtual void setExtends(const QString &s) = 0; + + virtual void setDefaultPropertyValues(const QList &list) = 0; + virtual QList defaultPropertyValues() const = 0; +}; + +class QDESIGNER_SDK_EXPORT QDesignerWidgetDataBaseInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerWidgetDataBaseInterface(QObject *parent = nullptr); + virtual ~QDesignerWidgetDataBaseInterface(); + + virtual int count() const; + virtual QDesignerWidgetDataBaseItemInterface *item(int index) const; + + virtual int indexOf(QDesignerWidgetDataBaseItemInterface *item) const; + virtual void insert(int index, QDesignerWidgetDataBaseItemInterface *item); + virtual void append(QDesignerWidgetDataBaseItemInterface *item); + + virtual int indexOfObject(QObject *object, bool resolveName = true) const; + virtual int indexOfClassName(const QString &className, bool resolveName = true) const; + + virtual QDesignerFormEditorInterface *core() const; + + bool isContainer(QObject *object, bool resolveName = true) const; + bool isCustom(QObject *object, bool resolveName = true) const; + +Q_SIGNALS: + void changed(); + +protected: + QList m_items; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTWIDGETDATABASE_H diff --git a/src/designer/src/lib/sdk/abstractwidgetfactory.cpp b/src/designer/src/lib/sdk/abstractwidgetfactory.cpp new file mode 100644 index 0000000..52c41b6 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractwidgetfactory.cpp @@ -0,0 +1,72 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include "abstractformeditor.h" +#include "abstractwidgetdatabase.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerWidgetFactoryInterface + \brief The QDesignerWidgetFactoryInterface class provides an interface that is used to control + the widget factory used by \QD. + \inmodule QtDesigner + \internal +*/ + +/*! + \fn QDesignerWidgetFactoryInterface::QDesignerWidgetFactoryInterface(QObject *parent) + + Constructs an interface to a widget factory with the given \a parent. +*/ +QDesignerWidgetFactoryInterface::QDesignerWidgetFactoryInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! + \fn virtual QDesignerWidgetFactoryInterface::~QDesignerWidgetFactoryInterface() +*/ +QDesignerWidgetFactoryInterface::~QDesignerWidgetFactoryInterface() = default; + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerWidgetFactoryInterface::core() const = 0 + + Returns the core form editor interface associated with this interface. +*/ + +/*! + \fn virtual QWidget* QDesignerWidgetFactoryInterface::containerOfWidget(QWidget *child) const = 0 + + Returns the widget that contains the specified \a child widget. +*/ + +/*! + \fn virtual QWidget* QDesignerWidgetFactoryInterface::widgetOfContainer(QWidget *container) const = 0 + + +*/ + +/*! + \fn virtual QWidget *QDesignerWidgetFactoryInterface::createWidget(const QString &name, QWidget *parent) const = 0 + + Returns a new widget with the given \a name and \a parent widget. If no parent is specified, + the widget created will be a top-level widget. +*/ + +/*! + \fn virtual QLayout *QDesignerWidgetFactoryInterface::createLayout(QWidget *widget, QLayout *layout, int type) const = 0 + + Returns a new layout of the specified \a type for the given \a widget or \a layout. +*/ + +/*! + \fn virtual bool QDesignerWidgetFactoryInterface::isPassiveInteractor(QWidget *widget) = 0 +*/ + +/*! + \fn virtual void QDesignerWidgetFactoryInterface::initialize(QObject *object) const = 0 +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/abstractwidgetfactory.h b/src/designer/src/lib/sdk/abstractwidgetfactory.h new file mode 100644 index 0000000..a8c5cd2 --- /dev/null +++ b/src/designer/src/lib/sdk/abstractwidgetfactory.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTWIDGETFACTORY_H +#define ABSTRACTWIDGETFACTORY_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QWidget; +class QLayout; + +class QDESIGNER_SDK_EXPORT QDesignerWidgetFactoryInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerWidgetFactoryInterface(QObject *parent = nullptr); + virtual ~QDesignerWidgetFactoryInterface(); + + virtual QDesignerFormEditorInterface *core() const = 0; + + virtual QWidget* containerOfWidget(QWidget *w) const = 0; + virtual QWidget* widgetOfContainer(QWidget *w) const = 0; + + virtual QWidget *createWidget(const QString &name, QWidget *parentWidget = nullptr) const = 0; + virtual QLayout *createLayout(QWidget *widget, QLayout *layout, int type) const = 0; + + virtual bool isPassiveInteractor(QWidget *widget) = 0; + virtual void initialize(QObject *object) const = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTWIDGETFACTORY_H diff --git a/src/designer/src/lib/sdk/container.h b/src/designer/src/lib/sdk/container.h new file mode 100644 index 0000000..079e4d5 --- /dev/null +++ b/src/designer/src/lib/sdk/container.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONTAINER_H +#define CONTAINER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWidget; + +class QDesignerContainerExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerContainerExtension) + + QDesignerContainerExtension() = default; + virtual ~QDesignerContainerExtension() = default; + + virtual int count() const = 0; + virtual QWidget *widget(int index) const = 0; + + virtual int currentIndex() const = 0; + virtual void setCurrentIndex(int index) = 0; + + virtual bool canAddWidget() const = 0; + virtual void addWidget(QWidget *widget) = 0; + virtual void insertWidget(int index, QWidget *widget) = 0; + virtual bool canRemove(int index) const = 0; + virtual void remove(int index) = 0; +}; + +Q_DECLARE_EXTENSION_INTERFACE(QDesignerContainerExtension, "org.qt-project.Qt.Designer.Container") + +QT_END_NAMESPACE + +#endif // CONTAINER_H diff --git a/src/designer/src/lib/sdk/container.qdoc b/src/designer/src/lib/sdk/container.qdoc new file mode 100644 index 0000000..be3723c --- /dev/null +++ b/src/designer/src/lib/sdk/container.qdoc @@ -0,0 +1,175 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerContainerExtension + \brief The QDesignerContainerExtension class allows you to add pages to + a custom multi-page container in \QD's workspace. + \inmodule QtDesigner + + \image containerextension-example.webp + + QDesignerContainerExtension provide an interface for creating + custom container extensions. A container extension consists of a + collection of functions that \QD needs to manage a multi-page + container plugin, and a list of the container's pages. + + \warning This is \e not an extension for container plugins in + general, only custom \e multi-page containers. + + To create a container extension, your extension class must inherit + from both QObject and QDesignerContainerExtension. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 6 + + Since we are implementing an interface, we must ensure that it's + made known to the meta object system using the Q_INTERFACES() + macro. This enables \QD to use the qobject_cast() function to + query for supported interfaces using nothing but a QObject + pointer. + + You must reimplement several functions to enable \QD to manage a + custom multi-page container widget: \QD uses count() to keep track + of the number pages in your container, widget() to return the page + at a given index in the list of the container's pages, and + currentIndex() to return the list index of the selected page. \QD + uses the addWidget() function to add a given page to the + container, expecting it to be appended to the list of pages, while + it expects the insertWidget() function to add a given page to the + container by inserting it at a given index. + + In \QD the extensions are not created until they are + required. For that reason you must also create a + QExtensionFactory, i.e a class that is able to make an instance of + your extension, and register it using \QD's \l + {QExtensionManager}{extension manager}. + + When a container extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create a container + extension, is found. This factory will then create the extension + for the plugin. + + There are four available types of extensions in \QD: + QDesignerContainerExtension , QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and QDesignerTaskMenuExtension. + \QD's behavior is the same whether the requested extension is + associated with a multi page container, a member sheet, a property + sheet or a task menu. + + The QExtensionFactory class provides a standard extension factory, + and can also be used as an interface for custom extension + factories. You can either create a new QExtensionFactory and + reimplement the QExtensionFactory::createExtension() function. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 7 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create a container extension as well. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 8 + + For a complete example using the QDesignerContainerExtension + class, see the \l {containerextension}{Container + Extension example}. The example shows how to create a custom + multi-page plugin for \QD. + + \sa QExtensionFactory, QExtensionManager, {Creating Custom Widget + Extensions} +*/ + +/*! + \fn QDesignerContainerExtension::~QDesignerContainerExtension() + + Destroys the extension. +*/ + +/*! + \fn int QDesignerContainerExtension::count() const + + Returns the number of pages in the container. +*/ + +/*! + \fn QWidget *QDesignerContainerExtension::widget(int index) const + + Returns the page at the given \a index in the extension's list of + pages. + + \sa addWidget(), insertWidget() +*/ + +/*! + \fn int QDesignerContainerExtension::currentIndex() const + + Returns the index of the currently selected page in the + container. + + \sa setCurrentIndex() +*/ + +/*! + \fn void QDesignerContainerExtension::setCurrentIndex(int index) + + Sets the currently selected page in the container to be the + page at the given \a index in the extension's list of pages. + + \sa currentIndex() +*/ + +/*! + \fn void QDesignerContainerExtension::addWidget(QWidget *page) + + Adds the given \a page to the container by appending it to the + extension's list of pages. + + \sa insertWidget(), remove(), widget() +*/ + +/*! + \fn void QDesignerContainerExtension::insertWidget(int index, QWidget *page) + + Adds the given \a page to the container by inserting it at the + given \a index in the extension's list of pages. + + \sa addWidget(), remove(), widget() +*/ + +/*! + \fn void QDesignerContainerExtension::remove(int index) + + Removes the page at the given \a index from the extension's list + of pages. + + \sa addWidget(), insertWidget() +*/ + +/*! + \fn bool QDesignerContainerExtension::canAddWidget() const + + Returns whether a widget can be added. This determines whether + the context menu options to add or insert pages are enabled. + + This should return false for containers that have a single, fixed + page, for example QScrollArea or QDockWidget. + + \since 5.0 + \sa addWidget(), canRemove() +*/ + +/*! + \fn bool QDesignerContainerExtension::canRemove(int index) const + + Returns whether the widget at the given \a index can be removed. + This determines whether the context menu option to remove the current + page is enabled. + + This should return false for containers that have a single, fixed + page, for example QScrollArea or QDockWidget. + + \since 5.0 + \sa remove(), canAddWidget() +*/ diff --git a/src/designer/src/lib/sdk/dynamicpropertysheet.h b/src/designer/src/lib/sdk/dynamicpropertysheet.h new file mode 100644 index 0000000..9046d2e --- /dev/null +++ b/src/designer/src/lib/sdk/dynamicpropertysheet.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DYNAMICPROPERTYSHEET_H +#define DYNAMICPROPERTYSHEET_H + +#include + +QT_BEGIN_NAMESPACE + +class QString; // FIXME: fool syncqt + +class QDesignerDynamicPropertySheetExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerDynamicPropertySheetExtension) + + QDesignerDynamicPropertySheetExtension() = default; + virtual ~QDesignerDynamicPropertySheetExtension() = default; + + virtual bool dynamicPropertiesAllowed() const = 0; + virtual int addDynamicProperty(const QString &propertyName, const QVariant &value) = 0; + virtual bool removeDynamicProperty(int index) = 0; + virtual bool isDynamicProperty(int index) const = 0; + virtual bool canAddDynamicProperty(const QString &propertyName) const = 0; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerDynamicPropertySheetExtension, "org.qt-project.Qt.Designer.DynamicPropertySheet") + +QT_END_NAMESPACE + +#endif // DYNAMICPROPERTYSHEET_H diff --git a/src/designer/src/lib/sdk/dynamicpropertysheet.qdoc b/src/designer/src/lib/sdk/dynamicpropertysheet.qdoc new file mode 100644 index 0000000..df457a4 --- /dev/null +++ b/src/designer/src/lib/sdk/dynamicpropertysheet.qdoc @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerDynamicPropertySheetExtension + + \brief The QDesignerDynamicPropertySheetExtension class allows you to + manipulate a widget's dynamic properties in \QD's property editor. + + \sa QDesignerPropertySheetExtension, {QObject#Dynamic Properties}{Dynamic Properties} + + \inmodule QtDesigner + \since 4.3 +*/ + +/*! + \fn QDesignerDynamicPropertySheetExtension::~QDesignerDynamicPropertySheetExtension() + + Destroys the dynamic property sheet extension. +*/ + +/*! + \fn bool QDesignerDynamicPropertySheetExtension::dynamicPropertiesAllowed() const + + Returns true if the widget supports dynamic properties; otherwise returns false. +*/ + +/*! + \fn int QDesignerDynamicPropertySheetExtension::addDynamicProperty(const QString &propertyName, const QVariant &value) + + Adds a dynamic property named \a propertyName and sets its value to \a value. + Returns the index of the property if it was added successfully; otherwise returns -1 to + indicate failure. +*/ + +/*! + \fn bool QDesignerDynamicPropertySheetExtension::removeDynamicProperty(int index) + + Removes the dynamic property at the given \a index. + Returns true if the operation succeeds; otherwise returns false. +*/ + +/*! + \fn bool QDesignerDynamicPropertySheetExtension::isDynamicProperty(int index) const + + Returns true if the property at the given \a index is a dynamic property; otherwise + returns false. +*/ + +/*! + \fn bool QDesignerDynamicPropertySheetExtension::canAddDynamicProperty(const QString &propertyName) const + + Returns true if \a propertyName is a valid, unique name for a dynamic + property; otherwise returns false. + +*/ diff --git a/src/designer/src/lib/sdk/extrainfo.cpp b/src/designer/src/lib/sdk/extrainfo.cpp new file mode 100644 index 0000000..1b161c3 --- /dev/null +++ b/src/designer/src/lib/sdk/extrainfo.cpp @@ -0,0 +1,78 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "extrainfo.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerExtraInfoExtension + \brief The QDesignerExtraInfoExtension class provides extra information about a widget in + Qt Widgets Designer. + \inmodule QtDesigner + \internal +*/ + +/*! + Returns the path to the working directory used by this extension.*/ +QString QDesignerExtraInfoExtension::workingDirectory() const +{ + return m_workingDirectory; +} + +/*! + Sets the path to the working directory used by the extension to \a workingDirectory.*/ +void QDesignerExtraInfoExtension::setWorkingDirectory(const QString &workingDirectory) +{ + m_workingDirectory = workingDirectory; +} + +/*! + \fn virtual QDesignerExtraInfoExtension::~QDesignerExtraInfoExtension() + + Destroys the extension. +*/ + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerExtraInfoExtension::core() const = 0 + + \omit + ### Description required + \endomit +*/ + +/*! + \fn virtual QWidget *QDesignerExtraInfoExtension::widget() const = 0 + + Returns the widget described by this extension. +*/ + +/*! + \fn virtual bool QDesignerExtraInfoExtension::saveUiExtraInfo(DomUI *ui) = 0 + + Saves the information about the user interface specified by \a ui, and returns true if + successful; otherwise returns false. +*/ + +/*! + \fn virtual bool QDesignerExtraInfoExtension::loadUiExtraInfo(DomUI *ui) = 0 + + Loads extra information about the user interface specified by \a ui, and returns true if + successful; otherwise returns false. +*/ + +/*! + \fn virtual bool QDesignerExtraInfoExtension::saveWidgetExtraInfo(DomWidget *widget) = 0 + + Saves the information about the specified \a widget, and returns true if successful; + otherwise returns false. +*/ + +/*! + \fn virtual bool QDesignerExtraInfoExtension::loadWidgetExtraInfo(DomWidget *widget) = 0 + + Loads extra information about the specified \a widget, and returns true if successful; + otherwise returns false. +*/ + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/extrainfo.h b/src/designer/src/lib/sdk/extrainfo.h new file mode 100644 index 0000000..0ce315e --- /dev/null +++ b/src/designer/src/lib/sdk/extrainfo.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EXTRAINFO_H +#define EXTRAINFO_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class DomWidget; +class DomUI; +class QWidget; + +class QDesignerFormEditorInterface; + +class QDESIGNER_SDK_EXPORT QDesignerExtraInfoExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerExtraInfoExtension) + + QDesignerExtraInfoExtension() = default; + virtual ~QDesignerExtraInfoExtension() = default; + + virtual QDesignerFormEditorInterface *core() const = 0; + virtual QWidget *widget() const = 0; + + virtual bool saveUiExtraInfo(DomUI *ui) = 0; + virtual bool loadUiExtraInfo(DomUI *ui) = 0; + + virtual bool saveWidgetExtraInfo(DomWidget *ui_widget) = 0; + virtual bool loadWidgetExtraInfo(DomWidget *ui_widget) = 0; + + QString workingDirectory() const; + void setWorkingDirectory(const QString &workingDirectory); + +private: + QString m_workingDirectory; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerExtraInfoExtension, "org.qt-project.Qt.Designer.ExtraInfo.2") + +QT_END_NAMESPACE + +#endif // EXTRAINFO_H diff --git a/src/designer/src/lib/sdk/layoutdecoration.h b/src/designer/src/lib/sdk/layoutdecoration.h new file mode 100644 index 0000000..3808c5d --- /dev/null +++ b/src/designer/src/lib/sdk/layoutdecoration.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LAYOUTDECORATION_H +#define LAYOUTDECORATION_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPoint; +class QLayoutItem; +class QWidget; +class QRect; +class QLayout; + +class QDesignerLayoutDecorationExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerLayoutDecorationExtension) + + enum InsertMode + { + InsertWidgetMode, + InsertRowMode, + InsertColumnMode + }; + + QDesignerLayoutDecorationExtension() = default; + virtual ~QDesignerLayoutDecorationExtension() = default; + + virtual QList widgets(QLayout *layout) const = 0; + + virtual QRect itemInfo(int index) const = 0; + virtual int indexOf(QWidget *widget) const = 0; + virtual int indexOf(QLayoutItem *item) const = 0; + + virtual InsertMode currentInsertMode() const = 0; + virtual int currentIndex() const = 0; + virtual QPair currentCell() const = 0; + virtual void insertWidget(QWidget *widget, const QPair &cell) = 0; + virtual void removeWidget(QWidget *widget) = 0; + + virtual void insertRow(int row) = 0; + virtual void insertColumn(int column) = 0; + virtual void simplify() = 0; + + virtual int findItemAt(const QPoint &pos) const = 0; + virtual int findItemAt(int row, int column) const = 0; // atm only for grid. + + virtual void adjustIndicator(const QPoint &pos, int index) = 0; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerLayoutDecorationExtension, "org.qt-project.Qt.Designer.LayoutDecoration") + +QT_END_NAMESPACE + +#endif // LAYOUTDECORATION_H diff --git a/src/designer/src/lib/sdk/layoutdecoration.qdoc b/src/designer/src/lib/sdk/layoutdecoration.qdoc new file mode 100644 index 0000000..8f98af1 --- /dev/null +++ b/src/designer/src/lib/sdk/layoutdecoration.qdoc @@ -0,0 +1,127 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +// ### FIXME Qt 7: std::pair in QDesignerLayoutDecorationExtension (QTBUG-115841) + +/*! + \class QDesignerLayoutDecorationExtension + \brief The QDesignerLayoutDecorationExtension class provides an extension to a layout in \QD. + \inmodule QtDesigner + \internal +*/ + +/*! + \enum QDesignerLayoutDecorationExtension::InsertMode + + This enum describes the modes that are used to insert items into a layout. + + \value InsertWidgetMode Widgets are inserted into empty cells in a layout. + \value InsertRowMode Whole rows are inserted into a vertical or grid layout. + \value InsertColumnMode Whole columns are inserted into a horizontal or grid layout. +*/ + +/*! + \fn virtual QDesignerLayoutDecorationExtension::~QDesignerLayoutDecorationExtension() + + Destroys the extension. +*/ + +/*! + \fn virtual QList QDesignerLayoutDecorationExtension::widgets(QLayout *layout) const + + Returns the widgets that are managed by the given \a layout. + + \sa insertWidget(), removeWidget() +*/ + +/*! + \fn QRect QDesignerLayoutDecorationExtension::itemInfo(int index) const + + Returns the rectangle covered by the item at the given \a index in the layout. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::indexOf(QWidget *widget) const + + Returns the index of the specified \a widget in the layout. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::indexOf(QLayoutItem *item) const + + Returns the index of the specified layout \a item. +*/ + +/*! + \fn QDesignerLayoutDecorationExtension::InsertMode QDesignerLayoutDecorationExtension::currentInsertMode() const + + Returns the current insertion mode. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::currentIndex() const + + Returns the current index in the layout. +*/ + +/*! + \fn QPair QDesignerLayoutDecorationExtension::currentCell() const + + Returns a pair containing the row and column of the current cell in the layout. +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::insertWidget(QWidget *widget, const QPair &cell) + + Inserts the given \a widget into the specified \a cell in the layout. + + \sa removeWidget() +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::removeWidget(QWidget *widget) + + Removes the specified \a widget from the layout. + + \sa insertWidget() +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::insertRow(int row) + + Inserts a new row into the form at the position specified by \a row. +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::insertColumn(int column) + + Inserts a new column into the form at the position specified by \a column. +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::simplify() + + Simplifies the layout by removing unnecessary empty rows and columns, and by changing the + number of rows or columns spanned by widgets. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::findItemAt(const QPoint &position) const + + Returns the index of the item in the layout that covers the given \a position. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::findItemAt(int row, int column) const + + Returns the item in the layout that occupies the specified \a row and \a column in the layout. + + Currently, this only applies to grid layouts. +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::adjustIndicator(const QPoint &position, int index) + + Adjusts the indicator for the item specified by \a index so that + it lies at the given \a position on the form. +*/ diff --git a/src/designer/src/lib/sdk/membersheet.h b/src/designer/src/lib/sdk/membersheet.h new file mode 100644 index 0000000..06b773a --- /dev/null +++ b/src/designer/src/lib/sdk/membersheet.h @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MEMBERSHEET_H +#define MEMBERSHEET_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QString; // FIXME: fool syncqt + +class QDesignerMemberSheetExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMemberSheetExtension) + + QDesignerMemberSheetExtension() = default; + virtual ~QDesignerMemberSheetExtension() = default; + + virtual int count() const = 0; + + virtual int indexOf(const QString &name) const = 0; + + virtual QString memberName(int index) const = 0; + virtual QString memberGroup(int index) const = 0; + virtual void setMemberGroup(int index, const QString &group) = 0; + + virtual bool isVisible(int index) const = 0; + virtual void setVisible(int index, bool b) = 0; + + virtual bool isSignal(int index) const = 0; + virtual bool isSlot(int index) const = 0; + + virtual bool inheritedFromWidget(int index) const = 0; + + virtual QString declaredInClass(int index) const = 0; + + virtual QString signature(int index) const = 0; + virtual QList parameterTypes(int index) const = 0; + virtual QList parameterNames(int index) const = 0; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerMemberSheetExtension, "org.qt-project.Qt.Designer.MemberSheet") + +QT_END_NAMESPACE + +#endif // MEMBERSHEET_H diff --git a/src/designer/src/lib/sdk/membersheet.qdoc b/src/designer/src/lib/sdk/membersheet.qdoc new file mode 100644 index 0000000..65e56cd --- /dev/null +++ b/src/designer/src/lib/sdk/membersheet.qdoc @@ -0,0 +1,225 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerMemberSheetExtension + + \brief The QDesignerMemberSheetExtension class allows you to + manipulate a widget's member functions which is displayed when + configuring connections using \QD's mode for editing + signals and slots. + + \inmodule QtDesigner + + QDesignerMemberSheetExtension is a collection of functions that is + typically used to query a widget's member functions, and to + manipulate the member functions' appearance in \QD's signals and + slots editing mode. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 2 + + When implementing a custom widget plugin, a pointer to \QD's + current QDesignerFormEditorInterface object (\c formEditor in the + example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's parameter. + + The member sheet (and any other extension), can be retrieved by + querying \QD's extension manager using the qt_extension() + function. When you want to release the extension, you only need to + delete the pointer. + + All widgets have a default member sheet used in \QD's signals and + slots editing mode with the widget's member functions. But + QDesignerMemberSheetExtension also provides an interface for + creating custom member sheet extensions. + + \warning \QD uses the QDesignerMemberSheetExtension to facilitate + the signal and slot editing mode. Whenever a connection between + two widgets is requested, \QD will query for the widgets' member + sheet extensions. If a widget has an implemented member sheet + extension, this extension will override the default member sheet. + + To create a member sheet extension, your extension class must + inherit from both QObject and QDesignerMemberSheetExtension. Then, + since we are implementing an interface, we must ensure that it's + made known to the meta object system using the Q_INTERFACES() + macro: + + \snippet plugins/doc_src_qtdesigner.cpp 3 + + This enables \QD to use qobject_cast() to query for + supported interfaces using nothing but a QObject pointer. + + In \QD the extensions are not created until they are + required. For that reason, when implementing a member sheet + extension, you must also create a QExtensionFactory, i.e a class + that is able to make an instance of your extension, and register + it using \QD's \l {QExtensionManager}{extension manager}. + + When a widget's member sheet extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create a member sheet + extension for that widget, is found. This factory will then make + an instance of the extension. If no such factory is found, \QD + will use the default member sheet. + + There are four available types of extensions in \QD: + QDesignerContainerExtension, QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and + QDesignerTaskMenuExtension. \QD's behavior is the same whether the + requested extension is associated with a multi page container, a + member sheet, a property sheet or a task menu. + + The QExtensionFactory class provides a standard extension + factory, and can also be used as an interface for custom + extension factories. You can either create a new + QExtensionFactory and reimplement the + QExtensionFactory::createExtension() function. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 4 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create a member sheet extension as well. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 5 + + For a complete example using an extension class, see \l + {taskmenuextension}{Task Menu Extension example}. The + example shows how to create a custom widget plugin for Qt + Designer, and how to use the QDesignerTaskMenuExtension class + to add custom items to \QD's task menu. + + \sa QExtensionFactory, QExtensionManager, {Creating Custom Widget + Extensions} +*/ + +/*! + \fn QDesignerMemberSheetExtension::~QDesignerMemberSheetExtension() + + Destroys the member sheet extension. +*/ + +/*! + \fn int QDesignerMemberSheetExtension::count() const + + Returns the extension's number of member functions. +*/ + +/*! + \fn int QDesignerMemberSheetExtension::indexOf(const QString &name) const + + Returns the index of the member function specified by the given \a + name. + + \sa memberName() +*/ + +/*! + \fn QString QDesignerMemberSheetExtension::memberName(int index) const + + Returns the name of the member function with the given \a index. + + \sa indexOf() +*/ + +/*! + \fn QString QDesignerMemberSheetExtension::memberGroup(int index) const + + Returns the name of the member group specified for the function + with the given \a index. + + \sa indexOf(), setMemberGroup() +*/ + +/*! + \fn void QDesignerMemberSheetExtension::setMemberGroup(int index, const QString &group) + + Sets the member group of the member function with the given \a + index, to \a group. + + \sa indexOf(), memberGroup() +*/ + +/*! + \fn bool QDesignerMemberSheetExtension::isVisible(int index) const + + Returns true if the member function with the given \a index is + visible in \QD's signal and slot editor, otherwise false. + + \sa indexOf(), setVisible() +*/ + +/*! + \fn void QDesignerMemberSheetExtension::setVisible(int index, bool visible) + + If \a visible is true, the member function with the given \a index + is visible in \QD's signals and slots editing mode; otherwise the + member function is hidden. + + \sa indexOf(), isVisible() +*/ + +/*! + \fn virtual bool QDesignerMemberSheetExtension::isSignal(int index) const + + Returns true if the member function with the given \a index is a + signal, otherwise false. + + \sa indexOf() +*/ + +/*! + \fn bool QDesignerMemberSheetExtension::isSlot(int index) const + + Returns true if the member function with the given \a index is a + slot, otherwise false. + + \sa indexOf() +*/ + +/*! + \fn bool QDesignerMemberSheetExtension::inheritedFromWidget(int index) const + + Returns true if the member function with the given \a index is + inherited from QWidget, otherwise false. + + \sa indexOf() +*/ + +/*! + \fn QString QDesignerMemberSheetExtension::declaredInClass(int index) const + + Returns the name of the class in which the member function with + the given \a index is declared. + + \sa indexOf() +*/ + +/*! + \fn QString QDesignerMemberSheetExtension::signature(int index) const + + Returns the signature of the member function with the given \a + index. + + \sa indexOf() +*/ + +/*! + \fn QList QDesignerMemberSheetExtension::parameterTypes(int index) const + + Returns the parameter types of the member function with the given + \a index, as a QByteArray list. + + \sa indexOf(), parameterNames() +*/ + +/*! + \fn QList QDesignerMemberSheetExtension::parameterNames(int index) const + + Returns the parameter names of the member function with the given + \a index, as a QByteArray list. + + \sa indexOf(), parameterTypes() +*/ diff --git a/src/designer/src/lib/sdk/propertysheet.h b/src/designer/src/lib/sdk/propertysheet.h new file mode 100644 index 0000000..9aa49b8 --- /dev/null +++ b/src/designer/src/lib/sdk/propertysheet.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PROPERTYSHEET_H +#define PROPERTYSHEET_H + +#include + +QT_BEGIN_NAMESPACE + +class QVariant; + +class QDesignerPropertySheetExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerPropertySheetExtension) + + QDesignerPropertySheetExtension() = default; + virtual ~QDesignerPropertySheetExtension() = default; + + virtual int count() const = 0; + + virtual int indexOf(const QString &name) const = 0; + + virtual QString propertyName(int index) const = 0; + virtual QString propertyGroup(int index) const = 0; + virtual void setPropertyGroup(int index, const QString &group) = 0; + + virtual bool hasReset(int index) const = 0; + virtual bool reset(int index) = 0; + + virtual bool isVisible(int index) const = 0; + virtual void setVisible(int index, bool b) = 0; + + virtual bool isAttribute(int index) const = 0; + virtual void setAttribute(int index, bool b) = 0; + + virtual QVariant property(int index) const = 0; + virtual void setProperty(int index, const QVariant &value) = 0; + + virtual bool isChanged(int index) const = 0; + virtual void setChanged(int index, bool changed) = 0; + + virtual bool isEnabled(int index) const = 0; +}; + +Q_DECLARE_EXTENSION_INTERFACE(QDesignerPropertySheetExtension, + "org.qt-project.Qt.Designer.PropertySheet") + +QT_END_NAMESPACE + +#endif // PROPERTYSHEET_H diff --git a/src/designer/src/lib/sdk/propertysheet.qdoc b/src/designer/src/lib/sdk/propertysheet.qdoc new file mode 100644 index 0000000..5804ecb --- /dev/null +++ b/src/designer/src/lib/sdk/propertysheet.qdoc @@ -0,0 +1,288 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerPropertySheetExtension + + \brief The QDesignerPropertySheetExtension class allows you to + manipulate a widget's properties which is displayed in Qt + Designer's property editor. + + \sa QDesignerDynamicPropertySheetExtension + + \inmodule QtDesigner + + QDesignerPropertySheetExtension provides a collection of functions that + are typically used to query a widget's properties, and to + manipulate the properties' appearance in the property editor. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 15 + + Note that if you change the value of a property using the + QDesignerPropertySheetExtension::setProperty() function, the undo + stack is not updated. To ensure that a property's value can be + reverted using the undo stack, you must use the + QDesignerFormWindowCursorInterface::setProperty() function, or its + buddy \l + {QDesignerFormWindowCursorInterface::setWidgetProperty()}{setWidgetProperty()}, + instead. + + When implementing a custom widget plugin, a pointer to \QD's + current QDesignerFormEditorInterface object (\c formEditor in the + example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's parameter. + + The property sheet, or any other extension, can be retrieved by + querying \QD's extension manager using the qt_extension() + function. When you want to release the extension, you only need to + delete the pointer. + + All widgets have a default property sheet which populates \QD's + property editor with the widget's properties (i.e the ones defined + with the Q_PROPERTY() macro). But QDesignerPropertySheetExtension + also provides an interface for creating custom property sheet + extensions. + + Keep the following limitations in mind: + + \list + \li \QD uses the QDesignerPropertySheetExtension to feed its + property editor. Whenever a widget is selected in its workspace, + \QD will query for the widget's property sheet extension. If the + selected widget has an implemented property sheet extension, this + extension will override the default property sheet. + + \li The data types used by the property sheet for some properties + are opaque custom QVariant types containing additional information + instead of plain Qt data types. For example, this is the case for + enumerations, flags, icons, pixmaps and strings. + + \li \QD's property editor has no implementation for handling + Q_PROPERTY types for custom types that have been declared + with Q_DECLARE_METATYPE(). + \endlist + + To create a property sheet extension, your extension class must + inherit from both QObject and + QDesignerPropertySheetExtension. Then, since we are implementing + an interface, we must ensure that it's made known to the meta + object system using the Q_INTERFACES() macro: + + \snippet plugins/doc_src_qtdesigner.cpp 16 + + This enables \QD to use qobject_cast() to query for supported + interfaces using nothing but a QObject pointer. + + In \QD the extensions are not created until they are + required. For that reason, when implementing a property sheet + extension, you must also create a QExtensionFactory, i.e a class + that is able to make an instance of your extension, and register + it using \QD's \l {QExtensionManager}{extension manager}. + + When a property sheet extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create a property + sheet extension for the selected widget, is found. This factory + will then make an instance of the extension. If no such factory + can be found, \QD will use the default property sheet. + + There are four available types of extensions in \QD: + QDesignerContainerExtension, QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and QDesignerTaskMenuExtension. Qt + Designer's behavior is the same whether the requested extension is + associated with a multi page container, a member sheet, a property + sheet or a task menu. + + The QExtensionFactory class provides a standard extension factory, + and can also be used as an interface for custom extension + factories. You can either create a new QExtensionFactory and + reimplement the QExtensionFactory::createExtension() function. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 17 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create a property sheet extension as well. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 18 + + For a complete example using an extension class, see the \l + {taskmenuextension}{Task Menu Extension example}. The + example shows how to create a custom widget plugin for Qt + Designer, and how to use the QDesignerTaskMenuExtension class + to add custom items to \QD's task menu. + + \sa QExtensionFactory, QExtensionManager, {Creating Custom Widget + Extensions} +*/ + +/*! + \fn QDesignerPropertySheetExtension::~QDesignerPropertySheetExtension() + + Destroys the property sheet extension. +*/ + +/*! + \fn int QDesignerPropertySheetExtension::count() const + + Returns the selected widget's number of properties. +*/ + +/*! + \fn int QDesignerPropertySheetExtension::indexOf(const QString &name) const + + Returns the index for a given property \a name. + + \sa propertyName() +*/ + +/*! + \fn QString QDesignerPropertySheetExtension::propertyName(int index) const + + Returns the name of the property at the given \a index. + + \sa indexOf() +*/ + +/*! + \fn QString QDesignerPropertySheetExtension::propertyGroup(int index) const + + Returns the property group for the property at the given \a index. + + \QD's property editor supports property groups, i.e. sections of + related properties. A property can be related to a group using the + setPropertyGroup() function. The default group of any property is + the name of the class that defines it. For example, the + QObject::objectName property appears within the QObject property + group. + + \sa indexOf(), setPropertyGroup() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setPropertyGroup(int index, const QString &group) + + Sets the property group for the property at the given \a index to + \a group. + + Relating a property to a group makes it appear within that group's + section in the property editor. The default property group of any + property is the name of the class that defines it. For example, + the QObject::objectName property appears within the QObject + property group. + + \sa indexOf(), property(), propertyGroup() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::hasReset(int index) const + + Returns true if the property at the given \a index has a reset + button in \QD's property editor, otherwise false. + + \sa indexOf(), reset() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::reset(int index) + + Resets the value of the property at the given \a index, to the + default value. Returns true if a default value could be found, otherwise false. + + \sa indexOf(), hasReset(), isChanged() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::isVisible(int index) const + + Returns true if the property at the given \a index is visible in + \QD's property editor, otherwise false. + + \sa indexOf(), setVisible() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setVisible(int index, bool visible) + + If \a visible is true, the property at the given \a index is + visible in \QD's property editor; otherwise the property is + hidden. + + \sa indexOf(), isVisible() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::isAttribute(int index) const + + Returns true if the property at the given \a index is an attribute, + which will be \e excluded from the UI file, otherwise false. + + \sa indexOf(), setAttribute() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setAttribute(int index, bool attribute) + + If \a attribute is true, the property at the given \a index is + made an attribute which will be \e excluded from the UI file; + otherwise it will be included. + + \sa indexOf(), isAttribute() +*/ + +/*! + \fn QVariant QDesignerPropertySheetExtension::property(int index) const + + Returns the value of the property at the given \a index. + + \sa indexOf(), setProperty(), propertyGroup() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setProperty(int index, const QVariant &value) + + Sets the \a value of the property at the given \a index. + + \warning If you change the value of a property using this + function, the undo stack is not updated. To ensure that a + property's value can be reverted using the undo stack, you must + use the QDesignerFormWindowCursorInterface::setProperty() + function, or its buddy \l + {QDesignerFormWindowCursorInterface::setWidgetProperty()}{setWidgetProperty()}, + instead. + + \sa indexOf(), property(), propertyGroup() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::isChanged(int index) const + + Returns true if the value of the property at the given \a index + differs from the property's default value, otherwise false. + + \sa indexOf(), setChanged(), reset() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setChanged(int index, bool changed) + + Sets whether the property at the given \a index is different from + its default value, or not, depending on the \a changed parameter. + + \sa indexOf(), isChanged() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::isEnabled(int index) const + + Returns true if the property at the given \a index is enabled in + \QD's property editor, otherwise false. + + \since 5.0 + + \sa indexOf() +*/ diff --git a/src/designer/src/lib/sdk/sdk_global.h b/src/designer/src/lib/sdk/sdk_global.h new file mode 100644 index 0000000..b3ab7e1 --- /dev/null +++ b/src/designer/src/lib/sdk/sdk_global.h @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SDK_GLOBAL_H +#define SDK_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#define QDESIGNER_SDK_EXTERN Q_DECL_EXPORT +#define QDESIGNER_SDK_IMPORT Q_DECL_IMPORT + +#ifdef QT_DESIGNER_STATIC +# define QDESIGNER_SDK_EXPORT +#elif defined(QDESIGNER_SDK_LIBRARY) +# define QDESIGNER_SDK_EXPORT QDESIGNER_SDK_EXTERN +#else +# define QDESIGNER_SDK_EXPORT QDESIGNER_SDK_IMPORT +#endif + +QT_END_NAMESPACE + +#endif // SDK_GLOBAL_H diff --git a/src/designer/src/lib/sdk/taskmenu.cpp b/src/designer/src/lib/sdk/taskmenu.cpp new file mode 100644 index 0000000..3e41f43 --- /dev/null +++ b/src/designer/src/lib/sdk/taskmenu.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "taskmenu.h" + +QT_BEGIN_NAMESPACE + +QDesignerTaskMenuExtension::~QDesignerTaskMenuExtension() = default; + +QAction *QDesignerTaskMenuExtension::preferredEditAction() const +{ return nullptr; } + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/sdk/taskmenu.h b/src/designer/src/lib/sdk/taskmenu.h new file mode 100644 index 0000000..355e11d --- /dev/null +++ b/src/designer/src/lib/sdk/taskmenu.h @@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TASKMENU_H +#define TASKMENU_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; + +class QDESIGNER_SDK_EXPORT QDesignerTaskMenuExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerTaskMenuExtension) + + QDesignerTaskMenuExtension() = default; + virtual ~QDesignerTaskMenuExtension(); + + virtual QAction *preferredEditAction() const; + + virtual QList taskActions() const = 0; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerTaskMenuExtension, "org.qt-project.Qt.Designer.TaskMenu") + +QT_END_NAMESPACE + +#endif // TASKMENU_H diff --git a/src/designer/src/lib/sdk/taskmenu.qdoc b/src/designer/src/lib/sdk/taskmenu.qdoc new file mode 100644 index 0000000..78e76d9 --- /dev/null +++ b/src/designer/src/lib/sdk/taskmenu.qdoc @@ -0,0 +1,114 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerTaskMenuExtension + \brief The QDesignerTaskMenuExtension class allows you to add custom + menu entries to \QD's task menu. + \inmodule QtDesigner + + QDesignerTaskMenuExtension provides an interface for creating + custom task menu extensions. It is typically used to create task + menu entries that are specific to a plugin in \QD. + + \QD uses the QDesignerTaskMenuExtension to feed its task + menu. Whenever a task menu is requested, \QD will query + for the selected widget's task menu extension. + + \image taskmenuextension-example.webp + + A task menu extension is a collection of QActions. The actions + appear as entries in the task menu when the plugin with the + specified extension is selected. The image above shows the custom + \gui {Edit State...} action which appears in addition to \QD's + default task menu entries: \gui Cut, \gui Copy, \gui Paste etc. + + To create a custom task menu extension, your extension class must + inherit from both QObject and QDesignerTaskMenuExtension. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 9 + + Since we are implementing an interface, we must ensure that it + is made known to the meta-object system using the Q_INTERFACES() + macro. This enables \QD to use the qobject_cast() function to + query for supported interfaces using nothing but a QObject + pointer. + + You must reimplement the taskActions() function to return a list + of actions that will be included in \QD task menu. Optionally, you + can reimplement the preferredEditAction() function to set the + action that is invoked when selecting your plugin and pressing + \key F2. The preferred edit action must be one of the actions + returned by taskActions() and, if it's not defined, pressing the + \key F2 key will simply be ignored. + + In \QD, extensions are not created until they are required. A + task menu extension, for example, is created when you click the + right mouse button over a widget in \QD's workspace. For that + reason you must also construct an extension factory, using either + QExtensionFactory or a subclass, and register it using \QD's + \l {QExtensionManager}{extension manager}. + + When a task menu extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until it finds one that is able to create a task menu + extension for the selected widget. This factory will then make an + instance of the extension. + + There are four available types of extensions in \QD: + QDesignerContainerExtension, QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension, and QDesignerTaskMenuExtension. + \QD's behavior is the same whether the requested extension is + associated with a container, a member sheet, a property sheet or a + task menu. + + The QExtensionFactory class provides a standard extension factory, + and can also be used as an interface for custom extension + factories. You can either create a new QExtensionFactory and + reimplement the QExtensionFactory::createExtension() function. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 10 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create a task menu extension as well. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 11 + + For a complete example using the QDesignerTaskMenuExtension class, + see the \l {taskmenuextension}{Task Menu Extension + example}. The example shows how to create a custom widget plugin + for \QD, and how to use the QDesignerTaskMenuExtension + class to add custom items to \QD's task menu. + + \sa QExtensionFactory, QExtensionManager, {Creating Custom Widget + Extensions} +*/ + +/*! + \fn QDesignerTaskMenuExtension::~QDesignerTaskMenuExtension() + + Destroys the task menu extension. +*/ + +/*! + \fn QAction *QDesignerTaskMenuExtension::preferredEditAction() const + + Returns the action that is invoked when selecting a plugin with + the specified extension and pressing \key F2. + + The action must be one of the actions returned by taskActions(). +*/ + +/*! + \fn QList QDesignerTaskMenuExtension::taskActions() const + + Returns the task menu extension as a list of actions which will be + included in \QD's task menu when a plugin with the specified + extension is selected. + + The function must be reimplemented to add actions to the list. +*/ diff --git a/src/designer/src/lib/shared/actioneditor.cpp b/src/designer/src/lib/shared/actioneditor.cpp new file mode 100644 index 0000000..b6f3c77 --- /dev/null +++ b/src/designer/src/lib/shared/actioneditor.cpp @@ -0,0 +1,895 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "actioneditor_p.h" +#include "actionrepository_p.h" +#include "iconloader_p.h" +#include "newactiondialog_p.h" +#include "qdesigner_menu_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "qdesigner_objectinspector_p.h" +#include "qdesigner_utils_p.h" +#include "qsimpleresource_p.h" +#include "formwindowbase_p.h" +#include "qdesigner_taskmenu_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto actionEditorViewModeKey = "ActionEditorViewMode"_L1; + +static constexpr auto iconPropertyC = "icon"_L1; +static constexpr auto shortcutPropertyC = "shortcut"_L1; +static constexpr auto menuRolePropertyC = "menuRole"_L1; +static constexpr auto toolTipPropertyC = "toolTip"_L1; +static constexpr auto checkablePropertyC = "checkable"_L1; +static constexpr auto objectNamePropertyC = "objectName"_L1; +static constexpr auto textPropertyC = "text"_L1; + +namespace qdesigner_internal { +//-------- ActionGroupDelegate +class ActionGroupDelegate: public QItemDelegate +{ +public: + ActionGroupDelegate(QObject *parent) + : QItemDelegate(parent) {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + + QItemDelegate::paint(painter, option, index); + } + + void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const override {} +}; + +//-------- ActionEditor +ObjectNamingMode ActionEditor::m_objectNamingMode = CamelCase; + +ActionEditor::ActionEditor(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) : + QDesignerActionEditorInterface(parent, flags), + m_core(core), + m_actionGroups(nullptr), + m_actionView(new ActionView), + m_actionNew(new QAction(tr("New..."), this)), + m_actionEdit(new QAction(tr("Edit..."), this)), + m_actionNavigateToSlot(new QAction(tr("Go to slot..."), this)), +#if QT_CONFIG(clipboard) + m_actionCopy(new QAction(tr("Copy"), this)), + m_actionCut(new QAction(tr("Cut"), this)), + m_actionPaste(new QAction(tr("Paste"), this)), +#endif + m_actionSelectAll(new QAction(tr("Select all"), this)), + m_actionDelete(new QAction(tr("Delete"), this)), + m_viewModeGroup(new QActionGroup(this)), + m_iconViewAction(nullptr), + m_listViewAction(nullptr), + m_filterWidget(nullptr) +{ + m_actionView->initialize(m_core); + m_actionView->setSelectionMode(QAbstractItemView::ExtendedSelection); + setWindowTitle(tr("Actions")); + + QVBoxLayout *l = new QVBoxLayout(this); + l->setContentsMargins(QMargins()); + l->setSpacing(0); + + QToolBar *toolbar = new QToolBar; + toolbar->setIconSize(QSize(22, 22)); + toolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + l->addWidget(toolbar); + // edit actions + QIcon documentNewIcon = createIconSet(QIcon::ThemeIcon::DocumentNew, + "filenew.png"_L1); + m_actionNew->setIcon(documentNewIcon); + m_actionNew->setEnabled(false); + connect(m_actionNew, &QAction::triggered, this, &ActionEditor::slotNewAction); + toolbar->addAction(m_actionNew); + + connect(m_actionSelectAll, &QAction::triggered, m_actionView, &ActionView::selectAll); + +#if QT_CONFIG(clipboard) + m_actionCut->setEnabled(false); + connect(m_actionCut, &QAction::triggered, this, &ActionEditor::slotCut); + QIcon editCutIcon = createIconSet(QIcon::ThemeIcon::EditCut, + "editcut.png"_L1); + m_actionCut->setIcon(editCutIcon); + + m_actionCopy->setEnabled(false); + connect(m_actionCopy, &QAction::triggered, this, &ActionEditor::slotCopy); + QIcon editCopyIcon = createIconSet(QIcon::ThemeIcon::EditCopy, + "editcopy.png"_L1); + m_actionCopy->setIcon(editCopyIcon); + toolbar->addAction(m_actionCopy); + + connect(m_actionPaste, &QAction::triggered, this, &ActionEditor::slotPaste); + QIcon editPasteIcon = createIconSet(QIcon::ThemeIcon::EditPaste, + "editpaste.png"_L1); + m_actionPaste->setIcon(editPasteIcon); + toolbar->addAction(m_actionPaste); +#endif + + m_actionEdit->setEnabled(false); + connect(m_actionEdit, &QAction::triggered, this, &ActionEditor::editCurrentAction); + + connect(m_actionNavigateToSlot, &QAction::triggered, this, &ActionEditor::navigateToSlotCurrentAction); + + QIcon editDeleteIcon = createIconSet(QIcon::ThemeIcon::EditDelete, + "editdelete.png"_L1); + m_actionDelete->setIcon(editDeleteIcon); + m_actionDelete->setEnabled(false); + connect(m_actionDelete, &QAction::triggered, this, &ActionEditor::slotDelete); + toolbar->addAction(m_actionDelete); + + // Toolbutton with menu containing action group for detailed/icon view. Steal the icons from the file dialog. + // + QMenu *configureMenu; + toolbar->addWidget(createConfigureMenuButton(tr("Configure Action Editor"), &configureMenu)); + + connect(m_viewModeGroup, &QActionGroup::triggered, this, &ActionEditor::slotViewMode); + m_iconViewAction = m_viewModeGroup->addAction(tr("Icon View")); + m_iconViewAction->setData(QVariant(ActionView::IconView)); + m_iconViewAction->setCheckable(true); + m_iconViewAction->setIcon(style()->standardIcon (QStyle::SP_FileDialogListView)); + configureMenu->addAction(m_iconViewAction); + + m_listViewAction = m_viewModeGroup->addAction(tr("Detailed View")); + m_listViewAction->setData(QVariant(ActionView::DetailedView)); + m_listViewAction->setCheckable(true); + m_listViewAction->setIcon(style()->standardIcon (QStyle::SP_FileDialogDetailedView)); + configureMenu->addAction(m_listViewAction); + // filter + m_filterWidget = new QWidget(toolbar); + QHBoxLayout *filterLayout = new QHBoxLayout(m_filterWidget); + filterLayout->setContentsMargins(0, 0, 0, 0); + QLineEdit *filterLineEdit = new QLineEdit(m_filterWidget); + connect(filterLineEdit, &QLineEdit::textChanged, this, &ActionEditor::setFilter); + filterLineEdit->setPlaceholderText(tr("Filter")); + filterLineEdit->setClearButtonEnabled(true); + filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored)); + filterLayout->addWidget(filterLineEdit); + m_filterWidget->setEnabled(false); + toolbar->addWidget(m_filterWidget); + + // main layout + QSplitter *splitter = new QSplitter(Qt::Horizontal); + splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + splitter->addWidget(m_actionView); + l->addWidget(splitter); + +#if 0 // ### implement me + m_actionGroups = new QListWidget(splitter); + splitter->addWidget(m_actionGroups); + m_actionGroups->setItemDelegate(new ActionGroupDelegate(m_actionGroups)); + m_actionGroups->setMovement(QListWidget::Static); + m_actionGroups->setResizeMode(QListWidget::Fixed); + m_actionGroups->setIconSize(QSize(48, 48)); + m_actionGroups->setFlow(QListWidget::TopToBottom); + m_actionGroups->setViewMode(QListWidget::IconMode); + m_actionGroups->setWrapping(false); +#endif + + connect(m_actionView, &ActionView::resourceImageDropped, + this, &ActionEditor::resourceImageDropped); + + connect(m_actionView, &ActionView::currentChanged,this, &ActionEditor::slotCurrentItemChanged); + // make it possible for vs integration to reimplement edit action dialog + connect(m_actionView, &ActionView::activated, this, &ActionEditor::itemActivated); + + connect(m_actionView, &ActionView::selectionChanged, + this, &ActionEditor::slotSelectionChanged); + + connect(m_actionView, &ActionView::contextMenuRequested, + this, &ActionEditor::slotContextMenuRequested); + + connect(this, &ActionEditor::itemActivated, this, &ActionEditor::editAction); + + restoreSettings(); + updateViewModeActions(); +} + +// Utility to create a configure button with menu for usage on toolbars +QToolButton *ActionEditor::createConfigureMenuButton(const QString &t, QMenu **ptrToMenu) +{ + QToolButton *configureButton = new QToolButton; + QAction *configureAction = new QAction(t, configureButton); + QIcon configureIcon = QIcon::fromTheme(QIcon::ThemeIcon::DocumentProperties, + createIconSet("configure.png"_L1)); + configureAction->setIcon(configureIcon); + QMenu *configureMenu = new QMenu(configureButton); + configureAction->setMenu(configureMenu); + configureButton->setDefaultAction(configureAction); + configureButton->setPopupMode(QToolButton::InstantPopup); + *ptrToMenu = configureMenu; + return configureButton; +} + +ActionEditor::~ActionEditor() +{ + saveSettings(); +} + +QAction *ActionEditor::actionNew() const +{ + return m_actionNew; +} + +QAction *ActionEditor::actionDelete() const +{ + return m_actionDelete; +} + +QDesignerFormWindowInterface *ActionEditor::formWindow() const +{ + return m_formWindow; +} + +void ActionEditor::setFormWindow(QDesignerFormWindowInterface *formWindow) +{ + if (formWindow != nullptr && formWindow->mainContainer() == nullptr) + formWindow = nullptr; + + // we do NOT rely on this function to update the action editor + if (m_formWindow == formWindow) + return; + + if (m_formWindow != nullptr) { + const ActionList actionList = m_formWindow->mainContainer()->findChildren(); + for (QAction *action : actionList) + disconnect(action, &QAction::changed, this, &ActionEditor::slotActionChanged); + } + + m_formWindow = formWindow; + + m_actionView->model()->clearActions(); + + m_actionEdit->setEnabled(false); +#if QT_CONFIG(clipboard) + m_actionCopy->setEnabled(false); + m_actionCut->setEnabled(false); +#endif + m_actionDelete->setEnabled(false); + + if (!formWindow || !formWindow->mainContainer()) { + m_actionNew->setEnabled(false); + m_filterWidget->setEnabled(false); + return; + } + + m_actionNew->setEnabled(true); + m_filterWidget->setEnabled(true); + + const ActionList actionList = formWindow->mainContainer()->findChildren(); + for (QAction *action : actionList) + if (!action->isSeparator() && core()->metaDataBase()->item(action) != nullptr) { + // Show unless it has a menu. However, listen for change on menu actions also as it might be removed + if (!action->menu()) + m_actionView->model()->addAction(action); + connect(action, &QAction::changed, this, &ActionEditor::slotActionChanged); + } + + setFilter(m_filter); +} + +void ActionEditor::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/) +{ + const bool hasSelection = !selected.indexes().isEmpty(); +#if QT_CONFIG(clipboard) + m_actionCopy->setEnabled(hasSelection); + m_actionCut->setEnabled(hasSelection); +#endif + m_actionDelete->setEnabled(hasSelection); +} + +void ActionEditor::slotCurrentItemChanged(QAction *action) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (m_withinSelectAction || fw == nullptr) + return; + + const bool hasCurrentAction = action != nullptr; + m_actionEdit->setEnabled(hasCurrentAction); + + if (!action) { + fw->clearSelection(); + return; + } + + QDesignerObjectInspector *oi = qobject_cast(core()->objectInspector()); + + // Check if we have at least one associated QWidget: + const auto associatedObjects = action->associatedObjects(); + auto it = std::find_if(associatedObjects.cbegin(), associatedObjects.cend(), + [](QObject *obj) { + return qobject_cast(obj) != nullptr; + }); + if (it == associatedObjects.cend()) { + // Special case: action not in object tree. Deselect all and set in property editor + fw->clearSelection(false); + if (oi) + oi->clearSelection(); + core()->propertyEditor()->setObject(action); + } else { + if (oi) + oi->selectObject(action); + } +} + +void ActionEditor::slotActionChanged() +{ + QAction *action = qobject_cast(sender()); + Q_ASSERT(action != nullptr); + + ActionModel *model = m_actionView->model(); + const int row = model->findAction(action); + if (row == -1) { + if (action->menu() == nullptr) // action got its menu deleted, create item + model->addAction(action); + } else if (action->menu() != nullptr) { // action got its menu created, remove item + model->removeRow(row); + } else { + // action text or icon changed, update item + model->update(row); + } +} + +QDesignerFormEditorInterface *ActionEditor::core() const +{ + return m_core; +} + +QString ActionEditor::filter() const +{ + return m_filter; +} + +void ActionEditor::setFilter(const QString &f) +{ + m_filter = f; + m_actionView->filter(m_filter); +} + +// Set changed state of icon property, reset when icon is cleared +static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet) +{ + sheet->setChanged(sheet->indexOf(iconPropertyC), !action->icon().isNull()); +} + +void ActionEditor::manageAction(QAction *action) +{ + action->setParent(formWindow()->mainContainer()); + core()->metaDataBase()->add(action); + + if (action->isSeparator() || action->menu() != nullptr) + return; + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), action); + sheet->setChanged(sheet->indexOf(objectNamePropertyC), true); + sheet->setChanged(sheet->indexOf(textPropertyC), true); + refreshIconPropertyChanged(action, sheet); + + m_actionView->setCurrentIndex(m_actionView->model()->addAction(action)); + connect(action, &QAction::changed, this, &ActionEditor::slotActionChanged); +} + +void ActionEditor::unmanageAction(QAction *action) +{ + core()->metaDataBase()->remove(action); + action->setParent(nullptr); + + disconnect(action, &QAction::changed, this, &ActionEditor::slotActionChanged); + + const int row = m_actionView->model()->findAction(action); + if (row != -1) + m_actionView->model()->remove(row); +} + +// Set an initial property and mark it as changed in the sheet +static void setInitialProperty(QDesignerPropertySheetExtension *sheet, const QString &name, const QVariant &value) +{ + const int index = sheet->indexOf(name); + Q_ASSERT(index != -1); + sheet->setProperty(index, value); + sheet->setChanged(index, true); +} + +void ActionEditor::slotNewAction() +{ + NewActionDialog dlg(this); + dlg.setWindowTitle(tr("New action")); + + if (dlg.exec() == QDialog::Accepted) { + const ActionData actionData = dlg.actionData(); + m_actionView->clearSelection(); + QAction *action = new QAction(formWindow()); + action->setObjectName(actionData.name); + formWindow()->ensureUniqueObjectName(action); + action->setText(actionData.text); + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), action); + if (!actionData.toolTip.isEmpty()) + setInitialProperty(sheet, toolTipPropertyC, actionData.toolTip); + + if (actionData.checkable) + setInitialProperty(sheet, checkablePropertyC, QVariant(true)); + + if (!actionData.keysequence.value().isEmpty()) + setInitialProperty(sheet, shortcutPropertyC, QVariant::fromValue(actionData.keysequence)); + + sheet->setProperty(sheet->indexOf(iconPropertyC), QVariant::fromValue(actionData.icon)); + + setInitialProperty(sheet, menuRolePropertyC, QVariant::fromValue(actionData.menuRole)); + + AddActionCommand *cmd = new AddActionCommand(formWindow()); + cmd->init(action); + formWindow()->commandHistory()->push(cmd); + } +} + +// return a FormWindow command to apply an icon or a reset command in case it +// is empty. + +static QDesignerFormWindowCommand *setIconPropertyCommand(const PropertySheetIconValue &newIcon, QAction *action, QDesignerFormWindowInterface *fw) +{ + const QString iconProperty = iconPropertyC; + if (newIcon.isEmpty()) { + ResetPropertyCommand *cmd = new ResetPropertyCommand(fw); + cmd->init(action, iconProperty); + return cmd; + } + SetPropertyCommand *cmd = new SetPropertyCommand(fw); + cmd->init(action, iconProperty, QVariant::fromValue(newIcon)); + return cmd; +} + +// return a FormWindow command to apply a QKeySequence or a reset command +// in case it is empty. + +static QDesignerFormWindowCommand *setKeySequencePropertyCommand(const PropertySheetKeySequenceValue &ks, QAction *action, QDesignerFormWindowInterface *fw) +{ + const QString shortcutProperty = shortcutPropertyC; + if (ks.value().isEmpty()) { + ResetPropertyCommand *cmd = new ResetPropertyCommand(fw); + cmd->init(action, shortcutProperty); + return cmd; + } + SetPropertyCommand *cmd = new SetPropertyCommand(fw); + cmd->init(action, shortcutProperty, QVariant::fromValue(ks)); + return cmd; +} + +// return a FormWindow command to apply a POD value or reset command in case +// it is equal to the default value. + +template +QDesignerFormWindowCommand *setPropertyCommand(const QString &name, T value, T defaultValue, + QObject *o, QDesignerFormWindowInterface *fw) +{ + if (value == defaultValue) { + ResetPropertyCommand *cmd = new ResetPropertyCommand(fw); + cmd->init(o, name); + return cmd; + } + SetPropertyCommand *cmd = new SetPropertyCommand(fw); + cmd->init(o, name, QVariant(value)); + return cmd; +} + +// Return the text value of a string property via PropertySheetStringValue +static inline QString textPropertyValue(const QDesignerPropertySheetExtension *sheet, const QString &name) +{ + const int index = sheet->indexOf(name); + Q_ASSERT(index != -1); + const PropertySheetStringValue ps = qvariant_cast(sheet->property(index)); + return ps.value(); +} + +void ActionEditor::editAction(QAction *action, int column) +{ + if (!action) + return; + + NewActionDialog dlg(this); + dlg.setWindowTitle(tr("Edit action")); + + ActionData oldActionData; + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), action); + oldActionData.name = action->objectName(); + oldActionData.text = action->text(); + oldActionData.toolTip = textPropertyValue(sheet, toolTipPropertyC); + oldActionData.icon = qvariant_cast(sheet->property(sheet->indexOf(iconPropertyC))); + oldActionData.keysequence = ActionModel::actionShortCut(sheet); + oldActionData.checkable = action->isCheckable(); + oldActionData.menuRole.value = action->menuRole(); + dlg.setActionData(oldActionData); + + switch (column) { + case qdesigner_internal::ActionModel::NameColumn: + dlg.focusName(); + break; + case qdesigner_internal::ActionModel::TextColumn: + dlg.focusText(); + break; + case qdesigner_internal::ActionModel::ShortCutColumn: + dlg.focusShortcut(); + break; + case qdesigner_internal::ActionModel::CheckedColumn: + dlg.focusCheckable(); + break; + case qdesigner_internal::ActionModel::ToolTipColumn: + dlg.focusTooltip(); + break; + case qdesigner_internal::ActionModel::MenuRoleColumn: + dlg.focusMenuRole(); + break; + } + + if (!dlg.exec()) + return; + + // figure out changes and whether to start a macro + const ActionData newActionData = dlg.actionData(); + const unsigned changeMask = newActionData.compare(oldActionData); + if (changeMask == 0u) + return; + + const bool severalChanges = (changeMask != ActionData::TextChanged) && (changeMask != ActionData::NameChanged) + && (changeMask != ActionData::ToolTipChanged) && (changeMask != ActionData::IconChanged) + && (changeMask != ActionData::CheckableChanged) && (changeMask != ActionData::KeysequenceChanged) + && (changeMask != ActionData::MenuRoleChanged); + + QDesignerFormWindowInterface *fw = formWindow(); + QUndoStack *undoStack = fw->commandHistory(); + if (severalChanges) + fw->beginCommand(u"Edit action"_s); + + if (changeMask & ActionData::NameChanged) + undoStack->push(createTextPropertyCommand(objectNamePropertyC, newActionData.name, action, fw)); + + if (changeMask & ActionData::TextChanged) + undoStack->push(createTextPropertyCommand(textPropertyC, newActionData.text, action, fw)); + + if (changeMask & ActionData::ToolTipChanged) + undoStack->push(createTextPropertyCommand(toolTipPropertyC, newActionData.toolTip, action, fw)); + + if (changeMask & ActionData::IconChanged) + undoStack->push(setIconPropertyCommand(newActionData.icon, action, fw)); + + if (changeMask & ActionData::CheckableChanged) + undoStack->push(setPropertyCommand(checkablePropertyC, newActionData.checkable, false, action, fw)); + + if (changeMask & ActionData::KeysequenceChanged) + undoStack->push(setKeySequencePropertyCommand(newActionData.keysequence, action, fw)); + + if (changeMask & ActionData::MenuRoleChanged) + undoStack->push(setPropertyCommand(menuRolePropertyC, static_cast(newActionData.menuRole.value), QAction::NoRole, action, fw)); + + if (severalChanges) + fw->endCommand(); +} + +void ActionEditor::editCurrentAction() +{ + if (QAction *a = m_actionView->currentAction()) + editAction(a); +} + +void ActionEditor::navigateToSlotCurrentAction() +{ + if (QAction *a = m_actionView->currentAction()) + QDesignerTaskMenu::navigateToSlot(m_core, a, u"triggered()"_s); +} + +void ActionEditor::deleteActions(QDesignerFormWindowInterface *fw, const ActionList &actions) +{ + // We need a macro even in the case of single action because the commands might cause the + // scheduling of other commands (signal slots connections) + const QString description = actions.size() == 1 + ? tr("Remove action '%1'").arg(actions.constFirst()->objectName()) + : tr("Remove actions"); + fw->beginCommand(description); + for (QAction *action : actions) { + RemoveActionCommand *cmd = new RemoveActionCommand(fw); + cmd->init(action); + fw->commandHistory()->push(cmd); + } + fw->endCommand(); +} + +#if QT_CONFIG(clipboard) +void ActionEditor::copyActions(QDesignerFormWindowInterface *fwi, const ActionList &actions) +{ + FormWindowBase *fw = qobject_cast(fwi); + if (!fw ) + return; + + FormBuilderClipboard clipboard; + clipboard.m_actions = actions; + + if (clipboard.empty()) + return; + + QEditorFormBuilder *formBuilder = fw->createFormBuilder(); + Q_ASSERT(formBuilder); + + QBuffer buffer; + if (buffer.open(QIODevice::WriteOnly)) + if (formBuilder->copy(&buffer, clipboard)) + qApp->clipboard()->setText(QString::fromUtf8(buffer.buffer()), QClipboard::Clipboard); + delete formBuilder; +} +#endif + +void ActionEditor::slotDelete() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + + const ActionView::ActionList selection = m_actionView->selectedActions(); + if (selection.isEmpty()) + return; + + deleteActions(fw, selection); +} + +// UnderScore: "Open file" -> actionOpen_file +static QString underscore(QString text) +{ + static const QRegularExpression nonAsciiPattern(u"[^a-zA-Z_0-9]"_s); + Q_ASSERT(nonAsciiPattern.isValid()); + text.replace(nonAsciiPattern, "_"_L1); + static const QRegularExpression multipleSpacePattern(u"__*"_s); + Q_ASSERT(multipleSpacePattern.isValid()); + text.replace(multipleSpacePattern, "_"_L1); + if (text.endsWith(u'_')) + text.chop(1); + return text; +} + +// CamelCase: "Open file" -> actionOpenFile, ignoring non-ASCII letters. + +enum CharacterCategory { OtherCharacter, DigitOrAsciiLetter, NonAsciiLetter }; + +static inline CharacterCategory category(QChar c) +{ + if (c.isDigit()) + return DigitOrAsciiLetter; + if (c.isLetter()) { + const ushort uc = c.unicode(); + return (uc >= 'a' && uc <= 'z') || (uc >= 'A' && uc <= 'Z') + ? DigitOrAsciiLetter : NonAsciiLetter; + } + return OtherCharacter; +} + +static QString camelCase(const QString &text) +{ + QString result; + result.reserve(text.size()); + bool lastCharAccepted = false; + for (QChar c : text) { + const CharacterCategory cat = category(c); + if (cat != NonAsciiLetter) { + const bool acceptable = cat == DigitOrAsciiLetter; + if (acceptable) + result.append(lastCharAccepted ? c : c.toUpper()); // New word starts + lastCharAccepted = acceptable; + } + } + return result; +} + +QString ActionEditor::actionTextToName(const QString &text, const QString &prefix) +{ + QString name = text; + if (name.isEmpty()) + return QString(); + return prefix + (m_objectNamingMode == CamelCase ? camelCase(text) : underscore(text)); + +} + +void ActionEditor::resourceImageDropped(const QString &path, QAction *action) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), action); + const PropertySheetIconValue oldIcon = + qvariant_cast(sheet->property(sheet->indexOf(iconPropertyC))); + PropertySheetIconValue newIcon; + newIcon.setPixmap(QIcon::Normal, QIcon::Off, PropertySheetPixmapValue(path)); + if (newIcon.paths().isEmpty() || newIcon.paths() == oldIcon.paths()) + return; + + fw->commandHistory()->push(setIconPropertyCommand(newIcon , action, fw)); +} + +void ActionEditor::mainContainerChanged() +{ + // Invalidate references to objects kept in model + if (sender() == formWindow()) + setFormWindow(nullptr); +} + +void ActionEditor::clearSelection() +{ + // For use by the menu editor; block the syncing of the object inspector + // in slotCurrentItemChanged() since the menu editor updates it itself. + m_withinSelectAction = true; + m_actionView->clearSelection(); + m_withinSelectAction = false; +} + +void ActionEditor::selectAction(QAction *a) +{ + // For use by the menu editor; block the syncing of the object inspector + // in slotCurrentItemChanged() since the menu editor updates it itself. + m_withinSelectAction = true; + m_actionView->selectAction(a); + m_withinSelectAction = false; +} + +void ActionEditor::slotViewMode(QAction *a) +{ + m_actionView->setViewMode(a->data().toInt()); + updateViewModeActions(); +} + +void ActionEditor::slotSelectAssociatedWidget(QWidget *w) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw ) + return; + + QDesignerObjectInspector *oi = qobject_cast(core()->objectInspector()); + if (!oi) + return; + + fw->clearSelection(); // Actually, there are no widgets selected due to focus in event handling. Just to be sure. + oi->selectObject(w); +} + +void ActionEditor::restoreSettings() +{ + QDesignerSettingsInterface *settings = m_core->settingsManager(); + m_actionView->setViewMode(settings->value(actionEditorViewModeKey, 0).toInt()); + updateViewModeActions(); +} + +void ActionEditor::saveSettings() +{ + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->setValue(actionEditorViewModeKey, m_actionView->viewMode()); +} + +void ActionEditor::updateViewModeActions() +{ + switch (m_actionView->viewMode()) { + case ActionView::IconView: + m_iconViewAction->setChecked(true); + break; + case ActionView::DetailedView: + m_listViewAction->setChecked(true); + break; + } +} + +#if QT_CONFIG(clipboard) +void ActionEditor::slotCopy() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw ) + return; + + const ActionView::ActionList selection = m_actionView->selectedActions(); + if (selection.isEmpty()) + return; + + copyActions(fw, selection); +} + +void ActionEditor::slotCut() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw ) + return; + + const ActionView::ActionList selection = m_actionView->selectedActions(); + if (selection.isEmpty()) + return; + + copyActions(fw, selection); + deleteActions(fw, selection); +} + +void ActionEditor::slotPaste() +{ + FormWindowBase *fw = qobject_cast(formWindow()); + if (!fw) + return; + m_actionView->clearSelection(); + fw->paste(FormWindowBase::PasteActionsOnly); +} +#endif + +void ActionEditor::slotContextMenuRequested(QContextMenuEvent *e, QAction *item) +{ + QMenu menu(this); + menu.addAction(m_actionNew); + menu.addSeparator(); + menu.addAction(m_actionEdit); + if (QDesignerTaskMenu::isSlotNavigationEnabled(m_core)) + menu.addAction(m_actionNavigateToSlot); + + // Associated Widgets + if (QAction *action = m_actionView->currentAction()) { + const QWidgetList associatedWidgets = ActionModel::associatedWidgets(action); + if (!associatedWidgets.isEmpty()) { + QMenu *associatedWidgetsSubMenu = menu.addMenu(tr("Used In")); + for (QWidget *w : associatedWidgets) { + associatedWidgetsSubMenu->addAction(w->objectName(), + this, [this, w] { this->slotSelectAssociatedWidget(w); }); + } + } + } + + menu.addSeparator(); +#if QT_CONFIG(clipboard) + menu.addAction(m_actionCut); + menu.addAction(m_actionCopy); + menu.addAction(m_actionPaste); +#endif + menu.addAction(m_actionSelectAll); + menu.addAction(m_actionDelete); + menu.addSeparator(); + menu.addAction(m_iconViewAction); + menu.addAction(m_listViewAction); + + emit contextMenuRequested(&menu, item); + + menu.exec(e->globalPos()); + e->accept(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + diff --git a/src/designer/src/lib/shared/actioneditor_p.h b/src/designer/src/lib/shared/actioneditor_p.h new file mode 100644 index 0000000..471d12d --- /dev/null +++ b/src/designer/src/lib/shared/actioneditor_p.h @@ -0,0 +1,145 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ACTIONEDITOR_H +#define ACTIONEDITOR_H + +#include "shared_global_p.h" +#include "shared_enums_p.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerPropertyEditorInterface; +class QDesignerSettingsInterface; +class QMenu; +class QActionGroup; +class QItemSelection; +class QListWidget; +class QPushButton; +class QLineEdit; +class QToolButton; + +namespace qdesigner_internal { + +class ActionView; +class ResourceMimeData; + +class QDESIGNER_SHARED_EXPORT ActionEditor: public QDesignerActionEditorInterface +{ + Q_OBJECT +public: + explicit ActionEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr, + Qt::WindowFlags flags = {}); + ~ActionEditor() override; + + QDesignerFormWindowInterface *formWindow() const; + void setFormWindow(QDesignerFormWindowInterface *formWindow) override; + + QDesignerFormEditorInterface *core() const override; + + QAction *actionNew() const; + QAction *actionDelete() const; + + QString filter() const; + + void manageAction(QAction *action) override; + void unmanageAction(QAction *action) override; + + static ObjectNamingMode objectNamingMode() { return m_objectNamingMode; } + static void setObjectNamingMode(ObjectNamingMode n) { m_objectNamingMode = n; } + + static QString actionTextToName(const QString &text, + const QString &prefix = QLatin1StringView("action")); + + // Utility to create a configure button with menu for usage on toolbars + static QToolButton *createConfigureMenuButton(const QString &t, QMenu **ptrToMenu); + +public slots: + void setFilter(const QString &filter); + void mainContainerChanged(); + void clearSelection(); + void selectAction(QAction *a); // For use by the menu editor + +private slots: + void slotCurrentItemChanged(QAction *item); + void slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void editAction(QAction *item, int column = -1); + void editCurrentAction(); + void navigateToSlotCurrentAction(); + void slotActionChanged(); + void slotNewAction(); + void slotDelete(); + void resourceImageDropped(const QString &path, QAction *action); + void slotContextMenuRequested(QContextMenuEvent *, QAction *); + void slotViewMode(QAction *a); + void slotSelectAssociatedWidget(QWidget *w); +#if QT_CONFIG(clipboard) + void slotCopy(); + void slotCut(); + void slotPaste(); +#endif + +signals: + void itemActivated(QAction *item, int column); + // Context menu for item or global menu if item == 0. + void contextMenuRequested(QMenu *menu, QAction *item); + +private: + using ActionList = QList; + void deleteActions(QDesignerFormWindowInterface *formWindow, const ActionList &); +#if QT_CONFIG(clipboard) + void copyActions(QDesignerFormWindowInterface *formWindow, const ActionList &); +#endif + + void restoreSettings(); + void saveSettings(); + + void updateViewModeActions(); + + static ObjectNamingMode m_objectNamingMode; + + QDesignerFormEditorInterface *m_core; + QPointer m_formWindow; + QListWidget *m_actionGroups; + + ActionView *m_actionView; + + QAction *m_actionNew; + QAction *m_actionEdit; + QAction *m_actionNavigateToSlot; +#if QT_CONFIG(clipboard) + QAction *m_actionCopy; + QAction *m_actionCut; + QAction *m_actionPaste; +#endif + QAction *m_actionSelectAll; + QAction *m_actionDelete; + + QActionGroup *m_viewModeGroup; + QAction *m_iconViewAction; + QAction *m_listViewAction; + + QString m_filter; + QWidget *m_filterWidget; + bool m_withinSelectAction = false; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ACTIONEDITOR_H diff --git a/src/designer/src/lib/shared/actionprovider_p.h b/src/designer/src/lib/shared/actionprovider_p.h new file mode 100644 index 0000000..16f987d --- /dev/null +++ b/src/designer/src/lib/shared/actionprovider_p.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ACTIONPROVIDER_H +#define ACTIONPROVIDER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; + +class QDesignerActionProviderExtension +{ +public: + virtual ~QDesignerActionProviderExtension() = default; + + virtual QRect actionGeometry(QAction *action) const = 0; + virtual QAction *actionAt(const QPoint &pos) const = 0; + + virtual void adjustIndicator(const QPoint &pos) = 0; +}; + +// Find action at the given position for a widget that has actionGeometry() (QToolBar, +// QMenuBar, QMenu). They usually have actionAt(), but that fails since Designer usually sets +// WA_TransparentForMouseEvents on the widgets. +template + int actionIndexAt(const Widget *w, const QPoint &pos, Qt::Orientation orientation) +{ + const auto actions = w->actions(); + if (actions.isEmpty()) + return -1; + // actionGeometry() can be wrong sometimes; it returns a geometry that + // stretches to the end of the toolbar/menu bar. So, check from the beginning + // in the case of a horizontal right-to-left orientation. + const bool checkTopRight = orientation == Qt::Horizontal && w->layoutDirection() == Qt::RightToLeft; + const QPoint topRight = QPoint(w->rect().width(), 0); + for (qsizetype index = 0, actionCount = actions.size(); index < actionCount; ++index) { + QRect g = w->actionGeometry(actions.at(index)); + if (checkTopRight) + g.setTopRight(topRight); + else + g.setTopLeft(QPoint(0, 0)); + + if (g.contains(pos)) + return int(index); + } + return -1; +} + +Q_DECLARE_EXTENSION_INTERFACE(QDesignerActionProviderExtension, "org.qt-project.Qt.Designer.ActionProvider") + +QT_END_NAMESPACE + +#endif // ACTIONPROVIDER_H diff --git a/src/designer/src/lib/shared/actionrepository.cpp b/src/designer/src/lib/shared/actionrepository.cpp new file mode 100644 index 0000000..59d8fc6 --- /dev/null +++ b/src/designer/src/lib/shared/actionrepository.cpp @@ -0,0 +1,655 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "actionrepository_p.h" +#include "qtresourceview_p.h" +#include "iconloader_p.h" +#include "qdesigner_utils_p.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { listModeIconSize = 16, iconModeIconSize = 24 }; +} + +static constexpr auto actionMimeType = "action-repository/actions"_L1; +static constexpr auto plainTextMimeType = "text/plain"_L1; + +static inline QAction *actionOfItem(const QStandardItem* item) +{ + return qvariant_cast(item->data(qdesigner_internal::ActionModel::ActionRole)); +} + +namespace qdesigner_internal { + +// ----------- ActionModel +ActionModel::ActionModel(QWidget *parent ) : + QStandardItemModel(parent), + m_emptyIcon(emptyIcon()) +{ + QStringList headers; + headers += tr("Name"); + headers += tr("Used"); + headers += tr("Text"); + headers += tr("Shortcut"); + headers += tr("Checkable"); + headers += tr("ToolTip"); + headers += tr("MenuRole"); + Q_ASSERT(NumColumns == headers.size()); + setHorizontalHeaderLabels(headers); +} + +void ActionModel::clearActions() +{ + removeRows(0, rowCount()); +} + +int ActionModel::findAction(QAction *action) const +{ + const int rows = rowCount(); + for (int i = 0; i < rows; i++) + if (action == actionOfItem(item(i))) + return i; + return -1; +} + +void ActionModel::update(int row) +{ + Q_ASSERT(m_core); + // need to create the row list ... grrr.. + if (row >= rowCount()) + return; + + QStandardItemList list; + for (int i = 0; i < NumColumns; i++) + list += item(row, i); + + setItems(m_core, actionOfItem(list.constFirst()), m_emptyIcon, list); +} + +void ActionModel::remove(int row) +{ + qDeleteAll(takeRow(row)); +} + +QModelIndex ActionModel::addAction(QAction *action) +{ + Q_ASSERT(m_core); + QStandardItemList items; + const Qt::ItemFlags flags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled; + + QVariant itemData; + itemData.setValue(action); + + for (int i = 0; i < NumColumns; i++) { + QStandardItem *item = new QStandardItem; + item->setData(itemData, ActionRole); + item->setFlags(flags); + items.push_back(item); + } + setItems(m_core, action, m_emptyIcon, items); + appendRow(items); + return indexFromItem(items.constFirst()); +} + +// Find the associated menus and toolbars, ignore toolbuttons +QWidgetList ActionModel::associatedWidgets(const QAction *action) +{ + const QObjectList rc = action->associatedObjects(); + QWidgetList result; + result.reserve(rc.size()); + for (QObject *obj : rc) { + if (QWidget *w = qobject_cast(obj)) { + if (qobject_cast(w) || qobject_cast(w)) + result.push_back(w); + } + } + return result; +} + +// shortcut is a fake property, need to retrieve it via property sheet. +PropertySheetKeySequenceValue ActionModel::actionShortCut(QDesignerFormEditorInterface *core, QAction *action) +{ + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), action); + if (!sheet) + return PropertySheetKeySequenceValue(); + return actionShortCut(sheet); +} + +PropertySheetKeySequenceValue ActionModel::actionShortCut(const QDesignerPropertySheetExtension *sheet) +{ + const int index = sheet->indexOf(u"shortcut"_s); + if (index == -1) + return PropertySheetKeySequenceValue(); + return qvariant_cast(sheet->property(index)); +} + +void ActionModel::setItems(QDesignerFormEditorInterface *core, QAction *action, + const QIcon &defaultIcon, + QStandardItemList &sl) +{ + + // Tooltip, mostly for icon view mode + QString firstTooltip = action->objectName(); + const QString text = action->text(); + if (!text.isEmpty()) + firstTooltip += u'\n' + text; + + Q_ASSERT(sl.size() == NumColumns); + + QStandardItem *item = sl[NameColumn]; + item->setText(action->objectName()); + QIcon icon = action->icon(); + if (icon.isNull()) + icon = defaultIcon; + item->setIcon(icon); + item->setToolTip(firstTooltip); + item->setWhatsThis(firstTooltip); + // Used + const QWidgetList associatedDesignerWidgets = associatedWidgets(action); + const bool used = !associatedDesignerWidgets.isEmpty(); + item = sl[UsedColumn]; + item->setCheckState(used ? Qt::Checked : Qt::Unchecked); + if (used) { + QString usedToolTip; + const auto separator = ", "_L1; + const int count = associatedDesignerWidgets.size(); + for (int i = 0; i < count; i++) { + if (i) + usedToolTip += separator; + usedToolTip += associatedDesignerWidgets.at(i)->objectName(); + } + item->setToolTip(usedToolTip); + } else { + item->setToolTip(QString()); + } + // text + item = sl[TextColumn]; + item->setText(action->text()); + item->setToolTip(action->text()); + // shortcut + const QString shortcut = actionShortCut(core, action).value().toString(QKeySequence::NativeText); + item = sl[ShortCutColumn]; + item->setText(shortcut); + item->setToolTip(shortcut); + // checkable + sl[CheckedColumn]->setCheckState(action->isCheckable() ? Qt::Checked : Qt::Unchecked); + // ToolTip. This might be multi-line, rich text + QString toolTip = action->toolTip(); + item = sl[ToolTipColumn]; + item->setToolTip(toolTip); + item->setText(toolTip.replace(u'\n', u' ')); + // menuRole + const auto menuRole = action->menuRole(); + item = sl[MenuRoleColumn]; + item->setText(QLatin1StringView(QMetaEnum::fromType().valueToKey(menuRole))); +} + +QMimeData *ActionModel::mimeData(const QModelIndexList &indexes ) const +{ + ActionRepositoryMimeData::ActionList actionList; + + QSet actions; + for (const QModelIndex &index : indexes) + if (QStandardItem *item = itemFromIndex(index)) + if (QAction *action = actionOfItem(item)) + actions.insert(action); + return new ActionRepositoryMimeData(actions.values(), Qt::CopyAction); +} + +// Resource images are plain text. The drag needs to be restricted, however. +QStringList ActionModel::mimeTypes() const +{ + return QStringList(plainTextMimeType); +} + +QString ActionModel::actionName(int row) const +{ + return item(row, NameColumn)->text(); +} + +bool ActionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &) +{ + if (action != Qt::CopyAction) + return false; + + QStandardItem *droppedItem = item(row, column); + if (!droppedItem) + return false; + + + QtResourceView::ResourceType type; + QString path; + if (!QtResourceView::decodeMimeData(data, &type, &path) || type != QtResourceView::ResourceImage) + return false; + + emit resourceImageDropped(path, actionOfItem(droppedItem)); + return true; +} + +QAction *ActionModel::actionAt(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + QStandardItem *i = itemFromIndex(index); + if (!i) + return nullptr; + return actionOfItem(i); +} + +QModelIndex ActionModel::indexOf(QAction *a) const +{ + for (int r = rowCount() - 1; r >= 0; --r) { + QStandardItem *stdItem = item(r, 0); + if (actionOfItem(stdItem) == a) + return indexFromItem(stdItem); + } + return {}; +} + +// helpers + +static bool handleImageDragEnterMoveEvent(QDropEvent *event) +{ + QtResourceView::ResourceType type; + const bool rc = QtResourceView::decodeMimeData(event->mimeData(), &type) && type == QtResourceView::ResourceImage; + if (rc) + event->acceptProposedAction(); + else + event->ignore(); + return rc; +} + +static void handleImageDropEvent(const QAbstractItemView *iv, QDropEvent *event, ActionModel *am) +{ + const QModelIndex index = iv->indexAt(event->position().toPoint()); + if (!index.isValid()) { + event->ignore(); + return; + } + + if (!handleImageDragEnterMoveEvent(event)) + return; + + am->dropMimeData(event->mimeData(), event->proposedAction(), index.row(), 0, iv->rootIndex()); +} + +// Basically mimic QAbstractItemView's startDrag routine, except that +// another pixmap is used, we don't want the whole row. + +void startActionDrag(QWidget *dragParent, ActionModel *model, const QModelIndexList &indexes, Qt::DropActions supportedActions) +{ + if (indexes.isEmpty()) + return; + + QDrag *drag = new QDrag(dragParent); + QMimeData *data = model->mimeData(indexes); + drag->setMimeData(data); + if (ActionRepositoryMimeData *actionMimeData = qobject_cast(data)) + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(actionMimeData->actionList().constFirst())); + + drag->exec(supportedActions); +} + +// ---------------- ActionTreeView: +ActionTreeView::ActionTreeView(ActionModel *model, QWidget *parent) : + QTreeView(parent), + m_model(model) +{ + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setDragDropMode(DragDrop); + setModel(model); + setRootIsDecorated(false); + setTextElideMode(Qt::ElideMiddle); + + setModel(model); + connect(this, &QTreeView::activated, this, &ActionTreeView::slotActivated); + connect(header(), &QHeaderView::sectionDoubleClicked, + this, &QTreeView::resizeColumnToContents); + + setIconSize(QSize(listModeIconSize, listModeIconSize)); + +} + +QAction *ActionTreeView::currentAction() const +{ + return m_model->actionAt(currentIndex()); +} + +void ActionTreeView::filter(const QString &text) +{ + const int rowCount = m_model->rowCount(); + const bool empty = text.isEmpty(); + const QModelIndex parent = rootIndex(); + for (int i = 0; i < rowCount; i++) + setRowHidden(i, parent, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive)); +} + +void ActionTreeView::dragEnterEvent(QDragEnterEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionTreeView::dragMoveEvent(QDragMoveEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionTreeView::dropEvent(QDropEvent *event) +{ + handleImageDropEvent(this, event, m_model); +} + +void ActionTreeView::focusInEvent(QFocusEvent *event) +{ + QTreeView::focusInEvent(event); + // Make property editor display current action + if (QAction *a = currentAction()) + emit currentActionChanged(a); +} + +void ActionTreeView::contextMenuEvent(QContextMenuEvent *event) +{ + emit actionContextMenuRequested(event, m_model->actionAt(indexAt(event->pos()))); +} + +void ActionTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + emit currentActionChanged(m_model->actionAt(current)); + QTreeView::currentChanged(current, previous); +} + +void ActionTreeView::slotActivated(const QModelIndex &index) +{ + emit actionActivated(m_model->actionAt(index), index.column()); +} + +void ActionTreeView::startDrag(Qt::DropActions supportedActions) +{ + startActionDrag(this, m_model, selectedIndexes(), supportedActions); +} + +// ---------------- ActionListView: +ActionListView::ActionListView(ActionModel *model, QWidget *parent) : + QListView(parent), + m_model(model) +{ + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setDragDropMode(DragDrop); + setModel(model); + setTextElideMode(Qt::ElideMiddle); + connect(this, &QListView::activated, this, &ActionListView::slotActivated); + + // We actually want 'Static' as the user should be able to + // drag away actions only (not to rearrange icons). + // We emulate that by not accepting our own + // drag data. 'Static' causes the list view to disable drag and drop + // on the viewport. + setMovement(Snap); + setViewMode(IconMode); + setIconSize(QSize(iconModeIconSize, iconModeIconSize)); + setGridSize(QSize(4 * iconModeIconSize, 2 * iconModeIconSize)); + setSpacing(iconModeIconSize / 3); +} + +QAction *ActionListView::currentAction() const +{ + return m_model->actionAt(currentIndex()); +} + +void ActionListView::filter(const QString &text) +{ + const int rowCount = m_model->rowCount(); + const bool empty = text.isEmpty(); + for (int i = 0; i < rowCount; i++) + setRowHidden(i, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive)); +} + +void ActionListView::dragEnterEvent(QDragEnterEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionListView::dragMoveEvent(QDragMoveEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionListView::dropEvent(QDropEvent *event) +{ + handleImageDropEvent(this, event, m_model); +} + +void ActionListView::focusInEvent(QFocusEvent *event) +{ + QListView::focusInEvent(event); + // Make property editor display current action + if (QAction *a = currentAction()) + emit currentActionChanged(a); +} + +void ActionListView::contextMenuEvent(QContextMenuEvent *event) +{ + emit actionContextMenuRequested(event, m_model->actionAt(indexAt(event->pos()))); +} + +void ActionListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + emit currentActionChanged(m_model->actionAt(current)); + QListView::currentChanged(current, previous); +} + +void ActionListView::slotActivated(const QModelIndex &index) +{ + emit actionActivated(m_model->actionAt(index)); +} + +void ActionListView::startDrag(Qt::DropActions supportedActions) +{ + startActionDrag(this, m_model, selectedIndexes(), supportedActions); +} + +// ActionView +ActionView::ActionView(QWidget *parent) : + QStackedWidget(parent), + m_model(new ActionModel(this)), + m_actionTreeView(new ActionTreeView(m_model)), + m_actionListView(new ActionListView(m_model)) +{ + addWidget(m_actionListView); + addWidget(m_actionTreeView); + // Wire signals + connect(m_actionTreeView, &ActionTreeView::actionContextMenuRequested, + this, &ActionView::contextMenuRequested); + connect(m_actionListView, &ActionListView::actionContextMenuRequested, + this, &ActionView::contextMenuRequested); + + // make it possible for vs integration to reimplement edit action dialog + // [which it shouldn't do actually] + connect(m_actionListView, &ActionListView::actionActivated, + this, [this](QAction *a) { this->activated(a, -1); }); + connect(m_actionTreeView, &ActionTreeView::actionActivated, this, &ActionView::activated); + + connect(m_actionListView, &ActionListView::currentActionChanged, + this, &ActionView::slotCurrentChanged); + connect(m_actionTreeView, &ActionTreeView::currentActionChanged, + this, &ActionView::slotCurrentChanged); + + connect(m_model, &ActionModel::resourceImageDropped, + this, &ActionView::resourceImageDropped); + + // sync selection models + QItemSelectionModel *selectionModel = m_actionTreeView->selectionModel(); + m_actionListView->setSelectionModel(selectionModel); + connect(selectionModel, &QItemSelectionModel::selectionChanged, + this, &ActionView::selectionChanged); +} + +int ActionView::viewMode() const +{ + return currentWidget() == m_actionListView ? IconView : DetailedView; +} + +void ActionView::setViewMode(int lm) +{ + if (viewMode() == lm) + return; + + switch (lm) { + case IconView: + setCurrentWidget(m_actionListView); + break; + case DetailedView: + setCurrentWidget(m_actionTreeView); + break; + default: + break; + } +} + +void ActionView::slotCurrentChanged(QAction *action) +{ + // emit only for currently visible + if (sender() == currentWidget()) + emit currentChanged(action); +} + +void ActionView::filter(const QString &text) +{ + m_actionTreeView->filter(text); + m_actionListView->filter(text); +} + +void ActionView::selectAll() +{ + m_actionTreeView->selectAll(); +} + +void ActionView::clearSelection() +{ + m_actionTreeView->selectionModel()->clearSelection(); +} + +void ActionView::selectAction(QAction *a) +{ + const QModelIndex index = m_model->indexOf(a); + if (index.isValid()) + setCurrentIndex(index); +} + +void ActionView::setCurrentIndex(const QModelIndex &index) +{ + m_actionTreeView->setCurrentIndex(index); +} + +QAction *ActionView::currentAction() const +{ + return m_actionListView->currentAction(); +} + +void ActionView::setSelectionMode(QAbstractItemView::SelectionMode sm) +{ + m_actionTreeView->setSelectionMode(sm); + m_actionListView->setSelectionMode(sm); +} + +QAbstractItemView::SelectionMode ActionView::selectionMode() const +{ + return m_actionListView->selectionMode(); +} + +QItemSelection ActionView::selection() const +{ + return m_actionListView->selectionModel()->selection(); +} + +ActionView::ActionList ActionView::selectedActions() const +{ + ActionList rc; + const QModelIndexList &indexes = selection().indexes(); + for (const QModelIndex &index : indexes) { + if (index.column() == 0) + rc += actionOfItem(m_model->itemFromIndex(index)); + } + return rc; +} +// ---------- ActionRepositoryMimeData +ActionRepositoryMimeData::ActionRepositoryMimeData(QAction *a, Qt::DropAction dropAction) : + m_dropAction(dropAction) +{ + m_actionList += a; +} + +ActionRepositoryMimeData::ActionRepositoryMimeData(const ActionList &al, Qt::DropAction dropAction) : + m_dropAction(dropAction), + m_actionList(al) +{ +} + +QStringList ActionRepositoryMimeData::formats() const +{ + return QStringList(actionMimeType); +} + +QPixmap ActionRepositoryMimeData::actionDragPixmap(const QAction *action) +{ + + // Try to find a suitable pixmap. Grab either widget or icon. + const QIcon icon = action->icon(); + if (!icon.isNull()) + return icon.pixmap(QSize(22, 22)); + + const QObjectList associatedObjects = action->associatedObjects(); + for (QObject *o : associatedObjects) { + if (QToolButton *tb = qobject_cast(o)) + return tb->grab(QRect(0, 0, -1, -1)); + } + + // Create a QToolButton + QToolButton *tb = new QToolButton; + tb->setText(action->text()); + tb->setToolButtonStyle(Qt::ToolButtonTextOnly); + tb->adjustSize(); + const QPixmap rc = tb->grab(QRect(0, 0, -1, -1)); + tb->deleteLater(); + return rc; +} + +void ActionRepositoryMimeData::accept(QDragMoveEvent *event) const +{ + if (event->proposedAction() == m_dropAction) { + event->acceptProposedAction(); + } else { + event->setDropAction(m_dropAction); + event->accept(); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/actionrepository_p.h b/src/designer/src/lib/shared/actionrepository_p.h new file mode 100644 index 0000000..bc31ccd --- /dev/null +++ b/src/designer/src/lib/shared/actionrepository_p.h @@ -0,0 +1,233 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ACTIONREPOSITORY_H +#define ACTIONREPOSITORY_H + +#include "shared_global_p.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPixmap; + +class QDesignerFormEditorInterface; +class QDesignerPropertySheetExtension; + +namespace qdesigner_internal { + +class PropertySheetKeySequenceValue; + +// Shared model of actions, to be used for several views (detailed/icon view). +class QDESIGNER_SHARED_EXPORT ActionModel: public QStandardItemModel +{ + Q_OBJECT +public: + enum Columns { NameColumn, UsedColumn, TextColumn, ShortCutColumn, CheckedColumn, ToolTipColumn, MenuRoleColumn, NumColumns }; + enum { ActionRole = Qt::UserRole + 1000 }; + + explicit ActionModel(QWidget *parent = nullptr); + void initialize(QDesignerFormEditorInterface *core) { m_core = core; } + + void clearActions(); + QModelIndex addAction(QAction *a); + // remove row + void remove(int row); + // update the row from the underlying action + void update(int row); + + // return row of action or -1. + int findAction(QAction *) const; + + QString actionName(int row) const; + QAction *actionAt(const QModelIndex &index) const; + QModelIndex indexOf(QAction *a) const; + + QMimeData *mimeData(const QModelIndexList &indexes) const override; + QStringList mimeTypes() const override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + + // Find the associated menus and toolbars, ignore toolbuttons + static QWidgetList associatedWidgets(const QAction *action); + + // Retrieve shortcut via property sheet as it is a fake property + static PropertySheetKeySequenceValue actionShortCut(QDesignerFormEditorInterface *core, QAction *action); + static PropertySheetKeySequenceValue actionShortCut(const QDesignerPropertySheetExtension *ps); + +signals: + void resourceImageDropped(const QString &path, QAction *action); + +private: + using QStandardItemList = QList; + + void initializeHeaders(); + static void setItems(QDesignerFormEditorInterface *core, QAction *a, + const QIcon &defaultIcon, + QStandardItemList &sl); + + const QIcon m_emptyIcon; + + QDesignerFormEditorInterface *m_core = nullptr; +}; + +// Internal class that provides the detailed view of actions. +class ActionTreeView: public QTreeView +{ + Q_OBJECT +public: + explicit ActionTreeView(ActionModel *model, QWidget *parent = nullptr); + QAction *currentAction() const; + +public slots: + void filter(const QString &text); + +signals: + void actionContextMenuRequested(QContextMenuEvent *event, QAction *); + void currentActionChanged(QAction *action); + void actionActivated(QAction *action, int column); + +protected slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; + +protected: + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + void startDrag(Qt::DropActions supportedActions) override; + +private slots: + void slotActivated(const QModelIndex &); + +private: + ActionModel *m_model; +}; + +// Internal class that provides the icon view of actions. +class ActionListView: public QListView +{ + Q_OBJECT +public: + explicit ActionListView(ActionModel *model, QWidget *parent = nullptr); + QAction *currentAction() const; + +public slots: + void filter(const QString &text); + +signals: + void actionContextMenuRequested(QContextMenuEvent *event, QAction *); + void currentActionChanged(QAction *action); + void actionActivated(QAction *action); + +protected slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; + +protected: + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + void startDrag(Qt::DropActions supportedActions) override; + +private slots: + void slotActivated(const QModelIndex &); + +private: + ActionModel *m_model; +}; + +// Action View that can be switched between detailed and icon view +// using a QStackedWidget of ActionListView / ActionTreeView +// that share the item model and the selection model. + +class ActionView : public QStackedWidget { + Q_OBJECT +public: + // Separate initialize() function takes core argument to make this + // thing usable as promoted widget. + explicit ActionView(QWidget *parent = nullptr); + void initialize(QDesignerFormEditorInterface *core) { m_model->initialize(core); } + + // View mode + enum { DetailedView, IconView }; + int viewMode() const; + void setViewMode(int lm); + + void setSelectionMode(QAbstractItemView::SelectionMode sm); + QAbstractItemView::SelectionMode selectionMode() const; + + ActionModel *model() const { return m_model; } + + QAction *currentAction() const; + void setCurrentIndex(const QModelIndex &index); + + using ActionList = QList; + ActionList selectedActions() const; + QItemSelection selection() const; + +public slots: + void filter(const QString &text); + void selectAll(); + void clearSelection(); + void selectAction(QAction *a); + +signals: + void contextMenuRequested(QContextMenuEvent *event, QAction *); + void currentChanged(QAction *action); + void activated(QAction *action, int column); + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void resourceImageDropped(const QString &data, QAction *action); + +private slots: + void slotCurrentChanged(QAction *action); + +private: + ActionModel *m_model; + ActionTreeView *m_actionTreeView; + ActionListView *m_actionListView; +}; + +class QDESIGNER_SHARED_EXPORT ActionRepositoryMimeData: public QMimeData +{ + Q_OBJECT +public: + using ActionList = QList; + + ActionRepositoryMimeData(const ActionList &, Qt::DropAction dropAction); + ActionRepositoryMimeData(QAction *, Qt::DropAction dropAction); + + const ActionList &actionList() const { return m_actionList; } + QStringList formats() const override; + + static QPixmap actionDragPixmap(const QAction *action); + + // Utility to accept with right action + void accept(QDragMoveEvent *event) const; +private: + const Qt::DropAction m_dropAction; + ActionList m_actionList; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ACTIONREPOSITORY_H diff --git a/src/designer/src/lib/shared/addlinkdialog.ui b/src/designer/src/lib/shared/addlinkdialog.ui new file mode 100644 index 0000000..3171159 --- /dev/null +++ b/src/designer/src/lib/shared/addlinkdialog.ui @@ -0,0 +1,112 @@ + + AddLinkDialog + + + Insert Link + + + false + + + true + + + + + + + + Title: + + + + + + + + 337 + 0 + + + + + + + + URL: + + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AddLinkDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AddLinkDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/designer/src/lib/shared/codedialog.cpp b/src/designer/src/lib/shared/codedialog.cpp new file mode 100644 index 0000000..3f25752 --- /dev/null +++ b/src/designer/src/lib/shared/codedialog.cpp @@ -0,0 +1,264 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "codedialog_p.h" +#include "qdesigner_utils_p.h" +#include "iconloader_p.h" + +#include + +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +// ----------------- CodeDialogPrivate +struct CodeDialog::CodeDialogPrivate { + CodeDialogPrivate(); + + QTextEdit *m_textEdit; + TextEditFindWidget *m_findWidget; + QString m_formFileName; + QString m_mimeType; +}; + +CodeDialog::CodeDialogPrivate::CodeDialogPrivate() + : m_textEdit(new QTextEdit) + , m_findWidget(new TextEditFindWidget) +{ +} + +// ----------------- CodeDialog +CodeDialog::CodeDialog(QWidget *parent) : + QDialog(parent), + m_impl(new CodeDialogPrivate) +{ + QVBoxLayout *vBoxLayout = new QVBoxLayout; + + // Edit tool bar + QToolBar *toolBar = new QToolBar; + + const QIcon saveIcon = createIconSet(QIcon::ThemeIcon::DocumentSave, + "filesave.png"_L1); + QAction *saveAction = toolBar->addAction(saveIcon, tr("Save...")); + connect(saveAction, &QAction::triggered, this, &CodeDialog::slotSaveAs); + +#if QT_CONFIG(clipboard) + const QIcon copyIcon = createIconSet(QIcon::ThemeIcon::EditCopy, + "editcopy.png"_L1); + QAction *copyAction = toolBar->addAction(copyIcon, tr("Copy All")); + connect(copyAction, &QAction::triggered, this, &CodeDialog::copyAll); +#endif + + toolBar->addAction(m_impl->m_findWidget->createFindAction(toolBar)); + + vBoxLayout->addWidget(toolBar); + + // Edit + m_impl->m_textEdit->setReadOnly(true); + const auto font = QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont); + const int editorWidth = QFontMetrics(font, this).averageCharWidth() * 100; + m_impl->m_textEdit->setFont(font); + m_impl->m_textEdit->setMinimumSize(QSize( + qMax(editorWidth, m_impl->m_findWidget->minimumSize().width()), + 500)); + vBoxLayout->addWidget(m_impl->m_textEdit); + + // Find + m_impl->m_findWidget->setTextEdit(m_impl->m_textEdit); + vBoxLayout->addWidget(m_impl->m_findWidget); + + // Button box + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + // Disable auto default + QPushButton *closeButton = buttonBox->button(QDialogButtonBox::Close); + closeButton->setAutoDefault(false); + vBoxLayout->addWidget(buttonBox); + + setLayout(vBoxLayout); +} + +CodeDialog::~CodeDialog() +{ + delete m_impl; +} + +void CodeDialog::setCode(const QString &code) +{ + m_impl->m_textEdit->setPlainText(code); +} + +QString CodeDialog::code() const +{ + return m_impl->m_textEdit->toPlainText(); +} + +void CodeDialog::setFormFileName(const QString &f) +{ + m_impl->m_formFileName = f; +} + +QString CodeDialog::formFileName() const +{ + return m_impl->m_formFileName; +} + +void CodeDialog::setMimeType(const QString &m) +{ + m_impl->m_mimeType = m; +} + +bool CodeDialog::generateCode(const QDesignerFormWindowInterface *fw, + UicLanguage language, + QString *code, + QString *errorMessage) +{ + // Generate temporary file name similar to form file name + // (for header guards) + QString tempPattern = QDir::tempPath(); + if (!tempPattern.endsWith(QDir::separator())) // platform-dependant + tempPattern += QDir::separator(); + const QString fileName = fw->fileName(); + if (fileName.isEmpty()) { + tempPattern += "designer"_L1; + } else { + tempPattern += QFileInfo(fileName).baseName(); + } + tempPattern += "XXXXXX.ui"_L1; + // Write to temp file + QTemporaryFile tempFormFile(tempPattern); + + tempFormFile.setAutoRemove(true); + if (!tempFormFile.open()) { + *errorMessage = tr("A temporary form file could not be created in %1.").arg(QDir::tempPath()); + return false; + } + const QString tempFormFileName = tempFormFile.fileName(); + tempFormFile.write(fw->contents().toUtf8()); + if (!tempFormFile.flush()) { + *errorMessage = tr("The temporary form file %1 could not be written.").arg(tempFormFileName); + return false; + } + tempFormFile.close(); + // Run uic + QByteArray rc; + if (!runUIC(tempFormFileName, language, rc, *errorMessage)) + return false; + *code = QString::fromUtf8(rc); + return true; +} + +bool CodeDialog::showCodeDialog(const QDesignerFormWindowInterface *fw, + UicLanguage language, + QWidget *parent, + QString *errorMessage) +{ + QString code; + if (!generateCode(fw, language, &code, errorMessage)) + return false; + + auto dialog = new CodeDialog(parent); + dialog->setModal(false); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setCode(code); + dialog->setFormFileName(fw->fileName()); + QLatin1StringView languageName; + switch (language) { + case UicLanguage::Cpp: + languageName = "C++"_L1; + dialog->setMimeType(u"text/x-chdr"_s); + break; + case UicLanguage::Python: + languageName = "Python"_L1; + dialog->setMimeType(u"text/x-python"_s); + break; + } + dialog->setWindowTitle(tr("%1 - [%2 Code]"). + arg(fw->mainContainer()->windowTitle(), languageName)); + dialog->show(); + return true; +} + +void CodeDialog::slotSaveAs() +{ + // build the default relative name 'ui_sth.h' + QMimeDatabase mimeDb; + const QString suffix = mimeDb.mimeTypeForName(m_impl->m_mimeType).preferredSuffix(); + + // file dialog + QFileDialog fileDialog(this, tr("Save Code")); + fileDialog.setMimeTypeFilters(QStringList(m_impl->m_mimeType)); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + fileDialog.setDefaultSuffix(suffix); + const QString uiFile = formFileName(); + if (!uiFile.isEmpty()) { + QFileInfo uiFi(uiFile); + fileDialog.setDirectory(uiFi.absolutePath()); + fileDialog.selectFile("ui_"_L1 + uiFi.baseName() + + '.'_L1 + suffix); + } + + while (true) { + if (fileDialog.exec() != QDialog::Accepted) + break; + const QString fileName = fileDialog.selectedFiles().constFirst(); + + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly|QIODevice::Text)) { + warning(tr("The file %1 could not be opened: %2") + .arg(fileName, file.errorString())); + continue; + } + file.write(code().toUtf8()); + if (!file.flush()) { + warning(tr("The file %1 could not be written: %2") + .arg(fileName, file.errorString())); + continue; + } + file.close(); + break; + } +} + +void CodeDialog::warning(const QString &msg) +{ + QMessageBox::warning( + this, tr("%1 - Error").arg(windowTitle()), + msg, QMessageBox::Close); +} + +#if QT_CONFIG(clipboard) +void CodeDialog::copyAll() +{ + QApplication::clipboard()->setText(code()); +} +#endif + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/codedialog_p.h b/src/designer/src/lib/shared/codedialog_p.h new file mode 100644 index 0000000..e68f48a --- /dev/null +++ b/src/designer/src/lib/shared/codedialog_p.h @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef CODEPREVIEWDIALOG_H +#define CODEPREVIEWDIALOG_H + +#include "shared_global_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +enum class UicLanguage; + +// Dialog for viewing code. +class QDESIGNER_SHARED_EXPORT CodeDialog : public QDialog +{ + Q_OBJECT + explicit CodeDialog(QWidget *parent = nullptr); +public: + ~CodeDialog() override; + + static bool generateCode(const QDesignerFormWindowInterface *fw, + UicLanguage language, + QString *code, + QString *errorMessage); + + static bool showCodeDialog(const QDesignerFormWindowInterface *fw, + UicLanguage language, + QWidget *parent, + QString *errorMessage); + +private slots: + void slotSaveAs(); +#if QT_CONFIG(clipboard) + void copyAll(); +#endif + +private: + void setCode(const QString &code); + QString code() const; + void setFormFileName(const QString &f); + QString formFileName() const; + void setMimeType(const QString &m); + + void warning(const QString &msg); + + struct CodeDialogPrivate; + CodeDialogPrivate *m_impl; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // CODEPREVIEWDIALOG_H diff --git a/src/designer/src/lib/shared/connectionedit.cpp b/src/designer/src/lib/shared/connectionedit.cpp new file mode 100644 index 0000000..d92ca00 --- /dev/null +++ b/src/designer/src/lib/shared/connectionedit.cpp @@ -0,0 +1,1572 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "connectionedit_p.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +static const int BG_ALPHA = 32; +static const int LINE_PROXIMITY_RADIUS = 3; +static const int LOOP_MARGIN = 20; +static const int VLABEL_MARGIN = 1; +static const int HLABEL_MARGIN = 3; +static const int GROUND_W = 20; +static const int GROUND_H = 25; + +/******************************************************************************* +** Tools +*/ + +static QRect fixRect(const QRect &r) +{ + return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1); +} + +static QRect expand(const QRect &r, int i) +{ + return QRect(r.x() - i, r.y() - i, r.width() + 2*i, r.height() + 2*i); +} + +static QRect endPointRectHelper(const QPoint &pos) +{ + const QRect r(pos + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS), + QSize(2*LINE_PROXIMITY_RADIUS, 2*LINE_PROXIMITY_RADIUS)); + return r; +} + +static void paintGround(QPainter *p, QRect r) +{ + const QPoint mid = r.center(); + p->drawLine(mid.x(), r.top(), mid.x(), mid.y()); + p->drawLine(r.left(), mid.y(), r.right(), mid.y()); + int y = r.top() + 4*r.height()/6; + int x = GROUND_W/6; + p->drawLine(r.left() + x, y, r.right() - x, y); + y = r.top() + 5*r.height()/6; + x = 2*GROUND_W/6; + p->drawLine(r.left() + x, y, r.right() - x, y); + p->drawLine(mid.x(), r.bottom(), mid.x() + 1, r.bottom()); +} + +static void paintEndPoint(QPainter *p, const QPoint &pos) +{ + const QRect r(pos + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS), + QSize(2*LINE_PROXIMITY_RADIUS, 2*LINE_PROXIMITY_RADIUS)); + p->fillRect(fixRect(r), p->pen().color()); +} + +static qdesigner_internal::CETypes::LineDir classifyLine(const QPoint &p1, const QPoint &p2) +{ + if (p1.x() == p2.x()) + return p1.y() < p2.y() ? qdesigner_internal::CETypes::DownDir : qdesigner_internal::CETypes::UpDir; + Q_ASSERT(p1.y() == p2.y()); + return p1.x() < p2.x() ? qdesigner_internal::CETypes::RightDir : qdesigner_internal::CETypes::LeftDir; +} + +static QPoint pointInsideRect(const QRect &r, QPoint p) +{ + if (p.x() < r.left()) + p.setX(r.left()); + else if (p.x() > r.right()) + p.setX(r.right()); + + if (p.y() < r.top()) + p.setY(r.top()); + else if (p.y() > r.bottom()) + p.setY(r.bottom()); + + return p; +} + +namespace qdesigner_internal { + +/******************************************************************************* +** Commands +*/ + +AddConnectionCommand::AddConnectionCommand(ConnectionEdit *edit, Connection *con) + : CECommand(edit), m_con(con) +{ + setText(QApplication::translate("Command", "Add connection")); +} + +void AddConnectionCommand::redo() +{ + edit()->selectNone(); + emit edit()->aboutToAddConnection(edit()->m_con_list.size()); + edit()->m_con_list.append(m_con); + m_con->inserted(); + emit edit()->connectionAdded(m_con); + edit()->setSelected(m_con, true); +} + +void AddConnectionCommand::undo() +{ + const int idx = edit()->indexOfConnection(m_con); + emit edit()->aboutToRemoveConnection(m_con); + edit()->setSelected(m_con, false); + m_con->update(); + m_con->removed(); + edit()->m_con_list.removeAll(m_con); + emit edit()->connectionRemoved(idx); +} + +class AdjustConnectionCommand : public CECommand +{ +public: + AdjustConnectionCommand(ConnectionEdit *edit, Connection *con, + const QPoint &old_source_pos, + const QPoint &old_target_pos, + const QPoint &new_source_pos, + const QPoint &new_target_pos); + void redo() override; + void undo() override; +private: + Connection *m_con; + const QPoint m_old_source_pos; + const QPoint m_old_target_pos; + const QPoint m_new_source_pos; + const QPoint m_new_target_pos; +}; + +AdjustConnectionCommand::AdjustConnectionCommand(ConnectionEdit *edit, Connection *con, + const QPoint &old_source_pos, + const QPoint &old_target_pos, + const QPoint &new_source_pos, + const QPoint &new_target_pos) : + CECommand(edit), + m_con(con), + m_old_source_pos(old_source_pos), + m_old_target_pos(old_target_pos), + m_new_source_pos(new_source_pos), + m_new_target_pos(new_target_pos) +{ + setText(QApplication::translate("Command", "Adjust connection")); +} + +void AdjustConnectionCommand::undo() +{ + m_con->setEndPoint(EndPoint::Source, m_con->widget(EndPoint::Source), m_old_source_pos); + m_con->setEndPoint(EndPoint::Target, m_con->widget(EndPoint::Target), m_old_target_pos); +} + +void AdjustConnectionCommand::redo() +{ + m_con->setEndPoint(EndPoint::Source, m_con->widget(EndPoint::Source), m_new_source_pos); + m_con->setEndPoint(EndPoint::Target, m_con->widget(EndPoint::Target), m_new_target_pos); +} + +DeleteConnectionsCommand::DeleteConnectionsCommand(ConnectionEdit *edit, + const ConnectionList &con_list) + : CECommand(edit), m_con_list(con_list) +{ + setText(QApplication::translate("Command", "Delete connections")); +} + +void DeleteConnectionsCommand::redo() +{ + for (Connection *con : std::as_const(m_con_list)) { + const int idx = edit()->indexOfConnection(con); + emit edit()->aboutToRemoveConnection(con); + Q_ASSERT(edit()->m_con_list.contains(con)); + edit()->setSelected(con, false); + con->update(); + con->removed(); + edit()->m_con_list.removeAll(con); + emit edit()->connectionRemoved(idx); + } +} + +void DeleteConnectionsCommand::undo() +{ + for (Connection *con : std::as_const(m_con_list)) { + Q_ASSERT(!edit()->m_con_list.contains(con)); + emit edit()->aboutToAddConnection(edit()->m_con_list.size()); + edit()->m_con_list.append(con); + edit()->selectNone(); + con->update(); + con->inserted(); + emit edit()->connectionAdded(con); + edit()->setSelected(con, true); + } +} + +class SetEndPointCommand : public CECommand +{ +public: + SetEndPointCommand(ConnectionEdit *edit, Connection *con, EndPoint::Type type, QObject *object); + void redo() override; + void undo() override; +private: + Connection *m_con; + const EndPoint::Type m_type; + QObject *m_old_widget, *m_new_widget; + const QPoint m_old_pos; + QPoint m_new_pos; +}; + +SetEndPointCommand::SetEndPointCommand(ConnectionEdit *edit, Connection *con, + EndPoint::Type type, QObject *object) : + CECommand(edit), + m_con(con), + m_type(type), + m_old_widget(con->object(type)), + m_new_widget(object), + m_old_pos(con->endPointPos(type)) +{ + if (QWidget *widget = qobject_cast(object)) { + m_new_pos = edit->widgetRect(widget).center(); + } + + if (m_type == EndPoint::Source) + setText(QApplication::translate("Command", "Change source")); + else + setText(QApplication::translate("Command", "Change target")); +} + +void SetEndPointCommand::redo() +{ + m_con->setEndPoint(m_type, m_new_widget, m_new_pos); + emit edit()->connectionChanged(m_con); +} + +void SetEndPointCommand::undo() +{ + m_con->setEndPoint(m_type, m_old_widget, m_old_pos); + emit edit()->connectionChanged(m_con); +} + +/******************************************************************************* +** Connection +*/ + +Connection::Connection(ConnectionEdit *edit) : + m_source_pos(QPoint(-1, -1)), + m_target_pos(QPoint(-1, -1)), + m_source(nullptr), + m_target(nullptr), + m_edit(edit), + m_visible(true) +{ + +} + +Connection::Connection(ConnectionEdit *edit, QObject *source, QObject *target) : + m_source_pos(QPoint(-1, -1)), + m_target_pos(QPoint(-1, -1)), + m_source(source), + m_target(target), + m_edit(edit), + m_visible(true) +{ +} + +void Connection::setVisible(bool b) +{ + m_visible = b; +} + +void Connection::updateVisibility() +{ + QWidget *source = widget(EndPoint::Source); + QWidget *target = widget(EndPoint::Target); + + if (source == nullptr || target == nullptr) { + setVisible(false); + return; + } + + QWidget *w = source; + while (w && w->parentWidget()) { + if (!w->isVisibleTo(w->parentWidget())) { + setVisible(false); + return; + } + w = w->parentWidget(); + } + + w = target; + while (w && w->parentWidget()) { + if (!w->isVisibleTo(w->parentWidget())) { + setVisible(false); + return; + } + w = w->parentWidget(); + } + + setVisible(true); +} + +bool Connection::isVisible() const +{ + return m_visible; +} + +bool Connection::ground() const +{ + return m_target != nullptr && m_target == m_edit->m_bg_widget; +} + +QPoint Connection::endPointPos(EndPoint::Type type) const +{ + return type == EndPoint::Source ? m_source_pos : m_target_pos; +} + +static QPoint lineEntryPos(const QPoint &p1, const QPoint &p2, const QRect &rect) +{ + QPoint result; + + switch (classifyLine(p1, p2)) { + case CETypes::UpDir: + result = QPoint(p1.x(), rect.bottom()); + break; + case CETypes::DownDir: + result = QPoint(p1.x(), rect.top()); + break; + case CETypes::LeftDir: + result = QPoint(rect.right(), p1.y()); + break; + case CETypes::RightDir: + result = QPoint(rect.left(), p1.y()); + break; + } + + return result; +} + +static QPolygonF arrowHead(const QPoint &p1, const QPoint &p2) +{ + QPolygonF result; + + switch (classifyLine(p1, p2)) { + case CETypes::UpDir: + result.append(p2 + QPoint(0, 1)); + result.append(p2 + QPoint(LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1)); + result.append(p2 + QPoint(-LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1)); + break; + case CETypes::DownDir: + result.append(p2); + result.append(p2 + QPoint(LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2)); + result.append(p2 + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2)); + break; + case CETypes::LeftDir: + result.append(p2 + QPoint(1, 0)); + result.append(p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, -LINE_PROXIMITY_RADIUS)); + result.append(p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, LINE_PROXIMITY_RADIUS)); + break; + case CETypes::RightDir: + result.append(p2); + result.append(p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS)); + result.append(p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS)); + break; + } + + return result; +} + +static CETypes::LineDir closestEdge(const QPoint &p, const QRect &r) +{ + CETypes::LineDir result = CETypes::UpDir; + int min = p.y() - r.top(); + + int d = p.x() - r.left(); + if (d < min) { + min = d; + result = CETypes::LeftDir; + } + + d = r.bottom() - p.y(); + if (d < min) { + min = d; + result = CETypes::DownDir; + } + + d = r.right() - p.x(); + if (d < min) { + min = d; + result = CETypes::RightDir; + } + + return result; +} + +static bool pointAboveLine(const QPoint &l1, const QPoint &l2, const QPoint &p) +{ + if (l1.x() == l2.x()) + return p.x() >= l1.x(); + return p.y() <= l1.y() + (p.x() - l1.x())*(l2.y() - l1.y())/(l2.x() - l1.x()); +} + +void Connection::updateKneeList() +{ + const LineDir old_source_label_dir = labelDir(EndPoint::Source); + const LineDir old_target_label_dir = labelDir(EndPoint::Target); + + QPoint s = endPointPos(EndPoint::Source); + QPoint t = endPointPos(EndPoint::Target); + const QRect sr = m_source_rect; + const QRect tr = m_target_rect; + + m_knee_list.clear(); + m_arrow_head.clear(); + + if (m_source == nullptr || s == QPoint(-1, -1) || t == QPoint(-1, -1)) + return; + + const QRect r = sr | tr; + + m_knee_list.append(s); + if (m_target == nullptr) { + m_knee_list.append(QPoint(t.x(), s.y())); + } else if (m_target == m_edit->m_bg_widget) { + m_knee_list.append(QPoint(s.x(), t.y())); + } else if (tr.contains(sr) || sr.contains(tr)) { +/* + +------------------+ + | +----------+ | + | | | | + | | o | | + | +---|------+ | + | | x | + +-----|-----|------+ + +-----+ + + We find out which edge of the outer rectangle is closest to the target + point, and make a loop which exits and re-enters through that edge. +*/ + const LineDir dir = closestEdge(t, tr); + switch (dir) { + case UpDir: + m_knee_list.append(QPoint(s.x(), r.top() - LOOP_MARGIN)); + m_knee_list.append(QPoint(t.x(), r.top() - LOOP_MARGIN)); + break; + case DownDir: + m_knee_list.append(QPoint(s.x(), r.bottom() + LOOP_MARGIN)); + m_knee_list.append(QPoint(t.x(), r.bottom() + LOOP_MARGIN)); + break; + case LeftDir: + m_knee_list.append(QPoint(r.left() - LOOP_MARGIN, s.y())); + m_knee_list.append(QPoint(r.left() - LOOP_MARGIN, t.y())); + break; + case RightDir: + m_knee_list.append(QPoint(r.right() + LOOP_MARGIN, s.y())); + m_knee_list.append(QPoint(r.right() + LOOP_MARGIN, t.y())); + break; + } + } else { + if (r.height() < sr.height() + tr.height()) { + if ((s.y() >= tr.top() && s.y() <= tr.bottom()) || (t.y() >= sr.bottom() || t.y() <= sr.top())) { +/* + +--------+ + | | +--------+ + | o--+---+--x | + | o | | | + +-----|--+ | | + +------+--x | + +--------+ + + When dragging one end point, move the other end point to the same y position, + if that does not cause it to exit it's rectangle. +*/ + if (m_edit->state() == ConnectionEdit::Dragging) { + if (m_edit->m_drag_end_point.type == EndPoint::Source) { + const QPoint p(t.x(), s.y()); + m_knee_list.append(p); + if (tr.contains(p)) + t = m_target_pos = p; + } else { + const QPoint p(s.x(), t.y()); + m_knee_list.append(p); + if (sr.contains(p)) + s = m_source_pos = p; + } + } else { + m_knee_list.append(QPoint(s.x(), t.y())); + } + } else { +/* + +--------+ + | o----+-------+ + | | +---|----+ + +--------+ | | | + | x | + +--------+ +*/ + m_knee_list.append(QPoint(t.x(), s.y())); + } + } else if (r.width() < sr.width() + tr.width()) { + if ((s.x() >= tr.left() && s.x() <= tr.right()) || t.x() >= sr.right() || t.x() <= sr.left()) { +/* + +--------+ + | | + | o o+--+ + +----|---+ | + +-|------|-+ + | x x | + | | + +----------+ + + When dragging one end point, move the other end point to the same x position, + if that does not cause it to exit it's rectangle. +*/ + if (m_edit->state() == ConnectionEdit::Dragging) { + if (m_edit->m_drag_end_point.type == EndPoint::Source) { + const QPoint p(s.x(), t.y()); + m_knee_list.append(p); + if (tr.contains(p)) + t = m_target_pos = p; + } else { + const QPoint p(t.x(), s.y()); + m_knee_list.append(p); + if (sr.contains(p)) + s = m_source_pos = p; + } + } else { + m_knee_list.append(QPoint(t.x(), s.y())); + } + } else { +/* + +--------+ + | | + | o | + +--|-----+ + | +--------+ + +---+-x | + | | + +--------+ + +*/ + m_knee_list.append(QPoint(s.x(), t.y())); + } + } else { +/* + +--------+ + | | + | o o-+--------+ + +--|-----+ | + | +-----|--+ + | | x | + +--------+-x | + +--------+ + + The line enters the target rectangle through the closest edge. +*/ + if (sr.topLeft() == r.topLeft()) { + if (pointAboveLine(tr.topLeft(), tr.bottomRight(), t)) + m_knee_list.append(QPoint(t.x(), s.y())); + else + m_knee_list.append(QPoint(s.x(), t.y())); + } else if (sr.topRight() == r.topRight()) { + if (pointAboveLine(tr.bottomLeft(), tr.topRight(), t)) + m_knee_list.append(QPoint(t.x(), s.y())); + else + m_knee_list.append(QPoint(s.x(), t.y())); + } else if (sr.bottomRight() == r.bottomRight()) { + if (pointAboveLine(tr.topLeft(), tr.bottomRight(), t)) + m_knee_list.append(QPoint(s.x(), t.y())); + else + m_knee_list.append(QPoint(t.x(), s.y())); + } else { + if (pointAboveLine(tr.bottomLeft(), tr.topRight(), t)) + m_knee_list.append(QPoint(s.x(), t.y())); + else + m_knee_list.append(QPoint(t.x(), s.y())); + } + } + } + m_knee_list.append(t); + + if (m_knee_list.size() == 2) + m_knee_list.clear(); + + trimLine(); + + const LineDir new_source_label_dir = labelDir(EndPoint::Source); + const LineDir new_target_label_dir = labelDir(EndPoint::Target); + if (new_source_label_dir != old_source_label_dir) + updatePixmap(EndPoint::Source); + if (new_target_label_dir != old_target_label_dir) + updatePixmap(EndPoint::Target); +} + +void Connection::trimLine() +{ + if (m_source == nullptr || m_source_pos == QPoint(-1, -1) || m_target_pos == QPoint(-1, -1)) + return; + auto cnt = m_knee_list.size(); + if (cnt < 2) + return; + + const QRect sr = m_source_rect; + const QRect tr = m_target_rect; + + if (sr.contains(m_knee_list.at(1))) + m_knee_list.removeFirst(); + + cnt = m_knee_list.size(); + if (cnt < 2) + return; + + if (!tr.contains(sr) && tr.contains(m_knee_list.at(cnt - 2))) + m_knee_list.removeLast(); + + cnt = m_knee_list.size(); + if (cnt < 2) + return; + + if (sr.contains(m_knee_list.at(0)) && !sr.contains(m_knee_list.at(1))) + m_knee_list[0] = lineEntryPos(m_knee_list.at(1), m_knee_list.at(0), sr); + + if (tr.contains(m_knee_list.at(cnt - 1)) && !tr.contains(m_knee_list.at(cnt - 2))) { + m_knee_list[cnt - 1] + = lineEntryPos(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1), tr); + m_arrow_head = arrowHead(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1)); + } +} + +void Connection::setSource(QObject *source, const QPoint &pos) +{ + if (source == m_source && m_source_pos == pos) + return; + + update(false); + + m_source = source; + if (QWidget *widget = qobject_cast(source)) { + m_source_pos = pos; + m_source_rect = m_edit->widgetRect(widget); + updateKneeList(); + } + + update(false); +} + +void Connection::setTarget(QObject *target, const QPoint &pos) +{ + if (target == m_target && m_target_pos == pos) + return; + + update(false); + + m_target = target; + if (QWidget *widget = qobject_cast(target)) { + m_target_pos = pos; + m_target_rect = m_edit->widgetRect(widget); + updateKneeList(); + } + + update(false); +} + +static QRect lineRect(const QPoint &a, const QPoint &b) +{ + const QPoint c(qMin(a.x(), b.x()), qMin(a.y(), b.y())); + const QPoint d(qMax(a.x(), b.x()), qMax(a.y(), b.y())); + + QRect result(c, d); + return expand(result, LINE_PROXIMITY_RADIUS); +} + +QRect Connection::groundRect() const +{ + if (!ground()) + return QRect(); + if (m_knee_list.isEmpty()) + return QRect(); + + const QPoint p = m_knee_list.last(); + return QRect(p.x() - GROUND_W/2, p.y(), GROUND_W, GROUND_H); +} + +QRegion Connection::region() const +{ + QRegion result; + + for (qsizetype i = 0; i < m_knee_list.size() - 1; ++i) + result = result.united(lineRect(m_knee_list.at(i), m_knee_list.at(i + 1))); + + if (!m_arrow_head.isEmpty()) { + QRect r = m_arrow_head.boundingRect().toRect(); + r = expand(r, 1); + result = result.united(r); + } else if (ground()) { + result = result.united(groundRect()); + } + + result = result.united(labelRect(EndPoint::Source)); + result = result.united(labelRect(EndPoint::Target)); + + return result; +} + +void Connection::update(bool update_widgets) const +{ + m_edit->update(region()); + if (update_widgets) { + if (m_source != nullptr) + m_edit->update(m_source_rect); + if (m_target != nullptr) + m_edit->update(m_target_rect); + } + + m_edit->update(endPointRect(EndPoint::Source)); + m_edit->update(endPointRect(EndPoint::Target)); +} + +void Connection::paint(QPainter *p) const +{ + for (qsizetype i = 0; i < m_knee_list.size() - 1; ++i) + p->drawLine(m_knee_list.at(i), m_knee_list.at(i + 1)); + + if (!m_arrow_head.isEmpty()) { + p->save(); + p->setBrush(p->pen().color()); + p->drawPolygon(m_arrow_head); + p->restore(); + } else if (ground()) { + paintGround(p, groundRect()); + } +} + +bool Connection::contains(const QPoint &pos) const +{ + return region().contains(pos); +} + +QRect Connection::endPointRect(EndPoint::Type type) const +{ + if (type == EndPoint::Source) { + if (m_source_pos != QPoint(-1, -1)) + return endPointRectHelper(m_source_pos); + } else { + if (m_target_pos != QPoint(-1, -1)) + return endPointRectHelper(m_target_pos); + } + return QRect(); +} + +CETypes::LineDir Connection::labelDir(EndPoint::Type type) const +{ + const auto cnt = m_knee_list.size(); + if (cnt < 2) + return RightDir; + + LineDir dir; + if (type == EndPoint::Source) + dir = classifyLine(m_knee_list.at(0), m_knee_list.at(1)); + else + dir = classifyLine(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1)); + + if (dir == LeftDir) + dir = RightDir; + if (dir == UpDir) + dir = DownDir; + + return dir; +} + +QRect Connection::labelRect(EndPoint::Type type) const +{ + const auto cnt = m_knee_list.size(); + if (cnt < 2) + return QRect(); + const QString text = label(type); + if (text.isEmpty()) + return QRect(); + + const QSize size = labelPixmap(type).size(); + QPoint p1, p2; + if (type == EndPoint::Source) { + p1 = m_knee_list.at(0); + p2 = m_knee_list.at(1); + } else { + p1 = m_knee_list.at(cnt - 1); + p2 = m_knee_list.at(cnt - 2); + } + const LineDir dir = classifyLine(p1, p2); + + QRect result; + switch (dir) { + case UpDir: + result = QRect(p1 + QPoint(-size.width()/2, 0), size); + break; + case DownDir: + result = QRect(p1 + QPoint(-size.width()/2, -size.height()), size); + break; + case LeftDir: + result = QRect(p1 + QPoint(0, -size.height()/2), size); + break; + case RightDir: + result = QRect(p1 + QPoint(-size.width(), -size.height()/2), size); + break; + } + + return result; +} + +void Connection::setLabel(EndPoint::Type type, const QString &text) +{ + if (text == label(type)) + return; + + if (type == EndPoint::Source) + m_source_label = text; + else + m_target_label = text; + + updatePixmap(type); +} + +void Connection::updatePixmap(EndPoint::Type type) +{ + QPixmap *pm = type == EndPoint::Source ? &m_source_label_pm : &m_target_label_pm; + + const QString text = label(type); + if (text.isEmpty()) { + *pm = QPixmap(); + return; + } + + const QFontMetrics fm = m_edit->fontMetrics(); + const QSize size = fm.size(Qt::TextSingleLine, text) + QSize(HLABEL_MARGIN*2, VLABEL_MARGIN*2); + *pm = QPixmap(size); + QColor color = m_edit->palette().color(QPalette::Normal, QPalette::Base); + color.setAlpha(190); + pm->fill(color); + + QPainter p(pm); + p.setPen(m_edit->palette().color(QPalette::Normal, QPalette::Text)); + p.drawText(-fm.leftBearing(text.at(0)) + HLABEL_MARGIN, fm.ascent() + VLABEL_MARGIN, text); + p.end(); + + const LineDir dir = labelDir(type); + + if (dir == DownDir) + *pm = pm->transformed(QTransform(0.0, -1.0, 1.0, 0.0, 0.0, 0.0)); +} + +void Connection::checkWidgets() +{ + bool changed = false; + + if (QWidget *sourceWidget = qobject_cast(m_source)) { + const QRect r = m_edit->widgetRect(sourceWidget); + if (r != m_source_rect) { + if (m_source_pos != QPoint(-1, -1) && !r.contains(m_source_pos)) { + QPoint offset = m_source_pos - m_source_rect.topLeft(); + m_source_pos = pointInsideRect(r, r.topLeft() + offset); + } + m_edit->update(m_source_rect); + m_source_rect = r; + changed = true; + } + } + + if (QWidget *targetWidget = qobject_cast(m_target)) { + const QRect r = m_edit->widgetRect(targetWidget); + if (r != m_target_rect) { + if (m_target_pos != QPoint(-1, -1) && !r.contains(m_target_pos)) { + const QPoint offset = m_target_pos - m_target_rect.topLeft(); + m_target_pos = pointInsideRect(r, r.topLeft() + offset); + } + m_edit->update(m_target_rect); + m_target_rect = r; + changed = true; + } + } + + if (changed) { + update(); + updateKneeList(); + update(); + } +} + +/******************************************************************************* +** ConnectionEdit +*/ + +ConnectionEdit::ConnectionEdit(QWidget *parent, QDesignerFormWindowInterface *form) : + QWidget(parent), + m_bg_widget(nullptr), + m_undo_stack(form->commandHistory()), + m_enable_update_background(false), + m_tmp_con(nullptr), + m_start_connection_on_drag(true), + m_widget_under_mouse(nullptr), + m_inactive_color(Qt::blue), + m_active_color(Qt::red) +{ + setAttribute(Qt::WA_MouseTracking, true); + setFocusPolicy(Qt::ClickFocus); + + connect(form, &QDesignerFormWindowInterface::widgetRemoved, this, &ConnectionEdit::widgetRemoved); + connect(form, &QDesignerFormWindowInterface::objectRemoved, this, &ConnectionEdit::objectRemoved); +} + +ConnectionEdit::~ConnectionEdit() +{ + qDeleteAll(m_con_list); +} + +void ConnectionEdit::clear() +{ + m_con_list.clear(); + m_sel_con_set.clear(); + m_bg_widget = nullptr; + m_widget_under_mouse = nullptr; + m_tmp_con = nullptr; +} + +void ConnectionEdit::setBackground(QWidget *background) +{ + if (background == m_bg_widget) { + // nothing to do + return; + } + + m_bg_widget = background; + updateBackground(); +} + +void ConnectionEdit::enableUpdateBackground(bool enable) +{ + m_enable_update_background = enable; + if (enable) + updateBackground(); +} + +void ConnectionEdit::updateBackground() +{ + // Might happen while reloading a form. + if (m_bg_widget == nullptr) + return; + + if (!m_enable_update_background) + return; + + for (Connection *c : std::as_const(m_con_list)) + c->updateVisibility(); + + updateLines(); + update(); +} + +QWidget *ConnectionEdit::widgetAt(const QPoint &pos) const +{ + if (m_bg_widget == nullptr) + return nullptr; + QWidget *widget = m_bg_widget->childAt(pos); + if (widget == nullptr) + widget = m_bg_widget; + + return widget; +} + + +QRect ConnectionEdit::widgetRect(QWidget *w) const +{ + if (w == nullptr) + return QRect(); + QRect r = w->geometry(); + QPoint pos = w->mapToGlobal(QPoint(0, 0)); + pos = mapFromGlobal(pos); + r.moveTopLeft(pos); + return r; +} + +ConnectionEdit::State ConnectionEdit::state() const +{ + if (m_tmp_con != nullptr) + return Connecting; + if (!m_drag_end_point.isNull()) + return Dragging; + return Editing; +} + +void ConnectionEdit::paintLabel(QPainter *p, EndPoint::Type type, Connection *con) +{ + if (con->label(type).isEmpty()) + return; + + const bool heavy = selected(con) || con == m_tmp_con; + p->setPen(heavy ? m_active_color : m_inactive_color); + p->setBrush(Qt::NoBrush); + const QRect r = con->labelRect(type); + p->drawPixmap(r.topLeft(), con->labelPixmap(type)); + p->drawRect(fixRect(r)); +} + +void ConnectionEdit::paintConnection(QPainter *p, Connection *con, + WidgetSet *heavy_highlight_set, + WidgetSet *light_highlight_set) const +{ + QWidget *source = con->widget(EndPoint::Source); + QWidget *target = con->widget(EndPoint::Target); + + const bool heavy = selected(con) || con == m_tmp_con; + WidgetSet *set = heavy ? heavy_highlight_set : light_highlight_set; + p->setPen(heavy ? m_active_color : m_inactive_color); + con->paint(p); + + if (source != nullptr && source != m_bg_widget) + set->insert(source, source); + + if (target != nullptr && target != m_bg_widget) + set->insert(target, target); +} + +void ConnectionEdit::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + p.setClipRegion(e->region()); + + WidgetSet heavy_highlight_set, light_highlight_set; + + for (Connection *con : std::as_const(m_con_list)) { + if (!con->isVisible()) + continue; + + paintConnection(&p, con, &heavy_highlight_set, &light_highlight_set); + } + + if (m_tmp_con != nullptr) + paintConnection(&p, m_tmp_con, &heavy_highlight_set, &light_highlight_set); + + if (!m_widget_under_mouse.isNull() && m_widget_under_mouse != m_bg_widget) + heavy_highlight_set.insert(m_widget_under_mouse, m_widget_under_mouse); + + QColor c = m_active_color; + p.setPen(c); + c.setAlpha(BG_ALPHA); + p.setBrush(c); + + for (QWidget *w : std::as_const(heavy_highlight_set)) { + p.drawRect(fixRect(widgetRect(w))); + light_highlight_set.remove(w); + } + + c = m_inactive_color; + p.setPen(c); + c.setAlpha(BG_ALPHA); + p.setBrush(c); + + for (QWidget *w : std::as_const(light_highlight_set)) + p.drawRect(fixRect(widgetRect(w))); + + p.setBrush(palette().color(QPalette::Base)); + p.setPen(palette().color(QPalette::Text)); + for (Connection *con : std::as_const(m_con_list)) { + if (con->isVisible()) { + paintLabel(&p, EndPoint::Source, con); + paintLabel(&p, EndPoint::Target, con); + } + } + + p.setPen(m_active_color); + p.setBrush(m_active_color); + + for (Connection *con : std::as_const(m_con_list)) { + if (!selected(con) || !con->isVisible()) + continue; + + paintEndPoint(&p, con->endPointPos(EndPoint::Source)); + + if (con->widget(EndPoint::Target) != nullptr) + paintEndPoint(&p, con->endPointPos(EndPoint::Target)); + } +} + +void ConnectionEdit::abortConnection() +{ + m_tmp_con->update(); + delete m_tmp_con; + m_tmp_con = nullptr; +#if QT_CONFIG(cursor) + setCursor(QCursor()); +#endif + if (m_widget_under_mouse == m_bg_widget) + m_widget_under_mouse = nullptr; +} + +void ConnectionEdit::mousePressEvent(QMouseEvent *e) +{ + // Right click only to cancel + const Qt::MouseButton button = e->button(); + const State cstate = state(); + if (button != Qt::LeftButton && !(button == Qt::RightButton && cstate == Connecting)) { + QWidget::mousePressEvent(e); + return; + } + + e->accept(); + // Prefer a non-background widget over the connection, + // otherwise, widgets covered by the connection labels cannot be accessed + Connection *con_under_mouse = nullptr; + if (!m_widget_under_mouse || m_widget_under_mouse == m_bg_widget) + con_under_mouse = connectionAt(e->position().toPoint()); + + m_start_connection_on_drag = false; + const bool toggleSelection = e->modifiers().testFlag(Qt::ControlModifier); + switch (cstate) { + case Connecting: + if (button == Qt::RightButton) + abortConnection(); + break; + case Dragging: + break; + case Editing: + if (!m_end_point_under_mouse.isNull()) { + if (!toggleSelection) + startDrag(m_end_point_under_mouse, e->position().toPoint()); + } else if (con_under_mouse != nullptr) { + if (toggleSelection) { + setSelected(con_under_mouse, !selected(con_under_mouse)); + } else { + selectNone(); + setSelected(con_under_mouse, true); + } + } else { + if (!toggleSelection) { + selectNone(); + if (!m_widget_under_mouse.isNull()) + m_start_connection_on_drag = true; + } + } + break; + } +} + +void ConnectionEdit::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) { + QWidget::mouseDoubleClickEvent(e); + return; + } + + e->accept(); + switch (state()) { + case Connecting: + abortConnection(); + break; + case Dragging: + break; + case Editing: + if (!m_widget_under_mouse.isNull()) + emit widgetActivated(m_widget_under_mouse); + else if (m_sel_con_set.size() == 1) + modifyConnection(m_sel_con_set.constBegin().key()); + break; + } + +} + +void ConnectionEdit::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) { + QWidget::mouseReleaseEvent(e); + return; + } + e->accept(); + + switch (state()) { + case Connecting: + if (m_widget_under_mouse.isNull()) + abortConnection(); + else + endConnection(m_widget_under_mouse, e->position().toPoint()); +#if QT_CONFIG(cursor) + setCursor(QCursor()); +#endif + break; + case Editing: + break; + case Dragging: + endDrag(e->position().toPoint()); + break; + } +} + + +void ConnectionEdit::findObjectsUnderMouse(const QPoint &pos) +{ + Connection *con_under_mouse = connectionAt(pos); + + QWidget *w = widgetAt(pos); + // Prefer a non-background widget over the connection, + // otherwise, widgets covered by the connection labels cannot be accessed + if (w == m_bg_widget && con_under_mouse) + w = nullptr; + else + con_under_mouse = nullptr; + + if (w != m_widget_under_mouse) { + if (!m_widget_under_mouse.isNull()) + update(widgetRect(m_widget_under_mouse)); + m_widget_under_mouse = w; + if (!m_widget_under_mouse.isNull()) + update(widgetRect(m_widget_under_mouse)); + } + + const EndPoint hs = endPointAt(pos); + if (hs != m_end_point_under_mouse) { +#if QT_CONFIG(cursor) + if (m_end_point_under_mouse.isNull()) + setCursor(Qt::PointingHandCursor); + else + setCursor(QCursor()); +#endif + m_end_point_under_mouse = hs; + } +} + +void ConnectionEdit::mouseMoveEvent(QMouseEvent *e) +{ + findObjectsUnderMouse(e->position().toPoint()); + switch (state()) { + case Connecting: + continueConnection(m_widget_under_mouse, e->position().toPoint()); + break; + case Editing: + if ((e->buttons() & Qt::LeftButton) + && m_start_connection_on_drag + && !m_widget_under_mouse.isNull()) { + m_start_connection_on_drag = false; + startConnection(m_widget_under_mouse, e->position().toPoint()); +#if QT_CONFIG(cursor) + setCursor(Qt::CrossCursor); +#endif + } + break; + case Dragging: + continueDrag(e->position().toPoint()); + break; + } + + e->accept(); +} + +void ConnectionEdit::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) { + case Qt::Key_Delete: + if (state() == Editing) + deleteSelected(); + break; + case Qt::Key_Escape: + if (state() == Connecting) + abortConnection(); + break; + } + + e->accept(); +} + +void ConnectionEdit::startConnection(QWidget *source, const QPoint &pos) +{ + Q_ASSERT(m_tmp_con == nullptr); + + m_tmp_con = new Connection(this); + m_tmp_con->setEndPoint(EndPoint::Source, source, pos); +} + +void ConnectionEdit::endConnection(QWidget *target, const QPoint &pos) +{ + Q_ASSERT(m_tmp_con != nullptr); + + m_tmp_con->setEndPoint(EndPoint::Target, target, pos); + + QWidget *source = m_tmp_con->widget(EndPoint::Source); + Q_ASSERT(source != nullptr); + Q_ASSERT(target != nullptr); + setEnabled(false); + Connection *new_con = createConnection(source, target); + setEnabled(true); + if (new_con != nullptr) { + new_con->setEndPoint(EndPoint::Source, source, m_tmp_con->endPointPos(EndPoint::Source)); + new_con->setEndPoint(EndPoint::Target, target, m_tmp_con->endPointPos(EndPoint::Target)); + m_undo_stack->push(new AddConnectionCommand(this, new_con)); + emit connectionChanged(new_con); + } + + delete m_tmp_con; + m_tmp_con = nullptr; + + findObjectsUnderMouse(mapFromGlobal(QCursor::pos())); +} + +void ConnectionEdit::continueConnection(QWidget *target, const QPoint &pos) +{ + Q_ASSERT(m_tmp_con != nullptr); + + m_tmp_con->setEndPoint(EndPoint::Target, target, pos); +} + +void ConnectionEdit::modifyConnection(Connection *) +{ +} + +Connection *ConnectionEdit::createConnection(QWidget *source, QWidget *target) +{ + Connection *con = new Connection(this, source, target); + return con; +} + +// Find all connections which in which a sequence of objects is involved +template +static ConnectionEdit::ConnectionSet findConnectionsOf(const ConnectionEdit::ConnectionList &cl, ObjectIterator oi1, const ObjectIterator &oi2) +{ + ConnectionEdit::ConnectionSet rc; + + const auto ccend = cl.cend(); + for ( ; oi1 != oi2; ++oi1) { + for (auto cit = cl.constBegin(); cit != ccend; ++cit) { + Connection *con = *cit; + if (con->object(ConnectionEdit::EndPoint::Source) == *oi1 || con->object(ConnectionEdit::EndPoint::Target) == *oi1) + rc.insert(con, con); + } + } + return rc; +} + +void ConnectionEdit::widgetRemoved(QWidget *widget) +{ + // Remove all connections of that widget and its children. + if (m_con_list.isEmpty()) + return; + + QWidgetList child_list = widget->findChildren(); + child_list.prepend(widget); + + const ConnectionSet remove_set = findConnectionsOf(m_con_list, child_list.constBegin(), child_list.constEnd()); + + if (!remove_set.isEmpty()) { + auto cmd = new DeleteConnectionsCommand(this, ConnectionList(remove_set.cbegin(), remove_set.cend())); + m_undo_stack->push(cmd); + } + + updateBackground(); +} + +void ConnectionEdit::objectRemoved(QObject *o) +{ + // Remove all connections of that object and its children (in case of action groups). + if (m_con_list.isEmpty()) + return; + + QObjectList child_list = o->children(); + child_list.prepend(o); + const ConnectionSet remove_set = findConnectionsOf(m_con_list, child_list.constBegin(), child_list.constEnd()); + if (!remove_set.isEmpty()) { + auto cmd = new DeleteConnectionsCommand(this, ConnectionList(remove_set.cbegin(), remove_set.cend())); + m_undo_stack->push(cmd); + } + + updateBackground(); +} + +void ConnectionEdit::setSelected(Connection *con, bool sel) +{ + if (!con || sel == m_sel_con_set.contains(con)) + return; + + if (sel) { + m_sel_con_set.insert(con, con); + emit connectionSelected(con); + } else { + m_sel_con_set.remove(con); + } + + con->update(); +} + +bool ConnectionEdit::selected(const Connection *con) const +{ + return m_sel_con_set.contains(const_cast(con)); +} + +void ConnectionEdit::selectNone() +{ + for (Connection *con : std::as_const(m_sel_con_set)) + con->update(); + + m_sel_con_set.clear(); +} + +void ConnectionEdit::selectAll() +{ + if (m_sel_con_set.size() == m_con_list.size()) + return; + for (Connection *con : std::as_const(m_con_list)) + setSelected(con, true); +} + +Connection *ConnectionEdit::connectionAt(const QPoint &pos) const +{ + for (Connection *con : m_con_list) { + if (con->contains(pos)) + return con; + } + return nullptr; +} + +CETypes::EndPoint ConnectionEdit::endPointAt(const QPoint &pos) const +{ + for (Connection *con : m_con_list) { + if (!selected(con)) + continue; + const QRect sr = con->endPointRect(EndPoint::Source); + const QRect tr = con->endPointRect(EndPoint::Target); + + if (sr.contains(pos)) + return EndPoint(con, EndPoint::Source); + if (tr.contains(pos)) + return EndPoint(con, EndPoint::Target); + } + return EndPoint(); +} + +void ConnectionEdit::startDrag(const EndPoint &end_point, const QPoint &pos) +{ + Q_ASSERT(m_drag_end_point.isNull()); + m_drag_end_point = end_point; + m_old_source_pos = m_drag_end_point.con->endPointPos(EndPoint::Source); + m_old_target_pos = m_drag_end_point.con->endPointPos(EndPoint::Target); + adjustHotSopt(m_drag_end_point, pos); +} + +void ConnectionEdit::continueDrag(const QPoint &pos) +{ + Q_ASSERT(!m_drag_end_point.isNull()); + adjustHotSopt(m_drag_end_point, pos); +} + +void ConnectionEdit::endDrag(const QPoint &pos) +{ + Q_ASSERT(!m_drag_end_point.isNull()); + adjustHotSopt(m_drag_end_point, pos); + + Connection *con = m_drag_end_point.con; + const QPoint new_source_pos = con->endPointPos(EndPoint::Source); + const QPoint new_target_pos = con->endPointPos(EndPoint::Target); + m_undo_stack->push(new AdjustConnectionCommand(this, con, m_old_source_pos, m_old_target_pos, + new_source_pos, new_target_pos)); + + m_drag_end_point = EndPoint(); +} + +void ConnectionEdit::adjustHotSopt(const EndPoint &end_point, const QPoint &pos) +{ + QWidget *w = end_point.con->widget(end_point.type); + end_point.con->setEndPoint(end_point.type, w, pointInsideRect(widgetRect(w), pos)); +} + +void ConnectionEdit::deleteSelected() +{ + if (m_sel_con_set.isEmpty()) + return; + auto cmd = new DeleteConnectionsCommand(this, ConnectionList(m_sel_con_set.cbegin(), m_sel_con_set.cend())); + m_undo_stack->push(cmd); +} + +void ConnectionEdit::addConnection(Connection *con) +{ + m_con_list.append(con); +} + +void ConnectionEdit::updateLines() +{ + for (Connection *con : std::as_const(m_con_list)) + con->checkWidgets(); +} + +void ConnectionEdit::resizeEvent(QResizeEvent *e) +{ + updateBackground(); + QWidget::resizeEvent(e); +} + +void ConnectionEdit::setSource(Connection *con, const QString &obj_name) +{ + QObject *object = nullptr; + if (!obj_name.isEmpty()) { + object = m_bg_widget->findChild(obj_name); + if (object == nullptr && m_bg_widget->objectName() == obj_name) + object = m_bg_widget; + + if (object == con->object(EndPoint::Source)) + return; + } + m_undo_stack->push(new SetEndPointCommand(this, con, EndPoint::Source, object)); +} + +void ConnectionEdit::setTarget(Connection *con, const QString &obj_name) +{ + QObject *object = nullptr; + if (!obj_name.isEmpty()) { + object = m_bg_widget->findChild(obj_name); + if (object == nullptr && m_bg_widget->objectName() == obj_name) + object = m_bg_widget; + + if (object == con->object(EndPoint::Target)) + return; + } + m_undo_stack->push(new SetEndPointCommand(this, con, EndPoint::Target, object)); +} + +Connection *ConnectionEdit::takeConnection(Connection *con) +{ + if (!m_con_list.contains(con)) + return nullptr; + m_con_list.removeAll(con); + return con; +} + +void ConnectionEdit::clearNewlyAddedConnection() +{ + delete m_tmp_con; + m_tmp_con = nullptr; +} + +void ConnectionEdit::createContextMenu(QMenu &menu) +{ + // Select + QAction *selectAllAction = menu.addAction(tr("Select All")); + selectAllAction->setEnabled(!connectionList().isEmpty()); + connect(selectAllAction, &QAction::triggered, this, &ConnectionEdit::selectAll); + QAction *deselectAllAction = menu.addAction(tr("Deselect All")); + deselectAllAction->setEnabled(!selection().isEmpty()); + connect(deselectAllAction, &QAction::triggered, this, &ConnectionEdit::selectNone); + menu.addSeparator(); + // Delete + QAction *deleteAction = menu.addAction(tr("Delete")); + deleteAction->setShortcut(QKeySequence::Delete); + deleteAction->setEnabled(!selection().isEmpty()); + connect(deleteAction, &QAction::triggered, this, &ConnectionEdit::deleteSelected); +} + +void ConnectionEdit::contextMenuEvent(QContextMenuEvent * event) +{ + QMenu menu; + createContextMenu(menu); + menu.exec(event->globalPos()); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/connectionedit_p.h b/src/designer/src/lib/shared/connectionedit_p.h new file mode 100644 index 0000000..14cd16b --- /dev/null +++ b/src/designer/src/lib/shared/connectionedit_p.h @@ -0,0 +1,285 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + + +#ifndef CONNECTIONEDIT_H +#define CONNECTIONEDIT_H + +#include "shared_global_p.h" + +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QUndoStack; +class QMenu; + +namespace qdesigner_internal { + +class Connection; +class ConnectionEdit; + +class QDESIGNER_SHARED_EXPORT CETypes +{ +public: + using ConnectionList = QList; + using ConnectionSet = QHash ; + using WidgetSet = QHash; + + class EndPoint { + public: + enum Type { Source, Target }; + explicit EndPoint(Connection *_con = nullptr, Type _type = Source) : con(_con), type(_type) {} + bool isNull() const { return con == nullptr; } + bool operator == (const EndPoint &other) const { return con == other.con && type == other.type; } + bool operator != (const EndPoint &other) const { return !operator == (other); } + Connection *con; + Type type; + }; + enum LineDir { UpDir = 0, DownDir, RightDir, LeftDir }; +}; + +class QDESIGNER_SHARED_EXPORT Connection : public CETypes +{ +public: + explicit Connection(ConnectionEdit *edit); + explicit Connection(ConnectionEdit *edit, QObject *source, QObject *target); + virtual ~Connection() = default; + + QObject *object(EndPoint::Type type) const + { + return (type == EndPoint::Source ? m_source : m_target); + } + + QWidget *widget(EndPoint::Type type) const + { + return qobject_cast(object(type)); + } + + QPoint endPointPos(EndPoint::Type type) const; + QRect endPointRect(EndPoint::Type) const; + void setEndPoint(EndPoint::Type type, QObject *w, const QPoint &pos) + { type == EndPoint::Source ? setSource(w, pos) : setTarget(w, pos); } + + bool isVisible() const; + virtual void updateVisibility(); + void setVisible(bool b); + + virtual QRegion region() const; + bool contains(const QPoint &pos) const; + virtual void paint(QPainter *p) const; + + void update(bool update_widgets = true) const; + void checkWidgets(); + + QString label(EndPoint::Type type) const + { return type == EndPoint::Source ? m_source_label : m_target_label; } + void setLabel(EndPoint::Type type, const QString &text); + QRect labelRect(EndPoint::Type type) const; + QPixmap labelPixmap(EndPoint::Type type) const + { return type == EndPoint::Source ? m_source_label_pm : m_target_label_pm; } + + ConnectionEdit *edit() const { return m_edit; } + + virtual void inserted() {} + virtual void removed() {} + +private: + QPoint m_source_pos, m_target_pos; + QObject *m_source, *m_target; + QList m_knee_list; + QPolygonF m_arrow_head; + ConnectionEdit *m_edit; + QString m_source_label, m_target_label; + QPixmap m_source_label_pm, m_target_label_pm; + QRect m_source_rect, m_target_rect; + bool m_visible; + + void setSource(QObject *source, const QPoint &pos); + void setTarget(QObject *target, const QPoint &pos); + void updateKneeList(); + void trimLine(); + void updatePixmap(EndPoint::Type type); + LineDir labelDir(EndPoint::Type type) const; + bool ground() const; + QRect groundRect() const; +}; + +class QDESIGNER_SHARED_EXPORT ConnectionEdit : public QWidget, public CETypes +{ + Q_OBJECT +public: + ConnectionEdit(QWidget *parent, QDesignerFormWindowInterface *form); + ~ConnectionEdit() override; + + inline const QPointer &background() const { return m_bg_widget; } + + void setSelected(Connection *con, bool sel); + bool selected(const Connection *con) const; + + int connectionCount() const { return m_con_list.size(); } + Connection *connection(int i) const { return m_con_list.at(i); } + int indexOfConnection(Connection *con) const { return m_con_list.indexOf(con); } + + virtual void setSource(Connection *con, const QString &obj_name); + virtual void setTarget(Connection *con, const QString &obj_name); + + QUndoStack *undoStack() const { return m_undo_stack; } + + void clear(); + + void showEvent(QShowEvent * /*e*/) override + { + updateBackground(); + } + +signals: + void aboutToAddConnection(int idx); + void connectionAdded(qdesigner_internal::Connection *con); + void aboutToRemoveConnection(qdesigner_internal::Connection *con); + void connectionRemoved(int idx); + void connectionSelected(qdesigner_internal::Connection *con); + void widgetActivated(QWidget *wgt); + void connectionChanged(qdesigner_internal::Connection *con); + +public slots: + void selectNone(); + void selectAll(); + virtual void deleteSelected(); + virtual void setBackground(QWidget *background); + virtual void updateBackground(); + virtual void widgetRemoved(QWidget *w); + virtual void objectRemoved(QObject *o); + + void updateLines(); + void enableUpdateBackground(bool enable); + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void contextMenuEvent(QContextMenuEvent * event) override; + + virtual Connection *createConnection(QWidget *source, QWidget *target); + virtual void modifyConnection(Connection *con); + + virtual QWidget *widgetAt(const QPoint &pos) const; + virtual void createContextMenu(QMenu &menu); + void addConnection(Connection *con); + QRect widgetRect(QWidget *w) const; + + enum State { Editing, Connecting, Dragging }; + State state() const; + + virtual void endConnection(QWidget *target, const QPoint &pos); + + const ConnectionList &connectionList() const { return m_con_list; } + const ConnectionSet &selection() const { return m_sel_con_set; } + Connection *takeConnection(Connection *con); + Connection *newlyAddedConnection() { return m_tmp_con; } + void clearNewlyAddedConnection(); + + void findObjectsUnderMouse(const QPoint &pos); + +private: + void startConnection(QWidget *source, const QPoint &pos); + void continueConnection(QWidget *target, const QPoint &pos); + void abortConnection(); + + void startDrag(const EndPoint &end_point, const QPoint &pos); + void continueDrag(const QPoint &pos); + void endDrag(const QPoint &pos); + void adjustHotSopt(const EndPoint &end_point, const QPoint &pos); + Connection *connectionAt(const QPoint &pos) const; + EndPoint endPointAt(const QPoint &pos) const; + void paintConnection(QPainter *p, Connection *con, + WidgetSet *heavy_highlight_set, + WidgetSet *light_highlight_set) const; + void paintLabel(QPainter *p, EndPoint::Type type, Connection *con); + + + QPointer m_bg_widget; + QUndoStack *m_undo_stack; + bool m_enable_update_background; + + Connection *m_tmp_con; // the connection we are currently editing + ConnectionList m_con_list; + bool m_start_connection_on_drag; + EndPoint m_end_point_under_mouse; + QPointer m_widget_under_mouse; + + EndPoint m_drag_end_point; + QPoint m_old_source_pos, m_old_target_pos; + ConnectionSet m_sel_con_set; + const QColor m_inactive_color; + const QColor m_active_color; + +private: + friend class Connection; + friend class AddConnectionCommand; + friend class DeleteConnectionsCommand; + friend class SetEndPointCommand; +}; + +class QDESIGNER_SHARED_EXPORT CECommand : public QUndoCommand, public CETypes +{ +public: + explicit CECommand(ConnectionEdit *edit) + : m_edit(edit) {} + + bool mergeWith(const QUndoCommand *) override { return false; } + + ConnectionEdit *edit() const { return m_edit; } + +private: + ConnectionEdit *m_edit; +}; + +class QDESIGNER_SHARED_EXPORT AddConnectionCommand : public CECommand +{ +public: + AddConnectionCommand(ConnectionEdit *edit, Connection *con); + void redo() override; + void undo() override; +private: + Connection *m_con; +}; + +class QDESIGNER_SHARED_EXPORT DeleteConnectionsCommand : public CECommand +{ +public: + DeleteConnectionsCommand(ConnectionEdit *edit, const ConnectionList &con_list); + void redo() override; + void undo() override; +private: + ConnectionList m_con_list; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // CONNECTIONEDIT_H diff --git a/src/designer/src/lib/shared/csshighlighter.cpp b/src/designer/src/lib/shared/csshighlighter.cpp new file mode 100644 index 0000000..d34e7e4 --- /dev/null +++ b/src/designer/src/lib/shared/csshighlighter.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "csshighlighter_p.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +CssHighlighter::CssHighlighter(const CssHighlightColors &colors, + QTextDocument *document) + : QSyntaxHighlighter(document), m_colors(colors) +{ +} + +void CssHighlighter::highlightBlock(const QString& text) +{ + enum Token { ALNUM, LBRACE, RBRACE, COLON, SEMICOLON, COMMA, QUOTE, SLASH, STAR }; + static const int transitions[10][9] = { + { Selector, Property, Selector, Pseudo, Property, Selector, Quote, MaybeComment, Selector }, // Selector + { Property, Property, Selector, Value, Property, Property, Quote, MaybeComment, Property }, // Property + { Value, Property, Selector, Value, Property, Value, Quote, MaybeComment, Value }, // Value + { Pseudo1, Property, Selector, Pseudo2, Selector, Selector, Quote, MaybeComment, Pseudo }, // Pseudo + { Pseudo1, Property, Selector, Pseudo, Selector, Selector, Quote, MaybeComment, Pseudo1 }, // Pseudo1 + { Pseudo2, Property, Selector, Pseudo, Selector, Selector, Quote, MaybeComment, Pseudo2 }, // Pseudo2 + { Quote, Quote, Quote, Quote, Quote, Quote, -1, Quote, Quote }, // Quote + { -1, -1, -1, -1, -1, -1, -1, -1, Comment }, // MaybeComment + { Comment, Comment, Comment, Comment, Comment, Comment, Comment, Comment, MaybeCommentEnd }, // Comment + { Comment, Comment, Comment, Comment, Comment, Comment, Comment, -1, MaybeCommentEnd } // MaybeCommentEnd + }; + + qsizetype lastIndex = 0; + bool lastWasSlash = false; + int state = previousBlockState(), save_state; + if (state == -1) { + // As long as the text is empty, leave the state undetermined + if (text.isEmpty()) { + setCurrentBlockState(-1); + return; + } + // The initial state is based on the precense of a : and the absense of a {. + // This is because Qt style sheets support both a full stylesheet as well as + // an inline form with just properties. + state = save_state = (text.indexOf(u':') > -1 && + text.indexOf(u'{') == -1) ? Property : Selector; + } else { + save_state = state>>16; + state &= 0x00ff; + } + + if (state == MaybeCommentEnd) { + state = Comment; + } else if (state == MaybeComment) { + state = save_state; + } + + for (qsizetype i = 0; i < text.size(); ++i) { + int token = ALNUM; + const QChar c = text.at(i); + const char a = c.toLatin1(); + + if (state == Quote) { + if (a == '\\') { + lastWasSlash = true; + } else { + if (a == '\"' && !lastWasSlash) { + token = QUOTE; + } + lastWasSlash = false; + } + } else { + switch (a) { + case '{': token = LBRACE; break; + case '}': token = RBRACE; break; + case ':': token = COLON; break; + case ';': token = SEMICOLON; break; + case ',': token = COMMA; break; + case '\"': token = QUOTE; break; + case '/': token = SLASH; break; + case '*': token = STAR; break; + default: break; + } + } + + int new_state = transitions[state][token]; + + if (new_state != state) { + bool include_token = new_state == MaybeCommentEnd || (state == MaybeCommentEnd && new_state!= Comment) + || state == Quote; + highlight(text, lastIndex, i-lastIndex+include_token, state); + + if (new_state == Comment) { + lastIndex = i-1; // include the slash and star + } else { + lastIndex = i + ((token == ALNUM || new_state == Quote) ? 0 : 1); + } + } + + if (new_state == -1) { + state = save_state; + } else if (state <= Pseudo2) { + save_state = state; + state = new_state; + } else { + state = new_state; + } + } + + highlight(text, lastIndex, text.size() - lastIndex, state); + setCurrentBlockState(state + (save_state<<16)); +} + +void CssHighlighter::highlight(const QString &text, int start, int length, int state) +{ + if (start >= text.size() || length <= 0) + return; + + QTextCharFormat format; + + switch (state) { + case Selector: + setFormat(start, length, m_colors.selector); + break; + case Property: + setFormat(start, length, m_colors.property); + break; + case Value: + setFormat(start, length, m_colors.value); + break; + case Pseudo1: + setFormat(start, length, m_colors.pseudo1); + break; + case Pseudo2: + setFormat(start, length, m_colors.pseudo2); + break; + case Quote: + setFormat(start, length, m_colors.quote); + break; + case Comment: + case MaybeCommentEnd: + format.setForeground(m_colors.comment); + setFormat(start, length, format); + break; + default: + break; + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/csshighlighter_p.h b/src/designer/src/lib/shared/csshighlighter_p.h new file mode 100644 index 0000000..bad7e9f --- /dev/null +++ b/src/designer/src/lib/shared/csshighlighter_p.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef CSSHIGHLIGHTER_H +#define CSSHIGHLIGHTER_H + +#include +#include +#include "shared_global_p.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +struct CssHighlightColors +{ + QColor selector; + QColor property; + QColor value; + QColor pseudo1; + QColor pseudo2; + QColor quote; + QColor comment; +}; + +class QDESIGNER_SHARED_EXPORT CssHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT +public: + explicit CssHighlighter(const CssHighlightColors &colors, + QTextDocument *document); + +protected: + void highlightBlock(const QString&) override; + void highlight(const QString&, int, int, int/*State*/); + +private: + enum State { Selector, Property, Value, Pseudo, Pseudo1, Pseudo2, Quote, + MaybeComment, Comment, MaybeCommentEnd }; + + const CssHighlightColors m_colors; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // CSSHIGHLIGHTER_H diff --git a/src/designer/src/lib/shared/defaultgradients.xml b/src/designer/src/lib/shared/defaultgradients.xml new file mode 100644 index 0000000..70559ad --- /dev/null +++ b/src/designer/src/lib/shared/defaultgradients.xml @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/designer/src/lib/shared/deviceprofile.cpp b/src/designer/src/lib/shared/deviceprofile.cpp new file mode 100644 index 0000000..25148b5 --- /dev/null +++ b/src/designer/src/lib/shared/deviceprofile.cpp @@ -0,0 +1,422 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "deviceprofile_p.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + + +static const char dpiXPropertyC[] = "_q_customDpiX"; +static const char dpiYPropertyC[] = "_q_customDpiY"; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// XML serialization +static const char *xmlVersionC="1.0"; +static const char *rootElementC="deviceprofile"; +static constexpr auto nameElementC = "name"_L1; +static constexpr auto fontFamilyElementC = "fontfamily"_L1; +static constexpr auto fontPointSizeElementC = "fontpointsize"_L1; +static constexpr auto dPIXElementC = "dpix"_L1; +static constexpr auto dPIYElementC = "dpiy"_L1; +static constexpr auto styleElementC = "style"_L1; + +/* DeviceProfile: + * For preview purposes (preview, widget box, new form dialog), the + * QDesignerFormBuilder applies the settings to the form main container + * (Point being here that the DPI must be set directly for size calculations + * to be correct). + * For editing purposes, FormWindow applies the settings to the form container + * as not to interfere with the font settings of the form main container. + * In addition, the widgetfactory maintains the system settings style + * and applies it when creating widgets. */ + +// ---------------- DeviceProfileData +class DeviceProfileData : public QSharedData { +public: + DeviceProfileData() = default; + void fromSystem(); + void clear(); + + QString m_fontFamily; + QString m_style; + QString m_name; + int m_fontPointSize = -1; + int m_dpiX = -1; + int m_dpiY = -1; +}; + +void DeviceProfileData::clear() +{ + m_fontPointSize = -1; + m_dpiX = 0; + m_dpiY = 0; + m_name.clear(); + m_style.clear(); +} + +void DeviceProfileData::fromSystem() +{ + const QFont appFont = QApplication::font(); + m_fontFamily = appFont.family(); + m_fontPointSize = appFont.pointSize(); + DeviceProfile::systemResolution(&m_dpiX, &m_dpiY); + m_style.clear(); +} + +// ---------------- DeviceProfile +DeviceProfile::DeviceProfile() : + m_d(new DeviceProfileData) +{ +} + +DeviceProfile::DeviceProfile(const DeviceProfile &o) : + m_d(o.m_d) + +{ +} + +DeviceProfile& DeviceProfile::operator=(const DeviceProfile &o) +{ + m_d.operator=(o.m_d); + return *this; +} + +DeviceProfile::~DeviceProfile() = default; + +void DeviceProfile::clear() +{ + m_d->clear(); +} + +bool DeviceProfile::isEmpty() const +{ + return m_d->m_name.isEmpty(); +} + +QString DeviceProfile::fontFamily() const +{ + return m_d->m_fontFamily; +} + +void DeviceProfile::setFontFamily(const QString &f) +{ + m_d->m_fontFamily = f; +} + +int DeviceProfile::fontPointSize() const +{ + return m_d->m_fontPointSize; +} + +void DeviceProfile::setFontPointSize(int p) +{ + m_d->m_fontPointSize = p; +} + +QString DeviceProfile::style() const +{ + return m_d->m_style; +} + +void DeviceProfile::setStyle(const QString &s) +{ + m_d->m_style = s; +} + +int DeviceProfile::dpiX() const +{ + return m_d->m_dpiX; +} + +void DeviceProfile::setDpiX(int d) +{ + m_d->m_dpiX = d; +} + +int DeviceProfile::dpiY() const +{ + return m_d->m_dpiY; +} + +void DeviceProfile::setDpiY(int d) +{ + m_d->m_dpiY = d; +} + +void DeviceProfile::fromSystem() +{ + m_d->fromSystem(); +} + +QString DeviceProfile::name() const +{ + return m_d->m_name; +} + +void DeviceProfile::setName(const QString &n) +{ + m_d->m_name = n; +} + +void DeviceProfile::systemResolution(int *dpiX, int *dpiY) +{ + auto s = qApp->primaryScreen(); + *dpiX = s->logicalDotsPerInchX(); + *dpiY = s->logicalDotsPerInchY(); +} + +class FriendlyWidget : public QWidget { + friend class DeviceProfile; +}; + +void DeviceProfile::widgetResolution(const QWidget *w, int *dpiX, int *dpiY) +{ + const FriendlyWidget *fw = static_cast(w); + *dpiX = fw->metric(QPaintDevice::PdmDpiX); + *dpiY = fw->metric(QPaintDevice::PdmDpiY); +} + +QString DeviceProfile::toString() const +{ + const DeviceProfileData &d = *m_d; + QString rc; + QTextStream(&rc) << "DeviceProfile:name=" << d.m_name << " Font=" << d.m_fontFamily << ' ' + << d.m_fontPointSize << " Style=" << d.m_style << " DPI=" << d.m_dpiX << ',' << d.m_dpiY; + return rc; +} + +// Apply font to widget +static void applyFont(const QString &family, int size, DeviceProfile::ApplyMode am, QWidget *widget) +{ + QFont currentFont = widget->font(); + if (currentFont.pointSize() == size && currentFont.family() == family) + return; + switch (am) { + case DeviceProfile::ApplyFormParent: + // Invisible form parent: Apply all + widget->setFont(QFont(family, size)); + break; + case DeviceProfile::ApplyPreview: { + // Preview: Apply only subproperties that have not been changed by designer properties + bool apply = false; + const uint resolve = currentFont.resolveMask(); + if (!(resolve & QFont::FamilyResolved)) { + currentFont.setFamily(family); + apply = true; + } + if (!(resolve & QFont::SizeResolved)) { + currentFont.setPointSize(size); + apply = true; + } + if (apply) + widget->setFont(currentFont); + } + break; + } +} + +void DeviceProfile::applyDPI(int dpiX, int dpiY, QWidget *widget) +{ + int sysDPIX, sysDPIY; // Set dynamic variables in case values are different from system DPI + systemResolution(&sysDPIX, &sysDPIY); + if (dpiX != sysDPIX && dpiY != sysDPIY) { + widget->setProperty(dpiXPropertyC, QVariant(dpiX)); + widget->setProperty(dpiYPropertyC, QVariant(dpiY)); + } +} + +void DeviceProfile::apply(const QDesignerFormEditorInterface *core, QWidget *widget, ApplyMode am) const +{ + if (isEmpty()) + return; + + const DeviceProfileData &d = *m_d; + + if (!d.m_fontFamily.isEmpty()) + applyFont(d.m_fontFamily, d.m_fontPointSize, am, widget); + + applyDPI(d.m_dpiX, d.m_dpiY, widget); + + if (!d.m_style.isEmpty()) { + if (WidgetFactory *wf = qobject_cast(core->widgetFactory())) + wf->applyStyleTopLevel(d.m_style, widget); + } +} + +bool comparesEqual(const DeviceProfile &lhs, const DeviceProfile &rhs) noexcept +{ + const DeviceProfileData &d = *lhs.m_d; + const DeviceProfileData &rhs_d = *rhs.m_d; + return d.m_fontPointSize == rhs_d.m_fontPointSize && + d.m_dpiX == rhs_d.m_dpiX && d.m_dpiY == rhs_d.m_dpiY && d.m_fontFamily == rhs_d.m_fontFamily && + d.m_style == rhs_d.m_style && d.m_name == rhs_d.m_name; +} + +static inline void writeElement(QXmlStreamWriter &writer, const QString &element, const QString &cdata) +{ + writer.writeStartElement(element); + writer.writeCharacters(cdata); + writer.writeEndElement(); +} + +QString DeviceProfile::toXml() const +{ + const DeviceProfileData &d = *m_d; + QString rc; + QXmlStreamWriter writer(&rc); + writer.writeStartDocument(QLatin1StringView(xmlVersionC)); + writer.writeStartElement(QLatin1StringView(rootElementC)); + writeElement(writer, nameElementC, d.m_name); + + if (!d.m_fontFamily.isEmpty()) + writeElement(writer, fontFamilyElementC, d.m_fontFamily); + if (d.m_fontPointSize >= 0) + writeElement(writer, fontPointSizeElementC, QString::number(d.m_fontPointSize)); + if (d.m_dpiX > 0) + writeElement(writer, dPIXElementC, QString::number(d.m_dpiX)); + if (d.m_dpiY > 0) + writeElement(writer, dPIYElementC, QString::number(d.m_dpiY)); + if (!d.m_style.isEmpty()) + writeElement(writer, styleElementC, d.m_style); + + writer.writeEndElement(); + writer.writeEndDocument(); + return rc; +} + +/* Switch stages when encountering a start element (state table) */ +enum ParseStage { ParseBeginning, ParseWithinRoot, + ParseName, ParseFontFamily, ParseFontPointSize, ParseDPIX, ParseDPIY, ParseStyle, + ParseError }; + +static ParseStage nextStage(ParseStage currentStage, QStringView startElement) +{ + switch (currentStage) { + case ParseBeginning: + if (startElement == QLatin1StringView(rootElementC)) + return ParseWithinRoot; + break; + case ParseWithinRoot: + case ParseName: + case ParseFontFamily: + case ParseFontPointSize: + case ParseDPIX: + case ParseDPIY: + case ParseStyle: + if (startElement == nameElementC) + return ParseName; + if (startElement == fontFamilyElementC) + return ParseFontFamily; + if (startElement == fontPointSizeElementC) + return ParseFontPointSize; + if (startElement == dPIXElementC) + return ParseDPIX; + if (startElement == dPIYElementC) + return ParseDPIY; + if (startElement == styleElementC) + return ParseStyle; + break; + case ParseError: + break; + } + return ParseError; +} + +static bool readIntegerElement(QXmlStreamReader &reader, int *v) +{ + const QString e = reader.readElementText(); + bool ok; + *v = e.toInt(&ok); + //: Reading a number for an embedded device profile + if (!ok) + reader.raiseError(QApplication::translate("DeviceProfile", "'%1' is not a number.").arg(e)); + return ok; +} + +bool DeviceProfile::fromXml(const QString &xml, QString *errorMessage) +{ + DeviceProfileData &d = *m_d; + d.fromSystem(); + + QXmlStreamReader reader(xml); + + ParseStage ps = ParseBeginning; + QXmlStreamReader::TokenType tt = QXmlStreamReader::NoToken; + int iv = 0; + do { + tt = reader.readNext(); + if (tt == QXmlStreamReader::StartElement) { + ps = nextStage(ps, reader.name()); + switch (ps) { + case ParseBeginning: + case ParseWithinRoot: + break; + case ParseError: + reader.raiseError(QApplication::translate("DeviceProfile", "An invalid tag <%1> was encountered.").arg(reader.name().toString())); + tt = QXmlStreamReader::Invalid; + break; + case ParseName: + d.m_name = reader.readElementText(); + break; + case ParseFontFamily: + d.m_fontFamily = reader.readElementText(); + break; + case ParseFontPointSize: + if (readIntegerElement(reader, &iv)) { + d.m_fontPointSize = iv; + } else { + tt = QXmlStreamReader::Invalid; + } + break; + case ParseDPIX: + if (readIntegerElement(reader, &iv)) { + d.m_dpiX = iv; + } else { + tt = QXmlStreamReader::Invalid; + } + break; + case ParseDPIY: + if (readIntegerElement(reader, &iv)) { + d.m_dpiY = iv; + } else { + tt = QXmlStreamReader::Invalid; + } + break; + case ParseStyle: + d.m_style = reader.readElementText(); + break; + } + } + } while (tt != QXmlStreamReader::Invalid && tt != QXmlStreamReader::EndDocument); + + if (reader.hasError()) { + *errorMessage = reader.errorString(); + return false; + } + + return true; +} +} + +QT_END_NAMESPACE + diff --git a/src/designer/src/lib/shared/deviceprofile_p.h b/src/designer/src/lib/shared/deviceprofile_p.h new file mode 100644 index 0000000..273a1a1 --- /dev/null +++ b/src/designer/src/lib/shared/deviceprofile_p.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DEVICEPROFILE_H +#define DEVICEPROFILE_H + +#include "shared_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QWidget; +class QStyle; + +namespace qdesigner_internal { + +class DeviceProfileData; + +/* DeviceProfile for embedded design. They influence + * default properties (for example, fonts), dpi and + * style of the form. This class represents a device + * profile. */ + +class QDESIGNER_SHARED_EXPORT DeviceProfile { +public: + DeviceProfile(); + + DeviceProfile(const DeviceProfile&); + DeviceProfile& operator=(const DeviceProfile&); + ~DeviceProfile(); + + void clear(); + + // Device name + QString name() const; + void setName(const QString &); + + // System settings active + bool isEmpty() const; + + // Default font family of the embedded system + QString fontFamily() const; + void setFontFamily(const QString &); + + // Default font size of the embedded system + int fontPointSize() const; + void setFontPointSize(int p); + + // Display resolution of the embedded system + int dpiX() const; + void setDpiX(int d); + int dpiY() const; + void setDpiY(int d); + + // Style + QString style() const; + void setStyle(const QString &); + + // Initialize from desktop system + void fromSystem(); + + static void systemResolution(int *dpiX, int *dpiY); + static void widgetResolution(const QWidget *w, int *dpiX, int *dpiY); + + // Apply to form/preview (using font inheritance) + enum ApplyMode { + /* Pre-Apply to parent widget of form being edited: Apply font + * and make use of property inheritance to be able to modify the + * font property freely. */ + ApplyFormParent, + /* Post-Apply to preview widget: Change only inherited font + * sub properties. */ + ApplyPreview + }; + void apply(const QDesignerFormEditorInterface *core, QWidget *widget, ApplyMode am) const; + + static void applyDPI(int dpiX, int dpiY, QWidget *widget); + + QString toString() const; + + QString toXml() const; + bool fromXml(const QString &xml, QString *errorMessage); + +private: + friend QDESIGNER_SHARED_EXPORT bool comparesEqual(const DeviceProfile &lhs, + const DeviceProfile &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(DeviceProfile) + + QSharedDataPointer m_d; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DEVICEPROFILE_H diff --git a/src/designer/src/lib/shared/dialoggui.cpp b/src/designer/src/lib/shared/dialoggui.cpp new file mode 100644 index 0000000..168462a --- /dev/null +++ b/src/designer/src/lib/shared/dialoggui.cpp @@ -0,0 +1,221 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "dialoggui_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +// QFileDialog on X11 does not provide an image preview. Display icons. +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +# define IMAGE_PREVIEW +#endif + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Icon provider that reads out the known image formats +class IconProvider : public QFileIconProvider { + Q_DISABLE_COPY_MOVE(IconProvider) + +public: + IconProvider(); + QIcon icon (const QFileInfo &info) const override; + + inline bool loadCheck(const QFileInfo &info) const; + QImage loadImage(const QString &fileName) const; + +private: + QSet m_imageFormats; +}; + +IconProvider::IconProvider() +{ + // Determine a list of readable extensions (upper and lower case) + const auto &fmts = QImageReader::supportedImageFormats(); + for (const QByteArray &fmt : fmts) { + const QString suffix = QString::fromUtf8(fmt); + m_imageFormats.insert(suffix.toLower()); + m_imageFormats.insert(suffix.toUpper()); + } +} + +// Check by extension and type if this appears to be a loadable image +bool IconProvider::loadCheck(const QFileInfo &info) const +{ + if (info.isFile() && info.isReadable()) { + const QString suffix = info.suffix(); + if (!suffix.isEmpty()) + return m_imageFormats.contains(suffix); + } + return false; +} + +QImage IconProvider::loadImage(const QString &fileName) const +{ + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + QImageReader imgReader(&file); + if (imgReader.canRead()) { + QImage image; + if (imgReader.read(&image)) + return image; + } + } + return QImage(); +} + +QIcon IconProvider::icon (const QFileInfo &info) const +{ + // Don't get stuck on large images. + const qint64 maxSize = 131072; + if (loadCheck(info) && info.size() < maxSize) { + const QImage image = loadImage(info.absoluteFilePath()); + if (!image.isNull()) + return QIcon(QPixmap::fromImage(image, Qt::ThresholdDither|Qt::AutoColor)); + } + return QFileIconProvider::icon(info); +} + +// ---------------- DialogGui +DialogGui::DialogGui() = default; + +DialogGui::~DialogGui() +{ + delete m_iconProvider; +} + +QFileIconProvider *DialogGui::ensureIconProvider() +{ + if (!m_iconProvider) + m_iconProvider = new IconProvider; + return m_iconProvider; +} + +QMessageBox::StandardButton + DialogGui::message(QWidget *parent, Message /*context*/, QMessageBox::Icon icon, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) +{ + QMessageBox::StandardButton rc = QMessageBox::NoButton; + switch (icon) { + case QMessageBox::Information: + rc = QMessageBox::information(parent, title, text, buttons, defaultButton); + break; + case QMessageBox::Warning: + rc = QMessageBox::warning(parent, title, text, buttons, defaultButton); + break; + case QMessageBox::Critical: + rc = QMessageBox::critical(parent, title, text, buttons, defaultButton); + break; + case QMessageBox::Question: + rc = QMessageBox::question(parent, title, text, buttons, defaultButton); + break; + case QMessageBox::NoIcon: + break; + } + return rc; +} + +QMessageBox::StandardButton + DialogGui::message(QWidget *parent, Message /*context*/, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) +{ + QMessageBox msgBox(icon, title, text, buttons, parent); + msgBox.setDefaultButton(defaultButton); + msgBox.setInformativeText(informativeText); + return static_cast(msgBox.exec()); +} + +QMessageBox::StandardButton + DialogGui::message(QWidget *parent, Message /*context*/, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, const QString &detailedText, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) +{ + QMessageBox msgBox(icon, title, text, buttons, parent); + msgBox.setDefaultButton(defaultButton); + msgBox.setInformativeText(informativeText); + msgBox.setDetailedText(detailedText); + return static_cast(msgBox.exec()); +} + +QString DialogGui::getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options) +{ + return QFileDialog::getExistingDirectory(parent, caption, dir, options); +} + +QString DialogGui::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); +} + +QStringList DialogGui::getOpenFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return QFileDialog::getOpenFileNames(parent, caption, dir, filter, selectedFilter, options); +} + +QString DialogGui::getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options); +} + +void DialogGui::initializeImageFileDialog(QFileDialog &fileDialog, QFileDialog::Options options, QFileDialog::FileMode fm) +{ + fileDialog.setOption(QFileDialog::DontConfirmOverwrite, options.testFlag(QFileDialog::DontConfirmOverwrite)); + fileDialog.setOption(QFileDialog::DontResolveSymlinks, options.testFlag(QFileDialog::DontResolveSymlinks)); + fileDialog.setIconProvider(ensureIconProvider()); + fileDialog.setFileMode(fm); +} + +QString DialogGui::getOpenImageFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options ) +{ + +#ifdef IMAGE_PREVIEW + QFileDialog fileDialog(parent, caption, dir, filter); + initializeImageFileDialog(fileDialog, options, QFileDialog::ExistingFile); + if (fileDialog.exec() != QDialog::Accepted) + return QString(); + + const QStringList selectedFiles = fileDialog.selectedFiles(); + if (selectedFiles.isEmpty()) + return QString(); + + if (selectedFilter) + *selectedFilter = fileDialog.selectedNameFilter(); + + return selectedFiles.constFirst(); +#else + return getOpenFileName(parent, caption, dir, filter, selectedFilter, options); +#endif +} + +QStringList DialogGui::getOpenImageFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options ) +{ +#ifdef IMAGE_PREVIEW + QFileDialog fileDialog(parent, caption, dir, filter); + initializeImageFileDialog(fileDialog, options, QFileDialog::ExistingFiles); + if (fileDialog.exec() != QDialog::Accepted) + return QStringList(); + + const QStringList selectedFiles = fileDialog.selectedFiles(); + if (!selectedFiles.isEmpty() && selectedFilter) + *selectedFilter = fileDialog.selectedNameFilter(); + + return selectedFiles; +#else + return getOpenFileNames(parent, caption, dir, filter, selectedFilter, options); +#endif +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/dialoggui_p.h b/src/designer/src/lib/shared/dialoggui_p.h new file mode 100644 index 0000000..a60c904 --- /dev/null +++ b/src/designer/src/lib/shared/dialoggui_p.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DIALOGGUI +#define DIALOGGUI + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include "shared_global_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QFileIconProvider; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT DialogGui : public QDesignerDialogGuiInterface +{ +public: + DialogGui(); + ~DialogGui() override; + + QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) override; + + QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) override; + + QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, const QString &detailedText, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) override; + + QString getExistingDirectory(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), QFileDialog::Options options = QFileDialog::ShowDirsOnly) override; + QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + QStringList getOpenFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + QString getSaveFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + + QString getOpenImageFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + QStringList getOpenImageFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + +private: + QFileIconProvider *ensureIconProvider(); + void initializeImageFileDialog(QFileDialog &fd, QFileDialog::Options options, QFileDialog::FileMode); + + QFileIconProvider *m_iconProvider = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DIALOGGUI diff --git a/src/designer/src/lib/shared/extensionfactory_p.h b/src/designer/src/lib/shared/extensionfactory_p.h new file mode 100644 index 0000000..7c34728 --- /dev/null +++ b/src/designer/src/lib/shared/extensionfactory_p.h @@ -0,0 +1,82 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef SHARED_EXTENSIONFACTORY_H +#define SHARED_EXTENSIONFACTORY_H + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Extension factory for registering an extension for an object type. +template +class ExtensionFactory: public QExtensionFactory +{ +public: + explicit ExtensionFactory(const QString &iid, QExtensionManager *parent = nullptr); + + // Convenience for registering the extension. Do not use for derived classes. + static void registerExtension(QExtensionManager *mgr, const QString &iid); + +protected: + QObject *createExtension(QObject *qObject, const QString &iid, QObject *parent) const override; + +private: + // Can be overwritten to perform checks on the object. + // Default does a qobject_cast to the desired class. + virtual Object *checkObject(QObject *qObject) const; + + const QString m_iid; +}; + +template +ExtensionFactory::ExtensionFactory(const QString &iid, QExtensionManager *parent) : + QExtensionFactory(parent), + m_iid(iid) +{ +} + +template +Object *ExtensionFactory::checkObject(QObject *qObject) const +{ + return qobject_cast(qObject); +} + +template +QObject *ExtensionFactory::createExtension(QObject *qObject, const QString &iid, QObject *parent) const +{ + if (iid != m_iid) + return nullptr; + + Object *object = checkObject(qObject); + if (!object) + return nullptr; + + return new Extension(object, parent); +} + +template +void ExtensionFactory::registerExtension(QExtensionManager *mgr, const QString &iid) +{ + ExtensionFactory *factory = new ExtensionFactory(iid, mgr); + mgr->registerExtensions(factory, iid); +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SHARED_EXTENSIONFACTORY_H diff --git a/src/designer/src/lib/shared/formlayoutmenu.cpp b/src/designer/src/lib/shared/formlayoutmenu.cpp new file mode 100644 index 0000000..0c73137 --- /dev/null +++ b/src/designer/src/lib/shared/formlayoutmenu.cpp @@ -0,0 +1,491 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formlayoutmenu_p.h" +#include "layoutinfo_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_propertycommand_p.h" +#include "ui_formlayoutrowdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto buddyPropertyC = "buddy"_L1; +static const char *fieldWidgetBaseClasses[] = { + "QLineEdit", "QComboBox", "QSpinBox", "QDoubleSpinBox", "QCheckBox", + "QDateEdit", "QTimeEdit", "QDateTimeEdit", "QDial", "QWidget" +}; + +namespace qdesigner_internal { + +// Struct that describes a row of controls (descriptive label and control) to +// be added to a form layout. +struct FormLayoutRow { + QString labelName; + QString labelText; + QString fieldClassName; + QString fieldName; + bool buddy{false}; +}; + +// A Dialog to edit a FormLayoutRow. Lets the user input a label text, label +// name, field widget type, field object name and buddy setting. As the +// user types the label text; the object names to be used for label and field +// are updated. It also checks the buddy setting depending on whether the +// label text contains a buddy marker. +class FormLayoutRowDialog : public QDialog { + Q_DISABLE_COPY_MOVE(FormLayoutRowDialog) + Q_OBJECT +public: + explicit FormLayoutRowDialog(QDesignerFormEditorInterface *core, + QWidget *parent); + + FormLayoutRow formLayoutRow() const; + + bool buddy() const; + void setBuddy(bool); + + // Accessors for form layout row numbers using 0..[n-1] convention + int row() const; + void setRow(int); + void setRowRange(int, int); + + QString fieldClass() const; + QString labelText() const; + + static QStringList fieldWidgetClasses(QDesignerFormEditorInterface *core); + +private slots: + void labelTextEdited(const QString &text); + void labelNameEdited(const QString &text); + void fieldNameEdited(const QString &text); + void buddyClicked(); + void fieldClassChanged(int); + +private: + bool isValid() const; + void updateObjectNames(bool updateLabel, bool updateField); + void updateOkButton(); + + // Check for buddy marker in string + const QRegularExpression m_buddyMarkerRegexp; + + QT_PREPEND_NAMESPACE(Ui)::FormLayoutRowDialog m_ui; + bool m_labelNameEdited; + bool m_fieldNameEdited; + bool m_buddyClicked; +}; + +FormLayoutRowDialog::FormLayoutRowDialog(QDesignerFormEditorInterface *core, + QWidget *parent) : + QDialog(parent), + m_buddyMarkerRegexp(u"\\&[^&]"_s), + m_labelNameEdited(false), + m_fieldNameEdited(false), + m_buddyClicked(false) +{ + Q_ASSERT(m_buddyMarkerRegexp.isValid()); + + setModal(true); + m_ui.setupUi(this); + connect(m_ui.labelTextLineEdit, &QLineEdit::textEdited, this, &FormLayoutRowDialog::labelTextEdited); + + auto *nameValidator = new QRegularExpressionValidator(QRegularExpression(u"^[a-zA-Z0-9_]+$"_s), this); + Q_ASSERT(nameValidator->regularExpression().isValid()); + + m_ui.labelNameLineEdit->setValidator(nameValidator); + connect(m_ui.labelNameLineEdit, &QLineEdit::textEdited, + this, &FormLayoutRowDialog::labelNameEdited); + + m_ui.fieldNameLineEdit->setValidator(nameValidator); + connect(m_ui.fieldNameLineEdit, &QLineEdit::textEdited, + this, &FormLayoutRowDialog::fieldNameEdited); + + connect(m_ui.buddyCheckBox, &QAbstractButton::clicked, this, &FormLayoutRowDialog::buddyClicked); + + m_ui.fieldClassComboBox->addItems(fieldWidgetClasses(core)); + m_ui.fieldClassComboBox->setCurrentIndex(0); + connect(m_ui.fieldClassComboBox, + &QComboBox::currentIndexChanged, + this, &FormLayoutRowDialog::fieldClassChanged); + + updateOkButton(); +} + +FormLayoutRow FormLayoutRowDialog::formLayoutRow() const +{ + FormLayoutRow rc; + rc.labelText = labelText(); + rc.labelName = m_ui.labelNameLineEdit->text(); + rc.fieldClassName = fieldClass(); + rc.fieldName = m_ui.fieldNameLineEdit->text(); + rc.buddy = buddy(); + return rc; +} + +bool FormLayoutRowDialog::buddy() const +{ + return m_ui.buddyCheckBox->checkState() == Qt::Checked; +} + +void FormLayoutRowDialog::setBuddy(bool b) +{ + m_ui.buddyCheckBox->setCheckState(b ? Qt::Checked : Qt::Unchecked); +} + +// Convert rows to 1..n convention for users +int FormLayoutRowDialog::row() const +{ + return m_ui.rowSpinBox->value() - 1; +} + +void FormLayoutRowDialog::setRow(int row) +{ + m_ui.rowSpinBox->setValue(row + 1); +} + +void FormLayoutRowDialog::setRowRange(int from, int to) +{ + m_ui.rowSpinBox->setMinimum(from + 1); + m_ui.rowSpinBox->setMaximum(to + 1); + m_ui.rowSpinBox->setEnabled(to - from > 0); +} + +QString FormLayoutRowDialog::fieldClass() const +{ + return m_ui.fieldClassComboBox->itemText(m_ui.fieldClassComboBox->currentIndex()); +} + +QString FormLayoutRowDialog::labelText() const +{ + return m_ui.labelTextLineEdit->text(); +} + +bool FormLayoutRowDialog::isValid() const +{ + // Check for non-empty names and presence of buddy marker if checked + const QString name = labelText(); + if (name.isEmpty() || m_ui.labelNameLineEdit->text().isEmpty() || m_ui.fieldNameLineEdit->text().isEmpty()) + return false; + if (buddy() && !name.contains(m_buddyMarkerRegexp)) + return false; + return true; +} + +void FormLayoutRowDialog::updateOkButton() +{ + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isValid()); +} + +void FormLayoutRowDialog::labelTextEdited(const QString &text) +{ + updateObjectNames(true, true); + // Set buddy if '&' is present unless the user changed it + if (!m_buddyClicked) + setBuddy(text.contains(m_buddyMarkerRegexp)); + + updateOkButton(); +} + +// Get a suitable object name postfix from a class name: +// "namespace::QLineEdit"->"LineEdit" +static inline QString postFixFromClassName(QString className) +{ + const int index = className.lastIndexOf("::"_L1); + if (index != -1) + className.remove(0, index + 2); + if (className.size() > 2) + if (className.at(0) == u'Q' || className.at(0) == u'K') + if (className.at(1).isUpper()) + className.remove(0, 1); + return className; +} + +// Helper routines to filter out characters for converting texts into +// class name prefixes. Only accepts ASCII characters/digits and underscores. + +enum PrefixCharacterKind { PC_Digit, PC_UpperCaseLetter, PC_LowerCaseLetter, + PC_Other, PC_Invalid }; + +static inline PrefixCharacterKind prefixCharacterKind(const QChar &c) +{ + switch (c.category()) { + case QChar::Number_DecimalDigit: + return PC_Digit; + case QChar::Letter_Lowercase: { + const char a = c.toLatin1(); + if (a >= 'a' && a <= 'z') + return PC_LowerCaseLetter; + } + break; + case QChar::Letter_Uppercase: { + const char a = c.toLatin1(); + if (a >= 'A' && a <= 'Z') + return PC_UpperCaseLetter; + } + break; + case QChar::Punctuation_Connector: + if (c.toLatin1() == '_') + return PC_Other; + break; + default: + break; + } + return PC_Invalid; +} + +// Convert the text the user types into a usable class name prefix by filtering +// characters, lower-casing the first character and camel-casing subsequent +// words. ("zip code:") --> ("zipCode"). + +static QString prefixFromLabel(const QString &prefix) +{ + QString rc; + bool lastWasAcceptable = false; + for (const QChar &c : prefix) { + const PrefixCharacterKind kind = prefixCharacterKind(c); + const bool acceptable = kind != PC_Invalid; + if (acceptable) { + if (rc.isEmpty()) { + // Lower-case first character + rc += kind == PC_UpperCaseLetter ? c.toLower() : c; + } else { + // Camel-case words + rc += !lastWasAcceptable && kind == PC_LowerCaseLetter ? c.toUpper() : c; + } + } + lastWasAcceptable = acceptable; + } + return rc; +} + +void FormLayoutRowDialog::updateObjectNames(bool updateLabel, bool updateField) +{ + // Generate label + field object names from the label text, that is, + // "&Zip code:" -> "zipcodeLabel", "zipcodeLineEdit" unless the user + // edited it. + const bool doUpdateLabel = !m_labelNameEdited && updateLabel; + const bool doUpdateField = !m_fieldNameEdited && updateField; + if (!doUpdateLabel && !doUpdateField) + return; + + const QString prefix = prefixFromLabel(labelText()); + // Set names + if (doUpdateLabel) + m_ui.labelNameLineEdit->setText(prefix + "Label"_L1); + if (doUpdateField) + m_ui.fieldNameLineEdit->setText(prefix + postFixFromClassName(fieldClass())); +} + +void FormLayoutRowDialog::fieldClassChanged(int) +{ + updateObjectNames(false, true); +} + +void FormLayoutRowDialog::labelNameEdited(const QString & /*text*/) +{ + m_labelNameEdited = true; // stop auto-updating after user change + updateOkButton(); +} + +void FormLayoutRowDialog::fieldNameEdited(const QString & /*text*/) +{ + m_fieldNameEdited = true; // stop auto-updating after user change + updateOkButton(); +} + +void FormLayoutRowDialog::buddyClicked() +{ + m_buddyClicked = true; // stop auto-updating after user change + updateOkButton(); +} + +/* Create a list of classes suitable for field widgets. Take the fixed base + * classes provided and look in the widget database for custom widgets derived + * from them ("QLineEdit", "CustomLineEdit", "QComboBox"...). */ +QStringList FormLayoutRowDialog::fieldWidgetClasses(QDesignerFormEditorInterface *core) +{ + static QStringList rc; + if (rc.isEmpty()) { + // Turn known base classes into list + QStringList baseClasses; + for (auto fw : fieldWidgetBaseClasses) + baseClasses.append(QLatin1StringView(fw)); + // Scan for custom widgets that inherit them and store them in a + // multimap of base class->custom widgets unless we have a language + // extension installed which might do funny things with custom widgets. + QMultiHash customClassMap; // Base class -> custom widgets map + if (qt_extension(core->extensionManager(), core) == nullptr) { + const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); + const int wdbCount = wdb->count(); + for (int w = 0; w < wdbCount; ++w) { + // Check for non-container custom types that extend the + // respective base class. + const QDesignerWidgetDataBaseItemInterface *dbItem = wdb->item(w); + if (!dbItem->isPromoted() && !dbItem->isContainer() && dbItem->isCustom()) { + const int index = baseClasses.indexOf(dbItem->extends()); + if (index != -1) + customClassMap.insert(baseClasses.at(index), dbItem->name()); + } + } + } + // Compile final list, taking each base class and append custom widgets + // based on it. + for (const auto &baseClass : baseClasses) { + rc.append(baseClass); + rc += customClassMap.values(baseClass); + } + } + return rc; +} + +// ------------------ Utilities + +static QFormLayout *managedFormLayout(const QDesignerFormEditorInterface *core, const QWidget *w) +{ + QLayout *l = nullptr; + if (LayoutInfo::managedLayoutType(core, w, &l) == LayoutInfo::Form) + return qobject_cast(l); + return nullptr; +} + +// Create the widgets of a control row and apply text properties contained +// in the struct, called by addFormLayoutRow() +static std::pair + createWidgets(const FormLayoutRow &row, QWidget *parent, + QDesignerFormWindowInterface *formWindow) +{ + QDesignerFormEditorInterface *core = formWindow->core(); + QDesignerWidgetFactoryInterface *wf = core->widgetFactory(); + + std::pair rc{wf->createWidget(u"QLabel"_s, parent), + wf->createWidget(row.fieldClassName, parent)}; + // Set up properties of the label + const QString objectNameProperty = u"objectName"_s; + QDesignerPropertySheetExtension *labelSheet = qt_extension(core->extensionManager(), rc.first); + int nameIndex = labelSheet->indexOf(objectNameProperty); + labelSheet->setProperty(nameIndex, QVariant::fromValue(PropertySheetStringValue(row.labelName))); + labelSheet->setChanged(nameIndex, true); + formWindow->ensureUniqueObjectName(rc.first); + const int textIndex = labelSheet->indexOf(u"text"_s); + labelSheet->setProperty(textIndex, QVariant::fromValue(PropertySheetStringValue(row.labelText))); + labelSheet->setChanged(textIndex, true); + // Set up properties of the control + QDesignerPropertySheetExtension *controlSheet = qt_extension(core->extensionManager(), rc.second); + nameIndex = controlSheet->indexOf(objectNameProperty); + controlSheet->setProperty(nameIndex, QVariant::fromValue(PropertySheetStringValue(row.fieldName))); + controlSheet->setChanged(nameIndex, true); + formWindow->ensureUniqueObjectName(rc.second); + return rc; +} + +// Create a command sequence on the undo stack of the form window that creates +// the widgets of the row and inserts them into the form layout. +static void addFormLayoutRow(const FormLayoutRow &formLayoutRow, int row, QWidget *w, + QDesignerFormWindowInterface *formWindow) +{ + QFormLayout *formLayout = managedFormLayout(formWindow->core(), w); + Q_ASSERT(formLayout); + QUndoStack *undoStack = formWindow->commandHistory(); + const QString macroName = QCoreApplication::translate("Command", "Add '%1' to '%2'").arg(formLayoutRow.labelText, formLayout->objectName()); + undoStack->beginMacro(macroName); + + // Create a list of widget insertion commands and pass them a cell position + const auto widgetPair = createWidgets(formLayoutRow, w, formWindow); + + InsertWidgetCommand *labelCmd = new InsertWidgetCommand(formWindow); + labelCmd->init(widgetPair.first, false, row, 0); + undoStack->push(labelCmd); + InsertWidgetCommand *controlCmd = new InsertWidgetCommand(formWindow); + controlCmd->init(widgetPair.second, false, row, 1); + undoStack->push(controlCmd); + if (formLayoutRow.buddy) { + SetPropertyCommand *buddyCommand = new SetPropertyCommand(formWindow); + buddyCommand->init(widgetPair.first, buddyPropertyC, widgetPair.second->objectName()); + undoStack->push(buddyCommand); + } + undoStack->endMacro(); +} + +// ---------------- FormLayoutMenu +FormLayoutMenu::FormLayoutMenu(QObject *parent) : + QObject(parent), + m_separator1(new QAction(this)), + m_populateFormAction(new QAction(tr("Add form layout row..."), this)), + m_separator2(new QAction(this)) +{ + m_separator1->setSeparator(true); + connect(m_populateFormAction, &QAction::triggered, this, &FormLayoutMenu::slotAddRow); + m_separator2->setSeparator(true); +} + +void FormLayoutMenu::populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList &actions) +{ + switch (LayoutInfo::managedLayoutType(fw->core(), w)) { + case LayoutInfo::Form: + if (!actions.isEmpty() && !actions.constLast()->isSeparator()) + actions.push_back(m_separator1); + actions.push_back(m_populateFormAction); + actions.push_back(m_separator2); + m_widget = w; + break; + default: + m_widget = nullptr; + break; + } +} + +void FormLayoutMenu::slotAddRow() +{ + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_widget); + Q_ASSERT(m_widget && fw); + const int rowCount = managedFormLayout(fw->core(), m_widget)->rowCount(); + + FormLayoutRowDialog dialog(fw->core(), fw); + dialog.setRowRange(0, rowCount); + dialog.setRow(rowCount); + + if (dialog.exec() != QDialog::Accepted) + return; + addFormLayoutRow(dialog.formLayoutRow(), dialog.row(), m_widget, fw); +} + +QAction *FormLayoutMenu::preferredEditAction(QWidget *w, QDesignerFormWindowInterface *fw) +{ + if (LayoutInfo::managedLayoutType(fw->core(), w) == LayoutInfo::Form) { + m_widget = w; + return m_populateFormAction; + } + return nullptr; +} +} + +QT_END_NAMESPACE + +#include "formlayoutmenu.moc" + diff --git a/src/designer/src/lib/shared/formlayoutmenu_p.h b/src/designer/src/lib/shared/formlayoutmenu_p.h new file mode 100644 index 0000000..3f4bac0 --- /dev/null +++ b/src/designer/src/lib/shared/formlayoutmenu_p.h @@ -0,0 +1,62 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMLAYOUTMENU +#define FORMLAYOUTMENU + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include "shared_global_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +class QAction; +class QWidget; + +namespace qdesigner_internal { + +// Task menu to be used for form layouts. Offers an options "Add row" which +// pops up a dialog in which the user can specify label name, text and buddy. +class QDESIGNER_SHARED_EXPORT FormLayoutMenu : public QObject +{ + Q_DISABLE_COPY_MOVE(FormLayoutMenu) + Q_OBJECT +public: + using ActionList = QList; + + explicit FormLayoutMenu(QObject *parent); + + // Populate a list of actions with the form layout actions. + void populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList &actions); + // For implementing QDesignerTaskMenuExtension::preferredEditAction(): + // Return appropriate action for double clicking. + QAction *preferredEditAction(QWidget *w, QDesignerFormWindowInterface *fw); + +private slots: + void slotAddRow(); + +private: + QAction *m_separator1; + QAction *m_populateFormAction; + QAction *m_separator2; + QPointer m_widget; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMLAYOUTMENU diff --git a/src/designer/src/lib/shared/formlayoutrowdialog.ui b/src/designer/src/lib/shared/formlayoutrowdialog.ui new file mode 100644 index 0000000..c0e0cfe --- /dev/null +++ b/src/designer/src/lib/shared/formlayoutrowdialog.ui @@ -0,0 +1,166 @@ + + + FormLayoutRowDialog + + + Add Form Layout Row + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + &Label text: + + + labelTextLineEdit + + + + + + + + 180 + 0 + + + + + + + + + + + Field &type: + + + fieldClassComboBox + + + + + + + + 0 + 0 + + + + + + + + &Field name: + + + fieldNameLineEdit + + + + + + + &Buddy: + + + buddyCheckBox + + + + + + + + + + + + + + &Row: + + + rowSpinBox + + + + + + + + + + + + + Label &name: + + + labelNameLineEdit + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + FormLayoutRowDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FormLayoutRowDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/designer/src/lib/shared/formwindowbase.cpp b/src/designer/src/lib/shared/formwindowbase.cpp new file mode 100644 index 0000000..17dde04 --- /dev/null +++ b/src/designer/src/lib/shared/formwindowbase.cpp @@ -0,0 +1,549 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindowbase_p.h" +#include "connectionedit_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertysheet_p.h" +#include "qdesigner_propertyeditor_p.h" +#include "qdesigner_menu_p.h" +#include "qdesigner_menubar_p.h" +#include "shared_settings_p.h" +#include "grid_p.h" +#include "deviceprofile_p.h" +#include "qdesigner_utils_p.h" +#include "spacer_widget_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +class FormWindowBasePrivate { +public: + explicit FormWindowBasePrivate(QDesignerFormEditorInterface *core); + + static Grid m_defaultGrid; + + QDesignerFormWindowInterface::Feature m_feature; + Grid m_grid; + bool m_hasFormGrid; + DesignerPixmapCache *m_pixmapCache; + DesignerIconCache *m_iconCache; + QtResourceSet *m_resourceSet; + QHash> m_reloadableResources; + QHash m_reloadablePropertySheets; + const DeviceProfile m_deviceProfile; + FormWindowBase::LineTerminatorMode m_lineTerminatorMode; + FormWindowBase::ResourceFileSaveMode m_saveResourcesBehaviour; + bool m_useIdBasedTranslations; + bool m_connectSlotsByName; +}; + +FormWindowBasePrivate::FormWindowBasePrivate(QDesignerFormEditorInterface *core) : + m_feature(QDesignerFormWindowInterface::DefaultFeature), + m_grid(m_defaultGrid), + m_hasFormGrid(false), + m_pixmapCache(nullptr), + m_iconCache(nullptr), + m_resourceSet(nullptr), + m_deviceProfile(QDesignerSharedSettings(core).currentDeviceProfile()), + m_lineTerminatorMode(FormWindowBase::NativeLineTerminator), + m_saveResourcesBehaviour(FormWindowBase::SaveAllResourceFiles), + m_useIdBasedTranslations(false), + m_connectSlotsByName(true) +{ +} + +Grid FormWindowBasePrivate::m_defaultGrid; + +FormWindowBase::FormWindowBase(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) : + QDesignerFormWindowInterface(parent, flags), + m_d(new FormWindowBasePrivate(core)) +{ + syncGridFeature(); + m_d->m_pixmapCache = new DesignerPixmapCache(this); + m_d->m_iconCache = new DesignerIconCache(m_d->m_pixmapCache, this); + if (core->integration()->hasFeature(QDesignerIntegrationInterface::DefaultWidgetActionFeature)) + connect(this, &QDesignerFormWindowInterface::activated, this, &FormWindowBase::triggerDefaultAction); +} + +FormWindowBase::~FormWindowBase() +{ + QSet sheets; + for (auto it = m_d->m_reloadableResources.cbegin(), end = m_d->m_reloadableResources.cend(); it != end; ++it) + sheets.insert(it.key()); + for (auto it = m_d->m_reloadablePropertySheets.cbegin(), end = m_d->m_reloadablePropertySheets.cend(); it != end; ++it) + sheets.insert(it.key()); + + m_d->m_reloadableResources.clear(); + m_d->m_reloadablePropertySheets.clear(); + + for (QDesignerPropertySheet *sheet : sheets) + disconnectSheet(sheet); + + delete m_d; +} + +DesignerPixmapCache *FormWindowBase::pixmapCache() const +{ + return m_d->m_pixmapCache; +} + +DesignerIconCache *FormWindowBase::iconCache() const +{ + return m_d->m_iconCache; +} + +QtResourceSet *FormWindowBase::resourceSet() const +{ + return m_d->m_resourceSet; +} + +void FormWindowBase::setResourceSet(QtResourceSet *resourceSet) +{ + m_d->m_resourceSet = resourceSet; +} + +void FormWindowBase::addReloadableProperty(QDesignerPropertySheet *sheet, int index) +{ + connectSheet(sheet); + m_d->m_reloadableResources[sheet].insert(index); +} + +void FormWindowBase::removeReloadableProperty(QDesignerPropertySheet *sheet, int index) +{ + m_d->m_reloadableResources[sheet].remove(index); + if (m_d->m_reloadableResources[sheet].isEmpty()) { + m_d->m_reloadableResources.remove(sheet); + disconnectSheet(sheet); + } +} + +void FormWindowBase::addReloadablePropertySheet(QDesignerPropertySheet *sheet, QObject *object) +{ + if (qobject_cast(object) || + qobject_cast(object) || + qobject_cast(object) || + qobject_cast(object)) { + connectSheet(sheet); + m_d->m_reloadablePropertySheets[sheet] = object; + } +} + +void FormWindowBase::connectSheet(QDesignerPropertySheet *sheet) +{ + if (m_d->m_reloadableResources.contains(sheet) + || m_d->m_reloadablePropertySheets.contains(sheet)) { + // already connected + return; + } + connect(sheet, &QObject::destroyed, this, &FormWindowBase::sheetDestroyed); +} + +void FormWindowBase::disconnectSheet(QDesignerPropertySheet *sheet) +{ + if (m_d->m_reloadableResources.contains(sheet) + || m_d->m_reloadablePropertySheets.contains(sheet)) { + // still need to be connected + return; + } + disconnect(sheet, &QObject::destroyed, this, &FormWindowBase::sheetDestroyed); +} + +void FormWindowBase::sheetDestroyed(QObject *object) +{ + // qobject_cast(object) + // will fail since the destructor of QDesignerPropertySheet + // has already finished + + for (auto it = m_d->m_reloadableResources.begin(); + it != m_d->m_reloadableResources.end(); ++it) { + if (it.key() == object) { + m_d->m_reloadableResources.erase(it); + break; + } + } + + for (auto it = m_d->m_reloadablePropertySheets.begin(); + it != m_d->m_reloadablePropertySheets.end(); ++it) { + if (it.key() == object) { + m_d->m_reloadablePropertySheets.erase(it); + break; + } + } +} + +void FormWindowBase::reloadProperties() +{ + pixmapCache()->clear(); + iconCache()->clear(); + for (auto it = m_d->m_reloadableResources.cbegin(), end = m_d->m_reloadableResources.cend(); it != end; ++it) { + QDesignerPropertySheet *sheet = it.key(); + for (int index : it.value()) { + const QVariant newValue = sheet->property(index); + if (qobject_cast(sheet->object()) && sheet->propertyName(index) == "text"_L1) { + const PropertySheetStringValue newString = qvariant_cast(newValue); + // optimize a bit, reset only if the text value might contain a reference to qt resources + // (however reloading of icons other than taken from resources might not work here) + if (newString.value().contains(":/"_L1)) { + const QVariant resetValue = QVariant::fromValue(PropertySheetStringValue()); + sheet->setProperty(index, resetValue); + } + } + sheet->setProperty(index, newValue); + } + if (QTabWidget *tabWidget = qobject_cast(sheet->object())) { + const int count = tabWidget->count(); + const int current = tabWidget->currentIndex(); + const QString currentTabIcon = u"currentTabIcon"_s; + for (int i = 0; i < count; i++) { + tabWidget->setCurrentIndex(i); + const int index = sheet->indexOf(currentTabIcon); + sheet->setProperty(index, sheet->property(index)); + } + tabWidget->setCurrentIndex(current); + } else if (QToolBox *toolBox = qobject_cast(sheet->object())) { + const int count = toolBox->count(); + const int current = toolBox->currentIndex(); + const QString currentItemIcon = u"currentItemIcon"_s; + for (int i = 0; i < count; i++) { + toolBox->setCurrentIndex(i); + const int index = sheet->indexOf(currentItemIcon); + sheet->setProperty(index, sheet->property(index)); + } + toolBox->setCurrentIndex(current); + } + } + for (QObject *object : std::as_const(m_d->m_reloadablePropertySheets)) { + reloadIconResources(iconCache(), object); + } +} + +void FormWindowBase::resourceSetActivated(QtResourceSet *resource, bool resourceSetChanged) +{ + if (resource == resourceSet() && resourceSetChanged) { + reloadProperties(); + emit pixmapCache()->reloaded(); + emit iconCache()->reloaded(); + if (QDesignerPropertyEditor *propertyEditor = qobject_cast(core()->propertyEditor())) + propertyEditor->reloadResourceProperties(); + } +} + +QVariantMap FormWindowBase::formData() +{ + QVariantMap rc; + if (m_d->m_hasFormGrid) + m_d->m_grid.addToVariantMap(rc, true); + return rc; +} + +void FormWindowBase::setFormData(const QVariantMap &vm) +{ + Grid formGrid; + m_d->m_hasFormGrid = formGrid.fromVariantMap(vm); + if (m_d->m_hasFormGrid) + m_d->m_grid = formGrid; +} + +QPoint FormWindowBase::grid() const +{ + return QPoint(m_d->m_grid.deltaX(), m_d->m_grid.deltaY()); +} + +void FormWindowBase::setGrid(const QPoint &grid) +{ + m_d->m_grid.setDeltaX(grid.x()); + m_d->m_grid.setDeltaY(grid.y()); +} + +bool FormWindowBase::hasFeature(Feature f) const +{ + return f & m_d->m_feature; +} + +static void recursiveUpdate(QWidget *w) +{ + w->update(); + + for (auto *child : w->children()) { + if (QWidget *w = qobject_cast(child)) + recursiveUpdate(w); + } +} + +void FormWindowBase::setFeatures(Feature f) +{ + m_d->m_feature = f; + const bool enableGrid = f & GridFeature; + m_d->m_grid.setVisible(enableGrid); + m_d->m_grid.setSnapX(enableGrid); + m_d->m_grid.setSnapY(enableGrid); + emit featureChanged(f); + recursiveUpdate(this); +} + +FormWindowBase::Feature FormWindowBase::features() const +{ + return m_d->m_feature; +} + +bool FormWindowBase::gridVisible() const +{ + return m_d->m_grid.visible() && currentTool() == 0; +} + +FormWindowBase::ResourceFileSaveMode FormWindowBase::resourceFileSaveMode() const +{ + return m_d->m_saveResourcesBehaviour; +} + +void FormWindowBase::setResourceFileSaveMode(ResourceFileSaveMode behaviour) +{ + m_d->m_saveResourcesBehaviour = behaviour; +} + +void FormWindowBase::syncGridFeature() +{ + if (m_d->m_grid.snapX() || m_d->m_grid.snapY()) + m_d->m_feature |= GridFeature; + else + m_d->m_feature &= ~GridFeature; +} + +void FormWindowBase::setDesignerGrid(const Grid& grid) +{ + m_d->m_grid = grid; + syncGridFeature(); + recursiveUpdate(this); +} + +const Grid &FormWindowBase::designerGrid() const +{ + return m_d->m_grid; +} + +bool FormWindowBase::hasFormGrid() const +{ + return m_d->m_hasFormGrid; +} + +void FormWindowBase::setHasFormGrid(bool b) +{ + m_d->m_hasFormGrid = b; +} + +void FormWindowBase::setDefaultDesignerGrid(const Grid& grid) +{ + FormWindowBasePrivate::m_defaultGrid = grid; +} + +const Grid &FormWindowBase::defaultDesignerGrid() +{ + return FormWindowBasePrivate::m_defaultGrid; +} + +QMenu *FormWindowBase::initializePopupMenu(QWidget * /*managedWidget*/) +{ + return nullptr; +} + +// Widget under mouse for finding the Widget to highlight +// when doing DnD. Restricts to pages by geometry if a container with +// a container extension (or one of its helper widgets) is hit; otherwise +// returns the widget as such (be it managed/unmanaged) + +QWidget *FormWindowBase::widgetUnderMouse(const QPoint &formPos, WidgetUnderMouseMode /* wum */) +{ + // widget_under_mouse might be some temporary thing like the dropLine. We need + // the actual widget that's part of the edited GUI. + QWidget *rc = widgetAt(formPos); + if (!rc || qobject_cast(rc)) + return nullptr; + + if (rc == mainContainer()) { + // Refuse main container areas if the main container has a container extension, + // for example when hitting QToolBox/QTabWidget empty areas. + if (qt_extension(core()->extensionManager(), rc)) + return nullptr; + return rc; + } + + // If we hit on container extension type container, make sure + // we use the top-most current page + if (QWidget *container = findContainer(rc, false)) + if (QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), container)) { + // For container that do not have a "stacked" nature (QToolBox, QMdiArea), + // make sure the position is within the current page + const int ci = c->currentIndex(); + if (ci < 0) + return nullptr; + QWidget *page = c->widget(ci); + QRect pageGeometry = page->geometry(); + pageGeometry.moveTo(page->mapTo(this, pageGeometry.topLeft())); + if (!pageGeometry.contains(formPos)) + return nullptr; + return page; + } + + return rc; +} + +void FormWindowBase::deleteWidgetList(const QWidgetList &widget_list) +{ + // We need a macro here even for single widgets because the some components (for example, + // the signal slot editor are connected to widgetRemoved() and add their + // own commands (for example, to delete w's connections) + const QString description = widget_list.size() == 1 ? + tr("Delete '%1'").arg(widget_list.constFirst()->objectName()) : tr("Delete"); + + commandHistory()->beginMacro(description); + for (QWidget *w : std::as_const(widget_list)) { + emit widgetRemoved(w); + DeleteWidgetCommand *cmd = new DeleteWidgetCommand(this); + cmd->init(w); + commandHistory()->push(cmd); + } + commandHistory()->endMacro(); +} + +QMenu *FormWindowBase::createExtensionTaskMenu(QDesignerFormWindowInterface *fw, QObject *o, bool trailingSeparator) +{ + using ActionList = QList; + ActionList actions; + // 1) Standard public extension + QExtensionManager *em = fw->core()->extensionManager(); + if (const QDesignerTaskMenuExtension *extTaskMenu = qt_extension(em, o)) + actions += extTaskMenu->taskActions(); + if (const auto *intTaskMenu = qobject_cast(em->extension(o, u"QDesignerInternalTaskMenuExtension"_s))) { + if (!actions.isEmpty()) { + QAction *a = new QAction(fw); + a->setSeparator(true); + actions.push_back(a); + } + actions += intTaskMenu->taskActions(); + } + if (actions.isEmpty()) + return nullptr; + if (trailingSeparator && !actions.constLast()->isSeparator()) { + QAction *a = new QAction(fw); + a->setSeparator(true); + actions.push_back(a); + } + QMenu *rc = new QMenu; + for (auto *a : std::as_const(actions)) + rc->addAction(a); + return rc; +} + +void FormWindowBase::emitObjectRemoved(QObject *o) +{ + emit objectRemoved(o); +} + +DeviceProfile FormWindowBase::deviceProfile() const +{ + return m_d->m_deviceProfile; +} + +QString FormWindowBase::styleName() const +{ + return m_d->m_deviceProfile.isEmpty() ? QString() : m_d->m_deviceProfile.style(); +} + +void FormWindowBase::emitWidgetRemoved(QWidget *w) +{ + emit widgetRemoved(w); +} + +QString FormWindowBase::deviceProfileName() const +{ + return m_d->m_deviceProfile.isEmpty() ? QString() : m_d->m_deviceProfile.name(); +} + +void FormWindowBase::setLineTerminatorMode(FormWindowBase::LineTerminatorMode mode) +{ + m_d->m_lineTerminatorMode = mode; +} + +FormWindowBase::LineTerminatorMode FormWindowBase::lineTerminatorMode() const +{ + return m_d->m_lineTerminatorMode; +} + +void FormWindowBase::triggerDefaultAction(QWidget *widget) +{ + if (QAction *action = qdesigner_internal::preferredEditAction(core(), widget)) + QTimer::singleShot(0, action, &QAction::trigger); +} + +bool FormWindowBase::useIdBasedTranslations() const +{ + return m_d->m_useIdBasedTranslations; +} + +void FormWindowBase::setUseIdBasedTranslations(bool v) +{ + m_d->m_useIdBasedTranslations = v; +} + +bool FormWindowBase::connectSlotsByName() const +{ + return m_d->m_connectSlotsByName; +} + +void FormWindowBase::setConnectSlotsByName(bool v) +{ + m_d->m_connectSlotsByName = v; +} + +QStringList FormWindowBase::checkContents() const +{ + if (!mainContainer()) + return QStringList(tr("Invalid form")); + // Test for non-laid toplevel spacers, which will not be saved + // as not to throw off uic. + QStringList problems; + const auto &spacers = mainContainer()->findChildren(); + for (const Spacer *spacer : spacers) { + if (spacer->parentWidget() && !spacer->parentWidget()->layout()) { + problems.push_back(tr("

    This file contains top level spacers.
    " + "They will not be saved.

    " + "Perhaps you forgot to create a layout?

    ")); + break; + } + } + return problems; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/formwindowbase_p.h b/src/designer/src/lib/shared/formwindowbase_p.h new file mode 100644 index 0000000..77fd7b8 --- /dev/null +++ b/src/designer/src/lib/shared/formwindowbase_p.h @@ -0,0 +1,165 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef FORMWINDOWBASE_H +#define FORMWINDOWBASE_H + +#include "shared_global_p.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerDnDItemInterface; +class QMenu; +class QtResourceSet; +class QDesignerPropertySheet; + +namespace qdesigner_internal { + +class QEditorFormBuilder; +class DeviceProfile; +class Grid; + +class DesignerPixmapCache; +class DesignerIconCache; +class FormWindowBasePrivate; + +class QDESIGNER_SHARED_EXPORT FormWindowBase: public QDesignerFormWindowInterface +{ + Q_OBJECT +public: + enum HighlightMode { Restore, Highlight }; + + explicit FormWindowBase(QDesignerFormEditorInterface *core, QWidget *parent = nullptr, + Qt::WindowFlags flags = {}); + ~FormWindowBase() override; + + QVariantMap formData(); + void setFormData(const QVariantMap &vm); + + QStringList checkContents() const override; + + // Deprecated + QPoint grid() const override; + + // Deprecated + void setGrid(const QPoint &grid) override; + + bool hasFeature(Feature f) const override; + Feature features() const override; + void setFeatures(Feature f) override; + + const Grid &designerGrid() const; + void setDesignerGrid(const Grid& grid); + + bool hasFormGrid() const; + void setHasFormGrid(bool b); + + bool gridVisible() const; + + ResourceFileSaveMode resourceFileSaveMode() const override; + void setResourceFileSaveMode(ResourceFileSaveMode behavior) override; + + static const Grid &defaultDesignerGrid(); + static void setDefaultDesignerGrid(const Grid& grid); + + // Overwrite to initialize and return a full popup menu for a managed widget + virtual QMenu *initializePopupMenu(QWidget *managedWidget); + // Helper to create a basic popup menu from task menu extensions (internal/public) + static QMenu *createExtensionTaskMenu(QDesignerFormWindowInterface *fw, QObject *o, bool trailingSeparator = true); + + virtual bool dropWidgets(const QList &item_list, QWidget *target, + const QPoint &global_mouse_pos) = 0; + + // Helper to find the widget at the mouse position with some flags. + enum WidgetUnderMouseMode { FindSingleSelectionDropTarget, FindMultiSelectionDropTarget }; + QWidget *widgetUnderMouse(const QPoint &formPos, WidgetUnderMouseMode m); + + virtual QWidget *widgetAt(const QPoint &pos) = 0; + virtual QWidget *findContainer(QWidget *w, bool excludeLayout) const = 0; + + void deleteWidgetList(const QWidgetList &widget_list); + + virtual void highlightWidget(QWidget *w, const QPoint &pos, HighlightMode mode = Highlight) = 0; + + enum PasteMode { PasteAll, PasteActionsOnly }; +#if QT_CONFIG(clipboard) + virtual void paste(PasteMode pasteMode) = 0; +#endif + + // Factory method to create a form builder + virtual QEditorFormBuilder *createFormBuilder() = 0; + + virtual bool blockSelectionChanged(bool blocked) = 0; + + DesignerPixmapCache *pixmapCache() const; + DesignerIconCache *iconCache() const; + QtResourceSet *resourceSet() const override; + void setResourceSet(QtResourceSet *resourceSet) override; + void addReloadableProperty(QDesignerPropertySheet *sheet, int index); + void removeReloadableProperty(QDesignerPropertySheet *sheet, int index); + void addReloadablePropertySheet(QDesignerPropertySheet *sheet, QObject *object); + void reloadProperties(); + + void emitWidgetRemoved(QWidget *w); + void emitObjectRemoved(QObject *o); + + DeviceProfile deviceProfile() const; + QString styleName() const; + QString deviceProfileName() const; + + enum LineTerminatorMode { + LFLineTerminator, + CRLFLineTerminator, + NativeLineTerminator = +#if defined (Q_OS_WIN) + CRLFLineTerminator +#else + LFLineTerminator +#endif + }; + + void setLineTerminatorMode(LineTerminatorMode mode); + LineTerminatorMode lineTerminatorMode() const; + + bool useIdBasedTranslations() const; + void setUseIdBasedTranslations(bool v); + + bool connectSlotsByName() const; + void setConnectSlotsByName(bool v); + +public slots: + void resourceSetActivated(QtResourceSet *resourceSet, bool resourceSetChanged); + +private slots: + void triggerDefaultAction(QWidget *w); + void sheetDestroyed(QObject *object); + +private: + void syncGridFeature(); + void connectSheet(QDesignerPropertySheet *sheet); + void disconnectSheet(QDesignerPropertySheet *sheet); + + FormWindowBasePrivate *m_d; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOWBASE_H diff --git a/src/designer/src/lib/shared/grid.cpp b/src/designer/src/lib/shared/grid.cpp new file mode 100644 index 0000000..0a8baaa --- /dev/null +++ b/src/designer/src/lib/shared/grid.cpp @@ -0,0 +1,154 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "grid_p.h" +#include "iconloader_p.h" + +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +static const bool defaultSnap = true; +static const bool defaultVisible = true; +static const int DEFAULT_GRID = 10; +static const char* KEY_VISIBLE = "gridVisible"; +static const char* KEY_SNAPX = "gridSnapX"; +static const char* KEY_SNAPY = "gridSnapY"; +static const char* KEY_DELTAX = "gridDeltaX"; +static const char* KEY_DELTAY = "gridDeltaY"; + +// Insert a value into the serialization map unless default +template + static inline void valueToVariantMap(T value, T defaultValue, const QString &key, QVariantMap &v, bool forceKey) { + if (forceKey || value != defaultValue) + v.insert(key, QVariant(value)); + } + +// Obtain a value form QVariantMap +template + static inline bool valueFromVariantMap(const QVariantMap &v, const QString &key, T &value) { + const auto it = v.constFind(key); + const bool found = it != v.constEnd(); + if (found) + value = qvariant_cast(it.value()); + return found; + } + +namespace qdesigner_internal +{ + +Grid::Grid() : + m_visible(defaultVisible), + m_snapX(defaultSnap), + m_snapY(defaultSnap), + m_deltaX(DEFAULT_GRID), + m_deltaY(DEFAULT_GRID) +{ +} + +bool Grid::fromVariantMap(const QVariantMap& vm) +{ + Grid grid; + bool anyData = valueFromVariantMap(vm, QLatin1StringView(KEY_VISIBLE), grid.m_visible); + anyData |= valueFromVariantMap(vm, QLatin1StringView(KEY_SNAPX), grid.m_snapX); + anyData |= valueFromVariantMap(vm, QLatin1StringView(KEY_SNAPY), grid.m_snapY); + anyData |= valueFromVariantMap(vm, QLatin1StringView(KEY_DELTAX), grid.m_deltaX); + anyData |= valueFromVariantMap(vm, QLatin1StringView(KEY_DELTAY), grid.m_deltaY); + if (!anyData) + return false; + if (grid.m_deltaX == 0 || grid.m_deltaY == 0) { + qWarning("Attempt to set invalid grid with a spacing of 0."); + return false; + } + *this = grid; + return true; +} + +QVariantMap Grid::toVariantMap(bool forceKeys) const +{ + QVariantMap rc; + addToVariantMap(rc, forceKeys); + return rc; +} + +void Grid::addToVariantMap(QVariantMap& vm, bool forceKeys) const +{ + valueToVariantMap(m_visible, defaultVisible, QLatin1StringView(KEY_VISIBLE), vm, forceKeys); + valueToVariantMap(m_snapX, defaultSnap, QLatin1StringView(KEY_SNAPX), vm, forceKeys); + valueToVariantMap(m_snapY, defaultSnap, QLatin1StringView(KEY_SNAPY), vm, forceKeys); + valueToVariantMap(m_deltaX, DEFAULT_GRID, QLatin1StringView(KEY_DELTAX), vm, forceKeys); + valueToVariantMap(m_deltaY, DEFAULT_GRID, QLatin1StringView(KEY_DELTAY), vm, forceKeys); +} + +void Grid::paint(QWidget *widget, QPaintEvent *e) const +{ + QPainter p(widget); + paint(p, widget, e); +} + +void Grid::paint(QPainter &p, const QWidget *widget, QPaintEvent *e) const +{ + const auto &palette = widget->palette(); + p.setPen(isDarkMode() ? palette.light().color() : palette.dark().color()); + + if (m_visible) { + const int xstart = (e->rect().x() / m_deltaX) * m_deltaX; + const int ystart = (e->rect().y() / m_deltaY) * m_deltaY; + + const int xend = e->rect().right(); + const int yend = e->rect().bottom(); + + using Points = QList; + static Points points; + points.clear(); + + for (int x = xstart; x <= xend; x += m_deltaX) { + points.reserve((yend - ystart) / m_deltaY + 1); + for (int y = ystart; y <= yend; y += m_deltaY) + points.push_back(QPointF(x, y)); + p.drawPoints( &(*points.begin()), points.size()); + points.clear(); + } + } +} + +int Grid::snapValue(int value, int grid) const +{ + const int rest = value % grid; + const int absRest = (rest < 0) ? -rest : rest; + int offset = 0; + if (2 * absRest > grid) + offset = 1; + if (rest < 0) + offset *= -1; + return (value / grid + offset) * grid; +} + +QPoint Grid::snapPoint(const QPoint &p) const +{ + const int sx = m_snapX ? snapValue(p.x(), m_deltaX) : p.x(); + const int sy = m_snapY ? snapValue(p.y(), m_deltaY) : p.y(); + return QPoint(sx, sy); +} + +int Grid::widgetHandleAdjustX(int x) const +{ + return m_snapX ? (x / m_deltaX) * m_deltaX + 1 : x; +} + +int Grid::widgetHandleAdjustY(int y) const +{ + return m_snapY ? (y / m_deltaY) * m_deltaY + 1 : y; +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/grid_p.h b/src/designer/src/lib/shared/grid_p.h new file mode 100644 index 0000000..6067328 --- /dev/null +++ b/src/designer/src/lib/shared/grid_p.h @@ -0,0 +1,85 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_GRID_H +#define QDESIGNER_GRID_H + +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWidget; +class QPaintEvent; +class QPainter; + +namespace qdesigner_internal { + +// Designer grid which is able to serialize to QVariantMap +class QDESIGNER_SHARED_EXPORT Grid +{ +public: + Grid(); + + bool fromVariantMap(const QVariantMap& vm); + + void addToVariantMap(QVariantMap& vm, bool forceKeys = false) const; + QVariantMap toVariantMap(bool forceKeys = false) const; + + inline bool visible() const { return m_visible; } + void setVisible(bool visible) { m_visible = visible; } + + inline bool snapX() const { return m_snapX; } + void setSnapX(bool snap) { m_snapX = snap; } + + inline bool snapY() const { return m_snapY; } + void setSnapY(bool snap) { m_snapY = snap; } + + inline int deltaX() const { return m_deltaX; } + void setDeltaX(int dx) { m_deltaX = dx; } + + inline int deltaY() const { return m_deltaY; } + void setDeltaY(int dy) { m_deltaY = dy; } + + void paint(QWidget *widget, QPaintEvent *e) const; + void paint(QPainter &p, const QWidget *widget, QPaintEvent *e) const; + + QPoint snapPoint(const QPoint &p) const; + + int widgetHandleAdjustX(int x) const; + int widgetHandleAdjustY(int y) const; + +private: + friend bool comparesEqual(const Grid &lhs, const Grid &rhs) noexcept + { + return lhs.m_visible == rhs.m_visible + && lhs.m_snapX == rhs.m_snapX && lhs.m_snapY == rhs.m_snapY + && lhs.m_deltaX == rhs.m_deltaX && lhs.m_deltaY == rhs.m_deltaY; + } + Q_DECLARE_EQUALITY_COMPARABLE(Grid) + + int snapValue(int value, int grid) const; + bool m_visible; + bool m_snapX; + bool m_snapY; + int m_deltaX; + int m_deltaY; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_GRID_H diff --git a/src/designer/src/lib/shared/gridpanel.cpp b/src/designer/src/lib/shared/gridpanel.cpp new file mode 100644 index 0000000..a5f6ae6 --- /dev/null +++ b/src/designer/src/lib/shared/gridpanel.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "gridpanel_p.h" +#include "ui_gridpanel.h" +#include "grid_p.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +GridPanel::GridPanel(QWidget *parentWidget) : + QWidget(parentWidget) +{ + m_ui = new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::GridPanel; + m_ui->setupUi(this); + + connect(m_ui->m_resetButton, &QAbstractButton::clicked, this, &GridPanel::reset); +} + +GridPanel::~GridPanel() +{ + delete m_ui; +} + +void GridPanel::setGrid(const Grid &g) +{ + m_ui->m_deltaXSpinBox->setValue(g.deltaX()); + m_ui->m_deltaYSpinBox->setValue(g.deltaY()); + m_ui->m_visibleCheckBox->setCheckState(g.visible() ? Qt::Checked : Qt::Unchecked); + m_ui->m_snapXCheckBox->setCheckState(g.snapX() ? Qt::Checked : Qt::Unchecked); + m_ui->m_snapYCheckBox->setCheckState(g.snapY() ? Qt::Checked : Qt::Unchecked); +} + +void GridPanel::setTitle(const QString &title) +{ + m_ui->m_gridGroupBox->setTitle(title); +} + +Grid GridPanel::grid() const +{ + Grid rc; + rc.setDeltaX(m_ui->m_deltaXSpinBox->value()); + rc.setDeltaY(m_ui->m_deltaYSpinBox->value()); + rc.setSnapX(m_ui->m_snapXCheckBox->checkState() == Qt::Checked); + rc.setSnapY(m_ui->m_snapYCheckBox->checkState() == Qt::Checked); + rc.setVisible(m_ui->m_visibleCheckBox->checkState() == Qt::Checked); + return rc; +} + +void GridPanel::reset() +{ + setGrid(Grid()); +} + +void GridPanel::setCheckable (bool c) +{ + m_ui->m_gridGroupBox->setCheckable(c); +} + +bool GridPanel::isCheckable () const +{ + return m_ui->m_gridGroupBox->isCheckable (); +} + +bool GridPanel::isChecked () const +{ + return m_ui->m_gridGroupBox->isChecked (); +} + +void GridPanel::setChecked(bool c) +{ + m_ui->m_gridGroupBox->setChecked(c); +} + +void GridPanel::setResetButtonVisible(bool v) +{ + m_ui->m_resetButton->setVisible(v); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/gridpanel.ui b/src/designer/src/lib/shared/gridpanel.ui new file mode 100644 index 0000000..adfdd36 --- /dev/null +++ b/src/designer/src/lib/shared/gridpanel.ui @@ -0,0 +1,144 @@ + + qdesigner_internal::GridPanel + + + + 0 + 0 + 393 + 110 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Grid + + + + + + + 0 + 0 + + + + Visible + + + + + + + Grid &X + + + m_deltaXSpinBox + + + + + + + 2 + + + 100 + + + + + + + + 0 + 0 + + + + Snap + + + + + + + + + Reset + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + Grid &Y + + + m_deltaYSpinBox + + + + + + + 2 + + + 100 + + + + + + + + 0 + 0 + + + + Snap + + + + + + + + + + + diff --git a/src/designer/src/lib/shared/gridpanel_p.h b/src/designer/src/lib/shared/gridpanel_p.h new file mode 100644 index 0000000..000a250 --- /dev/null +++ b/src/designer/src/lib/shared/gridpanel_p.h @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Qt tools. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef GRIDPANEL_H +#define GRIDPANEL_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class Grid; + +namespace Ui { + class GridPanel; +} + +class QDESIGNER_SHARED_EXPORT GridPanel : public QWidget +{ + Q_OBJECT +public: + GridPanel(QWidget *parent = nullptr); + ~GridPanel(); + + void setTitle(const QString &title); + + void setGrid(const Grid &g); + Grid grid() const; + + void setCheckable (bool c); + bool isCheckable () const; + + bool isChecked () const; + void setChecked(bool c); + + void setResetButtonVisible(bool v); + +private slots: + void reset(); + +private: + Ui::GridPanel *m_ui; +}; + +} // qdesigner_internal + +QT_END_NAMESPACE + +#endif // GRIDPANEL_H diff --git a/src/designer/src/lib/shared/htmlhighlighter.cpp b/src/designer/src/lib/shared/htmlhighlighter.cpp new file mode 100644 index 0000000..59e2a5e --- /dev/null +++ b/src/designer/src/lib/shared/htmlhighlighter.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include + +#include "htmlhighlighter_p.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +HtmlHighlighter::HtmlHighlighter(QTextEdit *textEdit) + : QSyntaxHighlighter(textEdit->document()) +{ + QTextCharFormat entityFormat; + entityFormat.setForeground(Qt::red); + setFormatFor(Entity, entityFormat); + + QTextCharFormat tagFormat; + tagFormat.setForeground(Qt::darkMagenta); + tagFormat.setFontWeight(QFont::Bold); + setFormatFor(Tag, tagFormat); + + QTextCharFormat commentFormat; + commentFormat.setForeground(Qt::gray); + commentFormat.setFontItalic(true); + setFormatFor(Comment, commentFormat); + + QTextCharFormat attributeFormat; + attributeFormat.setForeground(Qt::black); + attributeFormat.setFontWeight(QFont::Bold); + setFormatFor(Attribute, attributeFormat); + + QTextCharFormat valueFormat; + valueFormat.setForeground(Qt::blue); + setFormatFor(Value, valueFormat); +} + +void HtmlHighlighter::setFormatFor(Construct construct, + const QTextCharFormat &format) +{ + m_formats[construct] = format; + rehighlight(); +} + +void HtmlHighlighter::highlightBlock(const QString &text) +{ + static const QChar tab = u'\t'; + static const QChar space = u' '; + + int state = previousBlockState(); + qsizetype len = text.size(); + qsizetype start = 0; + qsizetype pos = 0; + + while (pos < len) { + switch (state) { + case NormalState: + default: + while (pos < len) { + QChar ch = text.at(pos); + if (ch == u'<') { + if (QStringView{text}.sliced(pos).startsWith(""_L1)) { + pos += 3; + state = NormalState; + break; + } + } + setFormat(start, pos - start, m_formats[Comment]); + break; + case InTag: + QChar quote = QChar::Null; + while (pos < len) { + QChar ch = text.at(pos); + if (quote.isNull()) { + start = pos; + if (ch == '\''_L1 || ch == u'"') { + quote = ch; + } else if (ch == u'>') { + ++pos; + setFormat(start, pos - start, m_formats[Tag]); + state = NormalState; + break; + } else if (QStringView{text}.sliced(pos).startsWith("/>"_L1)) { + pos += 2; + setFormat(start, pos - start, m_formats[Tag]); + state = NormalState; + break; + } else if (ch != space && text.at(pos) != tab) { + // Tag not ending, not a quote and no whitespace, so + // we must be dealing with an attribute. + ++pos; + while (pos < len && text.at(pos) != space + && text.at(pos) != tab + && text.at(pos) != u'=') + ++pos; + setFormat(start, pos - start, m_formats[Attribute]); + start = pos; + } + } else if (ch == quote) { + quote = QChar::Null; + + // Anything quoted is a value + setFormat(start, pos - start, m_formats[Value]); + } + ++pos; + } + break; + } + } + setCurrentBlockState(state); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/htmlhighlighter_p.h b/src/designer/src/lib/shared/htmlhighlighter_p.h new file mode 100644 index 0000000..e86bfce --- /dev/null +++ b/src/designer/src/lib/shared/htmlhighlighter_p.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef HTMLHIGHLIGHTER_H +#define HTMLHIGHLIGHTER_H + +#include + +QT_BEGIN_NAMESPACE + +class QTextEdit; + +namespace qdesigner_internal { + +/* HTML syntax highlighter based on Qt Quarterly example */ +class HtmlHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + enum Construct { + Entity, + Tag, + Comment, + Attribute, + Value, + LastConstruct = Value + }; + + HtmlHighlighter(QTextEdit *textEdit); + + void setFormatFor(Construct construct, const QTextCharFormat &format); + + QTextCharFormat formatFor(Construct construct) const + { return m_formats[construct]; } + +protected: + enum State { + NormalState = -1, + InComment, + InTag + }; + + void highlightBlock(const QString &text) override; + +private: + QTextCharFormat m_formats[LastConstruct + 1]; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // HTMLHIGHLIGHTER_H diff --git a/src/designer/src/lib/shared/icon-naming-spec.txt b/src/designer/src/lib/shared/icon-naming-spec.txt new file mode 100644 index 0000000..e9b8547 --- /dev/null +++ b/src/designer/src/lib/shared/icon-naming-spec.txt @@ -0,0 +1,309 @@ +# https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + +# Table 2. Standard Action Icons +address-book-new +application-exit +appointment-new +call-start +call-stop +contact-new +document-new +document-open +document-open-recent +document-page-setup +document-print +document-print-preview +document-properties +document-revert +document-save +document-save-as +document-send +edit-clear +edit-copy +edit-cut +edit-delete +edit-find +edit-find-replace +edit-paste +edit-redo +edit-select-all +edit-undo +folder-new +format-indent-less +format-indent-more +format-justify-center +format-justify-fill +format-justify-left +format-justify-right +format-text-direction-ltr +format-text-direction-rtl +format-text-bold +format-text-italic +format-text-underline +format-text-strikethrough +go-bottom +go-down +go-first +go-home +go-jump +go-last +go-next +go-previous +go-top +go-up +help-about +help-contents +help-faq +insert-image +insert-link +insert-object +insert-text +list-add +list-remove +mail-forward +mail-mark-important +mail-mark-junk +mail-mark-notjunk +mail-mark-read +mail-mark-unread +mail-message-new +mail-reply-all +mail-reply-sender +mail-send +mail-send-receive +media-eject +media-playback-pause +media-playback-start +media-playback-stop +media-record +media-seek-backward +media-seek-forward +media-skip-backward +media-skip-forward +object-flip-horizontal +object-flip-vertical +object-rotate-left +object-rotate-right +process-stop +system-lock-screen +system-log-out +system-run +system-search +system-reboot +system-shutdown +tools-check-spelling +view-fullscreen +view-refresh +view-restore +view-sort-ascending +view-sort-descending +window-close +window-new +zoom-fit-best +zoom-in +zoom-original +zoom-out + +# Table 3. Standard Animation Icons +process-working + +# Table 4. Standard Application Icons +accessories-calculator +accessories-character-map +accessories-dictionary +accessories-text-editor +help-browser +multimedia-volume-control +preferences-desktop-accessibility +preferences-desktop-font +preferences-desktop-keyboard +preferences-desktop-locale +preferences-desktop-multimedia +preferences-desktop-screensaver +preferences-desktop-theme +preferences-desktop-wallpaper +system-file-manager +system-software-install +system-software-update +utilities-system-monitor +utilities-terminal + +# Table 5. Standard Category Icons +applications-accessories +applications-development +applications-engineering +applications-games +applications-graphics +applications-internet +applications-multimedia +applications-office +applications-other +applications-science +applications-system +applications-utilities +preferences-desktop +preferences-desktop-peripherals +preferences-desktop-personal +preferences-other +preferences-system +preferences-system-network +system-help + +# Table 6. Standard Device Icons +audio-card +audio-input-microphone +battery +camera-photo +camera-video +camera-web +computer +drive-harddisk +drive-optical +drive-removable-media +input-gaming +input-keyboard +input-mouse +input-tablet +media-flash +media-floppy +media-optical +media-tape +modem +multimedia-player +network-wired +network-wireless +pda +phone +printer +scanner +video-display + +# Table 7. Standard Emblem Icons +emblem-default +emblem-documents +emblem-downloads +emblem-favorite +emblem-important +emblem-mail +emblem-photos +emblem-readonly +emblem-shared +emblem-symbolic-link +emblem-synchronized +emblem-system +emblem-unreadable + +# Table 8. Standard Emotion Icons +face-angel +face-angry +face-cool +face-crying +face-devilish +face-embarrassed +face-kiss +face-laugh +face-monkey +face-plain +face-raspberry +face-sad +face-sick +face-smile +face-smile-big +face-smirk +face-surprise +face-tired +face-uncertain +face-wink +face-worried + +# Table 9. Standard International Icons +flag-aa + +# Table 10. Standard MIME Type Icons +application-x-executable +audio-x-generic +font-x-generic +image-x-generic +package-x-generic +text-html +text-x-generic +text-x-generic-template +text-x-script +video-x-generic +x-office-address-book +x-office-calendar +x-office-document +x-office-presentation +x-office-spreadsheet + +# Table 11. Standard Place Icons +folder +folder-remote +network-server +network-workgroup +start-here +user-bookmarks +user-desktop +user-home +user-trash + +# Table 12. Standard Status Icons +appointment-missed +appointment-soon +audio-volume-high +audio-volume-low +audio-volume-medium +audio-volume-muted +battery-caution +battery-low +dialog-error +dialog-information +dialog-password +dialog-question +dialog-warning +folder-drag-accept +folder-open +folder-visiting +image-loading +image-missing +mail-attachment +mail-unread +mail-read +mail-replied +mail-signed +mail-signed-verified +media-playlist-repeat +media-playlist-shuffle +network-error +network-idle +network-offline +network-receive +network-transmit +network-transmit-receive +printer-error +printer-printing +security-high +security-medium +security-low +software-update-available +software-update-urgent +sync-error +sync-synchronizing +task-due +task-past-due +user-available +user-away +user-idle +user-offline +user-trash-full +weather-clear +weather-clear-night +weather-few-clouds +weather-few-clouds-night +weather-fog +weather-overcast +weather-severe-alert +weather-showers +weather-showers-scattered +weather-snow +weather-storm diff --git a/src/designer/src/lib/shared/iconloader.cpp b/src/designer/src/lib/shared/iconloader.cpp new file mode 100644 index 0000000..4be292d --- /dev/null +++ b/src/designer/src/lib/shared/iconloader.cpp @@ -0,0 +1,106 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "iconloader_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// Check for "Dark Mode", either system-wide or usage of a dark style +static bool isLight(const QColor &textColor) +{ + enum : int { DarkThreshold = 200 }; // Observed 239 on KDE/Dark + + return textColor.red() > DarkThreshold && textColor.green() > DarkThreshold + && textColor.blue() > DarkThreshold; +} + +QDESIGNER_SHARED_EXPORT bool isDarkMode() +{ + return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark + || isLight(QGuiApplication::palette().color(QPalette::WindowText)); +} + +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QIcon::ThemeIcon themeIcon, + QLatin1StringView name) +{ + return QOperatingSystemVersion::currentType() != QOperatingSystemVersion::MacOS + && QIcon::hasThemeIcon(themeIcon) + ? QIcon::fromTheme(themeIcon) : createIconSet(name); +} + +template +static inline QIcon createIconSetHelper(StringView name) +{ + constexpr QLatin1StringView prefixes[] = { + ":/qt-project.org/formeditor/images/"_L1, +#ifdef Q_OS_MACOS + ":/qt-project.org/formeditor/images/mac/"_L1, +#else + ":/qt-project.org/formeditor/images/win/"_L1, +#endif + ":/qt-project.org/formeditor/images/designer_"_L1 + }; + + for (QLatin1StringView prefix : prefixes) { + const QString f = prefix + name; + if (QFile::exists(f)) + return QIcon(f); + } + + return {}; +} + +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QStringView name) +{ + return createIconSetHelper(name); +} + +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QLatin1StringView name) +{ + return createIconSetHelper(name); +} + +QDESIGNER_SHARED_EXPORT QIcon emptyIcon() +{ + return QIcon(u":/qt-project.org/formeditor/images/emptyicon.png"_s); +} + +static QIcon buildIcon(const QString &prefix, const int *sizes, size_t sizeCount) +{ + QIcon result; + for (size_t i = 0; i < sizeCount; ++i) { + const QString size = QString::number(sizes[i]); + const QPixmap pixmap(prefix + size + 'x'_L1 + size + ".png"_L1); + Q_ASSERT(!pixmap.size().isEmpty()); + result.addPixmap(pixmap); + } + return result; +} + +QDESIGNER_SHARED_EXPORT QIcon qtLogoIcon() +{ + static const int sizes[] = {16, 24, 32, 64, 128}; + static const QIcon result = + buildIcon(u":/qt-project.org/formeditor/images/qtlogo"_s, + sizes, sizeof(sizes) / sizeof(sizes[0])); + return result; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + diff --git a/src/designer/src/lib/shared/iconloader_p.h b/src/designer/src/lib/shared/iconloader_p.h new file mode 100644 index 0000000..76909d6 --- /dev/null +++ b/src/designer/src/lib/shared/iconloader_p.h @@ -0,0 +1,41 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ICONLOADER_H +#define ICONLOADER_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QString; +class QIcon; + +namespace qdesigner_internal { + +QDESIGNER_SHARED_EXPORT bool isDarkMode(); +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QStringView name); +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QLatin1StringView name); +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QIcon::ThemeIcon themeIcon, + QLatin1StringView name); +QDESIGNER_SHARED_EXPORT QIcon emptyIcon(); +QDESIGNER_SHARED_EXPORT QIcon qtLogoIcon(); + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ICONLOADER_H diff --git a/src/designer/src/lib/shared/iconselector.cpp b/src/designer/src/lib/shared/iconselector.cpp new file mode 100644 index 0000000..60c4044 --- /dev/null +++ b/src/designer/src/lib/shared/iconselector.cpp @@ -0,0 +1,651 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "iconselector_p.h" +#include "qdesigner_utils_p.h" +#include "qtresourcemodel_p.h" +#include "qtresourceview_p.h" +#include "iconloader_p.h" +#include "formwindowbase_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +using ThemeIconEnumEntry = std::pair; + +static const QList &themeEnumIcons() +{ + static QList result; + if (result.isEmpty()) { + const QStringList &names = QResourceBuilder::themeIconNames(); + result.reserve(names.size()); + for (qsizetype i = 0, size = names.size(); i < size; ++i) + result.append({names.at(i), QIcon::fromTheme(QIcon::ThemeIcon(i))}); + } + return result; +} + +static void initThemeCombo(QComboBox *cb) +{ + cb->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + for (const auto &te : themeEnumIcons()) + cb->addItem(te.second, te.first); + + cb->setCurrentIndex(-1); +} + +// Validator for theme line edit, accepts empty or non-blank strings. +class BlankSuppressingValidator : public QValidator { +public: + explicit BlankSuppressingValidator(QObject * parent = nullptr) : QValidator(parent) {} + State validate(QString &input, int &pos) const override + { + const auto blankPos = input.indexOf(u' '); + if (blankPos != -1) { + pos = blankPos; + return Invalid; + } + return Acceptable; + } +}; + +// -------------------- LanguageResourceDialogPrivate +class LanguageResourceDialogPrivate { + LanguageResourceDialog *q_ptr; + Q_DECLARE_PUBLIC(LanguageResourceDialog) + +public: + LanguageResourceDialogPrivate(QDesignerResourceBrowserInterface *rb); + void init(LanguageResourceDialog *p); + + void setCurrentPath(const QString &filePath); + QString currentPath() const; + + void slotAccepted(); + void slotPathChanged(const QString &); + +private: + void setOkButtonEnabled(bool v) { m_dialogButtonBox->button(QDialogButtonBox::Ok)->setEnabled(v); } + static bool checkPath(const QString &p); + + QDesignerResourceBrowserInterface *m_browser; + QDialogButtonBox *m_dialogButtonBox; +}; + +LanguageResourceDialogPrivate::LanguageResourceDialogPrivate(QDesignerResourceBrowserInterface *rb) : + q_ptr(nullptr), + m_browser(rb), + m_dialogButtonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)) +{ + setOkButtonEnabled(false); +} + +void LanguageResourceDialogPrivate::init(LanguageResourceDialog *p) +{ + q_ptr = p; + QLayout *layout = new QVBoxLayout(p); + layout->addWidget(m_browser); + layout->addWidget(m_dialogButtonBox); + QObject::connect(m_dialogButtonBox, &QDialogButtonBox::accepted, p, [this] { slotAccepted(); }); + QObject::connect(m_dialogButtonBox, &QDialogButtonBox::rejected, p, &QDialog::reject); + QObject::connect(m_browser, &QDesignerResourceBrowserInterface::currentPathChanged, + p, [this](const QString &fileName) { slotPathChanged(fileName); }); + QObject::connect(m_browser, &QDesignerResourceBrowserInterface::pathActivated, + p, [this] { slotAccepted(); }); + p->setModal(true); + p->setWindowTitle(LanguageResourceDialog::tr("Choose Resource")); + setOkButtonEnabled(false); +} + +void LanguageResourceDialogPrivate::setCurrentPath(const QString &filePath) +{ + m_browser->setCurrentPath(filePath); + setOkButtonEnabled(checkPath(filePath)); +} + +QString LanguageResourceDialogPrivate::currentPath() const +{ + return m_browser->currentPath(); +} + +bool LanguageResourceDialogPrivate::checkPath(const QString &p) +{ + return p.isEmpty() ? false : IconSelector::checkPixmap(p, IconSelector::CheckFast); +} + +void LanguageResourceDialogPrivate::slotAccepted() +{ + if (checkPath(currentPath())) + q_ptr->accept(); +} + +void LanguageResourceDialogPrivate::slotPathChanged(const QString &p) +{ + setOkButtonEnabled(checkPath(p)); +} + +// ------------ LanguageResourceDialog +LanguageResourceDialog::LanguageResourceDialog(QDesignerResourceBrowserInterface *rb, QWidget *parent) : + QDialog(parent), + d_ptr(new LanguageResourceDialogPrivate(rb)) +{ + d_ptr->init( this); +} + +LanguageResourceDialog::~LanguageResourceDialog() = default; + +void LanguageResourceDialog::setCurrentPath(const QString &filePath) +{ + d_ptr->setCurrentPath(filePath); +} + +QString LanguageResourceDialog::currentPath() const +{ + return d_ptr->currentPath(); +} + +LanguageResourceDialog* LanguageResourceDialog::create(QDesignerFormEditorInterface *core, QWidget *parent) +{ + if (QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) + if (QDesignerResourceBrowserInterface *rb = lang->createResourceBrowser(nullptr)) + return new LanguageResourceDialog(rb, parent); + if (QDesignerResourceBrowserInterface *rb = core->integration()->createResourceBrowser(nullptr)) + return new LanguageResourceDialog(rb, parent); + return nullptr; +} + +// ------------ IconSelectorPrivate + +struct QIconStateName +{ + std::pair state; + const char *name; +}; + +constexpr QIconStateName stateToName[] = { + {{QIcon::Normal, QIcon::Off}, QT_TRANSLATE_NOOP("IconSelector", "Normal Off")}, + {{QIcon::Normal, QIcon::On}, QT_TRANSLATE_NOOP("IconSelector", "Normal On")}, + {{QIcon::Disabled, QIcon::Off}, QT_TRANSLATE_NOOP("IconSelector", "Disabled Off")}, + {{QIcon::Disabled, QIcon::On}, QT_TRANSLATE_NOOP("IconSelector", "Disabled On")}, + {{QIcon::Active, QIcon::Off}, QT_TRANSLATE_NOOP("IconSelector", "Active Off")}, + {{QIcon::Active, QIcon::On}, QT_TRANSLATE_NOOP("IconSelector", "Active On")}, + {{QIcon::Selected, QIcon::Off}, QT_TRANSLATE_NOOP("IconSelector", "Selected Off")}, + {{QIcon::Selected, QIcon::On}, QT_TRANSLATE_NOOP("IconSelector", "Selected On")} +}; + +constexpr int stateToNameSize = int(sizeof(stateToName) / sizeof(stateToName[0])); + +class IconSelectorPrivate +{ + IconSelector *q_ptr = nullptr; + Q_DECLARE_PUBLIC(IconSelector) +public: + IconSelectorPrivate() = default; + + void slotStateActivated(); + void slotSetActivated(); + void slotSetResourceActivated(); + void slotSetFileActivated(); + void slotResetActivated(); + void slotResetAllActivated(); + void slotUpdate(); + + std::pair currentState() const + { + const int i = m_stateComboBox->currentIndex(); + return i >= 0 && i < stateToNameSize + ? stateToName[i].state : std::pair{}; + } + + const QIcon m_emptyIcon; + QComboBox *m_stateComboBox = nullptr; + QToolButton *m_iconButton = nullptr; + QAction *m_resetAction = nullptr; + QAction *m_resetAllAction = nullptr; + PropertySheetIconValue m_icon; + DesignerIconCache *m_iconCache = nullptr; + DesignerPixmapCache *m_pixmapCache = nullptr; + QtResourceModel *m_resourceModel = nullptr; + QDesignerFormEditorInterface *m_core = nullptr; +}; + +void IconSelectorPrivate::slotUpdate() +{ + QIcon icon; + if (m_iconCache) + icon = m_iconCache->icon(m_icon); + + const auto &paths = m_icon.paths(); + for (int index = 0; index < stateToNameSize; ++index) { + const auto &state = stateToName[index].state; + const PropertySheetPixmapValue pixmap = paths.value(state); + QIcon pixmapIcon = QIcon(icon.pixmap(16, 16, state.first, state.second)); + if (pixmapIcon.isNull()) + pixmapIcon = m_emptyIcon; + m_stateComboBox->setItemIcon(index, pixmapIcon); + QFont font = q_ptr->font(); + if (!pixmap.path().isEmpty()) + font.setBold(true); + m_stateComboBox->setItemData(index, font, Qt::FontRole); + } + + PropertySheetPixmapValue currentPixmap = paths.value(currentState()); + m_resetAction->setEnabled(!currentPixmap.path().isEmpty()); + m_resetAllAction->setEnabled(!paths.isEmpty()); + m_stateComboBox->update(); +} + +void IconSelectorPrivate::slotStateActivated() +{ + slotUpdate(); +} + +void IconSelectorPrivate::slotSetActivated() +{ + const auto state = currentState(); + const PropertySheetPixmapValue pixmap = m_icon.pixmap(state.first, state.second); + // Default to resource + const PropertySheetPixmapValue::PixmapSource ps = pixmap.path().isEmpty() ? PropertySheetPixmapValue::ResourcePixmap : pixmap.pixmapSource(m_core); + switch (ps) { + case PropertySheetPixmapValue::LanguageResourcePixmap: + case PropertySheetPixmapValue::ResourcePixmap: + slotSetResourceActivated(); + break; + case PropertySheetPixmapValue::FilePixmap: + slotSetFileActivated(); + break; + } +} + +// Choose a pixmap from resource; use language-dependent resource browser if present +QString IconSelector::choosePixmapResource(QDesignerFormEditorInterface *core, QtResourceModel *resourceModel, const QString &oldPath, QWidget *parent) +{ + Q_UNUSED(resourceModel); + QString rc; + + if (LanguageResourceDialog* ldlg = LanguageResourceDialog::create(core, parent)) { + ldlg->setCurrentPath(oldPath); + if (ldlg->exec() == QDialog::Accepted) + rc = ldlg->currentPath(); + delete ldlg; + } else { + QtResourceViewDialog dlg(core, parent); + dlg.setResourceEditingEnabled(core->integration()->hasFeature(QDesignerIntegration::ResourceEditorFeature)); + + dlg.selectResource(oldPath); + if (dlg.exec() == QDialog::Accepted) + rc = dlg.selectedResource(); + } + return rc; +} + +void IconSelectorPrivate::slotSetResourceActivated() +{ + const auto state = currentState(); + + PropertySheetPixmapValue pixmap = m_icon.pixmap(state.first, state.second); + const QString oldPath = pixmap.path(); + const QString newPath = IconSelector::choosePixmapResource(m_core, m_resourceModel, oldPath, q_ptr); + if (newPath.isEmpty() || newPath == oldPath) + return; + const PropertySheetPixmapValue newPixmap = PropertySheetPixmapValue(newPath); + if (newPixmap != pixmap) { + m_icon.setPixmap(state.first, state.second, newPixmap); + slotUpdate(); + emit q_ptr->iconChanged(m_icon); + } +} + +// Helpers for choosing image files: Check for valid image. +bool IconSelector::checkPixmap(const QString &fileName, CheckMode cm, QString *errorMessage) +{ + const QFileInfo fi(fileName); + if (!fi.exists() || !fi.isFile() || !fi.isReadable()) { + if (errorMessage) + *errorMessage = tr("The pixmap file '%1' cannot be read.").arg(fileName); + return false; + } + QImageReader reader(fileName); + if (!reader.canRead()) { + if (errorMessage) + *errorMessage = tr("The file '%1' does not appear to be a valid pixmap file: %2") + .arg(fileName, reader.errorString()); + return false; + } + if (cm == CheckFast) + return true; + + const QImage image = reader.read(); + if (image.isNull()) { + if (errorMessage) + *errorMessage = tr("The file '%1' could not be read: %2") + .arg(fileName, reader.errorString()); + return false; + } + return true; +} + +// Helpers for choosing image files: Return an image filter for QFileDialog, courtesy of StyledButton +static QString imageFilter() +{ + QString filter = QApplication::translate("IconSelector", "All Pixmaps ("); + const auto supportedImageFormats = QImageReader::supportedImageFormats(); + const qsizetype count = supportedImageFormats.size(); + for (qsizetype i = 0; i < count; ++i) { + if (i) + filter += u' '; + filter += "*."_L1; + const QString outputFormat = QString::fromUtf8(supportedImageFormats.at(i)); + if (outputFormat != "JPEG"_L1) + filter += outputFormat.toLower(); + else + filter += "jpg *.jpeg"_L1; + } + filter += u')'; + return filter; +} + +// Helpers for choosing image files: Choose a file +QString IconSelector::choosePixmapFile(const QString &directory, QDesignerDialogGuiInterface *dlgGui,QWidget *parent) +{ + QString errorMessage; + QString newPath; + do { + const QString title = tr("Choose a Pixmap"); + static const QString filter = imageFilter(); + newPath = dlgGui->getOpenImageFileName(parent, title, directory, filter); + if (newPath.isEmpty()) + break; + if (checkPixmap(newPath, CheckFully, &errorMessage)) + break; + dlgGui->message(parent, QDesignerDialogGuiInterface::ResourceEditorMessage, QMessageBox::Warning, tr("Pixmap Read Error"), errorMessage); + } while(true); + return newPath; +} + +void IconSelectorPrivate::slotSetFileActivated() +{ + const auto state = currentState(); + + PropertySheetPixmapValue pixmap = m_icon.pixmap(state.first, state.second); + const QString newPath = IconSelector::choosePixmapFile(pixmap.path(), m_core->dialogGui(), q_ptr); + if (!newPath.isEmpty()) { + const PropertySheetPixmapValue newPixmap = PropertySheetPixmapValue(newPath); + if (!(newPixmap == pixmap)) { + m_icon.setPixmap(state.first, state.second, newPixmap); + slotUpdate(); + emit q_ptr->iconChanged(m_icon); + } + } +} + +void IconSelectorPrivate::slotResetActivated() +{ + const auto state = currentState(); + + PropertySheetPixmapValue pixmap = m_icon.pixmap(state.first, state.second); + const PropertySheetPixmapValue newPixmap; + if (!(newPixmap == pixmap)) { + m_icon.setPixmap(state.first, state.second, newPixmap); + slotUpdate(); + emit q_ptr->iconChanged(m_icon); + } +} + +void IconSelectorPrivate::slotResetAllActivated() +{ + const PropertySheetIconValue newIcon; + if (!(m_icon == newIcon)) { + m_icon = newIcon; + slotUpdate(); + emit q_ptr->iconChanged(m_icon); + } +} + +// ------------- IconSelector +IconSelector::IconSelector(QWidget *parent) : + QWidget(parent), d_ptr(new IconSelectorPrivate()) +{ + d_ptr->q_ptr = this; + + d_ptr->m_stateComboBox = new QComboBox(this); + + QHBoxLayout *l = new QHBoxLayout(this); + d_ptr->m_iconButton = new QToolButton(this); + d_ptr->m_iconButton->setText(tr("...")); + d_ptr->m_iconButton->setPopupMode(QToolButton::MenuButtonPopup); + l->addWidget(d_ptr->m_stateComboBox); + l->addWidget(d_ptr->m_iconButton); + l->setContentsMargins(QMargins()); + + QMenu *setMenu = new QMenu(this); + + QAction *setResourceAction = new QAction(tr("Choose Resource..."), this); + QAction *setFileAction = new QAction(tr("Choose File..."), this); + d_ptr->m_resetAction = new QAction(tr("Reset"), this); + d_ptr->m_resetAllAction = new QAction(tr("Reset All"), this); + d_ptr->m_resetAction->setEnabled(false); + d_ptr->m_resetAllAction->setEnabled(false); + //d_ptr->m_resetAction->setIcon(createIconSet("resetproperty.png"_L1)); + + setMenu->addAction(setResourceAction); + setMenu->addAction(setFileAction); + setMenu->addSeparator(); + setMenu->addAction(d_ptr->m_resetAction); + setMenu->addAction(d_ptr->m_resetAllAction); + + for (const auto &item : stateToName) + d_ptr->m_stateComboBox->addItem(tr(item.name)); + + d_ptr->m_iconButton->setMenu(setMenu); + + connect(d_ptr->m_stateComboBox, &QComboBox::activated, + this, [this] { d_ptr->slotStateActivated(); }); + connect(d_ptr->m_iconButton, &QAbstractButton::clicked, + this, [this] { d_ptr->slotSetActivated(); }); + connect(setResourceAction, &QAction::triggered, + this, [this] { d_ptr->slotSetResourceActivated(); }); + connect(setFileAction, &QAction::triggered, + this, [this] { d_ptr->slotSetFileActivated(); }); + connect(d_ptr->m_resetAction, &QAction::triggered, + this, [this] { d_ptr->slotResetActivated(); }); + connect(d_ptr->m_resetAllAction, &QAction::triggered, + this, [this] { d_ptr->slotResetAllActivated(); }); + d_ptr->slotUpdate(); +} + +IconSelector::~IconSelector() = default; + +void IconSelector::setIcon(const PropertySheetIconValue &icon) +{ + if (d_ptr->m_icon == icon) + return; + + d_ptr->m_icon = icon; + d_ptr->slotUpdate(); +} + +PropertySheetIconValue IconSelector::icon() const +{ + return d_ptr->m_icon; +} + +void IconSelector::setFormEditor(QDesignerFormEditorInterface *core) +{ + d_ptr->m_core = core; + d_ptr->m_resourceModel = core->resourceModel(); + d_ptr->slotUpdate(); +} + +void IconSelector::setIconCache(DesignerIconCache *iconCache) +{ + d_ptr->m_iconCache = iconCache; + connect(iconCache, &DesignerIconCache::reloaded, this, [this] { d_ptr->slotUpdate(); }); + d_ptr->slotUpdate(); +} + +void IconSelector::setPixmapCache(DesignerPixmapCache *pixmapCache) +{ + d_ptr->m_pixmapCache = pixmapCache; + connect(pixmapCache, &DesignerPixmapCache::reloaded, this, [this] { d_ptr->slotUpdate(); }); + d_ptr->slotUpdate(); +} + +// --- IconThemeEditor + +static const QMap &themeIcons() +{ + static QMap result; + if (result.isEmpty()) { + QFile file(u":/qt-project.org/designer/icon-naming-spec.txt"_s); + if (file.open(QIODevice::ReadOnly)) { + while (!file.atEnd()) { + const auto line = file.readLine().trimmed(); + if (line.isEmpty() || line.startsWith('#')) + continue; + const auto iconName = QString::fromUtf8(line); + result.insert(iconName, QIcon::fromTheme(iconName)); + } + file.close(); + } + } + return result; +} + +struct IconThemeEditorPrivate { + void create(QWidget *topLevel, bool wantResetButton); + + QComboBox *m_themeComboBox{}; + QToolButton *m_themeResetButton{}; +}; + +void IconThemeEditorPrivate::create(QWidget *topLevel, bool wantResetButton) +{ + m_themeComboBox = new QComboBox(); + QHBoxLayout *mainHLayout = new QHBoxLayout(topLevel); + mainHLayout->setContentsMargins({}); + mainHLayout->addWidget(m_themeComboBox); + if (wantResetButton) { + m_themeResetButton = new QToolButton; + m_themeResetButton->setIcon(createIconSet("resetproperty.png"_L1)); + mainHLayout->addWidget(m_themeResetButton); + } + topLevel->setFocusProxy(m_themeComboBox); +} + +IconThemeEditor::IconThemeEditor(QWidget *parent, bool wantResetButton) : + QWidget (parent), d(new IconThemeEditorPrivate) +{ + d->create(this, wantResetButton); + d->m_themeComboBox->setEditable(true); + + const auto icons = themeIcons(); + for (auto i = icons.constBegin(); i != icons.constEnd(); ++i) + d->m_themeComboBox->addItem(i.value(), i.key()); + d->m_themeComboBox->setCurrentIndex(-1); + d->m_themeComboBox->lineEdit()->setValidator(new BlankSuppressingValidator(this)); + connect(d->m_themeComboBox, &QComboBox::currentTextChanged, this, &IconThemeEditor::edited); + if (wantResetButton) + connect(d->m_themeResetButton, &QAbstractButton::clicked, this, &IconThemeEditor::reset); +} + +IconThemeEditor::~IconThemeEditor() = default; + +void IconThemeEditor::reset() +{ + d->m_themeComboBox->setCurrentIndex(-1); + emit edited(QString()); +} + +QString IconThemeEditor::theme() const +{ + return d->m_themeComboBox->currentText(); +} + +void IconThemeEditor::setTheme(const QString &t) +{ + d->m_themeComboBox->setCurrentText(t); +} + +IconThemeEnumEditor::IconThemeEnumEditor(QWidget *parent, bool wantResetButton) : + QWidget (parent), d(new IconThemeEditorPrivate) +{ + d->create(this, wantResetButton); + initThemeCombo(d->m_themeComboBox); + + connect(d->m_themeComboBox, &QComboBox::currentIndexChanged, + this, &IconThemeEnumEditor::edited); + if (wantResetButton) + connect(d->m_themeResetButton, &QAbstractButton::clicked, this, &IconThemeEnumEditor::reset); +} + +IconThemeEnumEditor::~IconThemeEnumEditor() = default; + +void IconThemeEnumEditor::reset() +{ + d->m_themeComboBox->setCurrentIndex(-1); + emit edited(-1); +} + +int IconThemeEnumEditor::themeEnum() const +{ + return d->m_themeComboBox->currentIndex(); +} + +void IconThemeEnumEditor::setThemeEnum(int t) +{ + Q_ASSERT(t >= -1 && t < int(QIcon::ThemeIcon::NThemeIcons)); + d->m_themeComboBox->setCurrentIndex(t); +} + +QString IconThemeEnumEditor::iconName(int e) +{ + return QResourceBuilder::themeIconNames().value(e); +} + +QComboBox *IconThemeEnumEditor::createComboBox(QWidget *parent) +{ + auto *result = new QComboBox(parent); + initThemeCombo(result); + return result; +} + +} // qdesigner_internal + +QT_END_NAMESPACE + +#include "moc_iconselector_p.cpp" diff --git a/src/designer/src/lib/shared/iconselector_p.h b/src/designer/src/lib/shared/iconselector_p.h new file mode 100644 index 0000000..4a4238e --- /dev/null +++ b/src/designer/src/lib/shared/iconselector_p.h @@ -0,0 +1,146 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + + +#ifndef ICONSELECTOR_H +#define ICONSELECTOR_H + +#include "shared_global_p.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QComboBox; + +class QtResourceModel; +class QDesignerFormEditorInterface; +class QDesignerDialogGuiInterface; +class QDesignerResourceBrowserInterface; + +namespace qdesigner_internal { + +class DesignerIconCache; +class DesignerPixmapCache; +class PropertySheetIconValue; +struct IconThemeEditorPrivate; + +// Resource Dialog that embeds the language-dependent resource widget as returned by the language extension +class QDESIGNER_SHARED_EXPORT LanguageResourceDialog : public QDialog +{ + Q_OBJECT + + explicit LanguageResourceDialog(QDesignerResourceBrowserInterface *rb, QWidget *parent = nullptr); + +public: + ~LanguageResourceDialog() override; + // Factory: Returns 0 if the language extension does not provide a resource browser. + static LanguageResourceDialog* create(QDesignerFormEditorInterface *core, QWidget *parent); + + void setCurrentPath(const QString &filePath); + QString currentPath() const; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(LanguageResourceDialog) + Q_DISABLE_COPY_MOVE(LanguageResourceDialog) + +}; + +class QDESIGNER_SHARED_EXPORT IconSelector: public QWidget +{ + Q_OBJECT +public: + IconSelector(QWidget *parent = nullptr); + ~IconSelector() override; + + void setFormEditor(QDesignerFormEditorInterface *core); // required for dialog gui. + void setIconCache(DesignerIconCache *iconCache); + void setPixmapCache(DesignerPixmapCache *pixmapCache); + + void setIcon(const PropertySheetIconValue &icon); + PropertySheetIconValue icon() const; + + // Check whether a pixmap may be read + enum CheckMode { CheckFast, CheckFully }; + static bool checkPixmap(const QString &fileName, CheckMode cm = CheckFully, QString *errorMessage = nullptr); + // Choose a pixmap from file + static QString choosePixmapFile(const QString &directory, QDesignerDialogGuiInterface *dlgGui, QWidget *parent); + // Choose a pixmap from resource; use language-dependent resource browser if present + static QString choosePixmapResource(QDesignerFormEditorInterface *core, QtResourceModel *resourceModel, const QString &oldPath, QWidget *parent); + +signals: + void iconChanged(const PropertySheetIconValue &icon); +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(IconSelector) + Q_DISABLE_COPY_MOVE(IconSelector) +}; + +// IconThemeEditor: Let's the user input theme icon names and shows a preview label. +class QDESIGNER_SHARED_EXPORT IconThemeEditor : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString theme READ theme WRITE setTheme DESIGNABLE true) +public: + explicit IconThemeEditor(QWidget *parent = nullptr, bool wantResetButton = true); + ~IconThemeEditor() override; + + QString theme() const; + void setTheme(const QString &theme); + +signals: + void edited(const QString &); + +public slots: + void reset(); + +private: + QScopedPointer d; +}; + +// IconThemeEnumEditor: Let's the user input theme icon enum values +// (QIcon::ThemeIcon) and shows a preview label. -1 means nothing selected. +class QDESIGNER_SHARED_EXPORT IconThemeEnumEditor : public QWidget +{ + Q_OBJECT +public: + explicit IconThemeEnumEditor(QWidget *parent = nullptr, bool wantResetButton = true); + ~IconThemeEnumEditor() override; + + int themeEnum() const; + void setThemeEnum(int); + + static QString iconName(int e); + static QComboBox *createComboBox(QWidget *parent = nullptr); + +signals: + void edited(int); + +public slots: + void reset(); + +private: + QScopedPointer d; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ICONSELECTOR_H + diff --git a/src/designer/src/lib/shared/invisible_widget.cpp b/src/designer/src/lib/shared/invisible_widget.cpp new file mode 100644 index 0000000..7fec394 --- /dev/null +++ b/src/designer/src/lib/shared/invisible_widget.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "invisible_widget_p.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +InvisibleWidget::InvisibleWidget(QWidget *parent) + : QWidget() +{ + setAttribute(Qt::WA_NoChildEventsForParent); + setParent(parent); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/invisible_widget_p.h b/src/designer/src/lib/shared/invisible_widget_p.h new file mode 100644 index 0000000..30a7970 --- /dev/null +++ b/src/designer/src/lib/shared/invisible_widget_p.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef INVISIBLE_WIDGET_H +#define INVISIBLE_WIDGET_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT InvisibleWidget: public QWidget +{ + Q_OBJECT +public: + InvisibleWidget(QWidget *parent = nullptr); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // INVISIBLE_WIDGET_H diff --git a/src/designer/src/lib/shared/layout.cpp b/src/designer/src/lib/shared/layout.cpp new file mode 100644 index 0000000..b35361a --- /dev/null +++ b/src/designer/src/lib/shared/layout.cpp @@ -0,0 +1,1219 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "layout_p.h" +#include "layoutdecoration.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_widgetitem_p.h" +#include "qlayout_widget_p.h" +#include "spacer_widget_p.h" +#include "widgetfactory_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +/* The wizard has a policy of setting a size policy of its external children + * according to the page being expanding or not (in the latter case, the + * page will be pushed to the top). When setting/breaking layouts, this needs + * to be updated, which happens via a fake style change event. */ + +void updateWizardLayout(QWidget *layoutBase); + +class FriendlyWizardPage : public QWizardPage { + friend void updateWizardLayout(QWidget *); +}; + +void updateWizardLayout(QWidget *layoutBase) +{ + if (QWizardPage *wizardPage = qobject_cast(layoutBase)) + if (QWizard *wizard = static_cast(wizardPage)->wizard()) { + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(wizard, &event); + } +} + +/*! + \class qdesigner_internal::Layout + \brief Baseclass for layouting widgets in the Designer (Helper for Layout commands) + \internal + + Classes derived from this abstract base class are used for layouting + operations in the Designer (creating/breaking layouts). + + Instances live in the Layout/BreakLayout commands. +*/ + +/*! \a p specifies the parent of the layoutBase \a lb. The parent + might be changed in setup(). If the layoutBase is a + container, the parent and the layoutBase are the same. Also they + always have to be a widget known to the designer (e.g. in the case + of the tabwidget parent and layoutBase are the tabwidget and not the + page which actually gets laid out. For actual usage the correct + widget is found later by Layout.) + */ + +Layout::Layout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, LayoutInfo::Type layoutType) : + m_widgets(wl), + m_parentWidget(p), + m_layoutBase(lb), + m_formWindow(fw), + m_layoutType(layoutType), + m_reparentLayoutWidget(true), + m_isBreak(false) +{ + if (m_layoutBase) + m_oldGeometry = m_layoutBase->geometry(); +} + +Layout::~Layout() = default; + +/*! The widget list we got in the constructor might contain too much + widgets (like widgets with different parents, already laid out + widgets, etc.). Here we set up the list and so the only the "best" + widgets get laid out. +*/ + +void Layout::setup() +{ + m_startPoint = QPoint(32767, 32767); + + // Go through all widgets of the list we got. As we can only + // layout widgets which have the same parent, we first do some + // sorting which means create a list for each parent containing + // its child here. After that we keep working on the list of + // children which has the most entries. + // Widgets which are already laid out are thrown away here too + + QMultiMap lists; + for (QWidget *w : std::as_const(m_widgets)) { + QWidget *p = w->parentWidget(); + + if (p && LayoutInfo::layoutType(m_formWindow->core(), p) != LayoutInfo::NoLayout + && m_formWindow->core()->metaDataBase()->item(p->layout()) != nullptr) + continue; + + lists.insert(p, w); + } + + QWidgetList lastList; + const QWidgetList &parents = lists.keys(); + for (QWidget *p : parents) { + if (lists.count(p) > lastList.size()) + lastList = lists.values(p); + } + + + // If we found no list (because no widget did fit at all) or the + // best list has only one entry and we do not layout a container, + // we leave here. + QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase(); + if (lastList.size() < 2 && + (!m_layoutBase || + (!widgetDataBase->isContainer(m_layoutBase, false) && + m_layoutBase != m_formWindow->mainContainer())) + ) { + m_widgets.clear(); + m_startPoint = QPoint(0, 0); + return; + } + + // Now we have a new and clean widget list, which makes sense + // to layout + m_widgets = lastList; + // Also use the only correct parent later, so store it + + Q_ASSERT(m_widgets.isEmpty() == false); + + m_parentWidget = m_formWindow->core()->widgetFactory()->widgetOfContainer(m_widgets.first()->parentWidget()); + // Now calculate the position where the layout-meta-widget should + // be placed and connect to widgetDestroyed() signals of the + // widgets to get informed if one gets deleted to be able to + // handle that and do not crash in this case + for (QWidget *w : std::as_const(m_widgets)) { + connect(w, &QObject::destroyed, this, &Layout::widgetDestroyed); + m_startPoint = QPoint(qMin(m_startPoint.x(), w->x()), qMin(m_startPoint.y(), w->y())); + const QRect rc(w->geometry()); + + m_geometries.insert(w, rc); + // Change the Z-order, as saving/loading uses the Z-order for + // writing/creating widgets and this has to be the same as in + // the layout. Else saving + loading will give different results + w->raise(); + } + + sort(); +} + +void Layout::widgetDestroyed() +{ + if (QWidget *w = qobject_cast(sender())) { + m_widgets.removeAt(m_widgets.indexOf(w)); + m_geometries.remove(w); + } +} + +bool Layout::prepareLayout(bool &needMove, bool &needReparent) +{ + for (QWidget *widget : std::as_const(m_widgets)) + widget->raise(); + + needMove = !m_layoutBase; + needReparent = needMove || (m_reparentLayoutWidget && qobject_cast(m_layoutBase)) || qobject_cast(m_layoutBase); + + QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory(); + QDesignerMetaDataBaseInterface *metaDataBase = m_formWindow->core()->metaDataBase(); + + if (m_layoutBase == nullptr) { + const bool useSplitter = m_layoutType == LayoutInfo::HSplitter || m_layoutType == LayoutInfo::VSplitter; + const QString baseWidgetClassName = useSplitter ? u"QSplitter"_s : u"QLayoutWidget"_s; + m_layoutBase = widgetFactory->createWidget(baseWidgetClassName, widgetFactory->containerOfWidget(m_parentWidget)); + if (useSplitter) { + m_layoutBase->setObjectName(u"splitter"_s); + m_formWindow->ensureUniqueObjectName(m_layoutBase); + } + } else { + LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); + } + + metaDataBase->add(m_layoutBase); + + Q_ASSERT(m_layoutBase->layout() == nullptr || metaDataBase->item(m_layoutBase->layout()) == nullptr); + + return true; +} + +static bool isMainContainer(QDesignerFormWindowInterface *fw, const QWidget *w) +{ + return w && (w == fw || w == fw->mainContainer()); +} + +static bool isPageOfContainerWidget(QDesignerFormWindowInterface *fw, QWidget *widget) +{ + QDesignerContainerExtension *c = qt_extension( + fw->core()->extensionManager(), widget->parentWidget()); + + if (c != nullptr) { + for (int i = 0; icount(); ++i) { + if (widget == c->widget(i)) + return true; + } + } + + return false; +} +void Layout::finishLayout(bool needMove, QLayout *layout) +{ + if (m_parentWidget == m_layoutBase) { + QWidget *widget = m_layoutBase; + m_oldGeometry = widget->geometry(); + + bool done = false; + while (!isMainContainer(m_formWindow, widget) && !done) { + if (!m_formWindow->isManaged(widget)) { + widget = widget->parentWidget(); + continue; + } + if (LayoutInfo::isWidgetLaidout(m_formWindow->core(), widget)) { + widget = widget->parentWidget(); + continue; + } + if (isPageOfContainerWidget(m_formWindow, widget)) { + widget = widget->parentWidget(); + continue; + } + if (widget->parentWidget()) { + QScrollArea *area = qobject_cast(widget->parentWidget()->parentWidget()); + if (area && area->widget() == widget) { + widget = area; + continue; + } + } + + done = true; + } + updateWizardLayout(m_layoutBase); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + // We don't want to resize the form window + if (!Utils::isCentralWidget(m_formWindow, widget)) + widget->adjustSize(); + + return; + } + + if (needMove) + m_layoutBase->move(m_startPoint); + + const QRect g(m_layoutBase->pos(), m_layoutBase->size()); + + if (LayoutInfo::layoutType(m_formWindow->core(), m_layoutBase->parentWidget()) == LayoutInfo::NoLayout && !m_isBreak) + m_layoutBase->adjustSize(); + else if (m_isBreak) + m_layoutBase->setGeometry(m_oldGeometry); + + m_oldGeometry = g; + if (layout) + layout->invalidate(); + m_layoutBase->show(); + + if (qobject_cast(m_layoutBase) || qobject_cast(m_layoutBase)) { + m_formWindow->clearSelection(false); + m_formWindow->manageWidget(m_layoutBase); + m_formWindow->selectWidget(m_layoutBase); + } +} + +void Layout::undoLayout() +{ + if (m_widgets.isEmpty()) + return; + + m_formWindow->selectWidget(m_layoutBase, false); + + QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory(); + for (auto it = m_geometries.cbegin(), end = m_geometries.cend(); it != end; ++it) { + if (!it.key()) + continue; + + QWidget* w = it.key(); + const QRect rc = it.value(); + + const bool showIt = w->isVisibleTo(m_formWindow); + QWidget *container = widgetFactory->containerOfWidget(m_parentWidget); + + // ### remove widget here + QWidget *parentWidget = w->parentWidget(); + QDesignerFormEditorInterface *core = m_formWindow->core(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); + + if (deco) + deco->removeWidget(w); + + w->setParent(container); + w->setGeometry(rc); + + if (showIt) + w->show(); + } + + LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); + + if (m_parentWidget != m_layoutBase && !qobject_cast(m_layoutBase)) { + m_formWindow->unmanageWidget(m_layoutBase); + m_layoutBase->hide(); + } else { + QMainWindow *mw = qobject_cast(m_formWindow->mainContainer()); + if (m_layoutBase != m_formWindow->mainContainer() && + (!mw || mw->centralWidget() != m_layoutBase)) + m_layoutBase->setGeometry(m_oldGeometry); + } +} + +void Layout::breakLayout() +{ + QHash rects; + /* Store the geometry of the widgets. The idea is to give the user space + * to rearrange them, so, we do a adjustSize() on them, unless they want + * to grow (expanding widgets like QTextEdit), in which the geometry is + * preserved. Note that historically, geometries were re-applied + * only after breaking splitters. */ + for (QWidget *w : std::as_const(m_widgets)) { + const QRect geom = w->geometry(); + const QSize sizeHint = w->sizeHint(); + const bool restoreGeometry = sizeHint.isEmpty() || sizeHint.width() > geom.width() || sizeHint.height() > geom.height(); + rects.insert(w, restoreGeometry ? w->geometry() : QRect(geom.topLeft(), QSize())); + } + const QPoint m_layoutBasePos = m_layoutBase->pos(); + QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase(); + + LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); + + const bool needReparent = (m_reparentLayoutWidget && qobject_cast(m_layoutBase)) || + qobject_cast(m_layoutBase) || + (!widgetDataBase->isContainer(m_layoutBase, false) && + m_layoutBase != m_formWindow->mainContainer()); + const bool add = m_geometries.isEmpty(); + + for (auto it = rects.cbegin(), end = rects.cend(); it != end; ++it) { + QWidget *w = it.key(); + if (needReparent) { + w->setParent(m_layoutBase->parentWidget(), {}); + w->move(m_layoutBasePos + it.value().topLeft()); + w->show(); + } + + const QRect oldGeometry = it.value(); + if (oldGeometry.isEmpty()) { + w->adjustSize(); + } else { + w->resize(oldGeometry.size()); + } + + if (add) + m_geometries.insert(w, QRect(w->pos(), w->size())); + } + + if (needReparent) { + m_layoutBase->hide(); + m_parentWidget = m_layoutBase->parentWidget(); + m_formWindow->unmanageWidget(m_layoutBase); + } else { + m_parentWidget = m_layoutBase; + } + updateWizardLayout(m_layoutBase); + + if (!m_widgets.isEmpty() && m_widgets.first() && m_widgets.first()->isVisibleTo(m_formWindow)) + m_formWindow->selectWidget(m_widgets.first()); + else + m_formWindow->selectWidget(m_formWindow); +} + +static QString suggestLayoutName(const char *className) +{ + // Legacy + if (!qstrcmp(className, "QHBoxLayout")) + return u"horizontalLayout"_s; + if (!qstrcmp(className, "QVBoxLayout")) + return u"verticalLayout"_s; + if (!qstrcmp(className, "QGridLayout")) + return u"gridLayout"_s; + + return qtify(QString::fromUtf8(className)); +} +QLayout *Layout::createLayout(int type) +{ + Q_ASSERT(m_layoutType != LayoutInfo::HSplitter && m_layoutType != LayoutInfo::VSplitter); + QLayout *layout = m_formWindow->core()->widgetFactory()->createLayout(m_layoutBase, nullptr, type); + // set a name + layout->setObjectName(suggestLayoutName(layout->metaObject()->className())); + m_formWindow->ensureUniqueObjectName(layout); + // QLayoutWidget + QDesignerPropertySheetExtension *sheet = qt_extension(m_formWindow->core()->extensionManager(), layout); + if (sheet && qobject_cast(m_layoutBase)) { + sheet->setProperty(sheet->indexOf(u"leftMargin"_s), 0); + sheet->setProperty(sheet->indexOf(u"topMargin"_s), 0); + sheet->setProperty(sheet->indexOf(u"rightMargin"_s), 0); + sheet->setProperty(sheet->indexOf(u"bottomMargin"_s), 0); + } + return layout; +} + +void Layout::reparentToLayoutBase(QWidget *w) +{ + if (w->parent() != m_layoutBase) { + w->setParent(m_layoutBase, {}); + w->move(QPoint(0,0)); + } +} + +namespace { // within qdesigner_internal + +// ----- PositionSortPredicate: Predicate to be usable as LessThan function to sort widgets by position +class PositionSortPredicate { +public: + PositionSortPredicate(Qt::Orientation orientation) : m_orientation(orientation) {} + bool operator()(const QWidget* w1, const QWidget* w2) { + return m_orientation == Qt::Horizontal ? w1->x() < w2->x() : w1->y() < w2->y(); + } + private: + const Qt::Orientation m_orientation; +}; + +// -------- BoxLayout +class BoxLayout : public Layout +{ +public: + BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, + Qt::Orientation orientation); + + void doLayout() override; + void sort() override; + +private: + const Qt::Orientation m_orientation; +}; + +BoxLayout::BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, + Qt::Orientation orientation) : + Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox), + m_orientation(orientation) +{ +} + +void BoxLayout::sort() +{ + QWidgetList wl = widgets(); + std::stable_sort(wl.begin(), wl.end(), PositionSortPredicate(m_orientation)); + setWidgets(wl); +} + +void BoxLayout::doLayout() +{ + bool needMove, needReparent; + if (!prepareLayout(needMove, needReparent)) + return; + + QBoxLayout *layout = static_cast(createLayout(m_orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox)); + + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + + for (auto *w : widgets()) { + if (needReparent) + reparentToLayoutBase(w); + + if (const Spacer *spacer = qobject_cast(w)) + layout->addWidget(w, 0, spacer->alignment()); + else + layout->addWidget(w); + w->show(); + } + finishLayout(needMove, layout); +} + +// -------- SplitterLayout +class SplitterLayout : public Layout +{ +public: + SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, + Qt::Orientation orientation); + + void doLayout() override; + void sort() override; + +private: + const Qt::Orientation m_orientation; +}; + +SplitterLayout::SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, + Qt::Orientation orientation) : + Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HSplitter : LayoutInfo::VSplitter), + m_orientation(orientation) +{ +} + +void SplitterLayout::sort() +{ + QWidgetList wl = widgets(); + std::stable_sort(wl.begin(), wl.end(), PositionSortPredicate(m_orientation)); + setWidgets(wl); +} + +void SplitterLayout::doLayout() +{ + bool needMove, needReparent; + if (!prepareLayout(needMove, needReparent)) + return; + + QSplitter *splitter = qobject_cast(layoutBaseWidget()); + Q_ASSERT(splitter != nullptr); + + for (auto *w : widgets()) { + if (needReparent) + reparentToLayoutBase(w); + splitter->addWidget(w); + w->show(); + } + + splitter->setOrientation(m_orientation); + finishLayout(needMove); +} + +// ---------- Grid: Helper for laying out grids + +class GridHelper +{ + Q_DISABLE_COPY_MOVE(GridHelper); +public: + enum { FormLayoutColumns = 2 }; + + enum Mode { + GridLayout, // Arbitrary size/supports span + FormLayout // 2-column/no span + }; + + GridHelper(Mode mode); + void resize(int nrows, int ncols); + + ~GridHelper(); + + QWidget* cell(int row, int col) const { return m_cells[ row * m_ncols + col]; } + + void setCells(const QRect &c, QWidget* w); + + bool empty() const { return !m_nrows || !m_ncols; } + int numRows() const { return m_nrows; } + int numCols() const { return m_ncols; } + + void simplify(); + bool locateWidget(QWidget* w, int& row, int& col, int& rowspan, int& colspan) const; + +private: + void setCell(int row, int col, QWidget* w) { m_cells[ row * m_ncols + col] = w; } + void shrink(); + void reallocFormLayout(); + int countRow(int r, int c) const; + int countCol(int r, int c) const; + void setRow(int r, int c, QWidget* w, int count); + void setCol(int r, int c, QWidget* w, int count); + bool isWidgetStartCol(int c) const; + bool isWidgetEndCol(int c) const; + bool isWidgetStartRow(int r) const; + bool isWidgetEndRow(int r) const; + bool isWidgetTopLeft(int r, int c) const; + void extendLeft(); + void extendRight(); + void extendUp(); + void extendDown(); + bool shrinkFormLayoutSpans(); + + const Mode m_mode; + int m_nrows; + int m_ncols; + + QWidget** m_cells; // widget matrix w11, w12, w21... +}; + +GridHelper::GridHelper(Mode mode) : + m_mode(mode), + m_nrows(0), + m_ncols(0), + m_cells(nullptr) +{ +} + +GridHelper::~GridHelper() +{ + delete [] m_cells; +} + +void GridHelper::resize(int nrows, int ncols) +{ + delete [] m_cells; + m_cells = nullptr; + m_nrows = nrows; + m_ncols = ncols; + if (const int allocSize = m_nrows * m_ncols) { + m_cells = new QWidget*[allocSize]; + std::fill(m_cells, m_cells + allocSize, nullptr); + } +} + +void GridHelper::setCells(const QRect &c, QWidget* w) +{ + const int bottom = c.top() + c.height(); + const int width = c.width(); + + for (int r = c.top(); r < bottom; r++) { + QWidget **pos = m_cells + r * m_ncols + c.left(); + std::fill(pos, pos + width, w); + } +} + +int GridHelper::countRow(int r, int c) const +{ + QWidget* w = cell(r, c); + int i = c + 1; + while (i < m_ncols && cell(r, i) == w) + i++; + return i - c; +} + +int GridHelper::countCol(int r, int c) const +{ + QWidget* w = cell(r, c); + int i = r + 1; + while (i < m_nrows && cell(i, c) == w) + i++; + return i - r; +} + +void GridHelper::setCol(int r, int c, QWidget* w, int count) +{ + for (int i = 0; i < count; i++) + setCell(r + i, c, w); +} + +void GridHelper::setRow(int r, int c, QWidget* w, int count) +{ + for (int i = 0; i < count; i++) + setCell(r, c + i, w); +} + +bool GridHelper::isWidgetStartCol(int c) const +{ + for (int r = 0; r < m_nrows; r++) { + if (cell(r, c) && ((c==0) || (cell(r, c) != cell(r, c-1)))) { + return true; + } + } + return false; +} + +bool GridHelper::isWidgetEndCol(int c) const +{ + for (int r = 0; r < m_nrows; r++) { + if (cell(r, c) && ((c == m_ncols-1) || (cell(r, c) != cell(r, c+1)))) + return true; + } + return false; +} + +bool GridHelper::isWidgetStartRow(int r) const +{ + for ( int c = 0; c < m_ncols; c++) { + if (cell(r, c) && ((r==0) || (cell(r, c) != cell(r-1, c)))) + return true; + } + return false; +} + +bool GridHelper::isWidgetEndRow(int r) const +{ + for (int c = 0; c < m_ncols; c++) { + if (cell(r, c) && ((r == m_nrows-1) || (cell(r, c) != cell(r+1, c)))) + return true; + } + return false; +} + + +bool GridHelper::isWidgetTopLeft(int r, int c) const +{ + QWidget* w = cell(r, c); + if (!w) + return false; + return (!r || cell(r-1, c) != w) && (!c || cell(r, c-1) != w); +} + +void GridHelper::extendLeft() +{ + for (int c = 1; c < m_ncols; c++) { + for (int r = 0; r < m_nrows; r++) { + QWidget* w = cell(r, c); + if (!w) + continue; + + const int cc = countCol(r, c); + int stretch = 0; + for (int i = c-1; i >= 0; i--) { + if (cell(r, i)) + break; + if (countCol(r, i) < cc) + break; + if (isWidgetEndCol(i)) + break; + if (isWidgetStartCol(i)) { + stretch = c - i; + break; + } + } + if (stretch) { + for (int i = 0; i < stretch; i++) + setCol(r, c-i-1, w, cc); + } + } + } +} + + +void GridHelper::extendRight() +{ + for (int c = m_ncols - 2; c >= 0; c--) { + for (int r = 0; r < m_nrows; r++) { + QWidget* w = cell(r, c); + if (!w) + continue; + const int cc = countCol(r, c); + int stretch = 0; + for (int i = c+1; i < m_ncols; i++) { + if (cell(r, i)) + break; + if (countCol(r, i) < cc) + break; + if (isWidgetStartCol(i)) + break; + if (isWidgetEndCol(i)) { + stretch = i - c; + break; + } + } + if (stretch) { + for (int i = 0; i < stretch; i++) + setCol(r, c+i+1, w, cc); + } + } + } + +} + +void GridHelper::extendUp() +{ + for (int r = 1; r < m_nrows; r++) { + for (int c = 0; c < m_ncols; c++) { + QWidget* w = cell(r, c); + if (!w) + continue; + const int cr = countRow(r, c); + int stretch = 0; + for (int i = r-1; i >= 0; i--) { + if (cell(i, c)) + break; + if (countRow(i, c) < cr) + break; + if (isWidgetEndRow(i)) + break; + if (isWidgetStartRow(i)) { + stretch = r - i; + break; + } + } + if (stretch) { + for (int i = 0; i < stretch; i++) + setRow(r-i-1, c, w, cr); + } + } + } +} + +void GridHelper::extendDown() +{ + for (int r = m_nrows - 2; r >= 0; r--) { + for (int c = 0; c < m_ncols; c++) { + QWidget* w = cell(r, c); + if (!w) + continue; + const int cr = countRow(r, c); + int stretch = 0; + for (int i = r+1; i < m_nrows; i++) { + if (cell(i, c)) + break; + if (countRow(i, c) < cr) + break; + if (isWidgetStartRow(i)) + break; + if (isWidgetEndRow(i)) { + stretch = i - r; + break; + } + } + if (stretch) { + for (int i = 0; i < stretch; i++) + setRow(r+i+1, c, w, cr); + } + } + } +} + +void GridHelper::simplify() +{ + switch (m_mode) { + case GridLayout: + // Grid: Extend all widgets to occupy most space and delete + // rows/columns that are not bordering on a widget + extendLeft(); + extendRight(); + extendUp(); + extendDown(); + shrink(); + break; + case FormLayout: + // Form: First treat it as a grid to get the same behaviour + // regarding spanning and shrinking. Then restrict the span to + // the horizontal span possible in the form, simplify again + // and spread the widgets over a 2-column layout + extendLeft(); + extendRight(); + extendUp(); + extendDown(); + shrink(); + if (shrinkFormLayoutSpans()) + shrink(); + reallocFormLayout(); + break; + } + +} + +void GridHelper::shrink() +{ + // tick off the occupied cols/rows (bordering on widget edges) + QList columns(m_ncols, false); + QList rows(m_nrows, false); + + for (int c = 0; c < m_ncols; c++) + for (int r = 0; r < m_nrows; r++) + if (isWidgetTopLeft(r, c)) + rows[r] = columns[c] = true; + + // remove empty cols/rows + const int simplifiedNCols = columns.count(true); + const int simplifiedNRows = rows.count(true); + if (simplifiedNCols == m_ncols && simplifiedNRows == m_nrows) + return; + // reallocate and copy omitting the empty cells + QWidget **simplifiedCells = new QWidget*[simplifiedNCols * simplifiedNRows]; + std::fill(simplifiedCells, simplifiedCells + simplifiedNCols * simplifiedNRows, nullptr); + QWidget **simplifiedPtr = simplifiedCells; + + for (int r = 0; r < m_nrows; r++) + if (rows[r]) + for (int c = 0; c < m_ncols; c++) + if (columns[c]) { + if (QWidget *w = cell(r, c)) + *simplifiedPtr = w; + simplifiedPtr++; + } + Q_ASSERT(simplifiedPtr == simplifiedCells + simplifiedNCols * simplifiedNRows); + delete [] m_cells; + m_cells = simplifiedCells; + m_nrows = simplifiedNRows; + m_ncols = simplifiedNCols; +} + +bool GridHelper::shrinkFormLayoutSpans() +{ + bool shrunk = false; + using WidgetSet = QSet; + // Determine unique set of widgets + WidgetSet widgets; + QWidget **end = m_cells + m_ncols * m_nrows; + for (QWidget **wptr = m_cells; wptr < end; wptr++) + if (QWidget *w = *wptr) + widgets.insert(w); + // Restrict the widget span: max horizontal span at column 0: 2, anything else: 1 + const int maxRowSpan = 1; + for (auto *w : std::as_const(widgets)) { + int row, col, rowspan, colspan; + if (!locateWidget(w, row, col, rowspan, colspan)) { + qDebug("ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData()); + row = col = rowspan = colspan = 0; + } + const int maxColSpan = col == 0 ? 2 : 1; + const int newColSpan = qMin(colspan, maxColSpan); + const int newRowSpan = qMin(rowspan, maxRowSpan); + if (newColSpan != colspan || newRowSpan != rowspan) { + // in case like this: + // W1 W1 + // W1 W2 + // do: + // W1 0 + // 0 W2 + for (int i = row; i < row + rowspan - 1; i++) + for (int j = col; j < col + colspan - 1; j++) + if (i > row + newColSpan - 1 || j > col + newRowSpan - 1) + if (cell(i, j) == w) + setCell(i, j, nullptr); + shrunk = true; + } + } + return shrunk; +} + +void GridHelper::reallocFormLayout() +{ + // Columns matching? -> happy! + if (m_ncols == FormLayoutColumns) + return; + + // If there are offset columns (starting past the field column), + // move them to the left and squeeze them. This also prevents the + // following reallocation from creating empty form rows. + int pastRightWidgetCount = 0; + if (m_ncols > FormLayoutColumns) { + for (int r = 0; r < m_nrows; r++) { + // Try to find a column where the form columns are empty and + // there are widgets further to the right. + if (cell(r, 0) == nullptr && cell(r, 1) == nullptr) { + int sourceCol = FormLayoutColumns; + QWidget *firstWidget = nullptr; + for ( ; sourceCol < m_ncols; sourceCol++) + if (QWidget *w = cell(r, sourceCol)) { + firstWidget = w; + break; + } + if (firstWidget) { + // Move/squeeze. Copy to beginning of column if it is a label, else field + int targetCol = qobject_cast(firstWidget) ? 0 : 1; + for ( ; sourceCol < m_ncols; sourceCol++) + if (QWidget *w = cell(r, sourceCol)) + setCell(r, targetCol++, w); + // Pad with zero + for ( ; targetCol < m_ncols; targetCol++) + setCell(r, targetCol, nullptr); + } + } + // Any protruding widgets left on that row? + for (int c = FormLayoutColumns; c < m_ncols; c++) + if (cell(r, c)) + pastRightWidgetCount++; + } + } + // Reallocate with 2 columns. Just insert the protruding ones as fields. + const int formNRows = m_nrows + pastRightWidgetCount; + QWidget **formCells = new QWidget*[FormLayoutColumns * formNRows]; + std::fill(formCells, formCells + FormLayoutColumns * formNRows, nullptr); + QWidget **formPtr = formCells; + const int matchingColumns = qMin(m_ncols, static_cast(FormLayoutColumns)); + for (int r = 0; r < m_nrows; r++) { + int c = 0; + for ( ; c < matchingColumns; c++) // Just copy over matching columns + *formPtr++ = cell(r, c); + formPtr += FormLayoutColumns - matchingColumns; // In case old format was 1 column + // protruding widgets: Insert as single-field rows + for ( ; c < m_ncols; c++) + if (QWidget *w = cell(r, c)) { + formPtr++; + *formPtr++ = w; + } + } + Q_ASSERT(formPtr == formCells + FormLayoutColumns * formNRows); + delete [] m_cells; + m_cells = formCells; + m_nrows = formNRows; + m_ncols = FormLayoutColumns; +} + +bool GridHelper::locateWidget(QWidget *w, int &row, int &col, int &rowspan, int &colspan) const +{ + const int end = m_nrows * m_ncols; + const int startIndex = std::find(m_cells, m_cells + end, w) - m_cells; + if (startIndex == end) + return false; + + row = startIndex / m_ncols; + col = startIndex % m_ncols; + for (rowspan = 1; row + rowspan < m_nrows && cell(row + rowspan, col) == w; rowspan++) {} + for (colspan = 1; col + colspan < m_ncols && cell(row, col + colspan) == w; colspan++) {} + return true; +} + +// QGridLayout/QFormLayout Helpers: get item position/add item (overloads to make templates work) + +void addWidgetToGrid(QGridLayout *lt, QWidget * widget, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment) +{ + lt->addWidget(widget, row, column, rowSpan, columnSpan, alignment); +} + +inline void addWidgetToGrid(QFormLayout *lt, QWidget * widget, int row, int column, int, int columnSpan, Qt::Alignment) +{ + formLayoutAddWidget(lt, widget, QRect(column, row, columnSpan, 1), false); +} + +// ----------- Base template for grid like layouts +template +class GridLayout : public Layout +{ +public: + GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb); + + void doLayout() override; + void sort() override { setWidgets(buildGrid(widgets())); } + +protected: + QWidgetList buildGrid(const QWidgetList &); + GridHelper m_grid; +}; + +template +GridLayout::GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb) : + Layout(wl, p, fw, lb, LayoutInfo::Grid), + m_grid(static_cast(GridMode)) +{ +} + +template +void GridLayout::doLayout() +{ + bool needMove, needReparent; + if (!prepareLayout(needMove, needReparent)) + return; + + GridLikeLayout *layout = static_cast(createLayout(LayoutType)); + + if (!m_grid.empty()) + sort(); + + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + + for (auto *w : widgets()) { + int r = 0, c = 0, rs = 0, cs = 0; + + if (m_grid.locateWidget(w, r, c, rs, cs)) { + if (needReparent) + reparentToLayoutBase(w); + + Qt::Alignment alignment; + if (const Spacer *spacer = qobject_cast(w)) + alignment = spacer->alignment(); + + addWidgetToGrid(layout, w, r, c, rs, cs, alignment); + + w->show(); + } else { + qDebug("ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData()); + } + } + + QLayoutSupport::createEmptyCells(layout); + + finishLayout(needMove, layout); +} + +// Remove duplicate entries (Remove next, if equal to current) +void removeIntVecDuplicates(QList &v) +{ + if (v.size() < 2) + return; + + for (auto current = v.begin() ; (current != v.end()) && ((current + 1) != v.end()) ; ) + if ( *current == *(current+1) ) + v.erase(current+1); + else + ++current; +} + +// Ensure a non-zero size for a widget geometry (squeezed spacers) +inline QRect expandGeometry(const QRect &rect) +{ + return rect.isEmpty() ? QRect(rect.topLeft(), rect.size().expandedTo(QSize(1, 1))) : rect; +} + +template +QWidgetList GridLayout::buildGrid(const QWidgetList &widgetList) +{ + if (widgetList.isEmpty()) + return QWidgetList(); + + // Pixel to cell conversion: + // By keeping a list of start'n'stop values (x & y) for each widget, + // it is possible to create a very small grid of cells to represent + // the widget layout. + // ----------------------------------------------------------------- + + // We need a list of both start and stop values for x- & y-axis + const auto widgetCount = widgetList.size(); + QList x( widgetCount * 2 ); + QList y( widgetCount * 2 ); + + // Using push_back would look nicer, but operator[] is much faster + qsizetype index = 0; + for (const auto *w : widgetList) { + const QRect widgetPos = expandGeometry(w->geometry()); + x[index] = widgetPos.left(); + x[index+1] = widgetPos.right(); + y[index] = widgetPos.top(); + y[index+1] = widgetPos.bottom(); + index += 2; + } + + std::sort(x.begin(), x.end()); + std::sort(y.begin(), y.end()); + + // Remove duplicate x entries (Remove next, if equal to current) + removeIntVecDuplicates(x); + removeIntVecDuplicates(y); + + // Note that left == right and top == bottom for size 1 items; reserve + // enough space + m_grid.resize(y.size(), x.size()); + + for (auto *w : widgetList) { + // Mark the cells in the grid that contains a widget + const QRect widgetPos = expandGeometry(w->geometry()); + QRect c(0, 0, 0, 0); // rect of columns/rows + + // From left til right (not including) + const int leftIdx = x.indexOf(widgetPos.left()); + Q_ASSERT(leftIdx != -1); + c.setLeft(leftIdx); + c.setRight(leftIdx); + for (qsizetype cw = leftIdx; cw < x.size(); ++cw) + if (x.at(cw) < widgetPos.right()) + c.setRight(cw); + else + break; + // From top til bottom (not including) + const int topIdx = y.indexOf(widgetPos.top()); + Q_ASSERT(topIdx != -1); + c.setTop(topIdx); + c.setBottom(topIdx); + for (qsizetype ch = topIdx; ch < y.size(); ++ch) + if (y.at(ch) < widgetPos.bottom()) + c.setBottom(ch); + else + break; + m_grid.setCells(c, w); // Mark cellblock + } + + m_grid.simplify(); + + QWidgetList ordered; + for (int i = 0; i < m_grid.numRows(); i++) + for (int j = 0; j < m_grid.numCols(); j++) { + QWidget *w = m_grid.cell(i, j); + if (w && !ordered.contains(w)) + ordered.append(w); + } + return ordered; +} +} // anonymous + +Layout* Layout::createLayout(const QWidgetList &widgets, QWidget *parentWidget, + QDesignerFormWindowInterface *fw, + QWidget *layoutBase, LayoutInfo::Type layoutType) +{ + switch (layoutType) { + case LayoutInfo::Grid: + return new GridLayout(widgets, parentWidget, fw, layoutBase); + case LayoutInfo::HBox: + case LayoutInfo::VBox: { + const Qt::Orientation orientation = layoutType == LayoutInfo::HBox ? Qt::Horizontal : Qt::Vertical; + return new BoxLayout(widgets, parentWidget, fw, layoutBase, orientation); + } + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: { + const Qt::Orientation orientation = layoutType == LayoutInfo::HSplitter ? Qt::Horizontal : Qt::Vertical; + return new SplitterLayout(widgets, parentWidget, fw, layoutBase, orientation); + } + case LayoutInfo::Form: + return new GridLayout(widgets, parentWidget, fw, layoutBase); + default: + break; + } + Q_ASSERT(0); + return nullptr; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/layout_p.h b/src/designer/src/lib/shared/layout_p.h new file mode 100644 index 0000000..2dd8ee7 --- /dev/null +++ b/src/designer/src/lib/shared/layout_p.h @@ -0,0 +1,113 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef LAYOUT_H +#define LAYOUT_H + +#include "shared_global_p.h" +#include "layoutinfo_p.h" + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { +class QDESIGNER_SHARED_EXPORT Layout : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(Layout) +protected: + Layout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, LayoutInfo::Type layoutType); + +public: + static Layout* createLayout(const QWidgetList &widgets, QWidget *parentWidget, + QDesignerFormWindowInterface *fw, + QWidget *layoutBase, LayoutInfo::Type layoutType); + + ~Layout() override; + + virtual void sort() = 0; + virtual void doLayout() = 0; + + virtual void setup(); + virtual void undoLayout(); + virtual void breakLayout(); + + const QWidgetList &widgets() const { return m_widgets; } + QWidget *parentWidget() const { return m_parentWidget; } + QWidget *layoutBaseWidget() const { return m_layoutBase; } + + /* Determines whether instances of QLayoutWidget are unmanaged/hidden + * after breaking a layout. Default is true. Can be turned off when + * morphing */ + bool reparentLayoutWidget() const { return m_reparentLayoutWidget; } + void setReparentLayoutWidget(bool v) { m_reparentLayoutWidget = v; } + +protected: + virtual void finishLayout(bool needMove, QLayout *layout = nullptr); + virtual bool prepareLayout(bool &needMove, bool &needReparent); + + void setWidgets(const QWidgetList &widgets) { m_widgets = widgets; } + QLayout *createLayout(int type); + void reparentToLayoutBase(QWidget *w); + +private slots: + void widgetDestroyed(); + +private: + QWidgetList m_widgets; + QWidget *m_parentWidget; + QHash m_geometries; + QWidget *m_layoutBase; + QDesignerFormWindowInterface *m_formWindow; + const LayoutInfo::Type m_layoutType; + QPoint m_startPoint; + QRect m_oldGeometry; + + bool m_reparentLayoutWidget; + const bool m_isBreak; +}; + +namespace Utils +{ + +inline int indexOfWidget(QLayout *layout, QWidget *widget) +{ + int index = 0; + while (QLayoutItem *item = layout->itemAt(index)) { + if (item->widget() == widget) + return index; + + ++index; + } + + return -1; +} + +} // namespace Utils + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LAYOUT_H diff --git a/src/designer/src/lib/shared/layoutinfo.cpp b/src/designer/src/lib/shared/layoutinfo.cpp new file mode 100644 index 0000000..182e42f --- /dev/null +++ b/src/designer/src/lib/shared/layoutinfo.cpp @@ -0,0 +1,269 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "layoutinfo_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +/*! + \overload +*/ +LayoutInfo::Type LayoutInfo::layoutType(const QDesignerFormEditorInterface *core, const QLayout *layout) +{ + Q_UNUSED(core); + if (!layout) + return NoLayout; + if (qobject_cast(layout)) + return HBox; + if (qobject_cast(layout)) + return VBox; + if (qobject_cast(layout)) + return Grid; + if (qobject_cast(layout)) + return Form; + return UnknownLayout; +} + +static const QHash &layoutNameTypeMap() +{ + static const QHash nameTypeMap = { + {u"QVBoxLayout"_s, LayoutInfo::VBox}, + {u"QHBoxLayout"_s, LayoutInfo::HBox}, + {u"QGridLayout"_s, LayoutInfo::Grid}, + {u"QFormLayout"_s, LayoutInfo::Form} + }; + return nameTypeMap; +} + +LayoutInfo::Type LayoutInfo::layoutType(const QString &typeName) +{ + return layoutNameTypeMap().value(typeName, NoLayout); +} + +QString LayoutInfo::layoutName(Type t) +{ + return layoutNameTypeMap().key(t); +} + +/*! + \overload +*/ +LayoutInfo::Type LayoutInfo::layoutType(const QDesignerFormEditorInterface *core, const QWidget *w) +{ + if (const QSplitter *splitter = qobject_cast(w)) + return splitter->orientation() == Qt::Horizontal ? HSplitter : VSplitter; + return layoutType(core, w->layout()); +} + +LayoutInfo::Type LayoutInfo::managedLayoutType(const QDesignerFormEditorInterface *core, + const QWidget *w, + QLayout **ptrToLayout) +{ + if (ptrToLayout) + *ptrToLayout = nullptr; + if (const QSplitter *splitter = qobject_cast(w)) + return splitter->orientation() == Qt::Horizontal ? HSplitter : VSplitter; + QLayout *layout = managedLayout(core, w); + if (!layout) + return NoLayout; + if (ptrToLayout) + *ptrToLayout = layout; + return layoutType(core, layout); +} + +QWidget *LayoutInfo::layoutParent(const QDesignerFormEditorInterface *core, QLayout *layout) +{ + Q_UNUSED(core); + + QObject *o = layout; + while (o) { + if (QWidget *widget = qobject_cast(o)) + return widget; + + o = o->parent(); + } + return nullptr; +} + +void LayoutInfo::deleteLayout(const QDesignerFormEditorInterface *core, QWidget *widget) +{ + if (QDesignerContainerExtension *container = qt_extension(core->extensionManager(), widget)) + widget = container->widget(container->currentIndex()); + + Q_ASSERT(widget != nullptr); + + QLayout *layout = managedLayout(core, widget); + + if (layout == nullptr || core->metaDataBase()->item(layout) != nullptr) { + delete layout; + widget->updateGeometry(); + return; + } + + qDebug() << "trying to delete an unmanaged layout:" << "widget:" << widget << "layout:" << layout; +} + +LayoutInfo::Type LayoutInfo::laidoutWidgetType(const QDesignerFormEditorInterface *core, + QWidget *widget, + bool *isManaged, + QLayout **ptrToLayout) +{ + if (isManaged) + *isManaged = false; + if (ptrToLayout) + *ptrToLayout = nullptr; + + QWidget *parent = widget->parentWidget(); + if (!parent) + return NoLayout; + + // 1) Splitter + if (QSplitter *splitter = qobject_cast(parent)) { + if (isManaged) + *isManaged = core->metaDataBase()->item(splitter); + return splitter->orientation() == Qt::Horizontal ? HSplitter : VSplitter; + } + + // 2) Layout of parent + QLayout *parentLayout = parent->layout(); + if (!parentLayout) + return NoLayout; + + if (parentLayout->indexOf(widget) != -1) { + if (isManaged) + *isManaged = core->metaDataBase()->item(parentLayout); + if (ptrToLayout) + *ptrToLayout = parentLayout; + return layoutType(core, parentLayout); + } + + // 3) Some child layout (see below comment about Q3GroupBox) + const auto childLayouts = parentLayout->findChildren(); + if (childLayouts.isEmpty()) + return NoLayout; + for (QLayout *layout : childLayouts) { + if (layout->indexOf(widget) != -1) { + if (isManaged) + *isManaged = core->metaDataBase()->item(layout); + if (ptrToLayout) + *ptrToLayout = layout; + return layoutType(core, layout); + } + } + + return NoLayout; +} + +QLayout *LayoutInfo::internalLayout(const QWidget *widget) +{ + return widget->layout(); +} + + +QLayout *LayoutInfo::managedLayout(const QDesignerFormEditorInterface *core, const QWidget *widget) +{ + if (widget == nullptr) + return nullptr; + + QLayout *layout = widget->layout(); + if (!layout) + return nullptr; + + return managedLayout(core, layout); +} + +QLayout *LayoutInfo::managedLayout(const QDesignerFormEditorInterface *core, QLayout *layout) +{ + if (!layout) + return nullptr; + + QDesignerMetaDataBaseInterface *metaDataBase = core->metaDataBase(); + + if (!metaDataBase) + return layout; + /* This code exists mainly for the Q3GroupBox class, for which + * widget->layout() returns an internal VBoxLayout. */ + const QDesignerMetaDataBaseItemInterface *item = metaDataBase->item(layout); + if (item == nullptr) { + layout = layout->findChild(); + item = metaDataBase->item(layout); + } + if (!item) + return nullptr; + return layout; +} + +// Is it a a dummy grid placeholder created by Designer? +bool LayoutInfo::isEmptyItem(QLayoutItem *item) +{ + if (item == nullptr) { + qDebug() << "** WARNING Zero-item passed on to isEmptyItem(). This indicates a layout inconsistency."; + return true; + } + return item->spacerItem() != nullptr; +} + +QDESIGNER_SHARED_EXPORT void getFormLayoutItemPosition(const QFormLayout *formLayout, int index, int *rowPtr, int *columnPtr, int *rowspanPtr, int *colspanPtr) +{ + int row; + QFormLayout::ItemRole role; + formLayout->getItemPosition(index, &row, &role); + const int columnspan = role == QFormLayout::SpanningRole ? 2 : 1; + const int column = (columnspan > 1 || role == QFormLayout::LabelRole) ? 0 : 1; + if (rowPtr) + *rowPtr = row; + if (columnPtr) + *columnPtr = column; + if (rowspanPtr) + *rowspanPtr = 1; + if (colspanPtr) + *colspanPtr = columnspan; +} + +static inline QFormLayout::ItemRole formLayoutRole(int column, int colspan) +{ + if (colspan > 1) + return QFormLayout::SpanningRole; + return column == 0 ? QFormLayout::LabelRole : QFormLayout::FieldRole; +} + +QDESIGNER_SHARED_EXPORT void formLayoutAddWidget(QFormLayout *formLayout, QWidget *w, const QRect &r, bool insert) +{ + // Consistent API galore... + if (insert) { + const bool spanning = r.width() > 1; + if (spanning) { + formLayout->insertRow(r.y(), w); + } else { + QWidget *label = nullptr; + QWidget *field = nullptr; + if (r.x() == 0) { + label = w; + } else { + field = w; + } + formLayout->insertRow(r.y(), label, field); + } + } else { + formLayout->setWidget(r.y(), formLayoutRole(r.x(), r.width()), w); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/layoutinfo_p.h b/src/designer/src/lib/shared/layoutinfo_p.h new file mode 100644 index 0000000..7ddb212 --- /dev/null +++ b/src/designer/src/lib/shared/layoutinfo_p.h @@ -0,0 +1,76 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef LAYOUTINFO_H +#define LAYOUTINFO_H + +#include "shared_global_p.h" + +QT_BEGIN_NAMESPACE + +class QWidget; +class QLayout; +class QLayoutItem; +class QDesignerFormEditorInterface; +class QFormLayout; +class QRect; +class QString; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT LayoutInfo +{ +public: + enum Type + { + NoLayout, + HSplitter, + VSplitter, + HBox, + VBox, + Grid, + Form, + UnknownLayout // QDockWindow inside QMainWindow is inside QMainWindowLayout - it doesn't mean there is no layout + }; + + static void deleteLayout(const QDesignerFormEditorInterface *core, QWidget *widget); + + // Examines the immediate layout of the widget. + static Type layoutType(const QDesignerFormEditorInterface *core, const QWidget *w); + // Examines the managed layout of the widget + static Type managedLayoutType(const QDesignerFormEditorInterface *core, const QWidget *w, QLayout **layout = nullptr); + static Type layoutType(const QDesignerFormEditorInterface *core, const QLayout *layout); + static Type layoutType(const QString &typeName); + static QString layoutName(Type t); + + static QWidget *layoutParent(const QDesignerFormEditorInterface *core, QLayout *layout); + + static Type laidoutWidgetType(const QDesignerFormEditorInterface *core, QWidget *widget, bool *isManaged = nullptr, QLayout **layout = nullptr); + static bool inline isWidgetLaidout(const QDesignerFormEditorInterface *core, QWidget *widget) { return laidoutWidgetType(core, widget) != NoLayout; } + + static QLayout *managedLayout(const QDesignerFormEditorInterface *core, const QWidget *widget); + static QLayout *managedLayout(const QDesignerFormEditorInterface *core, QLayout *layout); + static QLayout *internalLayout(const QWidget *widget); + + // Is it a a dummy grid placeholder created by Designer? + static bool isEmptyItem(QLayoutItem *item); +}; + +QDESIGNER_SHARED_EXPORT void getFormLayoutItemPosition(const QFormLayout *formLayout, int index, int *rowPtr, int *columnPtr = nullptr, int *rowspanPtr = nullptr, int *colspanPtr = nullptr); +QDESIGNER_SHARED_EXPORT void formLayoutAddWidget(QFormLayout *formLayout, QWidget *w, const QRect &r, bool insert); +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LAYOUTINFO_H diff --git a/src/designer/src/lib/shared/metadatabase.cpp b/src/designer/src/lib/shared/metadatabase.cpp new file mode 100644 index 0000000..77d6aa2 --- /dev/null +++ b/src/designer/src/lib/shared/metadatabase.cpp @@ -0,0 +1,243 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "metadatabase_p.h" +#include "widgetdatabase_p.h" + +// sdk +#include + +// Qt +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + const bool debugMetaDatabase = false; +} + +namespace qdesigner_internal { + +MetaDataBaseItem::MetaDataBaseItem(QObject *object) + : m_object(object), + m_enabled(true) +{ +} + +MetaDataBaseItem::~MetaDataBaseItem() = default; + +QString MetaDataBaseItem::name() const +{ + Q_ASSERT(m_object); + return m_object->objectName(); +} + +void MetaDataBaseItem::setName(const QString &name) +{ + Q_ASSERT(m_object); + m_object->setObjectName(name); +} + +QString MetaDataBaseItem::customClassName() const +{ + return m_customClassName; +} +void MetaDataBaseItem::setCustomClassName(const QString &customClassName) +{ + m_customClassName = customClassName; +} + + +QWidgetList MetaDataBaseItem::tabOrder() const +{ + return m_tabOrder; +} + +void MetaDataBaseItem::setTabOrder(const QWidgetList &tabOrder) +{ + m_tabOrder = tabOrder; +} + +bool MetaDataBaseItem::enabled() const +{ + return m_enabled; +} + +void MetaDataBaseItem::setEnabled(bool b) +{ + m_enabled = b; +} + +QStringList MetaDataBaseItem::fakeSlots() const +{ + return m_fakeSlots; +} + +void MetaDataBaseItem::setFakeSlots(const QStringList &fs) +{ + m_fakeSlots = fs; +} + +QStringList MetaDataBaseItem::fakeSignals() const +{ + return m_fakeSignals; +} + +void MetaDataBaseItem::setFakeSignals(const QStringList &fs) +{ + m_fakeSignals = fs; +} + +// ----------------------------------------------------- +MetaDataBase::MetaDataBase(QDesignerFormEditorInterface *core, QObject *parent) + : QDesignerMetaDataBaseInterface(parent), + m_core(core) +{ +} + +MetaDataBase::~MetaDataBase() +{ + qDeleteAll(m_items); +} + +MetaDataBaseItem *MetaDataBase::metaDataBaseItem(QObject *object) const +{ + MetaDataBaseItem *i = m_items.value(object); + if (i == nullptr || !i->enabled()) + return nullptr; + return i; +} + +void MetaDataBase::add(QObject *object) +{ + MetaDataBaseItem *item = m_items.value(object); + if (item != nullptr) { + item->setEnabled(true); + if (debugMetaDatabase) { + qDebug() << "MetaDataBase::add: Existing item for " << object->metaObject()->className() << item->name(); + } + return; + } + + item = new MetaDataBaseItem(object); + m_items.insert(object, item); + if (debugMetaDatabase) { + qDebug() << "MetaDataBase::add: New item " << object->metaObject()->className() << item->name(); + } + connect(object, &QObject::destroyed, this, &MetaDataBase::slotDestroyed); + + emit changed(); +} + +void MetaDataBase::remove(QObject *object) +{ + Q_ASSERT(object); + + if (MetaDataBaseItem *item = m_items.value(object)) { + item->setEnabled(false); + emit changed(); + } +} + +QObjectList MetaDataBase::objects() const +{ + QObjectList result; + + for (auto it = m_items.cbegin(), cend = m_items.cend(); it != cend; ++it) { + if (it.value()->enabled()) + result.append(it.key()); + } + + return result; +} + +QDesignerFormEditorInterface *MetaDataBase::core() const +{ + return m_core; +} + +void MetaDataBase::slotDestroyed(QObject *object) +{ + if (m_items.contains(object)) { + MetaDataBaseItem *item = m_items.value(object); + delete item; + m_items.remove(object); + } +} + +// promotion convenience +QDESIGNER_SHARED_EXPORT bool promoteWidget(QDesignerFormEditorInterface *core,QWidget *widget,const QString &customClassName) +{ + + MetaDataBase *db = qobject_cast(core->metaDataBase()); + if (!db) + return false; + MetaDataBaseItem *item = db->metaDataBaseItem(widget); + if (!item) { + db ->add(widget); + item = db->metaDataBaseItem(widget); + } + // Recursive promotion occurs if there is a plugin missing. + const QString oldCustomClassName = item->customClassName(); + if (!oldCustomClassName.isEmpty()) { + qDebug() << "WARNING: Recursive promotion of " << oldCustomClassName << " to " << customClassName + << ". A plugin is missing."; + } + item->setCustomClassName(customClassName); + if (debugMetaDatabase) { + qDebug() << "Promoting " << widget->metaObject()->className() << " to " << customClassName; + } + return true; +} + +QDESIGNER_SHARED_EXPORT void demoteWidget(QDesignerFormEditorInterface *core,QWidget *widget) +{ + MetaDataBase *db = qobject_cast(core->metaDataBase()); + if (!db) + return; + MetaDataBaseItem *item = db->metaDataBaseItem(widget); + item->setCustomClassName(QString()); + if (debugMetaDatabase) { + qDebug() << "Demoting " << widget; + } +} + +QDESIGNER_SHARED_EXPORT bool isPromoted(QDesignerFormEditorInterface *core, QWidget* widget) +{ + const MetaDataBase *db = qobject_cast(core->metaDataBase()); + if (!db) + return false; + const MetaDataBaseItem *item = db->metaDataBaseItem(widget); + if (!item) + return false; + return !item->customClassName().isEmpty(); +} + +QDESIGNER_SHARED_EXPORT QString promotedCustomClassName(QDesignerFormEditorInterface *core, QWidget* widget) +{ + const MetaDataBase *db = qobject_cast(core->metaDataBase()); + if (!db) + return QString(); + const MetaDataBaseItem *item = db->metaDataBaseItem(widget); + if (!item) + return QString(); + return item->customClassName(); +} + +QDESIGNER_SHARED_EXPORT QString promotedExtends(QDesignerFormEditorInterface *core, QWidget* widget) +{ + const QString customClassName = promotedCustomClassName(core,widget); + if (customClassName.isEmpty()) + return QString(); + const int i = core->widgetDataBase()->indexOfClassName(customClassName); + if (i == -1) + return QString(); + return core->widgetDataBase()->item(i)->extends(); +} + + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/metadatabase_p.h b/src/designer/src/lib/shared/metadatabase_p.h new file mode 100644 index 0000000..2117f59 --- /dev/null +++ b/src/designer/src/lib/shared/metadatabase_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef METADATABASE_H +#define METADATABASE_H + +#include "shared_global_p.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT MetaDataBaseItem: public QDesignerMetaDataBaseItemInterface +{ +public: + explicit MetaDataBaseItem(QObject *object); + ~MetaDataBaseItem() override; + + QString name() const override; + void setName(const QString &name) override; + + QWidgetList tabOrder() const override; + void setTabOrder(const QWidgetList &tabOrder) override; + + bool enabled() const override; + void setEnabled(bool b) override; + + QString customClassName() const; + void setCustomClassName(const QString &customClassName); + + QStringList fakeSlots() const; + void setFakeSlots(const QStringList &); + + QStringList fakeSignals() const; + void setFakeSignals(const QStringList &); + +private: + QObject *m_object; + QWidgetList m_tabOrder; + bool m_enabled; + QString m_customClassName; + QStringList m_fakeSlots; + QStringList m_fakeSignals; +}; + +class QDESIGNER_SHARED_EXPORT MetaDataBase: public QDesignerMetaDataBaseInterface +{ + Q_OBJECT +public: + explicit MetaDataBase(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~MetaDataBase() override; + + QDesignerFormEditorInterface *core() const override; + + QDesignerMetaDataBaseItemInterface *item(QObject *object) const override { return metaDataBaseItem(object); } + virtual MetaDataBaseItem *metaDataBaseItem(QObject *object) const; + void add(QObject *object) override; + void remove(QObject *object) override; + + QObjectList objects() const override; + +private slots: + void slotDestroyed(QObject *object); + +private: + QDesignerFormEditorInterface *m_core; + QHash m_items; +}; + + // promotion convenience + QDESIGNER_SHARED_EXPORT bool promoteWidget(QDesignerFormEditorInterface *core,QWidget *widget,const QString &customClassName); + QDESIGNER_SHARED_EXPORT void demoteWidget(QDesignerFormEditorInterface *core,QWidget *widget); + QDESIGNER_SHARED_EXPORT bool isPromoted(QDesignerFormEditorInterface *core, QWidget* w); + QDESIGNER_SHARED_EXPORT QString promotedCustomClassName(QDesignerFormEditorInterface *core, QWidget* w); + QDESIGNER_SHARED_EXPORT QString promotedExtends(QDesignerFormEditorInterface *core, QWidget* w); + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // METADATABASE_H diff --git a/src/designer/src/lib/shared/morphmenu.cpp b/src/designer/src/lib/shared/morphmenu.cpp new file mode 100644 index 0000000..3e7c053 --- /dev/null +++ b/src/designer/src/lib/shared/morphmenu.cpp @@ -0,0 +1,587 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "morphmenu_p.h" +#include "formwindowbase_p.h" +#include "widgetfactory_p.h" +#include "qdesigner_formwindowcommand_p.h" +#include "qlayout_widget_p.h" +#include "layoutinfo_p.h" +#include "qdesigner_propertycommand_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QWidgetList) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Helpers for the dynamic properties that store Z/Widget order +static const char widgetOrderPropertyC[] = "_q_widgetOrder"; +static const char zOrderPropertyC[] = "_q_zOrder"; + +/* Morphing in Designer: + * It is possible to morph: + * - Non-Containers into similar widgets by category + * - Simple page containers into similar widgets or page-based containers with + * a single page (in theory also into a QLayoutWidget, but this might + * not always be appropriate). + * - Page-based containers into page-based containers or simple containers if + * they have just one page + * [Page based containers meaning here having a container extension] + * Morphing types are restricted to the basic Qt types. Morphing custom + * widgets is considered risky since they might have unmanaged layouts + * or the like. + * + * Requirements: + * - The widget must be on a non-laid out parent or in a layout managed + * by Designer + * - Its child widgets must be non-laid out or in a layout managed + * by Designer + * Note that child widgets can be + * - On the widget itself in the case of simple containers + * - On several pages in the case of page-based containers + * This is what is called 'childContainers' in the code (the widget itself + * or the list of container extension pages). + * + * The Morphing process encompasses: + * - Create a target widget and apply properties as far as applicable + * If the target widget has a container extension, add a sufficient + * number of pages. + * - Transferring the child widgets over to the new childContainers. + * In the case of a managed layout on a childContainer, this is simply + * set on the target childContainer, which is a new Qt 4.5 + * functionality. + * - Replace the widget itself in the parent layout + */ + +namespace qdesigner_internal { + +enum MorphCategory { + MorphCategoryNone, MorphSimpleContainer, MorphPageContainer, MorphItemView, + MorphButton, MorphSpinBox, MorphTextEdit +}; + +// Determine category of a widget +static MorphCategory category(const QWidget *w) +{ + // Simple containers: Exact match + const QMetaObject *mo = w->metaObject(); + if (mo == &QWidget::staticMetaObject || mo == &QFrame::staticMetaObject || mo == &QGroupBox::staticMetaObject || mo == &QLayoutWidget::staticMetaObject) + return MorphSimpleContainer; + if (mo == &QTabWidget::staticMetaObject || mo == &QStackedWidget::staticMetaObject || mo == &QToolBox::staticMetaObject) + return MorphPageContainer; + if (qobject_cast(w)) + return MorphItemView; + if (qobject_cast(w)) + return MorphButton; + if (qobject_cast(w)) + return MorphSpinBox; + if (qobject_cast(w) || qobject_cast(w)) + return MorphTextEdit; + + return MorphCategoryNone; +} + +/* Return the similar classes of a category. This is currently restricted + * to the known Qt classes with no precautions to parse the Widget Database + * (which is too risky, custom classes might have container extensions + * or non-managed layouts, etc.). */ + +static QStringList classesOfCategory(MorphCategory cat) +{ + static QMap candidateCache; + auto it = candidateCache.find(cat); + if (it == candidateCache.end()) { + it = candidateCache.insert(cat, QStringList()); + QStringList &l = it.value(); + switch (cat) { + case MorphCategoryNone: + break; + case MorphSimpleContainer: + // Do not generally allow to morph into a layout. + // This can be risky in case of container pages,etc. + l << u"QWidget"_s << u"QFrame"_s << u"QGroupBox"_s; + break; + case MorphPageContainer: + l << u"QTabWidget"_s << u"QStackedWidget"_s << u"QToolBox"_s; + break; + case MorphItemView: + l << u"QListView"_s << u"QListWidget"_s + << u"QTreeView"_s << u"QTreeWidget"_s + << u"QTableView"_s << u"QTableWidget"_s + << u"QColumnView"_s; + break; + case MorphButton: + l << u"QCheckBox"_s << u"QRadioButton"_s + << u"QPushButton"_s << u"QToolButton"_s + << u"QCommandLinkButton"_s; + break; + case MorphSpinBox: + l << u"QDateTimeEdit"_s << u"QDateEdit"_s + << u"QTimeEdit"_s + << u"QSpinBox"_s << u"QDoubleSpinBox"_s; + break; + case MorphTextEdit: + l << u"QTextEdit"_s << u"QPlainTextEdit"_s << u"QTextBrowser"_s; + break; + } + } + return it.value(); +} + +// Return the widgets containing the children to be transferred to. This is the +// widget itself in most cases, except for QDesignerContainerExtension cases +static QWidgetList childContainers(const QDesignerFormEditorInterface *core, QWidget *w) +{ + if (const QDesignerContainerExtension *ce = qt_extension(core->extensionManager(), w)) { + QWidgetList children; + if (const int count = ce->count()) { + for (int i = 0; i < count; i++) + children.push_back(ce->widget(i)); + } + return children; + } + QWidgetList self; + self.push_back(w); + return self; +} + +// Suggest a suitable objectname for the widget to be morphed into +// Replace the class name parts: 'xxFrame' -> 'xxGroupBox', 'frame' -> 'groupBox' +static QString suggestObjectName(const QString &oldClassName, const QString &newClassName, const QString &oldName) +{ + QString oldClassPart = oldClassName; + QString newClassPart = newClassName; + if (oldClassPart.startsWith(u'Q')) + oldClassPart.remove(0, 1); + if (newClassPart.startsWith(u'Q')) + newClassPart.remove(0, 1); + + QString newName = oldName; + newName.replace(oldClassPart, newClassPart); + oldClassPart[0] = oldClassPart.at(0).toLower(); + newClassPart[0] = newClassPart.at(0).toLower(); + newName.replace(oldClassPart, newClassPart); + return newName; +} + +// Find the label whose buddy the widget is. +QLabel *buddyLabelOf(QDesignerFormWindowInterface *fw, QWidget *w) +{ + const auto labelList = fw->findChildren(); + for (QLabel *label : labelList) + if (label->buddy() == w) + return label; + return nullptr; +} + +// Replace widgets in a widget-list type dynamic property of the parent +// used for Z-order, etc. +static void replaceWidgetListDynamicProperty(QWidget *parentWidget, + QWidget *oldWidget, QWidget *newWidget, + const char *name) +{ + QWidgetList list = qvariant_cast(parentWidget->property(name)); + const int index = list.indexOf(oldWidget); + if (index != -1) { + list.replace(index, newWidget); + parentWidget->setProperty(name, QVariant::fromValue(list)); + } +} + +/* Morph a widget into another class. Use the static addMorphMacro() to + * add a respective command sequence to the undo stack as it emits signals + * which cause other commands to be added. */ +class MorphWidgetCommand : public QDesignerFormWindowCommand +{ + Q_DISABLE_COPY_MOVE(MorphWidgetCommand) +public: + + explicit MorphWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~MorphWidgetCommand() override; + + // Convenience to add a morph command sequence macro + static bool addMorphMacro(QDesignerFormWindowInterface *formWindow, QWidget *w, const QString &newClass); + + bool init(QWidget *widget, const QString &newClassName); + + QString newWidgetName() const { return m_afterWidget->objectName(); } + + void redo() override; + void undo() override; + + static QStringList candidateClasses(QDesignerFormWindowInterface *fw, QWidget *w); + +private: + static bool canMorph(QDesignerFormWindowInterface *fw, QWidget *w, int *childContainerCount = nullptr, MorphCategory *cat = nullptr); + void morph(QWidget *before, QWidget *after); + + QWidget *m_beforeWidget; + QWidget *m_afterWidget; +}; + +bool MorphWidgetCommand::addMorphMacro(QDesignerFormWindowInterface *fw, QWidget *w, const QString &newClass) +{ + MorphWidgetCommand *morphCmd = new MorphWidgetCommand(fw); + if (!morphCmd->init(w, newClass)) { + qWarning("*** Unable to create a MorphWidgetCommand"); + delete morphCmd; + return false; + } + QLabel *buddyLabel = buddyLabelOf(fw, w); + // Need a macro since it adds further commands + QUndoStack *us = fw->commandHistory(); + us->beginMacro(morphCmd->text()); + // Have the signal slot/buddy editors add their commands to delete widget + if (FormWindowBase *fwb = qobject_cast(fw)) + fwb->emitWidgetRemoved(w); + + const QString newWidgetName = morphCmd->newWidgetName(); + us->push(morphCmd); + + // restore buddy using the QByteArray name. + if (buddyLabel) { + SetPropertyCommand *buddyCmd = new SetPropertyCommand(fw); + buddyCmd->init(buddyLabel, u"buddy"_s, QVariant(newWidgetName.toUtf8())); + us->push(buddyCmd); + } + us->endMacro(); + return true; +} + +MorphWidgetCommand::MorphWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_beforeWidget(nullptr), + m_afterWidget(nullptr) +{ +} + +MorphWidgetCommand::~MorphWidgetCommand() = default; + +bool MorphWidgetCommand::init(QWidget *widget, const QString &newClassName) +{ + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + + if (!canMorph(fw, widget)) + return false; + + const QString oldClassName = WidgetFactory::classNameOf(core, widget); + const QString oldName = widget->objectName(); + //: MorphWidgetCommand description + setText(QApplication::translate("Command", "Morph %1/'%2' into %3").arg(oldClassName, oldName, newClassName)); + + m_beforeWidget = widget; + m_afterWidget = core->widgetFactory()->createWidget(newClassName, fw); + if (!m_afterWidget) + return false; + + // Set object name. Do not unique it (as to maintain it). + m_afterWidget->setObjectName(suggestObjectName(oldClassName, newClassName, oldName)); + + // If the target has a container extension, we add enough new pages to take + // up the children of the before widget + if (QDesignerContainerExtension* c = qt_extension(core->extensionManager(), m_afterWidget)) { + if (const auto pageCount = childContainers(core, m_beforeWidget).size()) { + const QString containerName = m_afterWidget->objectName(); + for (qsizetype i = 0; i < pageCount; ++i) { + QString name = containerName; + name += "Page"_L1; + name += QString::number(i + 1); + QWidget *page = core->widgetFactory()->createWidget(u"QWidget"_s); + page->setObjectName(name); + fw->ensureUniqueObjectName(page); + c->addWidget(page); + core->metaDataBase()->add(page); + } + } + } + + // Copy over applicable properties + const QDesignerPropertySheetExtension *beforeSheet = qt_extension(core->extensionManager(), widget); + QDesignerPropertySheetExtension *afterSheet = qt_extension(core->extensionManager(), m_afterWidget); + const int count = beforeSheet->count(); + for (int i = 0; i < count; i++) + if (beforeSheet->isVisible(i) && beforeSheet->isChanged(i)) { + const QString name = beforeSheet->propertyName(i); + if (name != "objectName"_L1) { + const int afterIndex = afterSheet->indexOf(name); + if (afterIndex != -1 && afterSheet->isVisible(afterIndex) && afterSheet->propertyGroup(afterIndex) == beforeSheet->propertyGroup(i)) { + afterSheet->setProperty(i, beforeSheet->property(i)); + afterSheet->setChanged(i, true); + } else { + // Some mismatch. The rest won't match, either + break; + } + } + } + return true; +} + +void MorphWidgetCommand::redo() +{ + morph(m_beforeWidget, m_afterWidget); +} + +void MorphWidgetCommand::undo() +{ + morph(m_afterWidget, m_beforeWidget); +} + +void MorphWidgetCommand::morph(QWidget *before, QWidget *after) +{ + QDesignerFormWindowInterface *fw = formWindow(); + + fw->unmanageWidget(before); + + const QRect oldGeom = before->geometry(); + QWidget *parent = before->parentWidget(); + Q_ASSERT(parent); + /* Morphing consists of main 2 steps + * 1) Move over children (laid out, non-laid out) + * 2) Register self with new parent (laid out, non-laid out) */ + + // 1) Move children. Loop over child containers + QWidgetList beforeChildContainers = childContainers(fw->core(), before); + QWidgetList afterChildContainers = childContainers(fw->core(), after); + Q_ASSERT(beforeChildContainers.size() == afterChildContainers.size()); + const auto childContainerCount = beforeChildContainers.size(); + for (qsizetype i = 0; i < childContainerCount; ++i) { + QWidget *beforeChildContainer = beforeChildContainers.at(i); + QWidget *afterChildContainer = afterChildContainers.at(i); + if (QLayout *childLayout = beforeChildContainer->layout()) { + // Laid-out: Move the layout (since 4.5) + afterChildContainer->setLayout(childLayout); + } else { + // Non-Laid-out: Reparent, move over + for (QObject *o : beforeChildContainer->children()) { + if (o->isWidgetType()) { + QWidget *w = static_cast(o); + if (fw->isManaged(w)) { + const QRect geom = w->geometry(); + w->setParent(afterChildContainer); + w->setGeometry(geom); + } + } + } + } + afterChildContainer->setProperty(widgetOrderPropertyC, beforeChildContainer->property(widgetOrderPropertyC)); + afterChildContainer->setProperty(zOrderPropertyC, beforeChildContainer->property(zOrderPropertyC)); + } + + // 2) Replace the actual widget in the parent layout + after->setGeometry(oldGeom); + if (QLayout *containingLayout = LayoutInfo::managedLayout(fw->core(), parent)) { + LayoutHelper *lh = LayoutHelper::createLayoutHelper(LayoutInfo::layoutType(fw->core(), containingLayout)); + Q_ASSERT(lh); + lh->replaceWidget(containingLayout, before, after); + delete lh; + } else if (QSplitter *splitter = qobject_cast(parent)) { + const int index = splitter->indexOf(before); + before->hide(); + before->setParent(nullptr); + splitter->insertWidget(index, after); + after->setParent(parent); + after->setGeometry(oldGeom); + } else { + before->hide(); + before->setParent(nullptr); + after->setParent(parent); + after->setGeometry(oldGeom); + } + + // Check various properties: Z order, form tab order + replaceWidgetListDynamicProperty(parent, before, after, widgetOrderPropertyC); + replaceWidgetListDynamicProperty(parent, before, after, zOrderPropertyC); + + QDesignerMetaDataBaseItemInterface *formItem = fw->core()->metaDataBase()->item(fw); + QWidgetList tabOrder = formItem->tabOrder(); + const int tabIndex = tabOrder.indexOf(before); + if (tabIndex != -1) { + tabOrder.replace(tabIndex, after); + formItem->setTabOrder(tabOrder); + } + + after->show(); + fw->manageWidget(after); + + fw->clearSelection(false); + fw->selectWidget(after); +} + +/* Check if morphing is possible. It must be a valid category and the parent/ + * child relationships must be either non-laidout or directly on + * Designer-managed layouts. */ +bool MorphWidgetCommand::canMorph(QDesignerFormWindowInterface *fw, QWidget *w, int *ptrToChildContainerCount, MorphCategory *ptrToCat) +{ + if (ptrToChildContainerCount) + *ptrToChildContainerCount = 0; + const MorphCategory cat = category(w); + if (ptrToCat) + *ptrToCat = cat; + if (cat == MorphCategoryNone) + return false; + + QDesignerFormEditorInterface *core = fw->core(); + // Don't know how to fiddle class names in Jambi.. + if (qt_extension(core->extensionManager(), core)) + return false; + if (!fw->isManaged(w) || w == fw->mainContainer()) + return false; + // Check the parent relationship. We accept only managed parent widgets + // with a single, managed layout in which widget is a member. + QWidget *parent = w->parentWidget(); + if (parent == nullptr) + return false; + if (QLayout *pl = LayoutInfo::managedLayout(core, parent)) + if (pl->indexOf(w) < 0 || !core->metaDataBase()->item(pl)) + return false; + // Check Widget database + const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); + const int wdbindex = wdb->indexOfObject(w); + if (wdbindex == -1) + return false; + const bool isContainer = wdb->item(wdbindex)->isContainer(); + if (!isContainer) + return true; + // Check children. All child containers must be non-laid-out or have managed layouts + const QWidgetList pages = childContainers(core, w); + const auto pageCount = pages.size(); + if (ptrToChildContainerCount) + *ptrToChildContainerCount = pageCount; + if (pageCount) { + for (qsizetype i = 0; i < pageCount; ++i) + if (QLayout *cl = pages.at(i)->layout()) + if (!core->metaDataBase()->item(cl)) + return false; + } + return true; +} + +QStringList MorphWidgetCommand::candidateClasses(QDesignerFormWindowInterface *fw, QWidget *w) +{ + int childContainerCount; + MorphCategory cat; + if (!canMorph(fw, w, &childContainerCount, &cat)) + return QStringList(); + + QStringList rc = classesOfCategory(cat); + switch (cat) { + // Frames, etc can always be morphed into one-page page containers + case MorphSimpleContainer: + rc += classesOfCategory(MorphPageContainer); + break; + // Multipage-Containers can be morphed into simple containers if they + // have 1 page. + case MorphPageContainer: + if (childContainerCount == 1) + rc += classesOfCategory(MorphSimpleContainer); + break; + default: + break; + } + return rc; +} + +// MorphMenu +MorphMenu::MorphMenu(QObject *parent) : + QObject(parent) +{ +} + +void MorphMenu::populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList& al) +{ + if (populateMenu(w, fw)) + al.push_back(m_subMenuAction); +} + +void MorphMenu::populate(QWidget *w, QDesignerFormWindowInterface *fw, QMenu& m) +{ + if (populateMenu(w, fw)) + m.addAction(m_subMenuAction); +} + +void MorphMenu::slotMorph(const QString &newClassName) +{ + MorphWidgetCommand::addMorphMacro(m_formWindow, m_widget, newClassName); +} + +bool MorphMenu::populateMenu(QWidget *w, QDesignerFormWindowInterface *fw) +{ + m_widget = nullptr; + m_formWindow = nullptr; + + // Clear menu + if (m_subMenuAction) { + m_subMenuAction->setVisible(false); + m_menu->clear(); + } + + // Checks: Must not be main container + if (w == fw->mainContainer()) + return false; + + const QStringList c = MorphWidgetCommand::candidateClasses(fw, w); + if (c.isEmpty()) + return false; + + // Pull up + m_widget = w; + m_formWindow = fw; + const QString oldClassName = WidgetFactory::classNameOf(fw->core(), w); + + if (!m_subMenuAction) { + m_subMenuAction = new QAction(tr("Morph into"), this); + m_menu = new QMenu; + m_subMenuAction->setMenu(m_menu); + } + + // Add actions + for (const auto &className : c) { + if (className != oldClassName) { + m_menu->addAction(className, + this, [this, className] { this->slotMorph(className); }); + } + } + m_subMenuAction->setVisible(true); + return true; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/morphmenu_p.h b/src/designer/src/lib/shared/morphmenu_p.h new file mode 100644 index 0000000..dbd94f6 --- /dev/null +++ b/src/designer/src/lib/shared/morphmenu_p.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef MORPH_COMMAND_H +#define MORPH_COMMAND_H + +#include "shared_global_p.h" +#include "qdesigner_formwindowcommand_p.h" + +QT_BEGIN_NAMESPACE + +class QAction; +class QMenu; + +namespace qdesigner_internal { + +/* Conveniene morph menu that acts on a single widget. */ +class QDESIGNER_SHARED_EXPORT MorphMenu : public QObject { + Q_DISABLE_COPY_MOVE(MorphMenu) + Q_OBJECT +public: + using ActionList = QList; + + explicit MorphMenu(QObject *parent = nullptr); + + void populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList& al); + void populate(QWidget *w, QDesignerFormWindowInterface *fw, QMenu& m); + +private slots: + void slotMorph(const QString &newClassName); + +private: + bool populateMenu(QWidget *w, QDesignerFormWindowInterface *fw); + + QAction *m_subMenuAction = nullptr; + QMenu *m_menu = nullptr; + QWidget *m_widget = nullptr; + QDesignerFormWindowInterface *m_formWindow = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // MORPH_COMMAND_H diff --git a/src/designer/src/lib/shared/newactiondialog.cpp b/src/designer/src/lib/shared/newactiondialog.cpp new file mode 100644 index 0000000..5bbb5d7 --- /dev/null +++ b/src/designer/src/lib/shared/newactiondialog.cpp @@ -0,0 +1,216 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "newactiondialog_p.h" +#include "ui_newactiondialog.h" +#include "richtexteditor_p.h" +#include "actioneditor_p.h" +#include "formwindowbase_p.h" +#include "qdesigner_utils_p.h" +#include "iconloader_p.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +// Returns a combination of ChangeMask flags +unsigned ActionData::compare(const ActionData &rhs) const +{ + unsigned rc = 0; + if (text != rhs.text) + rc |= TextChanged; + if (name != rhs.name) + rc |= NameChanged; + if (toolTip != rhs.toolTip) + rc |= ToolTipChanged ; + if (icon != rhs.icon) + rc |= IconChanged ; + if (checkable != rhs.checkable) + rc |= CheckableChanged; + if (keysequence != rhs.keysequence) + rc |= KeysequenceChanged ; + if (menuRole.value != rhs.menuRole.value) + rc |= MenuRoleChanged ; + return rc; +} + +// -------------------- NewActionDialog +NewActionDialog::NewActionDialog(ActionEditor *parent) : + QDialog(parent, Qt::Sheet), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::NewActionDialog), + m_actionEditor(parent), + m_autoUpdateObjectName(true) +{ + m_ui->setupUi(this); + + m_ui->tooltipEditor->setTextPropertyValidationMode(ValidationRichText); + connect(m_ui->toolTipToolButton, &QAbstractButton::clicked, this, &NewActionDialog::slotEditToolTip); + connect(m_ui->editActionText, &QLineEdit::textEdited, + this, &NewActionDialog::onEditActionTextTextEdited); + connect(m_ui->editObjectName, &QLineEdit::textEdited, + this, &NewActionDialog::onEditObjectNameTextEdited); + + m_ui->keysequenceResetToolButton->setIcon(createIconSet("resetproperty.png"_L1)); + connect(m_ui->keysequenceResetToolButton, &QAbstractButton::clicked, + this, &NewActionDialog::slotResetKeySequence); + + // Clear XDG icon once a theme enum is chosen and vv. + auto *iconThemeEnumEditor = m_ui->iconThemeEnumEditor; + auto *iconThemeEditor = m_ui->iconThemeEditor; + connect(iconThemeEnumEditor, &IconThemeEnumEditor::edited, + this, [iconThemeEditor](int i) { + if (i >= 0) + iconThemeEditor->reset(); + }); + connect(iconThemeEditor, &IconThemeEditor::edited, + this, [iconThemeEnumEditor](const QString &t) { + if (!t.isEmpty()) + iconThemeEnumEditor->reset(); + }); + + const auto menuRoles = QMetaEnum::fromType(); + for (int i = 0; i < menuRoles.keyCount(); i++) { + const auto key = menuRoles.key(i); + const auto value = menuRoles.value(i); + m_ui->menuRole->addItem(QLatin1StringView(key), value); + } + + focusText(); + updateButtons(); + + QDesignerFormWindowInterface *form = parent->formWindow(); + m_ui->iconSelector->setFormEditor(form->core()); + FormWindowBase *formBase = qobject_cast(form); + + if (formBase) { + m_ui->iconSelector->setPixmapCache(formBase->pixmapCache()); + m_ui->iconSelector->setIconCache(formBase->iconCache()); + } +} + +NewActionDialog::~NewActionDialog() +{ + delete m_ui; +} + +void NewActionDialog::focusName() +{ + m_ui->editObjectName->setFocus(); +} + +void NewActionDialog::focusText() +{ + m_ui->editActionText->setFocus(); +} + +void NewActionDialog::focusTooltip() +{ + m_ui->tooltipEditor->setFocus(); +} + +void NewActionDialog::focusShortcut() +{ + m_ui->keySequenceEdit->setFocus(); +} + +void NewActionDialog::focusCheckable() +{ + m_ui->checkableCheckBox->setFocus(); +} + +void NewActionDialog::focusMenuRole() +{ + m_ui->menuRole->setFocus(); +} + +QString NewActionDialog::actionText() const +{ + return m_ui->editActionText->text(); +} + +QString NewActionDialog::actionName() const +{ + return m_ui->editObjectName->text(); +} + +ActionData NewActionDialog::actionData() const +{ + ActionData rc; + rc.text = actionText(); + rc.name = actionName(); + rc.toolTip = m_ui->tooltipEditor->text(); + rc.icon = m_ui->iconSelector->icon(); + const int themeEnum = m_ui->iconThemeEnumEditor->themeEnum(); + rc.icon.setThemeEnum(themeEnum); + rc.icon.setTheme(themeEnum == -1 ? m_ui->iconThemeEditor->theme() : QString{}); + rc.checkable = m_ui->checkableCheckBox->checkState() == Qt::Checked; + rc.keysequence = PropertySheetKeySequenceValue(m_ui->keySequenceEdit->keySequence()); + rc.menuRole.value = m_ui->menuRole->currentData().toInt(); + return rc; +} + +void NewActionDialog::setActionData(const ActionData &d) +{ + m_ui->editActionText->setText(d.text); + m_ui->editObjectName->setText(d.name); + m_ui->iconSelector->setIcon(d.icon.unthemed()); + m_ui->iconThemeEnumEditor->setThemeEnum(d.icon.themeEnum()); + m_ui->iconThemeEditor->setTheme(d.icon.theme()); + m_ui->tooltipEditor->setText(d.toolTip); + m_ui->keySequenceEdit->setKeySequence(d.keysequence.value()); + m_ui->checkableCheckBox->setCheckState(d.checkable ? Qt::Checked : Qt::Unchecked); + m_ui->menuRole->setCurrentIndex(m_ui->menuRole->findData(d.menuRole.value)); + + // Suppress updating of the object name from the text for existing actions. + m_autoUpdateObjectName = d.name.isEmpty(); + updateButtons(); +} + +void NewActionDialog::onEditActionTextTextEdited(const QString &text) +{ + if (m_autoUpdateObjectName) + m_ui->editObjectName->setText(ActionEditor::actionTextToName(text)); + + updateButtons(); +} + +void NewActionDialog::onEditObjectNameTextEdited(const QString&) +{ + updateButtons(); + m_autoUpdateObjectName = false; +} + +void NewActionDialog::slotEditToolTip() +{ + const QString oldToolTip = m_ui->tooltipEditor->text(); + RichTextEditorDialog richTextDialog(m_actionEditor->core(), this); + richTextDialog.setText(oldToolTip); + if (richTextDialog.showDialog() == QDialog::Rejected) + return; + const QString newToolTip = richTextDialog.text(); + if (newToolTip != oldToolTip) + m_ui->tooltipEditor->setText(newToolTip); +} + +void NewActionDialog::slotResetKeySequence() +{ + m_ui->keySequenceEdit->setKeySequence(QKeySequence()); + m_ui->keySequenceEdit->setFocus(Qt::MouseFocusReason); +} + +void NewActionDialog::updateButtons() +{ + QPushButton *okButton = m_ui->buttonBox->button(QDialogButtonBox::Ok); + okButton->setEnabled(!actionText().isEmpty() && !actionName().isEmpty()); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/newactiondialog.ui b/src/designer/src/lib/shared/newactiondialog.ui new file mode 100644 index 0000000..7fd9984 --- /dev/null +++ b/src/designer/src/lib/shared/newactiondialog.ui @@ -0,0 +1,306 @@ + + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::NewActionDialog + + + + 0 + 0 + 381 + 291 + + + + New Action... + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + &Text: + + + editActionText + + + + + + + + 255 + 0 + + + + + + + + Object &name: + + + editObjectName + + + + + + + + + + T&oolTip: + + + tooltipEditor + + + + + + + + + + 0 + 0 + + + + + + + + ... + + + + + + + + + Icon &XDG theme: + + + iconThemeEditor + + + + + + + + + + &Icon: + + + iconSelector + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + &Checkable: + + + checkableCheckBox + + + + + + + &Shortcut: + + + keySequenceEdit + + + + + + + + + + 0 + 0 + + + + + + + + ... + + + + + + + + + &Menu role: + + + menuRole + + + + + + + + + + Icon &theme: + + + iconThemeEnumEditor + + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + qdesigner_internal::IconSelector + QWidget +
    iconselector_p.h
    + 1 +
    + + TextPropertyEditor + QWidget +
    textpropertyeditor_p.h
    + 1 +
    + + qdesigner_internal::IconThemeEditor + QWidget +
    iconselector_p.h
    + 1 +
    + + qdesigner_internal::IconThemeEnumEditor + QWidget +
    iconselector_p.h
    + 1 +
    +
    + + editActionText + editObjectName + + + + + buttonBox + accepted() + qdesigner_internal::NewActionDialog + accept() + + + 165 + 162 + + + 291 + 94 + + + + + buttonBox + rejected() + qdesigner_internal::NewActionDialog + reject() + + + 259 + 162 + + + 293 + 128 + + + + +
    diff --git a/src/designer/src/lib/shared/newactiondialog_p.h b/src/designer/src/lib/shared/newactiondialog_p.h new file mode 100644 index 0000000..de42d16 --- /dev/null +++ b/src/designer/src/lib/shared/newactiondialog_p.h @@ -0,0 +1,100 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NEWACTIONDIALOG_P_H +#define NEWACTIONDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdesigner_utils_p.h" // PropertySheetIconValue + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +namespace Ui { + class NewActionDialog; +} + +class ActionEditor; + +struct ActionData { + + enum ChangeMask { + TextChanged = 0x1, NameChanged = 0x2, ToolTipChanged = 0x4, + IconChanged = 0x8, CheckableChanged = 0x10, KeysequenceChanged = 0x20, + MenuRoleChanged = 0x40 + }; + + // Returns a combination of ChangeMask flags + unsigned compare(const ActionData &rhs) const; + + QString text; + QString name; + QString toolTip; + PropertySheetIconValue icon; + bool checkable{false}; + PropertySheetKeySequenceValue keysequence; + PropertySheetFlagValue menuRole; + + friend bool comparesEqual(const ActionData &lhs, const ActionData &rhs) noexcept + { + return lhs.compare(rhs) == 0; + } + Q_DECLARE_EQUALITY_COMPARABLE(ActionData) +}; + +class NewActionDialog: public QDialog +{ + Q_OBJECT +public: + explicit NewActionDialog(ActionEditor *parent); + ~NewActionDialog() override; + + ActionData actionData() const; + void setActionData(const ActionData &d); + + QString actionText() const; + QString actionName() const; + +public slots: + void focusName(); + void focusText(); + void focusTooltip(); + void focusShortcut(); + void focusCheckable(); + void focusMenuRole(); + +private slots: + void onEditActionTextTextEdited(const QString &text); + void onEditObjectNameTextEdited(const QString &text); + + void slotEditToolTip(); + void slotResetKeySequence(); + +private: + Ui::NewActionDialog *m_ui; + ActionEditor *m_actionEditor; + bool m_autoUpdateObjectName; + + void updateButtons(); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // NEWACTIONDIALOG_P_H diff --git a/src/designer/src/lib/shared/newformwidget.cpp b/src/designer/src/lib/shared/newformwidget.cpp new file mode 100644 index 0000000..383b3fc --- /dev/null +++ b/src/designer/src/lib/shared/newformwidget.cpp @@ -0,0 +1,560 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "newformwidget_p.h" +#include "ui_newformwidget.h" +#include "qdesigner_formbuilder_p.h" +#include "sheet_delegate_p.h" +#include "widgetdatabase_p.h" +#include "shared_settings_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { profileComboIndexOffset = 1 }; +enum { debugNewFormWidget = 0 }; + +enum NewForm_CustomRole { + // File name (templates from resources, paths) + TemplateNameRole = Qt::UserRole + 100, + // Class name (widgets from Widget data base) + ClassNameRole = Qt::UserRole + 101 +}; + +static constexpr auto newFormObjectNameC = "Form"_L1; + +// Create a form name for an arbitrary class. If it is Qt, qtify it, +// else return "Form". +static QString formName(const QString &className) +{ + if (!className.startsWith(u'Q')) + return newFormObjectNameC; + QString rc = className; + rc.remove(0, 1); + return rc; +} + +namespace qdesigner_internal { + +struct TemplateSize { + const char *name; + int width; + int height; +}; + +static const struct TemplateSize templateSizes[] = +{ + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "Default size"), 0, 0 }, + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "QVGA portrait (240x320)"), 240, 320 }, + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "QVGA landscape (320x240)"), 320, 240 }, + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "VGA portrait (480x640)"), 480, 640 }, + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "VGA landscape (640x480)"), 640, 480 } +}; + +/* -------------- NewForm dialog. + * Designer takes new form templates from: + * 1) Files located in directories specified in resources + * 2) Files located in directories specified as user templates + * 3) XML from container widgets deemed usable for form templates by the widget + * database + * 4) XML from custom container widgets deemed usable for form templates by the + * widget database + * + * The widget database provides helper functions to obtain lists of names + * and xml for 3,4. + * + * Fixed-size forms for embedded platforms are obtained as follows: + * 1) If the origin is a file: + * - Check if the file exists in the subdirectory "/x/" of + * the path (currently the case for the dialog box because the button box + * needs to be positioned) + * - Scale the form using the QWidgetDatabase::scaleFormTemplate routine. + * 2) If the origin is XML: + * - Scale the form using the QWidgetDatabase::scaleFormTemplate routine. + * + * The tree widget item roles indicate which type of entry it is + * (TemplateNameRole = file name 1,2, ClassNameRole = class name 3,4) + */ + +NewFormWidget::NewFormWidget(QDesignerFormEditorInterface *core, QWidget *parentWidget) : + QDesignerNewFormWidgetInterface(parentWidget), + m_core(core), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::NewFormWidget), + m_currentItem(nullptr), + m_acceptedItem(nullptr) +{ + // ### FIXME Qt 8: Remove (QTBUG-96005) +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + QDesignerSharedSettings::migrateTemplates(); +#endif + + m_ui->setupUi(this); + m_ui->treeWidget->setItemDelegate(new qdesigner_internal::SheetDelegate(m_ui->treeWidget, this)); + m_ui->treeWidget->header()->hide(); + m_ui->treeWidget->header()->setStretchLastSection(true); + m_ui->lblPreview->setBackgroundRole(QPalette::Base); + + connect(m_ui->treeWidget, &QTreeWidget::itemActivated, + this, &NewFormWidget::treeWidgetItemActivated); + connect(m_ui->treeWidget, &QTreeWidget::currentItemChanged, + this, &NewFormWidget::treeWidgetCurrentItemChanged); + connect(m_ui->treeWidget, &QTreeWidget::itemPressed, + this, &NewFormWidget::treeWidgetItemPressed); + + QDesignerSharedSettings settings(m_core); + + QString uiExtension = u"ui"_s; + QString templatePath = u":/qt-project.org/designer/templates/forms"_s; + + QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core); + if (lang) { + templatePath = u":/templates/forms"_s; + uiExtension = lang->uiExtension(); + } + + // Resource templates + const QString formTemplate = settings.formTemplate(); + QTreeWidgetItem *selectedItem = nullptr; + loadFrom(templatePath, true, uiExtension, formTemplate, selectedItem); + // Additional template paths + const QStringList formTemplatePaths = settings.formTemplatePaths(); + for (const auto &ftp : formTemplatePaths) + loadFrom(ftp, false, uiExtension, formTemplate, selectedItem); + + // Widgets/custom widgets + if (!lang) { + //: New Form Dialog Categories + loadFrom(tr("Widgets"), qdesigner_internal::WidgetDataBase::formWidgetClasses(core), formTemplate, selectedItem); + loadFrom(tr("Custom Widgets"), qdesigner_internal::WidgetDataBase::customFormWidgetClasses(core), formTemplate, selectedItem); + } + + // Still no selection - default to first item + if (selectedItem == nullptr && m_ui->treeWidget->topLevelItemCount() != 0) { + QTreeWidgetItem *firstTopLevel = m_ui->treeWidget->topLevelItem(0); + if (firstTopLevel->childCount() > 0) + selectedItem = firstTopLevel->child(0); + } + + // Open parent, select and make visible + if (selectedItem) { + m_ui->treeWidget->setCurrentItem(selectedItem); + selectedItem->setSelected(true); + m_ui->treeWidget->scrollToItem(selectedItem->parent()); + } + // Fill profile combo + m_deviceProfiles = settings.deviceProfiles(); + m_ui->profileComboBox->addItem(tr("None")); + connect(m_ui->profileComboBox, + &QComboBox::currentIndexChanged, + this, &NewFormWidget::slotDeviceProfileIndexChanged); + if (m_deviceProfiles.isEmpty()) { + m_ui->profileComboBox->setEnabled(false); + } else { + for (const auto &deviceProfile : std::as_const(m_deviceProfiles)) + m_ui->profileComboBox->addItem(deviceProfile.name()); + const int ci = settings.currentDeviceProfileIndex(); + if (ci >= 0) + m_ui->profileComboBox->setCurrentIndex(ci + profileComboIndexOffset); + } + // Fill size combo + for (const TemplateSize &t : templateSizes) + m_ui->sizeComboBox->addItem(tr(t.name), QSize(t.width, t.height)); + + setTemplateSize(settings.newFormSize()); + + if (debugNewFormWidget) + qDebug() << Q_FUNC_INFO << "Leaving"; +} + +NewFormWidget::~NewFormWidget() +{ + QDesignerSharedSettings settings (m_core); + settings.setNewFormSize(templateSize()); + // Do not change previously stored item if dialog was rejected + if (m_acceptedItem) + settings.setFormTemplate(m_acceptedItem->text(0)); + delete m_ui; +} + +void NewFormWidget::treeWidgetCurrentItemChanged(QTreeWidgetItem *current) +{ + if (debugNewFormWidget) + qDebug() << Q_FUNC_INFO << current; + if (!current) + return; + + if (!current->parent()) { // Top level item: Ensure expanded when browsing down + return; + } + + m_currentItem = current; + + emit currentTemplateChanged(showCurrentItemPixmap()); +} + +bool NewFormWidget::showCurrentItemPixmap() +{ + bool rc = false; + if (m_currentItem) { + const QPixmap pixmap = formPreviewPixmap(m_currentItem); + if (pixmap.isNull()) { + m_ui->lblPreview->setText(tr("Error loading form")); + } else { + m_ui->lblPreview->setPixmap(pixmap); + rc = true; + } + } + return rc; +} + +void NewFormWidget::treeWidgetItemActivated(QTreeWidgetItem *item) +{ + if (debugNewFormWidget) + qDebug() << Q_FUNC_INFO << item; + + if (item->data(0, TemplateNameRole).isValid() || item->data(0, ClassNameRole).isValid()) + emit templateActivated(); +} + +QPixmap NewFormWidget::formPreviewPixmap(const QTreeWidgetItem *item) +{ + // Cache pixmaps per item/device profile + const ItemPixmapCacheKey cacheKey(item, profileComboIndex()); + auto it = m_itemPixmapCache.find(cacheKey); + if (it == m_itemPixmapCache.end()) { + // file or string? + const QVariant fileName = item->data(0, TemplateNameRole); + QPixmap rc; + if (fileName.metaType().id() == QMetaType::QString) { + rc = formPreviewPixmap(fileName.toString()); + } else { + const QVariant classNameV = item->data(0, ClassNameRole); + Q_ASSERT(classNameV.metaType().id() == QMetaType::QString); + const QString className = classNameV.toString(); + QByteArray data = qdesigner_internal::WidgetDataBase::formTemplate(m_core, className, formName(className)).toUtf8(); + QBuffer buffer(&data); + buffer.open(QIODevice::ReadOnly); + rc = formPreviewPixmap(buffer); + } + if (rc.isNull()) // Retry invalid ones + return rc; + it = m_itemPixmapCache.insert(cacheKey, rc); + } + return it.value(); +} + +QPixmap NewFormWidget::formPreviewPixmap(const QString &fileName) const +{ + QFile f(fileName); + if (f.open(QFile::ReadOnly)) { + QFileInfo fi(fileName); + const QPixmap rc = formPreviewPixmap(f, fi.absolutePath()); + f.close(); + return rc; + } + qWarning() << "The file " << fileName << " could not be opened: " << f.errorString(); + return QPixmap(); +} + +QImage NewFormWidget::grabForm(QDesignerFormEditorInterface *core, + QIODevice &file, + const QString &workingDir, + const qdesigner_internal::DeviceProfile &dp) +{ + qdesigner_internal::NewFormWidgetFormBuilder + formBuilder(core, dp); + if (!workingDir.isEmpty()) + formBuilder.setWorkingDirectory(workingDir); + + QWidget *widget = formBuilder.load(&file, nullptr); + if (!widget) + return QImage(); + + const QPixmap pixmap = widget->grab(QRect(0, 0, -1, -1)); + widget->deleteLater(); + return pixmap.toImage(); +} + +QPixmap NewFormWidget::formPreviewPixmap(QIODevice &file, const QString &workingDir) const +{ + const QSizeF screenSize(screen()->geometry().size()); + const int previewSize = qRound(screenSize.width() / 7.5); // 256 on 1920px screens. + const int margin = previewSize / 32 - 1; // 7 on 1920px screens. + const int shadow = margin; + + const QImage wimage = grabForm(m_core, file, workingDir, currentDeviceProfile()); + if (wimage.isNull()) + return QPixmap(); + const qreal devicePixelRatio = wimage.devicePixelRatioF(); + const QSize imageSize(previewSize - margin * 2, previewSize - margin * 2); + QImage image = wimage.scaled((QSizeF(imageSize) * devicePixelRatio).toSize(), + Qt::KeepAspectRatio, Qt::SmoothTransformation); + image.setDevicePixelRatio(devicePixelRatio); + + QImage dest((QSizeF(previewSize, previewSize) * devicePixelRatio).toSize(), + QImage::Format_ARGB32_Premultiplied); + dest.setDevicePixelRatio(devicePixelRatio); + dest.fill(0); + + QPainter p(&dest); + p.drawImage(margin, margin, image); + + p.setPen(QPen(palette().brush(QPalette::WindowText), 0)); + + p.drawRect(QRectF(margin - 1, margin - 1, imageSize.width() + 1.5, imageSize.height() + 1.5)); + + const QColor dark(Qt::darkGray); + const QColor light(Qt::transparent); + + // right shadow + { + const QRect rect(margin + imageSize.width() + 1, margin + shadow, shadow, imageSize.height() - shadow + 1); + QLinearGradient lg(rect.topLeft(), rect.topRight()); + lg.setColorAt(0, dark); + lg.setColorAt(1, light); + p.fillRect(rect, lg); + } + + // bottom shadow + { + const QRect rect(margin + shadow, margin + imageSize.height() + 1, imageSize.width() - shadow + 1, shadow); + QLinearGradient lg(rect.topLeft(), rect.bottomLeft()); + lg.setColorAt(0, dark); + lg.setColorAt(1, light); + p.fillRect(rect, lg); + } + + // bottom/right corner shadow + { + const QRect rect(margin + imageSize.width() + 1, margin + imageSize.height() + 1, shadow, shadow); + QRadialGradient g(rect.topLeft(), shadow - 1); + g.setColorAt(0, dark); + g.setColorAt(1, light); + p.fillRect(rect, g); + } + + // top/right corner + { + const QRect rect(margin + imageSize.width() + 1, margin, shadow, shadow); + QRadialGradient g(rect.bottomLeft(), shadow - 1); + g.setColorAt(0, dark); + g.setColorAt(1, light); + p.fillRect(rect, g); + } + + // bottom/left corner + { + const QRect rect(margin, margin + imageSize.height() + 1, shadow, shadow); + QRadialGradient g(rect.topRight(), shadow - 1); + g.setColorAt(0, dark); + g.setColorAt(1, light); + p.fillRect(rect, g); + } + + p.end(); + + return QPixmap::fromImage(dest); +} + +void NewFormWidget::loadFrom(const QString &path, bool resourceFile, const QString &uiExtension, + const QString &selectedItem, QTreeWidgetItem *&selectedItemFound) +{ + const QDir dir(path); + + if (!dir.exists()) + return; + + // Iterate through the directory and add the templates + const QFileInfoList list = dir.entryInfoList(QStringList{"*."_L1 + uiExtension}, + QDir::Files); + + if (list.isEmpty()) + return; + + const QChar separator = resourceFile ? QChar(u'/') + : QDir::separator(); + QTreeWidgetItem *root = new QTreeWidgetItem(m_ui->treeWidget); + root->setFlags(root->flags() & ~Qt::ItemIsSelectable); + // Try to get something that is easy to read. + QString visiblePath = path; + int index = visiblePath.lastIndexOf(separator); + if (index != -1) { + // try to find a second slash, just to be a bit better. + const int index2 = visiblePath.lastIndexOf(separator, index - 1); + if (index2 != -1) + index = index2; + visiblePath = visiblePath.mid(index + 1); + visiblePath = QDir::toNativeSeparators(visiblePath); + } + + root->setText(0, visiblePath.replace(u'_', u' ')); + root->setToolTip(0, path); + + for (const auto &fi : list) { + if (!fi.isFile()) + continue; + + QTreeWidgetItem *item = new QTreeWidgetItem(root); + const QString text = fi.baseName().replace(u'_', u' '); + if (selectedItemFound == nullptr && text == selectedItem) + selectedItemFound = item; + item->setText(0, text); + item->setData(0, TemplateNameRole, fi.absoluteFilePath()); + } +} + +void NewFormWidget::loadFrom(const QString &title, const QStringList &nameList, + const QString &selectedItem, QTreeWidgetItem *&selectedItemFound) +{ + if (nameList.isEmpty()) + return; + QTreeWidgetItem *root = new QTreeWidgetItem(m_ui->treeWidget); + root->setFlags(root->flags() & ~Qt::ItemIsSelectable); + root->setText(0, title); + for (const auto &text : nameList) { + QTreeWidgetItem *item = new QTreeWidgetItem(root); + item->setText(0, text); + if (selectedItemFound == nullptr && text == selectedItem) + selectedItemFound = item; + item->setData(0, ClassNameRole, text); + } +} + +void NewFormWidget::treeWidgetItemPressed(QTreeWidgetItem *item) +{ + if (item && !item->parent()) + item->setExpanded(!item->isExpanded()); +} + +QSize NewFormWidget::templateSize() const +{ + return m_ui->sizeComboBox->itemData(m_ui->sizeComboBox->currentIndex()).toSize(); +} + +void NewFormWidget::setTemplateSize(const QSize &s) +{ + const int index = s.isNull() ? 0 : m_ui->sizeComboBox->findData(s); + if (index != -1) + m_ui->sizeComboBox->setCurrentIndex(index); +} + +static QString readAll(const QString &fileName, QString *errorMessage) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) { + *errorMessage = NewFormWidget::tr("Unable to open the form template file '%1': %2").arg(fileName, file.errorString()); + return QString(); + } + return QString::fromUtf8(file.readAll()); +} + +QString NewFormWidget::itemToTemplate(const QTreeWidgetItem *item, QString *errorMessage) const +{ + const QSize size = templateSize(); + // file name or string contents? + const QVariant templateFileName = item->data(0, TemplateNameRole); + if (templateFileName.metaType().id() == QMetaType::QString) { + const QString fileName = templateFileName.toString(); + // No fixed size: just open. + if (size.isNull()) + return readAll(fileName, errorMessage); + // try to find a file matching the size, like "../640x480/xx.ui" + const QFileInfo fiBase(fileName); + QString sizeFileName; + QTextStream(&sizeFileName) << fiBase.path() << QDir::separator() + << size.width() << 'x' << size.height() << QDir::separator() + << fiBase.fileName(); + if (QFileInfo(sizeFileName).isFile()) + return readAll(sizeFileName, errorMessage); + // Nothing found, scale via DOM/temporary file + QString contents = readAll(fileName, errorMessage); + if (!contents.isEmpty()) + contents = qdesigner_internal::WidgetDataBase::scaleFormTemplate(contents, size, false); + return contents; + } + // Content. + const QString className = item->data(0, ClassNameRole).toString(); + QString contents = qdesigner_internal::WidgetDataBase::formTemplate(m_core, className, formName(className)); + if (!size.isNull()) + contents = qdesigner_internal::WidgetDataBase::scaleFormTemplate(contents, size, false); + return contents; +} + +void NewFormWidget::slotDeviceProfileIndexChanged(int idx) +{ + // Store index for form windows to take effect and refresh pixmap + QDesignerSharedSettings settings(m_core); + settings.setCurrentDeviceProfileIndex(idx - profileComboIndexOffset); + showCurrentItemPixmap(); +} + +int NewFormWidget::profileComboIndex() const +{ + return m_ui->profileComboBox->currentIndex(); +} + +qdesigner_internal::DeviceProfile NewFormWidget::currentDeviceProfile() const +{ + const int ci = profileComboIndex(); + if (ci > 0) + return m_deviceProfiles.at(ci - profileComboIndexOffset); + return qdesigner_internal::DeviceProfile(); +} + +bool NewFormWidget::hasCurrentTemplate() const +{ + return m_currentItem != nullptr; +} + +QString NewFormWidget::currentTemplateI(QString *ptrToErrorMessage) +{ + if (m_currentItem == nullptr) { + *ptrToErrorMessage = tr("Internal error: No template selected."); + return QString(); + } + const QString contents = itemToTemplate(m_currentItem, ptrToErrorMessage); + if (contents.isEmpty()) + return contents; + + m_acceptedItem = m_currentItem; + return contents; +} + +QString NewFormWidget::currentTemplate(QString *ptrToErrorMessage) +{ + if (ptrToErrorMessage) + return currentTemplateI(ptrToErrorMessage); + // Do not loose the error + QString errorMessage; + const QString contents = currentTemplateI(&errorMessage); + if (!errorMessage.isEmpty()) + qWarning("%s", errorMessage.toUtf8().constData()); + return contents; +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/newformwidget.ui b/src/designer/src/lib/shared/newformwidget.ui new file mode 100644 index 0000000..8cf6f63 --- /dev/null +++ b/src/designer/src/lib/shared/newformwidget.ui @@ -0,0 +1,155 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::NewFormWidget + + + + 0 + 0 + 480 + 194 + + + + + 6 + + + 1 + + + + + + 200 + 0 + + + + + 128 + 128 + + + + false + + + 1 + + + + 0 + + + + + + + + + + + 0 + 0 + + + + 1 + + + Choose a template for a preview + + + Qt::AlignCenter + + + 5 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 7 + 0 + + + + + + + + Embedded Design + + + + + + + + + + + + Device: + + + + + + + Screen Size: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + diff --git a/src/designer/src/lib/shared/newformwidget_p.h b/src/designer/src/lib/shared/newformwidget_p.h new file mode 100644 index 0000000..d9acbd2 --- /dev/null +++ b/src/designer/src/lib/shared/newformwidget_p.h @@ -0,0 +1,105 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NEWFORMWIDGET_H +#define NEWFORMWIDGET_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "shared_global_p.h" +#include "deviceprofile_p.h" + +#include + +#include + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QIODevice; +class QTreeWidgetItem; + +namespace qdesigner_internal { + +namespace Ui { + class NewFormWidget; +} + +class QDESIGNER_SHARED_EXPORT NewFormWidget : public QDesignerNewFormWidgetInterface +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(NewFormWidget) + +public: + using DeviceProfileList = QList; + + explicit NewFormWidget(QDesignerFormEditorInterface *core, QWidget *parentWidget); + ~NewFormWidget() override; + + bool hasCurrentTemplate() const override; + QString currentTemplate(QString *errorMessage = nullptr) override; + + // Convenience for implementing file dialogs with preview + static QImage grabForm(QDesignerFormEditorInterface *core, + QIODevice &file, + const QString &workingDir, + const qdesigner_internal::DeviceProfile &dp); + +private slots: + void treeWidgetItemActivated(QTreeWidgetItem *item); + void treeWidgetCurrentItemChanged(QTreeWidgetItem *current); + void treeWidgetItemPressed(QTreeWidgetItem *item); + void slotDeviceProfileIndexChanged(int idx); + +private: + QPixmap formPreviewPixmap(const QString &fileName) const; + QPixmap formPreviewPixmap(QIODevice &file, const QString &workingDir = QString()) const; + QPixmap formPreviewPixmap(const QTreeWidgetItem *item); + + void loadFrom(const QString &path, bool resourceFile, const QString &uiExtension, + const QString &selectedItem, QTreeWidgetItem *&selectedItemFound); + void loadFrom(const QString &title, const QStringList &nameList, + const QString &selectedItem, QTreeWidgetItem *&selectedItemFound); + +private: + QString itemToTemplate(const QTreeWidgetItem *item, QString *errorMessage) const; + QString currentTemplateI(QString *ptrToErrorMessage); + + QSize templateSize() const; + void setTemplateSize(const QSize &s); + int profileComboIndex() const; + qdesigner_internal::DeviceProfile currentDeviceProfile() const; + bool showCurrentItemPixmap(); + + // Pixmap cache (item, profile combo index) + using ItemPixmapCacheKey = std::pair; + using ItemPixmapCache = QMap; + ItemPixmapCache m_itemPixmapCache; + + QDesignerFormEditorInterface *m_core; + Ui::NewFormWidget *m_ui; + QTreeWidgetItem *m_currentItem; + QTreeWidgetItem *m_acceptedItem; + DeviceProfileList m_deviceProfiles; +}; + +} + +QT_END_NAMESPACE + +#endif // NEWFORMWIDGET_H diff --git a/src/designer/src/lib/shared/orderdialog.cpp b/src/designer/src/lib/shared/orderdialog.cpp new file mode 100644 index 0000000..faa1350 --- /dev/null +++ b/src/designer/src/lib/shared/orderdialog.cpp @@ -0,0 +1,156 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "orderdialog_p.h" +#include "iconloader_p.h" +#include "ui_orderdialog.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// OrderDialog: Used to reorder the pages of QStackedWidget and QToolBox. +// Provides up and down buttons as well as DnD via QAbstractItemView::InternalMove mode +namespace qdesigner_internal { + +OrderDialog::OrderDialog(QWidget *parent) : + QDialog(parent), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::OrderDialog), + m_format(PageOrderFormat) +{ + m_ui->setupUi(this); + m_ui->upButton->setIcon(createIconSet("up.png"_L1)); + m_ui->downButton->setIcon(createIconSet("down.png"_L1)); + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + connect(m_ui->buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, + this, &OrderDialog::slotReset); + // Catch the remove operation of a DnD operation in QAbstractItemView::InternalMove mode to enable buttons + // Selection mode is 'contiguous' to enable DnD of groups + connect(m_ui->pageList->model(), &QAbstractItemModel::rowsRemoved, + this, &OrderDialog::slotEnableButtonsAfterDnD); + + connect(m_ui->upButton, &QAbstractButton::clicked, this, &OrderDialog::upButtonClicked); + connect(m_ui->downButton, &QAbstractButton::clicked, this, &OrderDialog::downButtonClicked); + connect(m_ui->pageList, &QListWidget::currentRowChanged, + this, &OrderDialog::pageListCurrentRowChanged); + + m_ui->upButton->setEnabled(false); + m_ui->downButton->setEnabled(false); +} + +OrderDialog::~OrderDialog() +{ + delete m_ui; +} + +void OrderDialog::setDescription(const QString &d) +{ + m_ui->groupBox->setTitle(d); +} + +void OrderDialog::setPageList(const QWidgetList &pages) +{ + // The QWidget* are stored in a map indexed by the old index. + // The old index is set as user data on the item instead of the QWidget* + // because DnD is enabled which requires the user data to serializable + m_orderMap.clear(); + const qsizetype count = pages.size(); + for (qsizetype i = 0; i < count; ++i) + m_orderMap.insert(int(i), pages.at(i)); + buildList(); +} + +void OrderDialog::buildList() +{ + m_ui->pageList->clear(); + for (auto it = m_orderMap.cbegin(), cend = m_orderMap.cend(); it != cend; ++it) { + QListWidgetItem *item = new QListWidgetItem(); + const int index = it.key(); + switch (m_format) { + case PageOrderFormat: + item->setText(tr("Index %1 (%2)").arg(index).arg(it.value()->objectName())); + break; + case TabOrderFormat: + item->setText(tr("%1 %2").arg(index+1).arg(it.value()->objectName())); + break; + } + item->setData(Qt::UserRole, QVariant(index)); + m_ui->pageList->addItem(item); + } + + if (m_ui->pageList->count() > 0) + m_ui->pageList->setCurrentRow(0); +} + +void OrderDialog::slotReset() +{ + buildList(); +} + +QWidgetList OrderDialog::pageList() const +{ + QWidgetList rc; + const int count = m_ui->pageList->count(); + for (int i=0; i < count; ++i) { + const int oldIndex = m_ui->pageList->item(i)->data(Qt::UserRole).toInt(); + rc.append(m_orderMap.value(oldIndex)); + } + return rc; +} + +void OrderDialog::upButtonClicked() +{ + const int row = m_ui->pageList->currentRow(); + if (row <= 0) + return; + + m_ui->pageList->insertItem(row - 1, m_ui->pageList->takeItem(row)); + m_ui->pageList->setCurrentRow(row - 1); +} + +void OrderDialog::downButtonClicked() +{ + const int row = m_ui->pageList->currentRow(); + if (row == -1 || row == m_ui->pageList->count() - 1) + return; + + m_ui->pageList->insertItem(row + 1, m_ui->pageList->takeItem(row)); + m_ui->pageList->setCurrentRow(row + 1); +} + +void OrderDialog::slotEnableButtonsAfterDnD() +{ + enableButtons(m_ui->pageList->currentRow()); +} + +void OrderDialog::pageListCurrentRowChanged(int r) +{ + enableButtons(r); +} + +void OrderDialog::enableButtons(int r) +{ + m_ui->upButton->setEnabled(r > 0); + m_ui->downButton->setEnabled(r >= 0 && r < m_ui->pageList->count() - 1); +} + +QWidgetList OrderDialog::pagesOfContainer(const QDesignerFormEditorInterface *core, QWidget *container) +{ + QWidgetList rc; + if (QDesignerContainerExtension* ce = qt_extension(core->extensionManager(), container)) { + const int count = ce->count(); + for (int i = 0; i < count ;i ++) + rc.push_back(ce->widget(i)); + } + return rc; +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/orderdialog.ui b/src/designer/src/lib/shared/orderdialog.ui new file mode 100644 index 0000000..0af976d --- /dev/null +++ b/src/designer/src/lib/shared/orderdialog.ui @@ -0,0 +1,162 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::OrderDialog + + + + 0 + 0 + 467 + 310 + + + + Change Page Order + + + + + + Page Order + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + 344 + 0 + + + + QAbstractItemView::InternalMove + + + QAbstractItemView::ContiguousSelection + + + QListView::Snap + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Move page up + + + + + + + Move page down + + + + + + + + 0 + 0 + + + + Qt::Vertical + + + + 20 + 99 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + + + + + + + buttonBox + accepted() + qdesigner_internal::OrderDialog + accept() + + + 50 + 163 + + + 6 + 151 + + + + + buttonBox + rejected() + qdesigner_internal::OrderDialog + reject() + + + 300 + 160 + + + 348 + 148 + + + + + diff --git a/src/designer/src/lib/shared/orderdialog_p.h b/src/designer/src/lib/shared/orderdialog_p.h new file mode 100644 index 0000000..2fb506f --- /dev/null +++ b/src/designer/src/lib/shared/orderdialog_p.h @@ -0,0 +1,75 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ORDERDIALOG_P_H +#define ORDERDIALOG_P_H + +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +namespace Ui { + class OrderDialog; +} + +class QDESIGNER_SHARED_EXPORT OrderDialog: public QDialog +{ + Q_OBJECT +public: + OrderDialog(QWidget *parent); + ~OrderDialog() override; + + static QWidgetList pagesOfContainer(const QDesignerFormEditorInterface *core, QWidget *container); + + void setPageList(const QWidgetList &pages); + QWidgetList pageList() const; + + void setDescription(const QString &d); + + enum Format { // Display format + PageOrderFormat, // Container pages, ranging 0..[n-1] + TabOrderFormat // List of widgets, ranging 1..1 + }; + + void setFormat(Format f) { m_format = f; } + Format format() const { return m_format; } + +private slots: + void upButtonClicked(); + void downButtonClicked(); + void pageListCurrentRowChanged(int row); + void slotEnableButtonsAfterDnD(); + void slotReset(); + +private: + void buildList(); + void enableButtons(int r); + + QMap m_orderMap; + Ui::OrderDialog* m_ui; + Format m_format; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ORDERDIALOG_P_H diff --git a/src/designer/src/lib/shared/plaintexteditor.cpp b/src/designer/src/lib/shared/plaintexteditor.cpp new file mode 100644 index 0000000..ad6708c --- /dev/null +++ b/src/designer/src/lib/shared/plaintexteditor.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "plaintexteditor_p.h" + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto PlainTextDialogC = "PlainTextDialog"_L1; +static constexpr auto PlainTextEditorGeometryC = "Geometry"_L1; + +namespace qdesigner_internal { + +PlainTextEditorDialog::PlainTextEditorDialog(QDesignerFormEditorInterface *core, QWidget *parent) : + QDialog(parent), + m_editor(new QPlainTextEdit), + m_core(core) +{ + setWindowTitle(tr("Edit text")); + + QVBoxLayout *vlayout = new QVBoxLayout(this); + vlayout->addWidget(m_editor); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); + QPushButton *ok_button = buttonBox->button(QDialogButtonBox::Ok); + ok_button->setDefault(true); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + vlayout->addWidget(buttonBox); + + QDesignerSettingsInterface *settings = core->settingsManager(); + settings->beginGroup(PlainTextDialogC); + + if (settings->contains(PlainTextEditorGeometryC)) + restoreGeometry(settings->value(PlainTextEditorGeometryC).toByteArray()); + + settings->endGroup(); +} + +PlainTextEditorDialog::~PlainTextEditorDialog() +{ + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(PlainTextDialogC); + + settings->setValue(PlainTextEditorGeometryC, saveGeometry()); + settings->endGroup(); +} + +int PlainTextEditorDialog::showDialog() +{ + m_editor->setFocus(); + return exec(); +} + +void PlainTextEditorDialog::setDefaultFont(const QFont &font) +{ + m_editor->setFont(font); +} + +void PlainTextEditorDialog::setText(const QString &text) +{ + m_editor->setPlainText(text); +} + +QString PlainTextEditorDialog::text() const +{ + return m_editor->toPlainText(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/plaintexteditor_p.h b/src/designer/src/lib/shared/plaintexteditor_p.h new file mode 100644 index 0000000..988a5f5 --- /dev/null +++ b/src/designer/src/lib/shared/plaintexteditor_p.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PLAINTEXTEDITOR_H +#define PLAINTEXTEDITOR_H + +#include +#include "shared_global_p.h" + +QT_BEGIN_NAMESPACE + +class QPlainTextEdit; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT PlainTextEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit PlainTextEditorDialog(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~PlainTextEditorDialog(); + + int showDialog(); + + void setDefaultFont(const QFont &font); + + void setText(const QString &text); + QString text() const; + +private: + QPlainTextEdit *m_editor; + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // RITCHTEXTEDITOR_H diff --git a/src/designer/src/lib/shared/plugindialog.cpp b/src/designer/src/lib/shared/plugindialog.cpp new file mode 100644 index 0000000..fb4ec4e --- /dev/null +++ b/src/designer/src/lib/shared/plugindialog.cpp @@ -0,0 +1,208 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "plugindialog_p.h" +#include "pluginmanager_p.h" +#include "iconloader_p.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#if QT_CONFIG(clipboard) +# include +#endif + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { ErrorItemRole = Qt::UserRole + 1 }; + +namespace qdesigner_internal { + +PluginDialog::PluginDialog(QDesignerFormEditorInterface *core, QWidget *parent) + : QDialog(parent +#ifdef Q_OS_MACOS + , Qt::Tool +#endif + ), m_core(core) +{ + ui.setupUi(this); + + ui.message->hide(); + + const QStringList headerLabels(tr("Components")); + + ui.treeWidget->setAlternatingRowColors(false); + ui.treeWidget->setSelectionMode(QAbstractItemView::NoSelection); + ui.treeWidget->setHeaderLabels(headerLabels); + ui.treeWidget->header()->hide(); + ui.treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui.treeWidget, &QWidget::customContextMenuRequested, + this, &PluginDialog::treeWidgetContextMenu); + + interfaceIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirOpenIcon), + QIcon::Normal, QIcon::On); + interfaceIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirClosedIcon), + QIcon::Normal, QIcon::Off); + featureIcon.addPixmap(style()->standardPixmap(QStyle::SP_FileIcon)); + + setWindowTitle(tr("Plugin Information")); + populateTreeWidget(); + + QPushButton *updateButton = new QPushButton(tr("Refresh")); + const QString tooltip = tr("Scan for newly installed custom widget plugins."); + updateButton->setToolTip(tooltip); + updateButton->setWhatsThis(tooltip); + connect(updateButton, &QAbstractButton::clicked, this, &PluginDialog::updateCustomWidgetPlugins); + ui.buttonBox->addButton(updateButton, QDialogButtonBox::ActionRole); + +} + +void PluginDialog::populateTreeWidget() +{ + ui.treeWidget->clear(); + QDesignerPluginManager *pluginManager = m_core->pluginManager(); + const QStringList fileNames = pluginManager->registeredPlugins(); + + if (!fileNames.isEmpty()) { + QTreeWidgetItem *topLevelItem = setTopLevelItem(tr("Loaded Plugins")); + QFont boldFont = topLevelItem->font(0); + + for (const QString &fileName : fileNames) { + QPluginLoader loader(fileName); + const QFileInfo fileInfo(fileName); + + QTreeWidgetItem *pluginItem = setPluginItem(topLevelItem, fileInfo, boldFont); + + if (QObject *plugin = loader.instance()) { + if (const QDesignerCustomWidgetCollectionInterface *c = qobject_cast(plugin)) { + const auto &collCustomWidgets = c->customWidgets(); + for (const QDesignerCustomWidgetInterface *p : collCustomWidgets) + setItem(pluginItem, p->name(), p->toolTip(), p->whatsThis(), p->icon()); + } else { + if (const QDesignerCustomWidgetInterface *p = qobject_cast(plugin)) + setItem(pluginItem, p->name(), p->toolTip(), p->whatsThis(), p->icon()); + } + } + } + } + + const QStringList notLoadedPlugins = pluginManager->failedPlugins(); + if (!notLoadedPlugins.isEmpty()) { + QTreeWidgetItem *topLevelItem = setTopLevelItem(tr("Failed Plugins")); + const QFont boldFont = topLevelItem->font(0); + for (const QString &plugin : notLoadedPlugins) { + const QString failureReason = pluginManager->failureReason(plugin); + const QString htmlFailureReason = "

    "_L1 + + failureReason.toHtmlEscaped() + + "

    "_L1; + QTreeWidgetItem *pluginItem = setPluginItem(topLevelItem, QFileInfo(plugin), boldFont); + auto errorItem = setItem(pluginItem, failureReason, + htmlFailureReason, QString(), QIcon()); + errorItem->setData(0, ErrorItemRole, QVariant(true)); + } + } + + if (ui.treeWidget->topLevelItemCount() == 0) { + ui.label->setText(tr("Qt Widgets Designer couldn't find any plugins")); + ui.treeWidget->hide(); + } else { + ui.label->setText(tr("Qt Widgets Designer found the following plugins")); + } +} + +QTreeWidgetItem* PluginDialog::setTopLevelItem(const QString &itemName) +{ + QTreeWidgetItem *topLevelItem = new QTreeWidgetItem(ui.treeWidget); + topLevelItem->setText(0, itemName); + topLevelItem->setExpanded(true); + topLevelItem->setIcon(0, style()->standardPixmap(QStyle::SP_DirOpenIcon)); + + QFont boldFont = topLevelItem->font(0); + boldFont.setBold(true); + topLevelItem->setFont(0, boldFont); + + return topLevelItem; +} + +QTreeWidgetItem* PluginDialog::setPluginItem(QTreeWidgetItem *topLevelItem, + const QFileInfo &file, const QFont &font) +{ + QTreeWidgetItem *pluginItem = new QTreeWidgetItem(topLevelItem); + QString toolTip = QDir::toNativeSeparators(file.absoluteFilePath()); + if (file.exists()) + toolTip += u'\n' + file.lastModified().toString(); + pluginItem->setFont(0, font); + pluginItem->setText(0, file.fileName()); + pluginItem->setToolTip(0, toolTip); + pluginItem->setExpanded(true); + pluginItem->setIcon(0, style()->standardPixmap(QStyle::SP_DirOpenIcon)); + + return pluginItem; +} + +QTreeWidgetItem *PluginDialog::setItem(QTreeWidgetItem *pluginItem, const QString &name, + const QString &toolTip, const QString &whatsThis, + const QIcon &icon) +{ + QTreeWidgetItem *item = new QTreeWidgetItem(pluginItem); + item->setText(0, name); + item->setToolTip(0, toolTip); + item->setWhatsThis(0, whatsThis); + item->setIcon(0, icon.isNull() ? qtLogoIcon() : icon); + return item; +} + +void PluginDialog::updateCustomWidgetPlugins() +{ + const int before = m_core->widgetDataBase()->count(); + m_core->integration()->updateCustomWidgetPlugins(); + const int after = m_core->widgetDataBase()->count(); + if (after > before) { + ui.message->setText(tr("New custom widget plugins have been found.")); + ui.message->show(); + } else { + ui.message->setText(QString()); + } + populateTreeWidget(); +} + +void PluginDialog::treeWidgetContextMenu(const QPoint &pos) +{ +#if QT_CONFIG(clipboard) + const QTreeWidgetItem *item = ui.treeWidget->itemAt(pos); + if (item == nullptr || !item->data(0, ErrorItemRole).toBool()) + return; + QMenu menu; + //: Copy error text + auto copyAction = menu.addAction(tr("Copy")); + auto chosenAction = menu.exec(ui.treeWidget->mapToGlobal(pos)); + if (chosenAction == nullptr) + return; + if (chosenAction == copyAction) + QGuiApplication::clipboard()->setText(item->text(0)); +#else + Q_UNUSED(pos); +#endif +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "moc_plugindialog_p.cpp" diff --git a/src/designer/src/lib/shared/plugindialog.ui b/src/designer/src/lib/shared/plugindialog.ui new file mode 100644 index 0000000..39cf22c --- /dev/null +++ b/src/designer/src/lib/shared/plugindialog.ui @@ -0,0 +1,99 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + PluginDialog + + + + 0 + 0 + 401 + 331 + + + + Plugin Information + + + + 6 + + + 8 + + + + + TextLabel + + + true + + + + + + + Qt::ElideNone + + + + 1 + + + + + + + + TextLabel + + + true + + + + + + + 6 + + + 0 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + rejected() + PluginDialog + reject() + + + 154 + 307 + + + 401 + 280 + + + + + diff --git a/src/designer/src/lib/shared/plugindialog_p.h b/src/designer/src/lib/shared/plugindialog_p.h new file mode 100644 index 0000000..cbdad60 --- /dev/null +++ b/src/designer/src/lib/shared/plugindialog_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PLUGINDIALOG_H +#define PLUGINDIALOG_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "ui_plugindialog.h" + +QT_BEGIN_NAMESPACE + +class QFileInfo; + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class PluginDialog : public QDialog +{ + Q_OBJECT +public: + explicit PluginDialog(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + +private slots: + void updateCustomWidgetPlugins(); + void treeWidgetContextMenu(const QPoint &pos); + +private: + void populateTreeWidget(); + QTreeWidgetItem* setTopLevelItem(const QString &itemName); + QTreeWidgetItem* setPluginItem(QTreeWidgetItem *topLevelItem, + const QFileInfo &file, const QFont &font); + QTreeWidgetItem *setItem(QTreeWidgetItem *pluginItem, const QString &name, + const QString &toolTip, const QString &whatsThis, + const QIcon &icon); + + QDesignerFormEditorInterface *m_core; + QT_PREPEND_NAMESPACE(Ui)::PluginDialog ui; + QIcon interfaceIcon; + QIcon featureIcon; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/lib/shared/pluginmanager.cpp b/src/designer/src/lib/shared/pluginmanager.cpp new file mode 100644 index 0000000..cc52099 --- /dev/null +++ b/src/designer/src/lib/shared/pluginmanager.cpp @@ -0,0 +1,755 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "pluginmanager_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_qsettings_p.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace Qt::StringLiterals; + +static constexpr auto uiElementC = "ui"_L1; +static constexpr auto languageAttributeC = "language"_L1; +static constexpr auto widgetElementC = "widget"_L1; +static constexpr auto displayNameAttributeC = "displayname"_L1; +static constexpr auto classAttributeC = "class"_L1; +static constexpr auto customwidgetElementC = "customwidget"_L1; +static constexpr auto extendsElementC = "extends"_L1; +static constexpr auto addPageMethodC = "addpagemethod"_L1; +static constexpr auto propertySpecsC = "propertyspecifications"_L1; +static constexpr auto stringPropertySpecC = "stringpropertyspecification"_L1; +static constexpr auto propertyToolTipC = "tooltip"_L1; +static constexpr auto stringPropertyNameAttrC = "name"_L1; +static constexpr auto stringPropertyTypeAttrC = "type"_L1; +static constexpr auto stringPropertyNoTrAttrC = "notr"_L1; +static constexpr auto jambiLanguageC = "jambi"_L1; + +enum { debugPluginManager = 0 }; + +/* Custom widgets: Loading custom widgets is a 2-step process: PluginManager + * scans for its plugins in the constructor. At this point, it might not be safe + * to immediately initialize the custom widgets it finds, because the rest of + * Designer is not initialized yet. + * Later on, in ensureInitialized(), the plugin instances (including static ones) + * are iterated and the custom widget plugins are initialized and added to internal + * list of custom widgets and parsed data. Should there be a parse error or a language + * mismatch, it kicks out the respective custom widget. The m_initialized flag + * is used to indicate the state. + * Later, someone might call registerNewPlugins(), which agains clears the flag via + * registerPlugin() and triggers the process again. + * Also note that Jambi fakes a custom widget collection that changes its contents + * every time the project is switched. So, custom widget plugins can actually + * disappear, and the custom widget list must be cleared and refilled in + * ensureInitialized() after registerNewPlugins. */ + +QT_BEGIN_NAMESPACE + +static QStringList unique(const QStringList &lst) +{ + const QSet s(lst.cbegin(), lst.cend()); + return s.values(); +} + +QStringList QDesignerPluginManager::defaultPluginPaths() +{ + QStringList result; + + const QStringList path_list = QCoreApplication::libraryPaths(); + + for (const QString &path : path_list) + result.append(path + "/designer"_L1); + + result.append(qdesigner_internal::dataDirectory() + "/plugins"_L1); + return result; +} + +// Figure out the language designer is running. ToDo: Introduce some +// Language name API to QDesignerLanguageExtension? + +static inline QString getDesignerLanguage(QDesignerFormEditorInterface *core) +{ + if (QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) { + if (lang->uiExtension() == "jui"_L1) + return jambiLanguageC; + return u"unknown"_s; + } + return u"c++"_s; +} + +// ---------------- QDesignerCustomWidgetSharedData + +class QDesignerCustomWidgetSharedData : public QSharedData { +public: + // Type of a string property + using StringPropertyType = std::pair; + + explicit QDesignerCustomWidgetSharedData(const QString &thePluginPath) : pluginPath(thePluginPath) {} + void clearXML(); + + QString pluginPath; + + QString xmlClassName; + QString xmlDisplayName; + QString xmlLanguage; + QString xmlAddPageMethod; + QString xmlExtends; + + QHash xmlStringPropertyTypeMap; + QHash propertyToolTipMap; +}; + +void QDesignerCustomWidgetSharedData::clearXML() +{ + xmlClassName.clear(); + xmlDisplayName.clear(); + xmlLanguage.clear(); + xmlAddPageMethod.clear(); + xmlExtends.clear(); + xmlStringPropertyTypeMap.clear(); +} + +// ---------------- QDesignerCustomWidgetData + +QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QString &pluginPath) : + m_d(new QDesignerCustomWidgetSharedData(pluginPath)) +{ +} + +QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QDesignerCustomWidgetData &o) : + m_d(o.m_d) +{ +} + +QDesignerCustomWidgetData& QDesignerCustomWidgetData::operator=(const QDesignerCustomWidgetData &o) +{ + m_d.operator=(o.m_d); + return *this; +} + +QDesignerCustomWidgetData::~QDesignerCustomWidgetData() +{ +} + +bool QDesignerCustomWidgetData::isNull() const +{ + return m_d->xmlClassName.isEmpty() || m_d->pluginPath.isEmpty(); +} + +QString QDesignerCustomWidgetData::xmlClassName() const +{ + return m_d->xmlClassName; +} + +QString QDesignerCustomWidgetData::xmlLanguage() const +{ + return m_d->xmlLanguage; +} + +QString QDesignerCustomWidgetData::xmlAddPageMethod() const +{ + return m_d->xmlAddPageMethod; +} + +QString QDesignerCustomWidgetData::xmlExtends() const +{ + return m_d->xmlExtends; +} + +QString QDesignerCustomWidgetData::xmlDisplayName() const +{ + return m_d->xmlDisplayName; +} + +QString QDesignerCustomWidgetData::pluginPath() const +{ + return m_d->pluginPath; +} + +bool QDesignerCustomWidgetData::xmlStringPropertyType(const QString &name, StringPropertyType *type) const +{ + const auto it = m_d->xmlStringPropertyTypeMap.constFind(name); + if (it == m_d->xmlStringPropertyTypeMap.constEnd()) { + *type = StringPropertyType(qdesigner_internal::ValidationRichText, true); + return false; + } + *type = it.value(); + return true; +} + +QString QDesignerCustomWidgetData::propertyToolTip(const QString &name) const +{ + return m_d->propertyToolTipMap.value(name); +} + +// Wind a QXmlStreamReader until it finds an element. Returns index or one of FindResult +enum FindResult { FindError = -2, ElementNotFound = -1 }; + +static int findElement(const QStringList &desiredElts, QXmlStreamReader &sr) +{ + while (true) { + switch(sr.readNext()) { + case QXmlStreamReader::EndDocument: + return ElementNotFound; + case QXmlStreamReader::Invalid: + return FindError; + case QXmlStreamReader::StartElement: { + const int index = desiredElts.indexOf(sr.name().toString().toLower()); + if (index >= 0) + return index; + } + break; + default: + break; + } + } + return FindError; +} + +static inline QString msgXmlError(const QString &name, const QString &errorMessage) +{ + return QDesignerPluginManager::tr("An XML error was encountered when parsing the XML of the custom widget %1: %2").arg(name, errorMessage); +} + +static inline QString msgAttributeMissing(const QString &name) +{ + return QDesignerPluginManager::tr("A required attribute ('%1') is missing.").arg(name); +} + +static qdesigner_internal::TextPropertyValidationMode typeStringToType(const QString &v, bool *ok) +{ + *ok = true; + if (v == "multiline"_L1) + return qdesigner_internal::ValidationMultiLine; + if (v == "richtext"_L1) + return qdesigner_internal::ValidationRichText; + if (v == "stylesheet"_L1) + return qdesigner_internal::ValidationStyleSheet; + if (v == "singleline"_L1) + return qdesigner_internal::ValidationSingleLine; + if (v == "objectname"_L1) + return qdesigner_internal::ValidationObjectName; + if (v == "objectnamescope"_L1) + return qdesigner_internal::ValidationObjectNameScope; + if (v == "url"_L1) + return qdesigner_internal::ValidationURL; + *ok = false; + return qdesigner_internal::ValidationRichText; +} + +static bool parsePropertySpecs(QXmlStreamReader &sr, + QDesignerCustomWidgetSharedData *data, + QString *errorMessage) +{ + const QString propertySpecs = propertySpecsC; + const QString stringPropertySpec = stringPropertySpecC; + const QString propertyToolTip = propertyToolTipC; + const QString stringPropertyTypeAttr = stringPropertyTypeAttrC; + const QString stringPropertyNoTrAttr = stringPropertyNoTrAttrC; + const QString stringPropertyNameAttr = stringPropertyNameAttrC; + + while (!sr.atEnd()) { + switch(sr.readNext()) { + case QXmlStreamReader::StartElement: { + if (sr.name() == stringPropertySpec) { + const QXmlStreamAttributes atts = sr.attributes(); + const QString name = atts.value(stringPropertyNameAttr).toString(); + const QString type = atts.value(stringPropertyTypeAttr).toString(); + const QString notrS = atts.value(stringPropertyNoTrAttr).toString(); //Optional + + if (type.isEmpty()) { + *errorMessage = msgAttributeMissing(stringPropertyTypeAttr); + return false; + } + if (name.isEmpty()) { + *errorMessage = msgAttributeMissing(stringPropertyNameAttr); + return false; + } + bool typeOk; + const bool noTr = notrS == "true"_L1 || notrS == "1"_L1; + QDesignerCustomWidgetSharedData::StringPropertyType v(typeStringToType(type, &typeOk), !noTr); + if (!typeOk) { + *errorMessage = QDesignerPluginManager::tr("'%1' is not a valid string property specification.").arg(type); + return false; + } + data->xmlStringPropertyTypeMap.insert(name, v); + } else if (sr.name() == propertyToolTip) { + const QString name = sr.attributes().value(stringPropertyNameAttr).toString(); + if (name.isEmpty()) { + *errorMessage = msgAttributeMissing(stringPropertyNameAttr); + return false; + } + data->propertyToolTipMap.insert(name, sr.readElementText().trimmed()); + } else { + *errorMessage = QDesignerPluginManager::tr("An invalid property specification ('%1') was encountered. Supported types: %2").arg(sr.name().toString(), stringPropertySpec); + return false; + } + } + break; + case QXmlStreamReader::EndElement: // Outer + if (sr.name() == propertySpecs) + return true; + break; + default: + break; + } + } + return true; +} + +QDesignerCustomWidgetData::ParseResult + QDesignerCustomWidgetData::parseXml(const QString &xml, const QString &name, QString *errorMessage) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << name; + + QDesignerCustomWidgetSharedData &data = *m_d; + data.clearXML(); + + QXmlStreamReader sr(xml); + + bool foundUI = false; + bool foundWidget = false; + ParseResult rc = ParseOk; + // Parse for the (optional) or the first element + QStringList elements; + elements.push_back(uiElementC); + elements.push_back(widgetElementC); + for (int i = 0; i < 2 && !foundWidget; i++) { + switch (findElement(elements, sr)) { + case FindError: + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + case ElementNotFound: + *errorMessage = QDesignerPluginManager::tr("The XML of the custom widget %1 does not contain any of the elements or .").arg(name); + return ParseError; + case 0: { // + const QXmlStreamAttributes attributes = sr.attributes(); + data.xmlLanguage = attributes.value(languageAttributeC).toString(); + data.xmlDisplayName = attributes.value(displayNameAttributeC).toString(); + foundUI = true; + } + break; + case 1: // : Do some sanity checks + data.xmlClassName = sr.attributes().value(classAttributeC).toString(); + if (data.xmlClassName.isEmpty()) { + *errorMessage = QDesignerPluginManager::tr("The class attribute for the class %1 is missing.").arg(name); + rc = ParseWarning; + } else { + if (data.xmlClassName != name) { + *errorMessage = QDesignerPluginManager::tr("The class attribute for the class %1 does not match the class name %2.").arg(data.xmlClassName, name); + rc = ParseWarning; + } + } + foundWidget = true; + break; + } + } + // Parse out the element which might be present if was there + if (!foundUI) + return rc; + elements.clear(); + elements.push_back(customwidgetElementC); + switch (findElement(elements, sr)) { + case FindError: + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + case ElementNotFound: + return rc; + default: + break; + } + // Find , , + elements = {extendsElementC, addPageMethodC, propertySpecsC}; + while (true) { + switch (findElement(elements, sr)) { + case FindError: + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + case ElementNotFound: + return rc; + case 0: // + data.xmlExtends = sr.readElementText(); + if (sr.tokenType() != QXmlStreamReader::EndElement) { + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + } + break; + case 1: // + data.xmlAddPageMethod = sr.readElementText(); + if (sr.tokenType() != QXmlStreamReader::EndElement) { + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + } + break; + case 2: // + if (!parsePropertySpecs(sr, m_d.data(), errorMessage)) { + *errorMessage = msgXmlError(name, *errorMessage); + return ParseError; + } + break; + } + } + return rc; +} + +// ---------------- QDesignerPluginManagerPrivate + +class QDesignerPluginManagerPrivate { + public: + using ClassNamePropertyNameKey = std::pair; + + QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core); + + void clearCustomWidgets(); + bool addCustomWidget(QDesignerCustomWidgetInterface *c, + const QString &pluginPath, + const QString &designerLanguage); + void addCustomWidgets(QObject *o, + const QString &pluginPath, + const QString &designerLanguage); + + QDesignerFormEditorInterface *m_core; + QStringList m_pluginPaths; + QStringList m_registeredPlugins; + // TODO: QPluginLoader also caches invalid plugins -> This seems to be dead code + QStringList m_disabledPlugins; + + QMap m_failedPlugins; + + // Synced lists of custom widgets and their data. Note that the list + // must be ordered for collections to appear in order. + QList m_customWidgets; + QList m_customWidgetData; + + QStringList defaultPluginPaths() const; + + bool m_initialized; +}; + +QDesignerPluginManagerPrivate::QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core) : + m_core(core), + m_initialized(false) +{ +} + +void QDesignerPluginManagerPrivate::clearCustomWidgets() +{ + m_customWidgets.clear(); + m_customWidgetData.clear(); +} + +// Add a custom widget to the list if it parses correctly +// and is of the right language +bool QDesignerPluginManagerPrivate::addCustomWidget(QDesignerCustomWidgetInterface *c, + const QString &pluginPath, + const QString &designerLanguage) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << c->name(); + + if (!c->isInitialized()) + c->initialize(m_core); + // Parse the XML even if the plugin is initialized as Jambi might play tricks here + QDesignerCustomWidgetData data(pluginPath); + const QString domXml = c->domXml(); + if (!domXml.isEmpty()) { // Legacy: Empty XML means: Do not show up in widget box. + QString errorMessage; + const QDesignerCustomWidgetData::ParseResult pr = data.parseXml(domXml, c->name(), &errorMessage); + switch (pr) { + case QDesignerCustomWidgetData::ParseOk: + break; + case QDesignerCustomWidgetData::ParseWarning: + qdesigner_internal::designerWarning(errorMessage); + break; + case QDesignerCustomWidgetData::ParseError: + qdesigner_internal::designerWarning(errorMessage); + return false; + } + // Does the language match? + const QString pluginLanguage = data.xmlLanguage(); + if (!pluginLanguage.isEmpty() && pluginLanguage.compare(designerLanguage, Qt::CaseInsensitive)) + return false; + } + m_customWidgets.push_back(c); + m_customWidgetData.push_back(data); + return true; +} + +// Check the plugin interface for either a custom widget or a collection and +// add all contained custom widgets. +void QDesignerPluginManagerPrivate::addCustomWidgets(QObject *o, + const QString &pluginPath, + const QString &designerLanguage) +{ + if (QDesignerCustomWidgetInterface *c = qobject_cast(o)) { + addCustomWidget(c, pluginPath, designerLanguage); + return; + } + if (QDesignerCustomWidgetCollectionInterface *coll = qobject_cast(o)) { + const auto &collCustomWidgets = coll->customWidgets(); + for (QDesignerCustomWidgetInterface *c : collCustomWidgets) + addCustomWidget(c, pluginPath, designerLanguage); + } +} + + +// ---------------- QDesignerPluginManager +// As of 4.4, the header will be distributed with the Eclipse plugin. + +QDesignerPluginManager::QDesignerPluginManager(QDesignerFormEditorInterface *core) : + QDesignerPluginManager(QStringList{}, core) +{ +} + +QDesignerPluginManager::QDesignerPluginManager(const QStringList &pluginPaths, + QDesignerFormEditorInterface *core) : + QObject(core), + m_d(new QDesignerPluginManagerPrivate(core)) +{ + m_d->m_pluginPaths = pluginPaths.isEmpty() ? defaultPluginPaths() : pluginPaths; + const QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName()); + m_d->m_disabledPlugins = unique(settings.value("PluginManager/DisabledPlugins").toStringList()); + + // Register plugins + updateRegisteredPlugins(); + + if (debugPluginManager) + qDebug() << "QDesignerPluginManager::disabled: " << m_d->m_disabledPlugins << " static " << m_d->m_customWidgets.size(); +} + +QDesignerPluginManager::~QDesignerPluginManager() +{ + syncSettings(); + delete m_d; +} + +QDesignerFormEditorInterface *QDesignerPluginManager::core() const +{ + return m_d->m_core; +} + +QStringList QDesignerPluginManager::findPlugins(const QString &path) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << path; + const QDir dir(path); + if (!dir.exists()) + return QStringList(); + + const QFileInfoList infoList = dir.entryInfoList(QDir::Files); + if (infoList.isEmpty()) + return QStringList(); + + // Load symbolic links but make sure all file names are unique as not + // to fall for something like 'libplugin.so.1 -> libplugin.so' + QStringList result; + for (const auto &fi : infoList) { + QString fileName; + if (fi.isSymLink()) { + const QFileInfo linkTarget = QFileInfo(fi.symLinkTarget()); + if (linkTarget.exists() && linkTarget.isFile()) + fileName = linkTarget.absoluteFilePath(); + } else { + fileName = fi.absoluteFilePath(); + } + if (!fileName.isEmpty() && QLibrary::isLibrary(fileName) && !result.contains(fileName)) + result += fileName; + } + return result; +} + +void QDesignerPluginManager::setDisabledPlugins(const QStringList &disabled_plugins) +{ + m_d->m_disabledPlugins = disabled_plugins; + updateRegisteredPlugins(); +} + +void QDesignerPluginManager::setPluginPaths(const QStringList &plugin_paths) +{ + m_d->m_pluginPaths = plugin_paths; + updateRegisteredPlugins(); +} + +QStringList QDesignerPluginManager::disabledPlugins() const +{ + return m_d->m_disabledPlugins; +} + +QStringList QDesignerPluginManager::failedPlugins() const +{ + return m_d->m_failedPlugins.keys(); +} + +QString QDesignerPluginManager::failureReason(const QString &pluginName) const +{ + return m_d->m_failedPlugins.value(pluginName); +} + +QStringList QDesignerPluginManager::registeredPlugins() const +{ + return m_d->m_registeredPlugins; +} + +QStringList QDesignerPluginManager::pluginPaths() const +{ + return m_d->m_pluginPaths; +} + +QObject *QDesignerPluginManager::instance(const QString &plugin) const +{ + if (m_d->m_disabledPlugins.contains(plugin)) + return nullptr; + + QPluginLoader loader(plugin); + return loader.instance(); +} + +void QDesignerPluginManager::updateRegisteredPlugins() +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO; + m_d->m_registeredPlugins.clear(); + for (const QString &path : std::as_const(m_d->m_pluginPaths)) + registerPath(path); +} + +bool QDesignerPluginManager::registerNewPlugins() +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO; + + const int before = m_d->m_registeredPlugins.size(); + for (const QString &path : std::as_const(m_d->m_pluginPaths)) + registerPath(path); + const bool newPluginsFound = m_d->m_registeredPlugins.size() > before; + // We force a re-initialize as Jambi collection might return + // different widget lists when switching projects. + m_d->m_initialized = false; + ensureInitialized(); + + return newPluginsFound; +} + +void QDesignerPluginManager::registerPath(const QString &path) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << path; + const QStringList &candidates = findPlugins(path); + for (const QString &plugin : candidates) + registerPlugin(plugin); +} + +void QDesignerPluginManager::registerPlugin(const QString &plugin) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << plugin; + if (m_d->m_disabledPlugins.contains(plugin)) + return; + if (m_d->m_registeredPlugins.contains(plugin)) + return; + + QPluginLoader loader(plugin); + if (loader.isLoaded() || loader.load()) { + m_d->m_registeredPlugins += plugin; + const auto fit = m_d->m_failedPlugins.find(plugin); + if (fit != m_d->m_failedPlugins.end()) + m_d->m_failedPlugins.erase(fit); + return; + } + + const QString errorMessage = loader.errorString(); + m_d->m_failedPlugins.insert(plugin, errorMessage); +} + + + +bool QDesignerPluginManager::syncSettings() +{ + QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName()); + settings.beginGroup("PluginManager"); + settings.setValue("DisabledPlugins", m_d->m_disabledPlugins); + settings.endGroup(); + return settings.status() == QSettings::NoError; +} + +void QDesignerPluginManager::ensureInitialized() +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << m_d->m_initialized << m_d->m_customWidgets.size(); + + if (m_d->m_initialized) + return; + + const QString designerLanguage = getDesignerLanguage(m_d->m_core); + + m_d->clearCustomWidgets(); + // Add the static custom widgets + const QObjectList staticPluginObjects = QPluginLoader::staticInstances(); + if (!staticPluginObjects.isEmpty()) { + const QString staticPluginPath = QCoreApplication::applicationFilePath(); + for (QObject *o : staticPluginObjects) + m_d->addCustomWidgets(o, staticPluginPath, designerLanguage); + } + for (const QString &plugin : std::as_const(m_d->m_registeredPlugins)) { + if (QObject *o = instance(plugin)) + m_d->addCustomWidgets(o, plugin, designerLanguage); + } + + m_d->m_initialized = true; +} + +QDesignerPluginManager::CustomWidgetList QDesignerPluginManager::registeredCustomWidgets() const +{ + const_cast(this)->ensureInitialized(); + return m_d->m_customWidgets; +} + +QDesignerCustomWidgetData QDesignerPluginManager::customWidgetData(QDesignerCustomWidgetInterface *w) const +{ + const int index = m_d->m_customWidgets.indexOf(w); + if (index == -1) + return QDesignerCustomWidgetData(); + return m_d->m_customWidgetData.at(index); +} + +QDesignerCustomWidgetData QDesignerPluginManager::customWidgetData(const QString &name) const +{ + for (qsizetype i = 0, count = m_d->m_customWidgets.size(); i < count; ++i) + if (m_d->m_customWidgets.at(i)->name() == name) + return m_d->m_customWidgetData.at(i); + return QDesignerCustomWidgetData(); +} + +QObjectList QDesignerPluginManager::instances() const +{ + const QStringList &plugins = registeredPlugins(); + + QObjectList lst; + for (const QString &plugin : plugins) { + if (QObject *o = instance(plugin)) + lst.append(o); + } + + return lst; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/pluginmanager_p.h b/src/designer/src/lib/shared/pluginmanager_p.h new file mode 100644 index 0000000..be0f4bf --- /dev/null +++ b/src/designer/src/lib/shared/pluginmanager_p.h @@ -0,0 +1,126 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include "shared_global_p.h" +#include "shared_enums_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerCustomWidgetInterface; +class QDesignerPluginManagerPrivate; + +class QDesignerCustomWidgetSharedData; + +/* Information contained in the Dom XML of a custom widget. */ +class QDESIGNER_SHARED_EXPORT QDesignerCustomWidgetData { +public: + // StringPropertyType: validation mode and translatable flag. + using StringPropertyType = std::pair; + + explicit QDesignerCustomWidgetData(const QString &pluginPath = QString()); + + enum ParseResult { ParseOk, ParseWarning, ParseError }; + ParseResult parseXml(const QString &xml, const QString &name, QString *errorMessage); + + QDesignerCustomWidgetData(const QDesignerCustomWidgetData&); + QDesignerCustomWidgetData& operator=(const QDesignerCustomWidgetData&); + ~QDesignerCustomWidgetData(); + + bool isNull() const; + + QString pluginPath() const; + + // Data as parsed from the widget's domXML(). + QString xmlClassName() const; + // Optional. The language the plugin is supposed to be used with. + QString xmlLanguage() const; + // Optional. method used to add pages to a container with a container extension + QString xmlAddPageMethod() const; + // Optional. Base class + QString xmlExtends() const; + // Optional. The name to be used in the widget box. + QString xmlDisplayName() const; + // Type of a string property + bool xmlStringPropertyType(const QString &name, StringPropertyType *type) const; + // Custom tool tip of property + QString propertyToolTip(const QString &name) const; + +private: + QSharedDataPointer m_d; +}; + +class QDESIGNER_SHARED_EXPORT QDesignerPluginManager: public QObject +{ + Q_OBJECT +public: + using CustomWidgetList = QList; + + explicit QDesignerPluginManager(QDesignerFormEditorInterface *core); + explicit QDesignerPluginManager(const QStringList &pluginPaths, + QDesignerFormEditorInterface *core); + ~QDesignerPluginManager() override; + + QDesignerFormEditorInterface *core() const; + + QObject *instance(const QString &plugin) const; + + QStringList registeredPlugins() const; + + QStringList findPlugins(const QString &path); + + QStringList pluginPaths() const; + void setPluginPaths(const QStringList &plugin_paths); + + QStringList disabledPlugins() const; + void setDisabledPlugins(const QStringList &disabled_plugins); + + QStringList failedPlugins() const; + QString failureReason(const QString &pluginName) const; + + QObjectList instances() const; + + CustomWidgetList registeredCustomWidgets() const; + QDesignerCustomWidgetData customWidgetData(QDesignerCustomWidgetInterface *w) const; + QDesignerCustomWidgetData customWidgetData(const QString &className) const; + + bool registerNewPlugins(); + + static QStringList defaultPluginPaths(); + +public slots: + bool syncSettings(); + void ensureInitialized(); + +private: + void updateRegisteredPlugins(); + void registerPath(const QString &path); + void registerPlugin(const QString &plugin); + +private: + QDesignerPluginManagerPrivate *m_d; +}; + +QT_END_NAMESPACE + +#endif // PLUGINMANAGER_H diff --git a/src/designer/src/lib/shared/previewconfigurationwidget.cpp b/src/designer/src/lib/shared/previewconfigurationwidget.cpp new file mode 100644 index 0000000..5d24ddf --- /dev/null +++ b/src/designer/src/lib/shared/previewconfigurationwidget.cpp @@ -0,0 +1,323 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "previewconfigurationwidget_p.h" +#include "ui_previewconfigurationwidget.h" +#include "previewmanager_p.h" +#include "shared_settings_p.h" + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto skinResourcePathC = ":/skins/"_L1; +static constexpr auto skinExtensionC = "skin"_L1; + +// Pair of skin name, path +using SkinNamePath = std::pair; +using Skins = QList; +enum { SkinComboNoneIndex = 0 }; + +// find default skins (resources) +static const Skins &defaultSkins() { + static Skins rc; + if (rc.isEmpty()) { + const QDir dir(skinResourcePathC, "*."_L1 + skinExtensionC); + const QFileInfoList list = dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot, QDir::Name); + if (list.isEmpty()) + return rc; + for (const auto &fi : list) + rc.append(SkinNamePath(fi.baseName(), fi.filePath())); + } + return rc; +} + +namespace qdesigner_internal { + +// ------------- PreviewConfigurationWidgetPrivate +class PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate { +public: + PreviewConfigurationWidgetPrivate(QDesignerFormEditorInterface *core, QGroupBox *g); + + void slotEditAppStyleSheet(); + void slotDeleteSkinEntry(); + void slotSkinChanged(int index); + + void retrieveSettings(); + void storeSettings() const; + + QAbstractButton *appStyleSheetChangeButton() const { return m_ui.m_appStyleSheetChangeButton; } + QAbstractButton *skinRemoveButton() const { return m_ui.m_skinRemoveButton; } + QComboBox *skinCombo() const { return m_ui.m_skinCombo; } + + QDesignerFormEditorInterface *m_core; + +private: + PreviewConfiguration previewConfiguration() const; + void setPreviewConfiguration(const PreviewConfiguration &pc); + + QStringList userSkins() const; + void addUserSkins(const QStringList &files); + bool canRemoveSkin(int index) const; + int browseSkin(); + + const QString m_defaultStyle; + QGroupBox *m_parent; + QT_PREPEND_NAMESPACE(Ui)::PreviewConfigurationWidget m_ui; + + int m_firstUserSkinIndex; + int m_browseSkinIndex; + int m_lastSkinIndex; // required in case browse fails +}; + +PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::PreviewConfigurationWidgetPrivate( + QDesignerFormEditorInterface *core, QGroupBox *g) : + m_core(core), + m_defaultStyle(PreviewConfigurationWidget::tr("Default")), + m_parent(g), + m_firstUserSkinIndex(0), + m_browseSkinIndex(0), + m_lastSkinIndex(0) +{ + m_ui.setupUi(g); + // styles + m_ui.m_styleCombo->setEditable(false); + QStringList styleItems(m_defaultStyle); + styleItems += QStyleFactory::keys(); + m_ui.m_styleCombo->addItems(styleItems); + + // sheet + m_ui.m_appStyleSheetLineEdit->setTextPropertyValidationMode(qdesigner_internal::ValidationStyleSheet); + m_ui.m_appStyleSheetClearButton->setIcon(qdesigner_internal::createIconSet("resetproperty.png"_L1)); + QObject::connect(m_ui.m_appStyleSheetClearButton, &QAbstractButton::clicked, + m_ui.m_appStyleSheetLineEdit, &qdesigner_internal::TextPropertyEditor::clear); + + m_ui.m_skinRemoveButton->setIcon(qdesigner_internal::createIconSet(QIcon::ThemeIcon::EditDelete, + "editdelete.png"_L1)); + // skins: find default skins (resources) + m_ui.m_skinRemoveButton->setEnabled(false); + Skins skins = defaultSkins(); + skins.push_front(SkinNamePath(PreviewConfigurationWidget::tr("None"), QString())); + + for (const auto &skin : std::as_const(skins)) + m_ui.m_skinCombo->addItem(skin.first, QVariant(skin.second)); + m_browseSkinIndex = m_firstUserSkinIndex = skins.size(); + m_ui.m_skinCombo->addItem(PreviewConfigurationWidget::tr("Browse..."), QString()); + + m_ui.m_skinCombo->setMaxVisibleItems (qMax(15, 2 * m_browseSkinIndex)); + m_ui.m_skinCombo->setEditable(false); + + retrieveSettings(); +} + +bool PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::canRemoveSkin(int index) const +{ + return index >= m_firstUserSkinIndex && index != m_browseSkinIndex; +} + +QStringList PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::userSkins() const +{ + QStringList rc; + for (int i = m_firstUserSkinIndex; i < m_browseSkinIndex; i++) + rc.push_back(m_ui.m_skinCombo->itemData(i).toString()); + return rc; +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::addUserSkins(const QStringList &files) +{ + if (files.isEmpty()) + return; + for (const auto &f : files) { + const QFileInfo fi(f); + if (fi.isDir() && fi.isReadable()) + m_ui.m_skinCombo->insertItem(m_browseSkinIndex++, fi.baseName(), QVariant(f)); + else + qWarning() << "Unable to access the skin directory '" << f << "'."; + } +} + +PreviewConfiguration PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::previewConfiguration() const +{ + PreviewConfiguration rc; + QString style = m_ui.m_styleCombo->currentText(); + if (style == m_defaultStyle) + style.clear(); + const QString applicationStyleSheet = m_ui.m_appStyleSheetLineEdit->text(); + // Figure out skin. 0 is None by definition.. + const int skinIndex = m_ui.m_skinCombo->currentIndex(); + QString deviceSkin; + if (skinIndex != SkinComboNoneIndex && skinIndex != m_browseSkinIndex) + deviceSkin = m_ui.m_skinCombo->itemData(skinIndex).toString(); + + return PreviewConfiguration(style, applicationStyleSheet, deviceSkin); +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::setPreviewConfiguration(const PreviewConfiguration &pc) +{ + int styleIndex = m_ui.m_styleCombo->findText(pc.style()); + if (styleIndex == -1) + styleIndex = m_ui.m_styleCombo->findText(m_defaultStyle); + m_ui.m_styleCombo->setCurrentIndex(styleIndex); + m_ui.m_appStyleSheetLineEdit->setText(pc.applicationStyleSheet()); + // find skin by file name. 0 is "none" + const QString deviceSkin = pc.deviceSkin(); + int skinIndex = deviceSkin.isEmpty() ? 0 : m_ui.m_skinCombo->findData(QVariant(deviceSkin)); + if (skinIndex == -1) { + qWarning() << "Unable to find skin '" << deviceSkin << "'."; + skinIndex = 0; + } + m_ui.m_skinCombo->setCurrentIndex(skinIndex); +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotEditAppStyleSheet() +{ + StyleSheetEditorDialog dlg(m_core, m_parent, StyleSheetEditorDialog::ModeGlobal); + dlg.setText(m_ui.m_appStyleSheetLineEdit->text()); + if (dlg.exec() == QDialog::Accepted) + m_ui.m_appStyleSheetLineEdit->setText(dlg.text()); +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotDeleteSkinEntry() +{ + const int index = m_ui.m_skinCombo->currentIndex(); + if (canRemoveSkin(index)) { + m_ui.m_skinCombo->setCurrentIndex(SkinComboNoneIndex); + m_ui.m_skinCombo->removeItem(index); + m_browseSkinIndex--; + } +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotSkinChanged(int index) +{ + if (index == m_browseSkinIndex) { + m_ui.m_skinCombo->setCurrentIndex(browseSkin()); + } else { + m_lastSkinIndex = index; + m_ui.m_skinRemoveButton->setEnabled(canRemoveSkin(index)); + m_ui.m_skinCombo->setToolTip(index != SkinComboNoneIndex ? m_ui.m_skinCombo->itemData(index).toString() : QString()); + } +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::retrieveSettings() +{ + QDesignerSharedSettings settings(m_core); + m_parent->setChecked(settings.isCustomPreviewConfigurationEnabled()); + setPreviewConfiguration(settings.customPreviewConfiguration()); + addUserSkins(settings.userDeviceSkins()); +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::storeSettings() const +{ + QDesignerSharedSettings settings(m_core); + settings.setCustomPreviewConfigurationEnabled(m_parent->isChecked()); + settings.setCustomPreviewConfiguration(previewConfiguration()); + settings.setUserDeviceSkins(userSkins()); +} + +int PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::browseSkin() +{ + QFileDialog dlg(m_parent); + dlg.setFileMode(QFileDialog::Directory); + dlg.setOption(QFileDialog::ShowDirsOnly); + const QString title = tr("Load Custom Device Skin"); + dlg.setWindowTitle(title); + dlg.setNameFilter(tr("All QVFB Skins (*.%1)").arg(skinExtensionC)); + + int rc = m_lastSkinIndex; + do { + if (!dlg.exec()) + break; + + const QStringList directories = dlg.selectedFiles(); + if (directories.size() != 1) + break; + + // check: 1) name already there + const QString directory = directories.constFirst(); + const QString name = QFileInfo(directory).baseName(); + const int existingIndex = m_ui.m_skinCombo->findText(name); + if (existingIndex != -1 && existingIndex != SkinComboNoneIndex && existingIndex != m_browseSkinIndex) { + const QString msgTitle = tr("%1 - Duplicate Skin").arg(title); + const QString msg = tr("The skin '%1' already exists.").arg(name); + QMessageBox::information(m_parent, msgTitle, msg); + break; + } + // check: 2) can read + DeviceSkinParameters parameters; + QString readError; + if (parameters.read(directory, DeviceSkinParameters::ReadSizeOnly, &readError)) { + const QString name = QFileInfo(directory).baseName(); + m_ui.m_skinCombo->insertItem(m_browseSkinIndex, name, QVariant(directory)); + rc = m_browseSkinIndex++; + + break; + } + const QString msgTitle = tr("%1 - Error").arg(title); + const QString msg = tr("%1 is not a valid skin directory:\n%2") + .arg(directory, readError); + QMessageBox::warning (m_parent, msgTitle, msg); + } while (true); + return rc; +} + +// ------------- PreviewConfigurationWidget +PreviewConfigurationWidget::PreviewConfigurationWidget(QDesignerFormEditorInterface *core, + QWidget *parent) : + QGroupBox(parent), + m_impl(new PreviewConfigurationWidgetPrivate(core, this)) +{ + connect(m_impl->appStyleSheetChangeButton(), &QAbstractButton::clicked, + this, &PreviewConfigurationWidget::slotEditAppStyleSheet); + connect(m_impl->skinRemoveButton(), &QAbstractButton::clicked, + this, &PreviewConfigurationWidget::slotDeleteSkinEntry); + connect(m_impl->skinCombo(), &QComboBox::currentIndexChanged, + this, &PreviewConfigurationWidget::slotSkinChanged); + + m_impl->retrieveSettings(); +} + +PreviewConfigurationWidget::~PreviewConfigurationWidget() +{ + delete m_impl; +} + +void PreviewConfigurationWidget::saveState() +{ + m_impl->storeSettings(); +} + +void PreviewConfigurationWidget::slotEditAppStyleSheet() +{ + m_impl->slotEditAppStyleSheet(); +} + +void PreviewConfigurationWidget::slotDeleteSkinEntry() +{ + m_impl->slotDeleteSkinEntry(); +} + +void PreviewConfigurationWidget::slotSkinChanged(int index) +{ + m_impl->slotSkinChanged(index); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/previewconfigurationwidget.ui b/src/designer/src/lib/shared/previewconfigurationwidget.ui new file mode 100644 index 0000000..2f18766 --- /dev/null +++ b/src/designer/src/lib/shared/previewconfigurationwidget.ui @@ -0,0 +1,91 @@ + + PreviewConfigurationWidget + + + Form + + + Print/Preview Configuration + + + true + + + + + + Style + + + + + + + + + + Style sheet + + + + + + + + + + 149 + 0 + + + + + + + + ... + + + + + + + ... + + + + + + + + + Device skin + + + + + + + + + + + + ... + + + + + + + + + + qdesigner_internal::TextPropertyEditor + QLineEdit +
    textpropertyeditor_p.h
    +
    +
    + + +
    diff --git a/src/designer/src/lib/shared/previewconfigurationwidget_p.h b/src/designer/src/lib/shared/previewconfigurationwidget_p.h new file mode 100644 index 0000000..ec5e2be --- /dev/null +++ b/src/designer/src/lib/shared/previewconfigurationwidget_p.h @@ -0,0 +1,58 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PREVIEWCONFIGURATIONWIDGET_H +#define PREVIEWCONFIGURATIONWIDGET_H + +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerSettingsInterface; + +namespace qdesigner_internal { + +// ----------- PreviewConfigurationWidget: Widget to edit the preview configuration. + +class QDESIGNER_SHARED_EXPORT PreviewConfigurationWidget : public QGroupBox +{ + Q_OBJECT +public: + explicit PreviewConfigurationWidget(QDesignerFormEditorInterface *core, + QWidget *parent = nullptr); + ~PreviewConfigurationWidget() override; + void saveState(); + +private slots: + void slotEditAppStyleSheet(); + void slotDeleteSkinEntry(); + void slotSkinChanged(int); + +private: + class PreviewConfigurationWidgetPrivate; + PreviewConfigurationWidgetPrivate *m_impl; + + PreviewConfigurationWidget(const PreviewConfigurationWidget &other); + PreviewConfigurationWidget &operator =(const PreviewConfigurationWidget &other); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PREVIEWCONFIGURATIONWIDGET_H diff --git a/src/designer/src/lib/shared/previewmanager.cpp b/src/designer/src/lib/shared/previewmanager.cpp new file mode 100644 index 0000000..96c9287 --- /dev/null +++ b/src/designer/src/lib/shared/previewmanager.cpp @@ -0,0 +1,900 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindowbase_p.h" +#include "previewmanager_p.h" +#include "qdesigner_formbuilder_p.h" +#include "shared_settings_p.h" +#include "widgetfactory_p.h" +#include "zoomwidget_p.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static inline int compare(const qdesigner_internal::PreviewConfiguration &pc1, const qdesigner_internal::PreviewConfiguration &pc2) +{ + int rc = pc1.style().compare(pc2.style()); + if (rc) + return rc; + rc = pc1.applicationStyleSheet().compare(pc2.applicationStyleSheet()); + if (rc) + return rc; + return pc1.deviceSkin().compare(pc2.deviceSkin()); +} + +namespace qdesigner_internal { + // ------ PreviewData (data associated with a preview window) + struct PreviewData { + PreviewData(const QPointer &widget, const QDesignerFormWindowInterface *formWindow, const qdesigner_internal::PreviewConfiguration &pc); + QPointer m_widget; + const QDesignerFormWindowInterface *m_formWindow; + qdesigner_internal::PreviewConfiguration m_configuration; + }; + + PreviewData::PreviewData(const QPointer& widget, + const QDesignerFormWindowInterface *formWindow, + const qdesigner_internal::PreviewConfiguration &pc) : + m_widget(widget), + m_formWindow(formWindow), + m_configuration(pc) + { + } + +/* In designer, we have the situation that laid-out maincontainers have + * a geometry set (which might differ from their sizeHint()). The QGraphicsItem + * should return that in its size hint, else such cases won't work */ + +class DesignerZoomProxyWidget : public ZoomProxyWidget { + Q_DISABLE_COPY_MOVE(DesignerZoomProxyWidget) +public: + DesignerZoomProxyWidget(QGraphicsItem *parent = nullptr, Qt::WindowFlags wFlags = {}); +protected: + QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF() ) const override; +}; + +DesignerZoomProxyWidget::DesignerZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) : + ZoomProxyWidget(parent, wFlags) +{ +} + +QSizeF DesignerZoomProxyWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const +{ + if (const QWidget *w = widget()) + return QSizeF(w->size()); + return ZoomProxyWidget::sizeHint(which, constraint); +} + +// DesignerZoomWidget which returns DesignerZoomProxyWidget in its factory function +class DesignerZoomWidget : public ZoomWidget { + Q_DISABLE_COPY_MOVE(DesignerZoomWidget) +public: + DesignerZoomWidget(QWidget *parent = nullptr); +private: + QGraphicsProxyWidget *createProxyWidget(QGraphicsItem *parent = nullptr, + Qt::WindowFlags wFlags = {}) const override; +}; + +DesignerZoomWidget::DesignerZoomWidget(QWidget *parent) : + ZoomWidget(parent) +{ +} + +QGraphicsProxyWidget *DesignerZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const +{ + return new DesignerZoomProxyWidget(parent, wFlags); +} + +// PreviewDeviceSkin: Forwards the key events to the window and +// provides context menu with rotation options. Derived class +// can apply additional transformations to the skin. + +class PreviewDeviceSkin : public DeviceSkin +{ + Q_OBJECT +public: + enum Direction { DirectionUp, DirectionLeft, DirectionRight }; + + explicit PreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent); + virtual void setPreview(QWidget *w); + QSize screenSize() const { return m_screenSize; } + +private slots: + void slotSkinKeyPressEvent(int code, const QString& text, bool autorep); + void slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep); + void slotPopupMenu(); + +protected: + virtual void populateContextMenu(QMenu *) {} + +private slots: + void slotDirection(QAction *); + +protected: + // Fit the widget in case the orientation changes (transposing screensize) + virtual void fitWidget(const QSize &size); + // Calculate the complete transformation for the skin + // (base class implementation provides rotation). + virtual QTransform skinTransform() const; + +private: + const QSize m_screenSize; + Direction m_direction; + + QAction *m_directionUpAction; + QAction *m_directionLeftAction; + QAction *m_directionRightAction; + QAction *m_closeAction; +}; + +PreviewDeviceSkin::PreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent) : + DeviceSkin(parameters, parent), + m_screenSize(parameters.screenSize()), + m_direction(DirectionUp), + m_directionUpAction(nullptr), + m_directionLeftAction(nullptr), + m_directionRightAction(nullptr), + m_closeAction(nullptr) +{ + connect(this, &PreviewDeviceSkin::skinKeyPressEvent, + this, &PreviewDeviceSkin::slotSkinKeyPressEvent); + connect(this, &PreviewDeviceSkin::skinKeyReleaseEvent, + this, &PreviewDeviceSkin::slotSkinKeyReleaseEvent); + connect(this, &PreviewDeviceSkin::popupMenu, this, &PreviewDeviceSkin::slotPopupMenu); +} + +void PreviewDeviceSkin::setPreview(QWidget *formWidget) +{ + formWidget->setFixedSize(m_screenSize); + formWidget->setParent(this, Qt::SubWindow); + formWidget->setAutoFillBackground(true); + setView(formWidget); +} + +void PreviewDeviceSkin::slotSkinKeyPressEvent(int code, const QString& text, bool autorep) +{ + if (QWidget *focusWidget = QApplication::focusWidget()) { + QKeyEvent e(QEvent::KeyPress, code, {}, text, autorep); + QApplication::sendEvent(focusWidget, &e); + } +} + +void PreviewDeviceSkin::slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep) +{ + if (QWidget *focusWidget = QApplication::focusWidget()) { + QKeyEvent e(QEvent::KeyRelease, code, {}, text, autorep); + QApplication::sendEvent(focusWidget, &e); + } +} + +// Create a checkable action with integer data and +// set it checked if it matches the currentState. +static inline QAction + *createCheckableActionIntData(const QString &label, + int actionValue, int currentState, + QActionGroup *ag, QObject *parent) +{ + QAction *a = new QAction(label, parent); + a->setData(actionValue); + a->setCheckable(true); + if (actionValue == currentState) + a->setChecked(true); + ag->addAction(a); + return a; +} + +void PreviewDeviceSkin::slotPopupMenu() +{ + QMenu menu(this); + // Create actions + if (!m_directionUpAction) { + QActionGroup *directionGroup = new QActionGroup(this); + connect(directionGroup, &QActionGroup::triggered, this, &PreviewDeviceSkin::slotDirection); + directionGroup->setExclusive(true); + m_directionUpAction = createCheckableActionIntData(tr("&Portrait"), DirectionUp, m_direction, directionGroup, this); + //: Rotate form preview counter-clockwise + m_directionLeftAction = createCheckableActionIntData(tr("Landscape (&CCW)"), DirectionLeft, m_direction, directionGroup, this); + //: Rotate form preview clockwise + m_directionRightAction = createCheckableActionIntData(tr("&Landscape (CW)"), DirectionRight, m_direction, directionGroup, this); + m_closeAction = new QAction(tr("&Close"), this); + connect(m_closeAction, &QAction::triggered, parentWidget(), &QWidget::close); + } + menu.addAction(m_directionUpAction); + menu.addAction(m_directionLeftAction); + menu.addAction(m_directionRightAction); + menu.addSeparator(); + populateContextMenu(&menu); + menu.addAction(m_closeAction); + menu.exec(QCursor::pos()); +} + +void PreviewDeviceSkin::slotDirection(QAction *a) +{ + const Direction newDirection = static_cast(a->data().toInt()); + if (m_direction == newDirection) + return; + const Qt::Orientation newOrientation = newDirection == DirectionUp ? Qt::Vertical : Qt::Horizontal; + const Qt::Orientation oldOrientation = m_direction == DirectionUp ? Qt::Vertical : Qt::Horizontal; + m_direction = newDirection; + QApplication::setOverrideCursor(Qt::WaitCursor); + if (oldOrientation != newOrientation) { + QSize size = screenSize(); + if (newOrientation == Qt::Horizontal) + size.transpose(); + fitWidget(size); + } + setTransform(skinTransform()); + QApplication::restoreOverrideCursor(); +} + +void PreviewDeviceSkin::fitWidget(const QSize &size) +{ + view()->setFixedSize(size); +} + +QTransform PreviewDeviceSkin::skinTransform() const +{ + QTransform newTransform; + switch (m_direction) { + case DirectionUp: + break; + case DirectionLeft: + newTransform.rotate(270.0); + break; + case DirectionRight: + newTransform.rotate(90.0); + break; + } + return newTransform; +} + +// ------------ PreviewConfigurationPrivate +class PreviewConfigurationData : public QSharedData { +public: + PreviewConfigurationData() = default; + explicit PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin); + + QString m_style; + // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()). + QString m_applicationStyleSheet; + QString m_deviceSkin; +}; + +PreviewConfigurationData::PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin) : + m_style(style), + m_applicationStyleSheet(applicationStyleSheet), + m_deviceSkin(deviceSkin) +{ +} + +/* ZoomablePreviewDeviceSkin: A Zoomable Widget Preview skin. Embeds preview + * into a ZoomWidget and this in turn into the DeviceSkin view and keeps + * Device skin zoom + ZoomWidget zoom in sync. */ + +class ZoomablePreviewDeviceSkin : public PreviewDeviceSkin +{ + Q_OBJECT +public: + explicit ZoomablePreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent); + void setPreview(QWidget *w) override; + + int zoomPercent() const; // Device Skins have a double 'zoom' property + +public slots: + void setZoomPercent(int); + +signals: + void zoomPercentChanged(int); + +protected: + void populateContextMenu(QMenu *m) override; + QTransform skinTransform() const override; + void fitWidget(const QSize &size) override; + +private: + ZoomMenu *m_zoomMenu; + QAction *m_zoomSubMenuAction; + ZoomWidget *m_zoomWidget; +}; + +ZoomablePreviewDeviceSkin::ZoomablePreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent) : + PreviewDeviceSkin(parameters, parent), + m_zoomMenu(new ZoomMenu(this)), + m_zoomSubMenuAction(nullptr), + m_zoomWidget(new DesignerZoomWidget) +{ + connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::setZoomPercent); + connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::zoomPercentChanged); + m_zoomWidget->setZoomContextMenuEnabled(false); + m_zoomWidget->setWidgetZoomContextMenuEnabled(false); + m_zoomWidget->resize(screenSize()); + m_zoomWidget->setParent(this, Qt::SubWindow); + m_zoomWidget->setAutoFillBackground(true); + setView(m_zoomWidget); +} + +static inline qreal zoomFactor(int percent) +{ + return qreal(percent) / 100.0; +} + +static inline QSize scaleSize(int zoomPercent, const QSize &size) +{ + return zoomPercent == 100 ? size : (QSizeF(size) * zoomFactor(zoomPercent)).toSize(); +} + +void ZoomablePreviewDeviceSkin::setPreview(QWidget *formWidget) +{ + m_zoomWidget->setWidget(formWidget); + m_zoomWidget->resize(scaleSize(zoomPercent(), screenSize())); +} + +int ZoomablePreviewDeviceSkin::zoomPercent() const +{ + return m_zoomWidget->zoom(); +} + +void ZoomablePreviewDeviceSkin::setZoomPercent(int zp) +{ + if (zp == zoomPercent()) + return; + + // If not triggered by the menu itself: Update it + if (m_zoomMenu->zoom() != zp) + m_zoomMenu->setZoom(zp); + + QApplication::setOverrideCursor(Qt::WaitCursor); + m_zoomWidget->setZoom(zp); + setTransform(skinTransform()); + QApplication::restoreOverrideCursor(); +} + +void ZoomablePreviewDeviceSkin::populateContextMenu(QMenu *menu) +{ + if (!m_zoomSubMenuAction) { + m_zoomSubMenuAction = new QAction(tr("&Zoom"), this); + QMenu *zoomSubMenu = new QMenu; + m_zoomSubMenuAction->setMenu(zoomSubMenu); + m_zoomMenu->addActions(zoomSubMenu); + } + menu->addAction(m_zoomSubMenuAction); + menu->addSeparator(); +} + +QTransform ZoomablePreviewDeviceSkin::skinTransform() const +{ + // Complete transformation consisting of base class rotation and zoom. + QTransform rc = PreviewDeviceSkin::skinTransform(); + const int zp = zoomPercent(); + if (zp != 100) { + const qreal factor = zoomFactor(zp); + rc.scale(factor, factor); + } + return rc; +} + +void ZoomablePreviewDeviceSkin::fitWidget(const QSize &size) +{ + m_zoomWidget->resize(scaleSize(zoomPercent(), size)); +} + +// ------------- PreviewConfiguration + +static constexpr auto styleKey = "Style"_L1; +static constexpr auto appStyleSheetKey = "AppStyleSheet"_L1; +static constexpr auto skinKey = "Skin"_L1; + +PreviewConfiguration::PreviewConfiguration() : + m_d(new PreviewConfigurationData) +{ +} + +PreviewConfiguration::PreviewConfiguration(const QString &sty, const QString &applicationSheet, const QString &skin) : + m_d(new PreviewConfigurationData(sty, applicationSheet, skin)) +{ +} + +PreviewConfiguration::PreviewConfiguration(const PreviewConfiguration &o) : + m_d(o.m_d) +{ +} + +PreviewConfiguration &PreviewConfiguration::operator=(const PreviewConfiguration &o) +{ + m_d.operator=(o.m_d); + return *this; +} + +PreviewConfiguration::~PreviewConfiguration() = default; + +void PreviewConfiguration::clear() +{ + PreviewConfigurationData &d = *m_d; + d.m_style.clear(); + d.m_applicationStyleSheet.clear(); + d.m_deviceSkin.clear(); +} + +QString PreviewConfiguration::style() const +{ + return m_d->m_style; +} + +void PreviewConfiguration::setStyle(const QString &s) +{ + m_d->m_style = s; +} + +// Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()). +QString PreviewConfiguration::applicationStyleSheet() const +{ + return m_d->m_applicationStyleSheet; +} + +void PreviewConfiguration::setApplicationStyleSheet(const QString &as) +{ + m_d->m_applicationStyleSheet = as; +} + +QString PreviewConfiguration::deviceSkin() const +{ + return m_d->m_deviceSkin; +} + +void PreviewConfiguration::setDeviceSkin(const QString &s) +{ + m_d->m_deviceSkin = s; +} + +void PreviewConfiguration::toSettings(const QString &prefix, QDesignerSettingsInterface *settings) const +{ + const PreviewConfigurationData &d = *m_d; + settings->beginGroup(prefix); + settings->setValue(styleKey, d.m_style); + settings->setValue(appStyleSheetKey, d.m_applicationStyleSheet); + settings->setValue(skinKey, d.m_deviceSkin); + settings->endGroup(); +} + +void PreviewConfiguration::fromSettings(const QString &prefix, const QDesignerSettingsInterface *settings) +{ + clear(); + QString key = prefix + u'/'; + const auto prefixSize = key.size(); + + PreviewConfigurationData &d = *m_d; + + const QVariant emptyString = QVariant(QString()); + + key += styleKey; + d.m_style = settings->value(key, emptyString).toString(); + + key.replace(prefixSize, key.size() - prefixSize, appStyleSheetKey); + d.m_applicationStyleSheet = settings->value(key, emptyString).toString(); + + key.replace(prefixSize, key.size() - prefixSize, skinKey); + d.m_deviceSkin = settings->value(key, emptyString).toString(); +} + + +QDESIGNER_SHARED_EXPORT bool operator<(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) +{ + return compare(pc1, pc2) < 0; +} + +QDESIGNER_SHARED_EXPORT bool operator==(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) +{ + return compare(pc1, pc2) == 0; +} + +QDESIGNER_SHARED_EXPORT bool operator!=(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) +{ + return compare(pc1, pc2) != 0; +} + +// ------------- PreviewManagerPrivate +class PreviewManagerPrivate { +public: + PreviewManagerPrivate(PreviewManager::PreviewMode mode); + + const PreviewManager::PreviewMode m_mode; + + QPointer m_activePreview; + + using PreviewDataList = QList; + + PreviewDataList m_previews; + + QMap m_deviceSkinConfigCache; + + QDesignerFormEditorInterface *m_core; + bool m_updateBlocked; +}; + +PreviewManagerPrivate::PreviewManagerPrivate(PreviewManager::PreviewMode mode) : + m_mode(mode), + m_core(nullptr), + m_updateBlocked(false) +{ +} + +// ------------- PreviewManager + +PreviewManager::PreviewManager(PreviewMode mode, QObject *parent) : + QObject(parent), + d(new PreviewManagerPrivate(mode)) +{ +} + +PreviewManager:: ~PreviewManager() +{ + delete d; +} + + +Qt::WindowFlags PreviewManager::previewWindowFlags(const QWidget *widget) const +{ +#ifdef Q_OS_WIN + Qt::WindowFlags windowFlags = (widget->windowType() == Qt::Window) ? + Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint : + Qt::WindowFlags(Qt::Dialog); +#else + Q_UNUSED(widget); + // Only Dialogs have close buttons on Mac. + // On Linux, we don't want an additional task bar item and we don't want a minimize button; + // we want the preview to be on top. + Qt::WindowFlags windowFlags = Qt::Dialog; +#endif + return windowFlags; +} + +QWidget *PreviewManager::createDeviceSkinContainer(const QDesignerFormWindowInterface *fw) const +{ + return new QDialog(fw->window()); +} + +// Some widgets might require fake containers + +static QWidget *fakeContainer(QWidget *w) +{ + // Prevent a dock widget from trying to dock to Designer's main window + // (which can be found in the parent hierarchy in MDI mode) by + // providing a fake mainwindow + if (QDockWidget *dock = qobject_cast(w)) { + // Reparent: Clear modality, propagate title and resize outer container + const QSize size = w->size(); + w->setWindowModality(Qt::NonModal); + dock->setFeatures(dock->features() & ~(QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetClosable)); + dock->setAllowedAreas(Qt::LeftDockWidgetArea); + QMainWindow *mw = new QMainWindow; + const QMargins cm = mw->contentsMargins(); + mw->addDockWidget(Qt::LeftDockWidgetArea, dock); + mw->resize(size + QSize(cm.left() + cm.right(), cm.top() + cm.bottom())); + return mw; + } + return w; +} + +static PreviewConfiguration configurationFromSettings(QDesignerFormEditorInterface *core, const QString &style) +{ + qdesigner_internal::PreviewConfiguration pc; + const QDesignerSharedSettings settings(core); + if (settings.isCustomPreviewConfigurationEnabled()) + pc = settings.customPreviewConfiguration(); + if (!style.isEmpty()) + pc.setStyle(style); + return pc; +} + +QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, const QString &style, int deviceProfileIndex, QString *errorMessage) +{ + return showPreview(fw, configurationFromSettings(fw->core(), style), deviceProfileIndex, errorMessage); +} + +QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage) +{ + return showPreview(fw, style, -1, errorMessage); +} + +QWidget *PreviewManager::createPreview(const QDesignerFormWindowInterface *fw, + const PreviewConfiguration &pc, + int deviceProfileIndex, + QString *errorMessage, + int initialZoom) +{ + if (!d->m_core) + d->m_core = fw->core(); + + const bool zoomable = initialZoom > 0; + // Figure out which profile to apply + DeviceProfile deviceProfile; + if (deviceProfileIndex >= 0) { + deviceProfile = QDesignerSharedSettings(fw->core()).deviceProfileAt(deviceProfileIndex); + } else { + if (const FormWindowBase *fwb = qobject_cast(fw)) + deviceProfile = fwb->deviceProfile(); + } + // Create + QWidget *formWidget = QDesignerFormBuilder::createPreview(fw, pc.style(), pc.applicationStyleSheet(), deviceProfile, errorMessage); + if (!formWidget) + return nullptr; + + const QString title = tr("%1 - [Preview]").arg(formWidget->windowTitle()); + formWidget = fakeContainer(formWidget); + formWidget->setWindowTitle(title); + + // Clear any modality settings, child widget modalities must not be higher than parent's + formWidget->setWindowModality(Qt::NonModal); + // No skin + const QString deviceSkin = pc.deviceSkin(); + if (deviceSkin.isEmpty()) { + if (zoomable) { // Embed into ZoomWidget + ZoomWidget *zw = new DesignerZoomWidget; + connect(zw->zoomMenu(), &ZoomMenu::zoomChanged, this, &PreviewManager::slotZoomChanged); + zw->setWindowTitle(title); + zw->setWidget(formWidget); + // Keep any widgets' context menus working, do not use global menu + zw->setWidgetZoomContextMenuEnabled(true); + zw->setParent(fw->window(), previewWindowFlags(formWidget)); + // Make preview close when Widget closes (Dialog/accept, etc) + formWidget->setAttribute(Qt::WA_DeleteOnClose, true); + connect(formWidget, &QObject::destroyed, zw, &QWidget::close); + zw->setZoom(initialZoom); + zw->setProperty(WidgetFactory::disableStyleCustomPaintingPropertyC, QVariant(true)); + return zw; + } + formWidget->setParent(fw->window(), previewWindowFlags(formWidget)); + formWidget->setProperty(WidgetFactory::disableStyleCustomPaintingPropertyC, QVariant(true)); + return formWidget; + } + // Embed into skin. find config in cache + auto it = d->m_deviceSkinConfigCache.find(deviceSkin); + if (it == d->m_deviceSkinConfigCache.end()) { + DeviceSkinParameters parameters; + if (!parameters.read(deviceSkin, DeviceSkinParameters::ReadAll, errorMessage)) { + formWidget->deleteLater(); + return nullptr; + } + it = d->m_deviceSkinConfigCache.insert(deviceSkin, parameters); + } + + QWidget *skinContainer = createDeviceSkinContainer(fw); + PreviewDeviceSkin *skin = nullptr; + if (zoomable) { + ZoomablePreviewDeviceSkin *zds = new ZoomablePreviewDeviceSkin(it.value(), skinContainer); + zds->setZoomPercent(initialZoom); + connect(zds, &ZoomablePreviewDeviceSkin::zoomPercentChanged, + this, &PreviewManager::slotZoomChanged); + skin = zds; + } else { + skin = new PreviewDeviceSkin(it.value(), skinContainer); + } + skin->setPreview(formWidget); + // Make preview close when Widget closes (Dialog/accept, etc) + formWidget->setAttribute(Qt::WA_DeleteOnClose, true); + connect(formWidget, &QObject::destroyed, skinContainer, &QWidget::close); + skinContainer->setWindowTitle(title); + skinContainer->setProperty(WidgetFactory::disableStyleCustomPaintingPropertyC, QVariant(true)); + return skinContainer; +} + +QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, + const PreviewConfiguration &pc, + int deviceProfileIndex, + QString *errorMessage) +{ + enum { Spacing = 10 }; + if (QWidget *existingPreviewWidget = raise(fw, pc)) + return existingPreviewWidget; + + const QDesignerSharedSettings settings(fw->core()); + const int initialZoom = settings.zoomEnabled() ? settings.zoom() : -1; + + QWidget *widget = createPreview(fw, pc, deviceProfileIndex, errorMessage, initialZoom); + if (!widget) + return nullptr; + // Install filter for Escape key + widget->setAttribute(Qt::WA_DeleteOnClose, true); + widget->installEventFilter(this); + + switch (d->m_mode) { + case ApplicationModalPreview: + // Cannot do this on the Mac as the dialog would have no close button + widget->setWindowModality(Qt::ApplicationModal); + break; + case SingleFormNonModalPreview: + case MultipleFormNonModalPreview: + widget->setWindowModality(Qt::NonModal); + connect(fw, &QDesignerFormWindowInterface::changed, widget, &QWidget::close); + connect(fw, &QObject::destroyed, widget, &QWidget::close); + if (d->m_mode == SingleFormNonModalPreview) { + connect(fw->core()->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + widget, &QWidget::close); + } + break; + } + // Semi-smart algorithm to position previews: + // If its the first one, position relative to form. + // 2nd, attempt to tile right (for comparing styles) or cascade + const QSize size = widget->size(); + const bool firstPreview = d->m_previews.isEmpty(); + if (firstPreview) { + widget->move(fw->mapToGlobal(QPoint(Spacing, Spacing))); + } else { + if (QWidget *lastPreview = d->m_previews.constLast().m_widget) { + const QRect lastPreviewGeometry = lastPreview->frameGeometry(); + const QRect availGeometry = lastPreview->screen()->availableGeometry(); + const QPoint newPos = lastPreviewGeometry.topRight() + QPoint(Spacing, 0); + if (newPos.x() + size.width() < availGeometry.right()) + widget->move(newPos); + else + widget->move(lastPreviewGeometry.topLeft() + QPoint(Spacing, Spacing)); + } + + } + d->m_previews.push_back(PreviewData(widget, fw, pc)); + widget->show(); + if (firstPreview) + emit firstPreviewOpened(); + return widget; +} + +QWidget *PreviewManager::raise(const QDesignerFormWindowInterface *fw, const PreviewConfiguration &pc) +{ + if (d->m_previews.isEmpty()) + return nullptr; + + // find matching window + for (const auto &pd : std::as_const(d->m_previews)) { + QWidget *w = pd.m_widget; + if (w && pd.m_formWindow == fw && pd.m_configuration == pc) { + w->raise(); + w->activateWindow(); + return w; + } + } + return nullptr; +} + +void PreviewManager::closeAllPreviews() +{ + if (!d->m_previews.isEmpty()) { + d->m_updateBlocked = true; + d->m_activePreview = nullptr; + for (const auto &pd : std::as_const(d->m_previews)) { + if (pd.m_widget) + pd.m_widget->close(); + } + d->m_previews.clear(); + d->m_updateBlocked = false; + emit lastPreviewClosed(); + } +} + +void PreviewManager::updatePreviewClosed(QWidget *w) +{ + if (d->m_updateBlocked) + return; + // Purge out all 0 or widgets to be deleted + for (auto it = d->m_previews.begin(); it != d->m_previews.end() ; ) { + QWidget *iw = it->m_widget; // Might be 0 when catching QEvent::Destroyed + if (iw == nullptr || iw == w) { + it = d->m_previews.erase(it); + } else { + ++it; + } + } + if (d->m_previews.isEmpty()) + emit lastPreviewClosed(); +} + +bool PreviewManager::eventFilter(QObject *watched, QEvent *event) +{ + // Courtesy of designer + do { + if (!watched->isWidgetType()) + break; + QWidget *previewWindow = qobject_cast(watched); + if (!previewWindow || !previewWindow->isWindow()) + break; + + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::ShortcutOverride: { + const QKeyEvent *keyEvent = static_cast(event); + const int key = keyEvent->key(); + if ((key == Qt::Key_Escape +#ifdef Q_OS_MACOS + || (keyEvent->modifiers() == Qt::ControlModifier && key == Qt::Key_Period) +#endif + )) { + previewWindow->close(); + return true; + } + } + break; + case QEvent::WindowActivate: + d->m_activePreview = previewWindow; + break; + case QEvent::Destroy: // We don't get QEvent::Close if someone accepts a QDialog. + updatePreviewClosed(previewWindow); + break; + case QEvent::Close: + updatePreviewClosed(previewWindow); + previewWindow->removeEventFilter (this); + break; + default: + break; + } + } while(false); + return QObject::eventFilter(watched, event); +} + +int PreviewManager::previewCount() const +{ + return d->m_previews.size(); +} + +QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, int deviceProfileIndex, QString *errorMessage) +{ + return createPreviewPixmap(fw, configurationFromSettings(fw->core(), style), deviceProfileIndex, errorMessage); +} + +QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage) +{ + return createPreviewPixmap(fw, style, -1, errorMessage); +} + +QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, + const PreviewConfiguration &pc, + int deviceProfileIndex, + QString *errorMessage) +{ + QWidget *widget = createPreview(fw, pc, deviceProfileIndex, errorMessage); + if (!widget) + return QPixmap(); + const QPixmap rc = widget->grab(QRect(0, 0, -1, -1)); + widget->deleteLater(); + return rc; +} + +void PreviewManager::slotZoomChanged(int z) +{ + if (d->m_core) { // Save the last zoom chosen by the user. + QDesignerSharedSettings settings(d->m_core); + settings.setZoom(z); + } +} +} + +QT_END_NAMESPACE + +#include "previewmanager.moc" diff --git a/src/designer/src/lib/shared/previewmanager_p.h b/src/designer/src/lib/shared/previewmanager_p.h new file mode 100644 index 0000000..aa17d38 --- /dev/null +++ b/src/designer/src/lib/shared/previewmanager_p.h @@ -0,0 +1,146 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PREVIEWMANAGER_H +#define PREVIEWMANAGER_H + +#include "shared_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QWidget; +class QPixmap; +class QAction; +class QActionGroup; +class QMenu; +class QWidget; +class QDesignerSettingsInterface; + +namespace qdesigner_internal { + +// ----------- PreviewConfiguration + +class PreviewConfigurationData; + +class QDESIGNER_SHARED_EXPORT PreviewConfiguration { +public: + PreviewConfiguration(); + explicit PreviewConfiguration(const QString &style, + const QString &applicationStyleSheet = QString(), + const QString &deviceSkin = QString()); + + PreviewConfiguration(const PreviewConfiguration&); + PreviewConfiguration& operator=(const PreviewConfiguration&); + ~PreviewConfiguration(); + + QString style() const; + void setStyle(const QString &); + + // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()). + QString applicationStyleSheet() const; + void setApplicationStyleSheet(const QString &); + + QString deviceSkin() const; + void setDeviceSkin(const QString &); + + void clear(); + void toSettings(const QString &prefix, QDesignerSettingsInterface *settings) const; + void fromSettings(const QString &prefix, const QDesignerSettingsInterface *settings); + +private: + QSharedDataPointer m_d; +}; + +QDESIGNER_SHARED_EXPORT bool operator<(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2); +QDESIGNER_SHARED_EXPORT bool operator==(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2); +QDESIGNER_SHARED_EXPORT bool operator!=(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2); + +// ----------- Preview window manager. +// Maintains a list of preview widgets with their associated form windows and configuration. + +class PreviewManagerPrivate; + +class QDESIGNER_SHARED_EXPORT PreviewManager : public QObject +{ + Q_OBJECT +public: + + enum PreviewMode { + // Modal preview. Do not use on Macs as dialogs would have no close button + ApplicationModalPreview, + // Non modal previewing of one form in different configurations (closes if form window changes) + SingleFormNonModalPreview, + // Non modal previewing of several forms in different configurations + MultipleFormNonModalPreview }; + + explicit PreviewManager(PreviewMode mode, QObject *parent); + ~PreviewManager() override; + + // Show preview. Raise existing preview window if there is one with a matching + // configuration, else create a new preview. + QWidget *showPreview(const QDesignerFormWindowInterface *, const PreviewConfiguration &pc, int deviceProfileIndex /*=-1*/, QString *errorMessage); + // Convenience that creates a preview using a configuration taken from the settings. + QWidget *showPreview(const QDesignerFormWindowInterface *, const QString &style, int deviceProfileIndex /*=-1*/, QString *errorMessage); + QWidget *showPreview(const QDesignerFormWindowInterface *, const QString &style, QString *errorMessage); + + int previewCount() const; + + // Create a pixmap for printing. + QPixmap createPreviewPixmap(const QDesignerFormWindowInterface *fw, const PreviewConfiguration &pc, int deviceProfileIndex /*=-1*/, QString *errorMessage); + // Convenience that creates a pixmap using a configuration taken from the settings. + QPixmap createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, int deviceProfileIndex /*=-1*/, QString *errorMessage); + QPixmap createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage); + + bool eventFilter(QObject *watched, QEvent *event) override; + +public slots: + void closeAllPreviews(); + +signals: + void firstPreviewOpened(); + void lastPreviewClosed(); + +private slots: + void slotZoomChanged(int); + +private: + + virtual Qt::WindowFlags previewWindowFlags(const QWidget *widget) const; + virtual QWidget *createDeviceSkinContainer(const QDesignerFormWindowInterface *) const; + + QWidget *raise(const QDesignerFormWindowInterface *, const PreviewConfiguration &pc); + QWidget *createPreview(const QDesignerFormWindowInterface *, + const PreviewConfiguration &pc, + int deviceProfileIndex /* = -1 */, + QString *errorMessage, + /*Disabled by default, <0 */ + int initialZoom = -1); + + void updatePreviewClosed(QWidget *w); + + PreviewManagerPrivate *d; + + PreviewManager(const PreviewManager &other); + PreviewManager &operator =(const PreviewManager &other); +}; +} + +QT_END_NAMESPACE + +#endif // PREVIEWMANAGER_H diff --git a/src/designer/src/lib/shared/promotionmodel.cpp b/src/designer/src/lib/shared/promotionmodel.cpp new file mode 100644 index 0000000..8d98519 --- /dev/null +++ b/src/designer/src/lib/shared/promotionmodel.cpp @@ -0,0 +1,169 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "promotionmodel_p.h" +#include "widgetdatabase_p.h" + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + using StandardItemList = QList; + + // Model columns. + enum { ClassNameColumn, IncludeFileColumn, IncludeTypeColumn, ReferencedColumn, NumColumns }; + + // Create a model row. + StandardItemList modelRow() { + StandardItemList rc; + for (int i = 0; i < NumColumns; i++) { + rc.push_back(new QStandardItem()); + } + return rc; + } + + // Create a model row for a base class (read-only, cannot be selected). + StandardItemList baseModelRow(const QDesignerWidgetDataBaseItemInterface *dbItem) { + StandardItemList rc = modelRow(); + + rc[ClassNameColumn]->setText(dbItem->name()); + for (int i = 0; i < NumColumns; i++) { + rc[i]->setFlags(Qt::ItemIsEnabled); + } + return rc; + } + + // Create an editable model row for a promoted class. + StandardItemList promotedModelRow(QDesignerWidgetDataBaseItemInterface *baseItem, + QDesignerWidgetDataBaseItemInterface *dbItem, + bool referenced) + { + qdesigner_internal::PromotionModel::ModelData data; + data.baseItem = baseItem; + data.promotedItem = dbItem; + data.referenced = referenced; + + const QVariant userData = QVariant::fromValue(data); + + StandardItemList rc = modelRow(); + // name + rc[ClassNameColumn]->setText(dbItem->name()); + rc[ClassNameColumn]->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable); + rc[ClassNameColumn]->setData(userData); + // header + const qdesigner_internal::IncludeSpecification spec = qdesigner_internal::includeSpecification(dbItem->includeFile()); + rc[IncludeFileColumn]->setText(spec.first); + rc[IncludeFileColumn]->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable); + rc[IncludeFileColumn]->setData(userData); + // global include + rc[IncludeTypeColumn]->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable|Qt::ItemIsUserCheckable); + rc[IncludeTypeColumn]->setData(userData); + rc[IncludeTypeColumn]->setCheckState(spec.second == qdesigner_internal::IncludeGlobal ? Qt::Checked : Qt::Unchecked); + // referenced + rc[ReferencedColumn]->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + rc[ClassNameColumn]->setData(userData); + if (!referenced) { + //: Usage of promoted widgets + static const QString notUsed = QCoreApplication::translate("PromotionModel", "Not used"); + rc[ReferencedColumn]->setText(notUsed); + } + return rc; + } +} + +namespace qdesigner_internal { + + PromotionModel::PromotionModel(QDesignerFormEditorInterface *core) : + m_core(core) + { + connect(this, &QStandardItemModel::itemChanged, this, &PromotionModel::slotItemChanged); + } + + void PromotionModel::initializeHeaders() { + setColumnCount(NumColumns); + QStringList horizontalLabels(tr("Name")); + horizontalLabels += tr("Header file"); + horizontalLabels += tr("Global include"); + horizontalLabels += tr("Usage"); + setHorizontalHeaderLabels (horizontalLabels); + } + + void PromotionModel::updateFromWidgetDatabase() { + using PromotedClasses = QDesignerPromotionInterface::PromotedClasses; + + clear(); + initializeHeaders(); + + // retrieve list of pairs from DB and convert into a tree structure. + // Set the item index as user data on the item. + const PromotedClasses promotedClasses = m_core->promotion()->promotedClasses(); + + if (promotedClasses.isEmpty()) + return; + + const QSet usedPromotedClasses = m_core->promotion()->referencedPromotedClassNames(); + + QDesignerWidgetDataBaseItemInterface *baseClass = nullptr; + QStandardItem *baseItem = nullptr; + + for (auto &pi : promotedClasses) { + // Start a new base class? + if (baseClass != pi.baseItem) { + baseClass = pi.baseItem; + const StandardItemList baseRow = baseModelRow(pi.baseItem); + baseItem = baseRow.constFirst(); + appendRow(baseRow); + } + Q_ASSERT(baseItem); + // Append derived + baseItem->appendRow(promotedModelRow(pi.baseItem, pi.promotedItem, + usedPromotedClasses.contains(pi.promotedItem->name()))); + } + } + + void PromotionModel::slotItemChanged(QStandardItem * changedItem) { + // Retrieve DB item + const ModelData data = modelData(changedItem); + Q_ASSERT(data.isValid()); + QDesignerWidgetDataBaseItemInterface *dbItem = data.promotedItem; + // Change header or type + switch (changedItem->column()) { + case ClassNameColumn: + emit classNameChanged(dbItem, changedItem->text()); + break; + case IncludeTypeColumn: + case IncludeFileColumn: { + // Get both file and type items via parent. + const QStandardItem *baseClassItem = changedItem->parent(); + const QStandardItem *fileItem = baseClassItem->child(changedItem->row(), IncludeFileColumn); + const QStandardItem *typeItem = baseClassItem->child(changedItem->row(), IncludeTypeColumn); + emit includeFileChanged(dbItem, buildIncludeFile(fileItem->text(), typeItem->checkState() == Qt::Checked ? IncludeGlobal : IncludeLocal)); + } + break; + } + } + + PromotionModel::ModelData PromotionModel::modelData(const QStandardItem *item) const + { + const QVariant userData = item->data(); + return userData.canConvert() ? userData.value() : ModelData(); + } + + PromotionModel::ModelData PromotionModel::modelData(const QModelIndex &index) const + { + return index.isValid() ? modelData(itemFromIndex(index)) : ModelData(); + } + + QModelIndex PromotionModel::indexOfClass(const QString &className) const { + const StandardItemList matches = findItems (className, Qt::MatchFixedString|Qt::MatchCaseSensitive|Qt::MatchRecursive); + return matches.isEmpty() ? QModelIndex() : indexFromItem (matches.constFirst()); + } +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/promotionmodel_p.h b/src/designer/src/lib/shared/promotionmodel_p.h new file mode 100644 index 0000000..c92017b --- /dev/null +++ b/src/designer/src/lib/shared/promotionmodel_p.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PROMOTIONMODEL_H +#define PROMOTIONMODEL_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerWidgetDataBaseItemInterface; + +namespace qdesigner_internal { + + // Item model representing the promoted widgets. + class PromotionModel : public QStandardItemModel { + Q_OBJECT + + public: + struct ModelData { + bool isValid() const { return promotedItem != nullptr; } + + QDesignerWidgetDataBaseItemInterface *baseItem{nullptr}; + QDesignerWidgetDataBaseItemInterface *promotedItem{nullptr}; + bool referenced{false}; + }; + + explicit PromotionModel(QDesignerFormEditorInterface *core); + + void updateFromWidgetDatabase(); + + ModelData modelData(const QModelIndex &index) const; + ModelData modelData(const QStandardItem *item) const; + + QModelIndex indexOfClass(const QString &className) const; + + signals: + void includeFileChanged(QDesignerWidgetDataBaseItemInterface *, const QString &includeFile); + void classNameChanged(QDesignerWidgetDataBaseItemInterface *, const QString &newName); + + private slots: + void slotItemChanged(QStandardItem * item); + + private: + void initializeHeaders(); + + QDesignerFormEditorInterface *m_core; + }; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(qdesigner_internal::PromotionModel::ModelData) + +#endif // PROMOTIONMODEL_H diff --git a/src/designer/src/lib/shared/promotiontaskmenu.cpp b/src/designer/src/lib/shared/promotiontaskmenu.cpp new file mode 100644 index 0000000..7cedb22 --- /dev/null +++ b/src/designer/src/lib/shared/promotiontaskmenu.cpp @@ -0,0 +1,313 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "promotiontaskmenu_p.h" +#include "qdesigner_promotiondialog_p.h" +#include "widgetfactory_p.h" +#include "metadatabase_p.h" +#include "widgetdatabase_p.h" +#include "qdesigner_command_p.h" +#include "signalslotdialog_p.h" +#include "qdesigner_objectinspector_p.h" +#include "abstractintrospection_p.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +static QAction *separatorAction(QObject *parent) +{ + QAction *rc = new QAction(parent); + rc->setSeparator(true); + return rc; +} + +static inline QDesignerLanguageExtension *languageExtension(QDesignerFormEditorInterface *core) +{ + return qt_extension(core->extensionManager(), core); +} + +namespace qdesigner_internal { + +PromotionTaskMenu::PromotionTaskMenu(QWidget *widget,Mode mode, QObject *parent) : + QObject(parent), + m_mode(mode), + m_widget(widget), + m_globalEditAction(new QAction(tr("Promoted widgets..."), this)), + m_EditPromoteToAction(new QAction(tr("Promote to ..."), this)), + m_EditSignalsSlotsAction(new QAction(tr("Change signals/slots..."), this)), + m_promoteLabel(tr("Promote to")), + m_demoteLabel(tr("Demote to %1")) +{ + connect(m_globalEditAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditPromotedWidgets); + connect(m_EditPromoteToAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditPromoteTo); + connect(m_EditSignalsSlotsAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditSignalsSlots); +} + +PromotionTaskMenu::Mode PromotionTaskMenu::mode() const +{ + return m_mode; +} + +void PromotionTaskMenu::setMode(Mode m) +{ + m_mode = m; +} + +void PromotionTaskMenu::setWidget(QWidget *widget) +{ + m_widget = widget; +} + +void PromotionTaskMenu::setPromoteLabel(const QString &promoteLabel) +{ + m_promoteLabel = promoteLabel; +} + +void PromotionTaskMenu::setEditPromoteToLabel(const QString &promoteEditLabel) +{ + m_EditPromoteToAction->setText(promoteEditLabel); +} + +void PromotionTaskMenu::setDemoteLabel(const QString &demoteLabel) +{ + m_demoteLabel = demoteLabel; +} + +PromotionTaskMenu::PromotionState PromotionTaskMenu::createPromotionActions(QDesignerFormWindowInterface *formWindow) +{ + // clear out old + if (!m_promotionActions.isEmpty()) { + qDeleteAll(m_promotionActions); + m_promotionActions.clear(); + } + // No promotion of main container + if (formWindow->mainContainer() == m_widget) + return NotApplicable; + + // Check for a homogenous selection + const PromotionSelectionList promotionSelection = promotionSelectionList(formWindow); + + if (promotionSelection.isEmpty()) + return NoHomogenousSelection; + + QDesignerFormEditorInterface *core = formWindow->core(); + // if it is promoted: demote only. + if (isPromoted(formWindow->core(), m_widget)) { + const QString label = m_demoteLabel.arg( promotedExtends(core , m_widget)); + QAction *demoteAction = new QAction(label, this); + connect(demoteAction, &QAction::triggered, this, &PromotionTaskMenu::slotDemoteFromCustomWidget); + m_promotionActions.push_back(demoteAction); + return CanDemote; + } + // figure out candidates + const QString baseClassName = WidgetFactory::classNameOf(core, m_widget); + const WidgetDataBaseItemList candidates = promotionCandidates(core->widgetDataBase(), baseClassName ); + if (candidates.isEmpty()) { + // Is this thing promotable at all? + return QDesignerPromotionDialog::baseClassNames(core->promotion()).contains(baseClassName) ? CanPromote : NotApplicable; + } + + QMenu *candidatesMenu = new QMenu(); + // Create a sub menu + // Set up actions and map class names + for (auto *item : candidates) { + const QString customClassName = item->name(); + candidatesMenu->addAction(customClassName, + this, [this, customClassName] { this->slotPromoteToCustomWidget(customClassName); }); + } + // Sub menu action + QAction *subMenuAction = new QAction(m_promoteLabel, this); + subMenuAction->setMenu(candidatesMenu); + m_promotionActions.push_back(subMenuAction); + return CanPromote; +} + +void PromotionTaskMenu::addActions(unsigned separatorFlags, ActionList &actionList) +{ + addActions(formWindow(), separatorFlags, actionList); +} + +void PromotionTaskMenu::addActions(QDesignerFormWindowInterface *fw, unsigned flags, + ActionList &actionList) +{ + Q_ASSERT(m_widget); + const auto previousSize = actionList.size(); + const PromotionState promotionState = createPromotionActions(fw); + + // Promotion candidates/demote + actionList += m_promotionActions; + + // Edit action depending on context + switch (promotionState) { + case CanPromote: + actionList += m_EditPromoteToAction; + break; + case CanDemote: + if (!(flags & SuppressGlobalEdit)) + actionList += m_globalEditAction; + if (!languageExtension(fw->core())) { + actionList += separatorAction(this); + actionList += m_EditSignalsSlotsAction; + } + break; + default: + if (!(flags & SuppressGlobalEdit)) + actionList += m_globalEditAction; + break; + } + // Add separators if required + if (actionList.size() > previousSize) { + if (flags & LeadingSeparator) + actionList.insert(previousSize, separatorAction(this)); + if (flags & TrailingSeparator) + actionList += separatorAction(this); + } +} + +void PromotionTaskMenu::addActions(QDesignerFormWindowInterface *fw, unsigned flags, QMenu *menu) +{ + ActionList actionList; + addActions(fw, flags, actionList); + menu->addActions(actionList); +} + +void PromotionTaskMenu::addActions(unsigned flags, QMenu *menu) +{ + addActions(formWindow(), flags, menu); +} + +void PromotionTaskMenu::promoteTo(QDesignerFormWindowInterface *fw, const QString &customClassName) +{ + Q_ASSERT(m_widget); + PromoteToCustomWidgetCommand *cmd = new PromoteToCustomWidgetCommand(fw); + cmd->init(promotionSelectionList(fw), customClassName); + fw->commandHistory()->push(cmd); +} + + +void PromotionTaskMenu::slotPromoteToCustomWidget(const QString &customClassName) +{ + promoteTo(formWindow(), customClassName); +} + +void PromotionTaskMenu::slotDemoteFromCustomWidget() +{ + QDesignerFormWindowInterface *fw = formWindow(); + const PromotionSelectionList promotedWidgets = promotionSelectionList(fw); + Q_ASSERT(!promotedWidgets.isEmpty() && isPromoted(fw->core(), promotedWidgets.constFirst())); + + // ### use the undo stack + DemoteFromCustomWidgetCommand *cmd = new DemoteFromCustomWidgetCommand(fw); + cmd->init(promotedWidgets); + fw->commandHistory()->push(cmd); +} + +void PromotionTaskMenu::slotEditPromoteTo() +{ + Q_ASSERT(m_widget); + // Check whether invoked over a promotable widget + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + const QString base_class_name = WidgetFactory::classNameOf(core, m_widget); + Q_ASSERT(QDesignerPromotionDialog::baseClassNames(core->promotion()).contains(base_class_name)); + // Show over promotable widget + QString promoteToClassName; + QDialog *promotionEditor = nullptr; + if (QDesignerLanguageExtension *lang = languageExtension(core)) + promotionEditor = lang->createPromotionDialog(core, base_class_name, &promoteToClassName, fw); + if (!promotionEditor) + promotionEditor = new QDesignerPromotionDialog(core, fw, base_class_name, &promoteToClassName); + if (promotionEditor->exec() == QDialog::Accepted && !promoteToClassName.isEmpty()) { + promoteTo(fw, promoteToClassName); + } + delete promotionEditor; +} + +void PromotionTaskMenu::slotEditPromotedWidgets() +{ + // Global context, show over non-promotable widget + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + editPromotedWidgets(fw->core(), fw); +} + +PromotionTaskMenu::PromotionSelectionList PromotionTaskMenu::promotionSelectionList(QDesignerFormWindowInterface *formWindow) const +{ + // In multi selection mode, check for a homogenous selection (same class, same promotion state) + // and return the list if this is the case. Also make sure m_widget + // is the last widget in the list so that it is re-selected as the last + // widget by the promotion commands. + + PromotionSelectionList rc; + + if (m_mode != ModeSingleWidget) { + QDesignerFormEditorInterface *core = formWindow->core(); + const QDesignerIntrospectionInterface *intro = core->introspection(); + const QString className = intro->metaObject(m_widget)->className(); + const bool promoted = isPromoted(formWindow->core(), m_widget); + // Just in case someone plugged an old-style Object Inspector + if (QDesignerObjectInspector *designerObjectInspector = qobject_cast(core->objectInspector())) { + Selection s; + designerObjectInspector->getSelection(s); + // Find objects of similar state + const QWidgetList &source = m_mode == ModeManagedMultiSelection ? s.managed : s.unmanaged; + for (auto *w : source) { + if (w != m_widget) { + // Selection state mismatch + if (intro->metaObject(w)->className() != className || isPromoted(core, w) != promoted) + return PromotionSelectionList(); + rc.push_back(w); + } + } + } + } + + rc.push_back(m_widget); + return rc; +} + +QDesignerFormWindowInterface *PromotionTaskMenu::formWindow() const +{ + // Use the QObject overload of QDesignerFormWindowInterface::findFormWindow since that works + // for QDesignerMenus also. + QObject *o = m_widget; + QDesignerFormWindowInterface *result = QDesignerFormWindowInterface::findFormWindow(o); + Q_ASSERT(result != nullptr); + return result; +} + +void PromotionTaskMenu::editPromotedWidgets(QDesignerFormEditorInterface *core, QWidget* parent) { + QDesignerLanguageExtension *lang = languageExtension(core); + // Show over non-promotable widget + QDialog *promotionEditor = nullptr; + if (lang) + lang->createPromotionDialog(core, parent); + if (!promotionEditor) + promotionEditor = new QDesignerPromotionDialog(core, parent); + promotionEditor->exec(); + delete promotionEditor; +} + +void PromotionTaskMenu::slotEditSignalsSlots() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + SignalSlotDialog::editPromotedClass(fw->core(), m_widget, fw); +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/promotiontaskmenu_p.h b/src/designer/src/lib/shared/promotiontaskmenu_p.h new file mode 100644 index 0000000..371d95d --- /dev/null +++ b/src/designer/src/lib/shared/promotiontaskmenu_p.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PROMOTIONTASKMENU_H +#define PROMOTIONTASKMENU_H + +#include "shared_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; + +class QAction; +class QMenu; +class QWidget; + +namespace qdesigner_internal { + +// A helper class for creating promotion context menus and handling promotion actions. + +class QDESIGNER_SHARED_EXPORT PromotionTaskMenu: public QObject +{ + Q_OBJECT +public: + enum Mode { + ModeSingleWidget, + ModeManagedMultiSelection, + ModeUnmanagedMultiSelection + }; + + explicit PromotionTaskMenu(QWidget *widget,Mode mode = ModeManagedMultiSelection, QObject *parent = nullptr); + + Mode mode() const; + void setMode(Mode m); + + void setWidget(QWidget *widget); + + // Set menu labels + void setPromoteLabel(const QString &promoteLabel); + void setEditPromoteToLabel(const QString &promoteEditLabel); + // Defaults to "Demote to %1".arg(class). + void setDemoteLabel(const QString &demoteLabel); + + using ActionList = QList; + + enum AddFlags { LeadingSeparator = 1, TrailingSeparator = 2, SuppressGlobalEdit = 4}; + + // Adds a list of promotion actions according to the current promotion state of the widget. + void addActions(QDesignerFormWindowInterface *fw, unsigned flags, ActionList &actionList); + // Convenience that finds the form window. + void addActions(unsigned flags, ActionList &actionList); + + void addActions(QDesignerFormWindowInterface *fw, unsigned flags, QMenu *menu); + void addActions(unsigned flags, QMenu *menu); + + // Pop up the editor in a global context. + static void editPromotedWidgets(QDesignerFormEditorInterface *core, QWidget* parent); + +private slots: + void slotPromoteToCustomWidget(const QString &customClassName); + void slotDemoteFromCustomWidget(); + void slotEditPromotedWidgets(); + void slotEditPromoteTo(); + void slotEditSignalsSlots(); + +private: + void promoteTo(QDesignerFormWindowInterface *fw, const QString &customClassName); + + enum PromotionState { NotApplicable, NoHomogenousSelection, CanPromote, CanDemote }; + PromotionState createPromotionActions(QDesignerFormWindowInterface *formWindow); + QDesignerFormWindowInterface *formWindow() const; + + using PromotionSelectionList = QList >; + PromotionSelectionList promotionSelectionList(QDesignerFormWindowInterface *formWindow) const; + + Mode m_mode; + + QPointer m_widget; + + // Per-Widget actions + QList m_promotionActions; + + QAction *m_globalEditAction; + QAction *m_EditPromoteToAction; + QAction *m_EditSignalsSlotsAction; + + QString m_promoteLabel; + QString m_demoteLabel; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PROMOTIONTASKMENU_H diff --git a/src/designer/src/lib/shared/propertylineedit.cpp b/src/designer/src/lib/shared/propertylineedit.cpp new file mode 100644 index 0000000..bea9101 --- /dev/null +++ b/src/designer/src/lib/shared/propertylineedit.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "propertylineedit_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + PropertyLineEdit::PropertyLineEdit(QWidget *parent) : + QLineEdit(parent), m_wantNewLine(false) + { + } + + bool PropertyLineEdit::event(QEvent *e) + { + // handle 'Select all' here as it is not done in the QLineEdit + if (e->type() == QEvent::ShortcutOverride && !isReadOnly()) { + QKeyEvent* ke = static_cast (e); + if (ke->modifiers() & Qt::ControlModifier) { + if(ke->key() == Qt::Key_A) { + ke->accept(); + return true; + } + } + } + return QLineEdit::event(e); + } + + void PropertyLineEdit::insertNewLine() { + insertText(u"\\n"_s); + } + + void PropertyLineEdit::insertText(const QString &text) { + // position cursor after new text and grab focus + const int oldCursorPosition = cursorPosition (); + insert(text); + setCursorPosition (oldCursorPosition + text.size()); + setFocus(Qt::OtherFocusReason); + } + + void PropertyLineEdit::contextMenuEvent(QContextMenuEvent *event) { + QMenu *menu = createStandardContextMenu (); + + if (m_wantNewLine) { + menu->addSeparator(); + menu->addAction(tr("Insert line break"), this, &PropertyLineEdit::insertNewLine); + } + + menu->exec(event->globalPos()); + } +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/propertylineedit_p.h b/src/designer/src/lib/shared/propertylineedit_p.h new file mode 100644 index 0000000..d1a1c94 --- /dev/null +++ b/src/designer/src/lib/shared/propertylineedit_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PROPERTYLINEEDIT_H +#define PROPERTYLINEEDIT_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + + // A line edit with a special context menu allowing for adding (escaped) new lines + class PropertyLineEdit : public QLineEdit { + Q_OBJECT + public: + explicit PropertyLineEdit(QWidget *parent); + void setWantNewLine(bool nl) { m_wantNewLine = nl; } + bool wantNewLine() const { return m_wantNewLine; } + + bool event(QEvent *e) override; + protected: + void contextMenuEvent (QContextMenuEvent *event ) override; + private slots: + void insertNewLine(); + private: + void insertText(const QString &); + bool m_wantNewLine; + }; +} + +QT_END_NAMESPACE + +#endif // PROPERTYLINEEDIT_H diff --git a/src/designer/src/lib/shared/qdesigner_command.cpp b/src/designer/src/lib/shared/qdesigner_command.cpp new file mode 100644 index 0000000..2633224 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_command.cpp @@ -0,0 +1,2872 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "qdesigner_utils_p.h" +#include "layout_p.h" +#include "qlayout_widget_p.h" +#include "qdesigner_widget_p.h" +#include "qdesigner_menu_p.h" +#include "shared_enums_p.h" +#include "metadatabase_p.h" +#include "formwindowbase_p.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QWidgetList) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static inline void setPropertySheetWindowTitle(const QDesignerFormEditorInterface *core, QObject *o, const QString &t) +{ + if (QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), o)) { + const int idx = sheet->indexOf(u"windowTitle"_s); + if (idx != -1) { + sheet->setProperty(idx, t); + sheet->setChanged(idx, true); + } + } +} + +namespace qdesigner_internal { + +// Helpers for the dynamic properties that store Z/Widget order +static const char widgetOrderPropertyC[] = "_q_widgetOrder"; +static const char zOrderPropertyC[] = "_q_zOrder"; + +static void addToWidgetListDynamicProperty(QWidget *parentWidget, QWidget *widget, const char *name, int index = -1) +{ + QWidgetList list = qvariant_cast(parentWidget->property(name)); + list.removeAll(widget); + if (index >= 0 && index < list.size()) { + list.insert(index, widget); + } else { + list.append(widget); + } + parentWidget->setProperty(name, QVariant::fromValue(list)); +} + +static int removeFromWidgetListDynamicProperty(QWidget *parentWidget, QWidget *widget, const char *name) +{ + QWidgetList list = qvariant_cast(parentWidget->property(name)); + const int firstIndex = list.indexOf(widget); + if (firstIndex != -1) { + list.removeAll(widget); + parentWidget->setProperty(name, QVariant::fromValue(list)); + } + return firstIndex; +} + +// ---- InsertWidgetCommand ---- +InsertWidgetCommand::InsertWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_insertMode(QDesignerLayoutDecorationExtension::InsertWidgetMode), + m_layoutHelper(nullptr), + m_widgetWasManaged(false) +{ +} + +InsertWidgetCommand::~InsertWidgetCommand() +{ + delete m_layoutHelper; +} + +void InsertWidgetCommand::init(QWidget *widget, bool already_in_form, int layoutRow, int layoutColumn) +{ + m_widget = widget; + + setText(QApplication::translate("Command", "Insert '%1'").arg(widget->objectName())); + + QWidget *parentWidget = m_widget->parentWidget(); + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); + + m_insertMode = deco ? deco->currentInsertMode() : QDesignerLayoutDecorationExtension::InsertWidgetMode; + if (layoutRow >= 0 && layoutColumn >= 0) { + m_cell.first = layoutRow; + m_cell.second = layoutColumn; + } else { + m_cell = deco ? deco->currentCell() : std::make_pair(0, 0); + } + m_widgetWasManaged = already_in_form; +} + +static void recursiveUpdate(QWidget *w) +{ + w->update(); + + for (auto *child : w->children()) { + if (QWidget *w = qobject_cast(child)) + recursiveUpdate(w); + } +} + +void InsertWidgetCommand::redo() +{ + QWidget *parentWidget = m_widget->parentWidget(); + Q_ASSERT(parentWidget); + + addToWidgetListDynamicProperty(parentWidget, m_widget, widgetOrderPropertyC); + addToWidgetListDynamicProperty(parentWidget, m_widget, zOrderPropertyC); + + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); + + if (deco != nullptr) { + const LayoutInfo::Type type = LayoutInfo::layoutType(core, LayoutInfo::managedLayout(core, parentWidget)); + m_layoutHelper = LayoutHelper::createLayoutHelper(type); + m_layoutHelper->pushState(core, parentWidget); + if (type == LayoutInfo::Grid) { + switch (m_insertMode) { + case QDesignerLayoutDecorationExtension::InsertRowMode: { + deco->insertRow(m_cell.first); + } break; + + case QDesignerLayoutDecorationExtension::InsertColumnMode: { + deco->insertColumn(m_cell.second); + } break; + + default: break; + } // end switch + } + deco->insertWidget(m_widget, m_cell); + } + + if (!m_widgetWasManaged) + formWindow()->manageWidget(m_widget); + m_widget->show(); + formWindow()->emitSelectionChanged(); + + if (parentWidget && parentWidget->layout()) { + recursiveUpdate(parentWidget); + parentWidget->layout()->invalidate(); + } + + refreshBuddyLabels(); +} + +void InsertWidgetCommand::undo() +{ + QWidget *parentWidget = m_widget->parentWidget(); + + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); + + if (deco) { + deco->removeWidget(m_widget); + m_layoutHelper->popState(core, parentWidget); + } + + if (!m_widgetWasManaged) { + formWindow()->unmanageWidget(m_widget); + m_widget->hide(); + } + + removeFromWidgetListDynamicProperty(parentWidget, m_widget, widgetOrderPropertyC); + removeFromWidgetListDynamicProperty(parentWidget, m_widget, zOrderPropertyC); + + formWindow()->emitSelectionChanged(); + + refreshBuddyLabels(); +} + +void InsertWidgetCommand::refreshBuddyLabels() +{ + const auto label_list = formWindow()->findChildren(); + if (label_list.isEmpty()) + return; + + const QString buddyProperty = u"buddy"_s; + const QByteArray objectNameU8 = m_widget->objectName().toUtf8(); + // Re-set the buddy (The sheet locates the object by name and sets it) + for (QLabel *label : label_list) { + if (QDesignerPropertySheetExtension* sheet = propertySheet(label)) { + const int idx = sheet->indexOf(buddyProperty); + if (idx != -1) { + const QVariant value = sheet->property(idx); + if (value.toByteArray() == objectNameU8) + sheet->setProperty(idx, value); + } + } + } +} + +// ---- ChangeZOrderCommand ---- +ChangeZOrderCommand::ChangeZOrderCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ +} + +void ChangeZOrderCommand::init(QWidget *widget) +{ + Q_ASSERT(widget); + + m_widget = widget; + + setText(QApplication::translate("Command", "Change Z-order of '%1'").arg(widget->objectName())); + + m_oldParentZOrder = qvariant_cast(widget->parentWidget()->property("_q_zOrder")); + const qsizetype index = m_oldParentZOrder.indexOf(m_widget); + if (index != -1 && index + 1 < m_oldParentZOrder.size()) + m_oldPreceding = m_oldParentZOrder.at(index + 1); +} + +void ChangeZOrderCommand::redo() +{ + m_widget->parentWidget()->setProperty("_q_zOrder", QVariant::fromValue(reorderWidget(m_oldParentZOrder, m_widget))); + + reorder(m_widget); +} + +void ChangeZOrderCommand::undo() +{ + m_widget->parentWidget()->setProperty("_q_zOrder", QVariant::fromValue(m_oldParentZOrder)); + + if (m_oldPreceding) + m_widget->stackUnder(m_oldPreceding); + else + m_widget->raise(); +} + +// ---- RaiseWidgetCommand ---- +RaiseWidgetCommand::RaiseWidgetCommand(QDesignerFormWindowInterface *formWindow) + : ChangeZOrderCommand(formWindow) +{ +} + +void RaiseWidgetCommand::init(QWidget *widget) +{ + ChangeZOrderCommand::init(widget); + setText(QApplication::translate("Command", "Raise '%1'").arg(widget->objectName())); +} + +QWidgetList RaiseWidgetCommand::reorderWidget(const QWidgetList &list, QWidget *widget) const +{ + QWidgetList l = list; + l.removeAll(widget); + l.append(widget); + return l; +} + +void RaiseWidgetCommand::reorder(QWidget *widget) const +{ + widget->raise(); +} + +// ---- LowerWidgetCommand ---- +LowerWidgetCommand::LowerWidgetCommand(QDesignerFormWindowInterface *formWindow) + : ChangeZOrderCommand(formWindow) +{ +} + +QWidgetList LowerWidgetCommand::reorderWidget(const QWidgetList &list, QWidget *widget) const +{ + QWidgetList l = list; + l.removeAll(widget); + l.prepend(widget); + return l; +} + +void LowerWidgetCommand::init(QWidget *widget) +{ + ChangeZOrderCommand::init(widget); + setText(QApplication::translate("Command", "Lower '%1'").arg(widget->objectName())); +} + +void LowerWidgetCommand::reorder(QWidget *widget) const +{ + widget->lower(); +} + +// ---- ManageWidgetCommandHelper +ManageWidgetCommandHelper::ManageWidgetCommandHelper() = default; + +void ManageWidgetCommandHelper::init(const QDesignerFormWindowInterface *fw, QWidget *widget) +{ + m_widget = widget; + m_managedChildren.clear(); + + const QWidgetList children = m_widget->findChildren(); + m_managedChildren.reserve(children.size()); + for (auto *w : children) { + if (fw->isManaged(w)) + m_managedChildren.push_back(w); + } +} + +void ManageWidgetCommandHelper::init(QWidget *widget, const QWidgetList &managedChildren) +{ + m_widget = widget; + m_managedChildren = managedChildren; +} + +void ManageWidgetCommandHelper::manage(QDesignerFormWindowInterface *fw) +{ + // Manage the managed children after parent + fw->manageWidget(m_widget); + for (auto *w : std::as_const(m_managedChildren)) + fw->manageWidget(w); +} + +void ManageWidgetCommandHelper::unmanage(QDesignerFormWindowInterface *fw) +{ + // Unmanage the managed children first + for (auto *w : std::as_const(m_managedChildren)) + fw->unmanageWidget(w); + fw->unmanageWidget(m_widget); +} + +// ---- DeleteWidgetCommand ---- +DeleteWidgetCommand::DeleteWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_layoutType(LayoutInfo::NoLayout), + m_layoutHelper(nullptr), + m_flags(0), + m_splitterIndex(-1), + m_layoutSimplified(false), + m_formItem(nullptr), + m_tabOrderIndex(-1), + m_widgetOrderIndex(-1), + m_zOrderIndex(-1) +{ +} + +DeleteWidgetCommand::~DeleteWidgetCommand() +{ + delete m_layoutHelper; +} + +void DeleteWidgetCommand::init(QWidget *widget, unsigned flags) +{ + m_widget = widget; + m_parentWidget = widget->parentWidget(); + m_geometry = widget->geometry(); + m_flags = flags; + m_layoutType = LayoutInfo::NoLayout; + m_splitterIndex = -1; + bool isManaged; // Check for a managed layout + QLayout *layout; + m_layoutType = LayoutInfo::laidoutWidgetType(formWindow()->core(), m_widget, &isManaged, &layout); + if (!isManaged) + m_layoutType = LayoutInfo::NoLayout; + switch (m_layoutType) { + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: { + QSplitter *splitter = qobject_cast(m_parentWidget); + Q_ASSERT(splitter); + m_splitterIndex = splitter->indexOf(widget); + } + break; + case LayoutInfo::NoLayout: + break; + default: + m_layoutHelper = LayoutHelper::createLayoutHelper(m_layoutType); + m_layoutPosition = m_layoutHelper->itemInfo(layout, m_widget); + break; + } + + m_formItem = formWindow()->core()->metaDataBase()->item(formWindow()); + m_tabOrderIndex = m_formItem->tabOrder().indexOf(widget); + + // Build the list of managed children + m_manageHelper.init(formWindow(), m_widget); + + setText(QApplication::translate("Command", "Delete '%1'").arg(widget->objectName())); +} + +void DeleteWidgetCommand::redo() +{ + formWindow()->clearSelection(); + QDesignerFormEditorInterface *core = formWindow()->core(); + + if (QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_parentWidget)) { + const int count = c->count(); + for (int i=0; iwidget(i) == m_widget) { + c->remove(i); + return; + } + } + } + + m_widgetOrderIndex = removeFromWidgetListDynamicProperty(m_parentWidget, m_widget, widgetOrderPropertyC); + m_zOrderIndex = removeFromWidgetListDynamicProperty(m_parentWidget, m_widget, zOrderPropertyC); + + if (QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), m_parentWidget)) + deco->removeWidget(m_widget); + + if (m_layoutHelper) + switch (m_layoutType) { + case LayoutInfo::NoLayout: + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: + break; + default: + // Attempt to simplify grids if a row/column becomes empty + m_layoutSimplified = (m_flags & DoNotSimplifyLayout) ? false : m_layoutHelper->canSimplify(core, m_parentWidget, m_layoutPosition); + if (m_layoutSimplified) { + m_layoutHelper->pushState(core, m_parentWidget); + m_layoutHelper->simplify(core, m_parentWidget, m_layoutPosition); + } + break; + } + + if (!(m_flags & DoNotUnmanage)) + m_manageHelper.unmanage(formWindow()); + + m_widget->setParent(formWindow()); + m_widget->hide(); + + if (m_tabOrderIndex != -1) { + QWidgetList tab_order = m_formItem->tabOrder(); + tab_order.removeAt(m_tabOrderIndex); + m_formItem->setTabOrder(tab_order); + } +} + +void DeleteWidgetCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + formWindow()->clearSelection(); + + m_widget->setParent(m_parentWidget); + + if (QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_parentWidget)) { + c->addWidget(m_widget); + return; + } + + addToWidgetListDynamicProperty(m_parentWidget, m_widget, widgetOrderPropertyC, m_widgetOrderIndex); + addToWidgetListDynamicProperty(m_parentWidget, m_widget, zOrderPropertyC, m_zOrderIndex); + + m_widget->setGeometry(m_geometry); + + if (!(m_flags & DoNotUnmanage)) + m_manageHelper.manage(formWindow()); + // ### set up alignment + switch (m_layoutType) { + case LayoutInfo::NoLayout: + break; + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: { + QSplitter *splitter = qobject_cast(m_widget->parent()); + Q_ASSERT(splitter); + splitter->insertWidget(m_splitterIndex, m_widget); + } break; + default: { + Q_ASSERT(m_layoutHelper); + if (m_layoutSimplified) + m_layoutHelper->popState(core, m_parentWidget); + QLayout *layout = LayoutInfo::managedLayout(core, m_parentWidget); + Q_ASSERT(m_layoutType == LayoutInfo::layoutType(core, layout)); + m_layoutHelper->insertWidget(layout, m_layoutPosition, m_widget); + } + break; + } + + m_widget->show(); + + if (m_tabOrderIndex != -1) { + QWidgetList tab_order = m_formItem->tabOrder(); + tab_order.insert(m_tabOrderIndex, m_widget); + m_formItem->setTabOrder(tab_order); + } +} + +// ---- ReparentWidgetCommand ---- +ReparentWidgetCommand::ReparentWidgetCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ +} + +void ReparentWidgetCommand::init(QWidget *widget, QWidget *parentWidget) +{ + Q_ASSERT(widget); + + m_widget = widget; + m_oldParentWidget = widget->parentWidget(); + m_newParentWidget = parentWidget; + + m_oldPos = m_widget->pos(); + m_newPos = m_newParentWidget->mapFromGlobal(m_oldParentWidget->mapToGlobal(m_oldPos)); + + setText(QApplication::translate("Command", "Reparent '%1'").arg(widget->objectName())); + + m_oldParentList = qvariant_cast(m_oldParentWidget->property("_q_widgetOrder")); + m_oldParentZOrder = qvariant_cast(m_oldParentWidget->property("_q_zOrder")); +} + +void ReparentWidgetCommand::redo() +{ + m_widget->setParent(m_newParentWidget); + m_widget->move(m_newPos); + + QWidgetList oldList = m_oldParentList; + oldList.removeAll(m_widget); + m_oldParentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(oldList)); + + QWidgetList newList = qvariant_cast(m_newParentWidget->property("_q_widgetOrder")); + newList.append(m_widget); + m_newParentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(newList)); + + QWidgetList oldZOrder = m_oldParentZOrder; + oldZOrder.removeAll(m_widget); + m_oldParentWidget->setProperty("_q_zOrder", QVariant::fromValue(oldZOrder)); + + QWidgetList newZOrder = qvariant_cast(m_newParentWidget->property("_q_zOrder")); + newZOrder.append(m_widget); + m_newParentWidget->setProperty("_q_zOrder", QVariant::fromValue(newZOrder)); + + m_widget->show(); + core()->objectInspector()->setFormWindow(formWindow()); +} + +void ReparentWidgetCommand::undo() +{ + m_widget->setParent(m_oldParentWidget); + m_widget->move(m_oldPos); + + m_oldParentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(m_oldParentList)); + + QWidgetList newList = qvariant_cast(m_newParentWidget->property("_q_widgetOrder")); + newList.removeAll(m_widget); + m_newParentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(newList)); + + m_oldParentWidget->setProperty("_q_zOrder", QVariant::fromValue(m_oldParentZOrder)); + + QWidgetList newZOrder = qvariant_cast(m_newParentWidget->property("_q_zOrder")); + newZOrder.removeAll(m_widget); + m_newParentWidget->setProperty("_q_zOrder", QVariant::fromValue(newZOrder)); + + m_widget->show(); + core()->objectInspector()->setFormWindow(formWindow()); +} + +PromoteToCustomWidgetCommand::PromoteToCustomWidgetCommand + (QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Promote to custom widget"), formWindow) +{ +} + +void PromoteToCustomWidgetCommand::init(const WidgetPointerList &widgets,const QString &customClassName) +{ + m_widgets = widgets; + m_customClassName = customClassName; +} + +void PromoteToCustomWidgetCommand::redo() +{ + for (QWidget *w : std::as_const(m_widgets)) { + if (w) + promoteWidget(core(), w, m_customClassName); + } + updateSelection(); +} + +void PromoteToCustomWidgetCommand::updateSelection() +{ + // Update class names in ObjectInspector, PropertyEditor + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + core->objectInspector()->setFormWindow(fw); + if (QObject *o = core->propertyEditor()->object()) + core->propertyEditor()->setObject(o); +} + +void PromoteToCustomWidgetCommand::undo() +{ + for (QWidget *w : std::as_const(m_widgets)) { + if (w) + demoteWidget(core(), w); + } + updateSelection(); +} + +// ---- DemoteFromCustomWidgetCommand ---- + +DemoteFromCustomWidgetCommand::DemoteFromCustomWidgetCommand + (QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Demote from custom widget"), formWindow), + m_promote_cmd(formWindow) +{ +} + +void DemoteFromCustomWidgetCommand::init(const WidgetList &promoted) +{ + m_promote_cmd.init(promoted, promotedCustomClassName(core(), promoted.constFirst())); +} + +void DemoteFromCustomWidgetCommand::redo() +{ + m_promote_cmd.undo(); +} + +void DemoteFromCustomWidgetCommand::undo() +{ + m_promote_cmd.redo(); +} + +// ---------- CursorSelectionState +CursorSelectionState::CursorSelectionState() = default; + +void CursorSelectionState::save(const QDesignerFormWindowInterface *formWindow) +{ + const QDesignerFormWindowCursorInterface *cursor = formWindow->cursor(); + m_selection.clear(); + m_current = cursor->current(); + if (cursor->hasSelection()) { + const int count = cursor->selectedWidgetCount(); + for(int i = 0; i < count; i++) + m_selection.push_back(cursor->selectedWidget(i)); + } +} + +void CursorSelectionState::restore(QDesignerFormWindowInterface *formWindow) const +{ + if (m_selection.isEmpty()) { + formWindow->clearSelection(true); + } else { + // Select current as last + formWindow->clearSelection(false); + for (const auto &wp : m_selection) { + if (!wp.isNull() && wp.data() != m_current) + formWindow->selectWidget(wp.data(), true); + } + if (m_current) + formWindow->selectWidget(m_current, true); + } +} + +// ---- LayoutCommand ---- + +LayoutCommand::LayoutCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_setup(false) +{ +} + +LayoutCommand::~LayoutCommand() +{ + delete m_layout; +} + +void LayoutCommand::init(QWidget *parentWidget, const QWidgetList &widgets, + LayoutInfo::Type layoutType, QWidget *layoutBase, + bool reparentLayoutWidget) +{ + m_parentWidget = parentWidget; + m_widgets = widgets; + formWindow()->simplifySelection(&m_widgets); + m_layout = Layout::createLayout(widgets, parentWidget, formWindow(), layoutBase, layoutType); + m_layout->setReparentLayoutWidget(reparentLayoutWidget); + + switch (layoutType) { + case LayoutInfo::Grid: + setText(QApplication::translate("Command", "Lay out using grid")); + break; + case LayoutInfo::VBox: + setText(QApplication::translate("Command", "Lay out vertically")); + break; + case LayoutInfo::HBox: + setText(QApplication::translate("Command", "Lay out horizontally")); + break; + default: + break; + } + // Delayed setup to avoid confusion in case we are chained + // with a BreakLayout in a morph layout macro + m_setup = false; +} + +void LayoutCommand::redo() +{ + if (!m_setup) { + m_layout->setup(); + m_cursorSelectionState.save(formWindow()); + m_setup = true; + } + m_layout->doLayout(); + core()->objectInspector()->setFormWindow(formWindow()); +} + +void LayoutCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + + QWidget *lb = m_layout->layoutBaseWidget(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), lb); + m_layout->undoLayout(); + delete deco; // release the extension + + // ### generalize (put in function) + if (!m_layoutBase && lb != nullptr && !(qobject_cast(lb) || qobject_cast(lb))) { + core->metaDataBase()->add(lb); + lb->show(); + } + m_cursorSelectionState.restore(formWindow()); + core->objectInspector()->setFormWindow(formWindow()); +} + +// ---- BreakLayoutCommand ---- +BreakLayoutCommand::BreakLayoutCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Break layout"), formWindow), + m_layoutHelper(nullptr), + m_properties(nullptr), + m_propertyMask(0) +{ +} + +BreakLayoutCommand::~BreakLayoutCommand() +{ + delete m_layoutHelper; + delete m_layout; + delete m_properties; +} + +const LayoutProperties *BreakLayoutCommand::layoutProperties() const +{ + return m_properties; +} + +int BreakLayoutCommand::propertyMask() const +{ + return m_propertyMask; +} + +void BreakLayoutCommand::init(const QWidgetList &widgets, QWidget *layoutBase, bool reparentLayoutWidget) +{ + enum Type { SplitterLayout, LayoutHasMarginSpacing, LayoutHasState }; + + const QDesignerFormEditorInterface *core = formWindow()->core(); + m_widgets = widgets; + m_layoutBase = core->widgetFactory()->containerOfWidget(layoutBase); + QLayout *layoutToBeBroken; + const LayoutInfo::Type layoutType = LayoutInfo::managedLayoutType(core, m_layoutBase, &layoutToBeBroken); + m_layout = Layout::createLayout(widgets, m_layoutBase, formWindow(), layoutBase, layoutType); + m_layout->setReparentLayoutWidget(reparentLayoutWidget); + + Type type = LayoutHasState; + switch (layoutType) { + case LayoutInfo::NoLayout: + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: + type = SplitterLayout; + break; + case LayoutInfo::HBox: + case LayoutInfo::VBox: // Margin/spacing need to be saved + type = LayoutHasMarginSpacing; + break; + default: // Margin/spacing need to be saved + has a state (empty rows/columns of a grid) + type = LayoutHasState; + break; + } + Q_ASSERT(m_layout != nullptr); + m_layout->sort(); + + + if (type >= LayoutHasMarginSpacing) { + m_properties = new LayoutProperties; + m_propertyMask = m_properties->fromPropertySheet(core, layoutToBeBroken, LayoutProperties::AllProperties); + } + if (type >= LayoutHasState) + m_layoutHelper = LayoutHelper::createLayoutHelper(layoutType); + m_cursorSelectionState.save(formWindow()); +} + +void BreakLayoutCommand::redo() +{ + if (!m_layout) + return; + + QDesignerFormEditorInterface *core = formWindow()->core(); + QWidget *lb = m_layout->layoutBaseWidget(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), lb); + formWindow()->clearSelection(false); + if (m_layoutHelper) + m_layoutHelper->pushState(core, m_layoutBase); + m_layout->breakLayout(); + delete deco; // release the extension + + for (QWidget *widget : std::as_const(m_widgets)) { + widget->resize(widget->size().expandedTo(QSize(16, 16))); + } + // Update unless we are in an intermediate state of morphing layout + // in which a QLayoutWidget will have no layout at all. + if (m_layout->reparentLayoutWidget()) + core->objectInspector()->setFormWindow(formWindow()); +} + +void BreakLayoutCommand::undo() +{ + if (!m_layout) + return; + + formWindow()->clearSelection(false); + m_layout->doLayout(); + if (m_layoutHelper) + m_layoutHelper->popState(formWindow()->core(), m_layoutBase); + + QLayout *layoutToRestored = LayoutInfo::managedLayout(formWindow()->core(), m_layoutBase); + if (m_properties && m_layoutBase && layoutToRestored) + m_properties->toPropertySheet(formWindow()->core(), layoutToRestored, m_propertyMask); + m_cursorSelectionState.restore(formWindow()); + core()->objectInspector()->setFormWindow(formWindow()); +} +// ---- SimplifyLayoutCommand +SimplifyLayoutCommand::SimplifyLayoutCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Simplify Grid Layout"), formWindow), + m_area(0, 0, 32767, 32767), + m_layoutBase(nullptr), + m_layoutHelper(nullptr), + m_layoutSimplified(false) +{ +} + +SimplifyLayoutCommand::~SimplifyLayoutCommand() +{ + delete m_layoutHelper; +} + +bool SimplifyLayoutCommand::canSimplify(QDesignerFormEditorInterface *core, const QWidget *w, int *layoutType) +{ + if (!w) + return false; + QLayout *layout; + const LayoutInfo::Type type = LayoutInfo::managedLayoutType(core, w, &layout); + if (layoutType) + *layoutType = type; + if (!layout) + return false; + switch (type) { // Known negatives + case LayoutInfo::NoLayout: + case LayoutInfo::UnknownLayout: + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: + case LayoutInfo::HBox: + case LayoutInfo::VBox: + return false; + default: + break; + } + switch (type) { + case LayoutInfo::Grid: + return QLayoutSupport::canSimplifyQuickCheck(qobject_cast(layout)); + case LayoutInfo::Form: + return QLayoutSupport::canSimplifyQuickCheck(qobject_cast(layout)); + default: + break; + } + return false; +} + +bool SimplifyLayoutCommand::init(QWidget *layoutBase) +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + m_layoutSimplified = false; + int type; + if (canSimplify(core, layoutBase, &type)) { + m_layoutBase = layoutBase; + m_layoutHelper = LayoutHelper::createLayoutHelper(type); + m_layoutSimplified = m_layoutHelper->canSimplify(core, layoutBase, m_area); + } + return m_layoutSimplified; +} + +void SimplifyLayoutCommand::redo() +{ + const QDesignerFormEditorInterface *core = formWindow()->core(); + if (m_layoutSimplified) { + m_layoutHelper->pushState(core, m_layoutBase); + m_layoutHelper->simplify(core, m_layoutBase, m_area); + } +} +void SimplifyLayoutCommand::undo() +{ + if (m_layoutSimplified) + m_layoutHelper->popState(formWindow()->core(), m_layoutBase); +} + +// ---- ToolBoxCommand ---- +ToolBoxCommand::ToolBoxCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_index(-1) +{ +} + +ToolBoxCommand::~ToolBoxCommand() = default; + +void ToolBoxCommand::init(QToolBox *toolBox) +{ + m_toolBox = toolBox; + m_index = m_toolBox->currentIndex(); + m_widget = m_toolBox->widget(m_index); + m_itemText = m_toolBox->itemText(m_index); + m_itemIcon = m_toolBox->itemIcon(m_index); +} + +void ToolBoxCommand::removePage() +{ + m_toolBox->removeItem(m_index); + + m_widget->hide(); + m_widget->setParent(formWindow()); + formWindow()->clearSelection(); + formWindow()->selectWidget(m_toolBox, true); + +} + +void ToolBoxCommand::addPage() +{ + m_widget->setParent(m_toolBox); + m_toolBox->insertItem(m_index, m_widget, m_itemIcon, m_itemText); + m_toolBox->setCurrentIndex(m_index); + + QDesignerPropertySheetExtension *sheet = qt_extension(formWindow()->core()->extensionManager(), m_toolBox); + if (sheet) { + qdesigner_internal::PropertySheetStringValue itemText(m_itemText); + sheet->setProperty(sheet->indexOf(u"currentItemText"_s), QVariant::fromValue(itemText)); + } + + m_widget->show(); + formWindow()->clearSelection(); + formWindow()->selectWidget(m_toolBox, true); +} + +// ---- MoveToolBoxPageCommand ---- +MoveToolBoxPageCommand::MoveToolBoxPageCommand(QDesignerFormWindowInterface *formWindow) : + ToolBoxCommand(formWindow), + m_newIndex(-1), + m_oldIndex(-1) +{ +} + +MoveToolBoxPageCommand::~MoveToolBoxPageCommand() = default; + +void MoveToolBoxPageCommand::init(QToolBox *toolBox, QWidget *page, int newIndex) +{ + ToolBoxCommand::init(toolBox); + setText(QApplication::translate("Command", "Move Page")); + + m_widget = page; + m_oldIndex = m_toolBox->indexOf(m_widget); + m_itemText = m_toolBox->itemText(m_oldIndex); + m_itemIcon = m_toolBox->itemIcon(m_oldIndex); + m_newIndex = newIndex; +} + +void MoveToolBoxPageCommand::redo() +{ + m_toolBox->removeItem(m_oldIndex); + m_toolBox->insertItem(m_newIndex, m_widget, m_itemIcon, m_itemText); +} + +void MoveToolBoxPageCommand::undo() +{ + m_toolBox->removeItem(m_newIndex); + m_toolBox->insertItem(m_oldIndex, m_widget, m_itemIcon, m_itemText); +} + +// ---- DeleteToolBoxPageCommand ---- +DeleteToolBoxPageCommand::DeleteToolBoxPageCommand(QDesignerFormWindowInterface *formWindow) + : ToolBoxCommand(formWindow) +{ +} + +DeleteToolBoxPageCommand::~DeleteToolBoxPageCommand() = default; + +void DeleteToolBoxPageCommand::init(QToolBox *toolBox) +{ + ToolBoxCommand::init(toolBox); + setText(QApplication::translate("Command", "Delete Page")); +} + +void DeleteToolBoxPageCommand::redo() +{ + removePage(); + cheapUpdate(); +} + +void DeleteToolBoxPageCommand::undo() +{ + addPage(); + cheapUpdate(); +} + +// ---- AddToolBoxPageCommand ---- +AddToolBoxPageCommand::AddToolBoxPageCommand(QDesignerFormWindowInterface *formWindow) + : ToolBoxCommand(formWindow) +{ +} + +AddToolBoxPageCommand::~AddToolBoxPageCommand() = default; + +void AddToolBoxPageCommand::init(QToolBox *toolBox) +{ + init(toolBox, InsertBefore); +} + +void AddToolBoxPageCommand::init(QToolBox *toolBox, InsertionMode mode) +{ + m_toolBox = toolBox; + + m_index = m_toolBox->currentIndex(); + if (mode == InsertAfter) + m_index++; + m_widget = new QDesignerWidget(formWindow(), m_toolBox); + m_itemText = QApplication::translate("Command", "Page"); + m_itemIcon = QIcon(); + m_widget->setObjectName(u"page"_s); + formWindow()->ensureUniqueObjectName(m_widget); + + setText(QApplication::translate("Command", "Insert Page")); + + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->add(m_widget); +} + +void AddToolBoxPageCommand::redo() +{ + addPage(); + cheapUpdate(); +} + +void AddToolBoxPageCommand::undo() +{ + removePage(); + cheapUpdate(); +} + +// ---- TabWidgetCommand ---- +TabWidgetCommand::TabWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_index(-1) +{ +} + +TabWidgetCommand::~TabWidgetCommand() = default; + +void TabWidgetCommand::init(QTabWidget *tabWidget) +{ + m_tabWidget = tabWidget; + m_index = m_tabWidget->currentIndex(); + m_widget = m_tabWidget->widget(m_index); + m_itemText = m_tabWidget->tabText(m_index); + m_itemIcon = m_tabWidget->tabIcon(m_index); +} + +void TabWidgetCommand::removePage() +{ + m_tabWidget->removeTab(m_index); + + m_widget->hide(); + m_widget->setParent(formWindow()); + m_tabWidget->setCurrentIndex(qMin(m_index, m_tabWidget->count())); + + formWindow()->clearSelection(); + formWindow()->selectWidget(m_tabWidget, true); +} + +void TabWidgetCommand::addPage() +{ + m_widget->setParent(nullptr); + m_tabWidget->insertTab(m_index, m_widget, m_itemIcon, m_itemText); + m_widget->show(); + m_tabWidget->setCurrentIndex(m_index); + + QDesignerPropertySheetExtension *sheet = qt_extension(formWindow()->core()->extensionManager(), m_tabWidget); + if (sheet) { + qdesigner_internal::PropertySheetStringValue itemText(m_itemText); + sheet->setProperty(sheet->indexOf(u"currentTabText"_s), + QVariant::fromValue(itemText)); + } + + formWindow()->clearSelection(); + formWindow()->selectWidget(m_tabWidget, true); +} + +// ---- DeleteTabPageCommand ---- +DeleteTabPageCommand::DeleteTabPageCommand(QDesignerFormWindowInterface *formWindow) + : TabWidgetCommand(formWindow) +{ +} + +DeleteTabPageCommand::~DeleteTabPageCommand() = default; + +void DeleteTabPageCommand::init(QTabWidget *tabWidget) +{ + TabWidgetCommand::init(tabWidget); + setText(QApplication::translate("Command", "Delete Page")); +} + +void DeleteTabPageCommand::redo() +{ + removePage(); + cheapUpdate(); +} + +void DeleteTabPageCommand::undo() +{ + addPage(); + cheapUpdate(); +} + +// ---- AddTabPageCommand ---- +AddTabPageCommand::AddTabPageCommand(QDesignerFormWindowInterface *formWindow) + : TabWidgetCommand(formWindow) +{ +} + +AddTabPageCommand::~AddTabPageCommand() = default; + +void AddTabPageCommand::init(QTabWidget *tabWidget) +{ + init(tabWidget, InsertBefore); +} + +void AddTabPageCommand::init(QTabWidget *tabWidget, InsertionMode mode) +{ + m_tabWidget = tabWidget; + + m_index = m_tabWidget->currentIndex(); + if (mode == InsertAfter) + m_index++; + m_widget = new QDesignerWidget(formWindow(), m_tabWidget); + m_itemText = QApplication::translate("Command", "Page"); + m_itemIcon = QIcon(); + m_widget->setObjectName(u"tab"_s); + formWindow()->ensureUniqueObjectName(m_widget); + + setText(QApplication::translate("Command", "Insert Page")); + + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->add(m_widget); +} + +void AddTabPageCommand::redo() +{ + addPage(); + cheapUpdate(); +} + +void AddTabPageCommand::undo() +{ + removePage(); + cheapUpdate(); +} + +// ---- MoveTabPageCommand ---- +MoveTabPageCommand::MoveTabPageCommand(QDesignerFormWindowInterface *formWindow) : + TabWidgetCommand(formWindow), + m_newIndex(-1), + m_oldIndex(-1) +{ +} + +MoveTabPageCommand::~MoveTabPageCommand() = default; + +void MoveTabPageCommand::init(QTabWidget *tabWidget, QWidget *page, + const QIcon &icon, const QString &label, + int index, int newIndex) +{ + TabWidgetCommand::init(tabWidget); + setText(QApplication::translate("Command", "Move Page")); + + m_page = page; + m_newIndex = newIndex; + m_oldIndex = index; + m_label = label; + m_icon = icon; +} + +void MoveTabPageCommand::redo() +{ + m_tabWidget->removeTab(m_oldIndex); + m_tabWidget->insertTab(m_newIndex, m_page, m_icon, m_label); + m_tabWidget->setCurrentIndex(m_newIndex); +} + +void MoveTabPageCommand::undo() +{ + m_tabWidget->removeTab(m_newIndex); + m_tabWidget->insertTab(m_oldIndex, m_page, m_icon, m_label); + m_tabWidget->setCurrentIndex(m_oldIndex); +} + +// ---- StackedWidgetCommand ---- +StackedWidgetCommand::StackedWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_index(-1) +{ +} + +StackedWidgetCommand::~StackedWidgetCommand() = default; + +void StackedWidgetCommand::init(QStackedWidget *stackedWidget) +{ + m_stackedWidget = stackedWidget; + m_index = m_stackedWidget->currentIndex(); + m_widget = m_stackedWidget->widget(m_index); +} + +void StackedWidgetCommand::removePage() +{ + m_stackedWidget->removeWidget(m_stackedWidget->widget(m_index)); + + m_widget->hide(); + m_widget->setParent(formWindow()); + + formWindow()->clearSelection(); + formWindow()->selectWidget(m_stackedWidget, true); +} + +void StackedWidgetCommand::addPage() +{ + m_stackedWidget->insertWidget(m_index, m_widget); + + m_widget->show(); + m_stackedWidget->setCurrentIndex(m_index); + + formWindow()->clearSelection(); + formWindow()->selectWidget(m_stackedWidget, true); +} + +// ---- MoveStackedWidgetCommand ---- +MoveStackedWidgetCommand::MoveStackedWidgetCommand(QDesignerFormWindowInterface *formWindow) : + StackedWidgetCommand(formWindow), + m_newIndex(-1), + m_oldIndex(-1) +{ +} + +MoveStackedWidgetCommand::~MoveStackedWidgetCommand() = default; + +void MoveStackedWidgetCommand::init(QStackedWidget *stackedWidget, QWidget *page, int newIndex) +{ + StackedWidgetCommand::init(stackedWidget); + setText(QApplication::translate("Command", "Move Page")); + + m_widget = page; + m_newIndex = newIndex; + m_oldIndex = m_stackedWidget->indexOf(m_widget); +} + +void MoveStackedWidgetCommand::redo() +{ + m_stackedWidget->removeWidget(m_widget); + m_stackedWidget->insertWidget(m_newIndex, m_widget); +} + +void MoveStackedWidgetCommand::undo() +{ + m_stackedWidget->removeWidget(m_widget); + m_stackedWidget->insertWidget(m_oldIndex, m_widget); +} + +// ---- DeleteStackedWidgetPageCommand ---- +DeleteStackedWidgetPageCommand::DeleteStackedWidgetPageCommand(QDesignerFormWindowInterface *formWindow) + : StackedWidgetCommand(formWindow) +{ +} + +DeleteStackedWidgetPageCommand::~DeleteStackedWidgetPageCommand() = default; + +void DeleteStackedWidgetPageCommand::init(QStackedWidget *stackedWidget) +{ + StackedWidgetCommand::init(stackedWidget); + setText(QApplication::translate("Command", "Delete Page")); +} + +void DeleteStackedWidgetPageCommand::redo() +{ + removePage(); + cheapUpdate(); +} + +void DeleteStackedWidgetPageCommand::undo() +{ + addPage(); + cheapUpdate(); +} + +// ---- AddStackedWidgetPageCommand ---- +AddStackedWidgetPageCommand::AddStackedWidgetPageCommand(QDesignerFormWindowInterface *formWindow) + : StackedWidgetCommand(formWindow) +{ +} + +AddStackedWidgetPageCommand::~AddStackedWidgetPageCommand() = default; + +void AddStackedWidgetPageCommand::init(QStackedWidget *stackedWidget) +{ + init(stackedWidget, InsertBefore); +} + +void AddStackedWidgetPageCommand::init(QStackedWidget *stackedWidget, InsertionMode mode) +{ + m_stackedWidget = stackedWidget; + + m_index = m_stackedWidget->currentIndex(); + if (mode == InsertAfter) + m_index++; + m_widget = new QDesignerWidget(formWindow(), m_stackedWidget); + m_widget->setObjectName(u"page"_s); + formWindow()->ensureUniqueObjectName(m_widget); + + setText(QApplication::translate("Command", "Insert Page")); + + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->add(m_widget); +} + +void AddStackedWidgetPageCommand::redo() +{ + addPage(); + cheapUpdate(); +} + +void AddStackedWidgetPageCommand::undo() +{ + removePage(); + cheapUpdate(); +} + +// ---- TabOrderCommand ---- +TabOrderCommand::TabOrderCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Change Tab order"), formWindow), + m_widgetItem(nullptr) +{ +} + +void TabOrderCommand::init(const QWidgetList &newTabOrder) +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + Q_ASSERT(core); + + m_widgetItem = core->metaDataBase()->item(formWindow()); + Q_ASSERT(m_widgetItem); + m_oldTabOrder = m_widgetItem->tabOrder(); + m_newTabOrder = newTabOrder; +} + +void TabOrderCommand::redo() +{ + m_widgetItem->setTabOrder(m_newTabOrder); +} + +void TabOrderCommand::undo() +{ + m_widgetItem->setTabOrder(m_oldTabOrder); +} + +// ---- CreateMenuBarCommand ---- +CreateMenuBarCommand::CreateMenuBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Create Menu Bar"), formWindow) +{ +} + +void CreateMenuBarCommand::init(QMainWindow *mainWindow) +{ + m_mainWindow = mainWindow; + QDesignerFormEditorInterface *core = formWindow()->core(); + m_menuBar = qobject_cast(core->widgetFactory()->createWidget(u"QMenuBar"_s, m_mainWindow)); + core->widgetFactory()->initialize(m_menuBar); +} + +void CreateMenuBarCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c; + c = qt_extension(core->extensionManager(), m_mainWindow); + c->addWidget(m_menuBar); + + m_menuBar->setObjectName(u"menuBar"_s); + formWindow()->ensureUniqueObjectName(m_menuBar); + core->metaDataBase()->add(m_menuBar); + formWindow()->emitSelectionChanged(); + m_menuBar->setFocus(); +} + +void CreateMenuBarCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c; + c = qt_extension(core->extensionManager(), m_mainWindow); + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == m_menuBar) { + c->remove(i); + break; + } + } + + core->metaDataBase()->remove(m_menuBar); + formWindow()->emitSelectionChanged(); +} + +// ---- DeleteMenuBarCommand ---- +DeleteMenuBarCommand::DeleteMenuBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Delete Menu Bar"), formWindow) +{ +} + +void DeleteMenuBarCommand::init(QMenuBar *menuBar) +{ + m_menuBar = menuBar; + m_mainWindow = qobject_cast(menuBar->parentWidget()); +} + +void DeleteMenuBarCommand::redo() +{ + if (m_mainWindow) { + QDesignerContainerExtension *c; + c = qt_extension(core()->extensionManager(), m_mainWindow); + Q_ASSERT(c != nullptr); + for (int i=0; icount(); ++i) { + if (c->widget(i) == m_menuBar) { + c->remove(i); + break; + } + } + } + + core()->metaDataBase()->remove(m_menuBar); + m_menuBar->hide(); + m_menuBar->setParent(formWindow()); + formWindow()->emitSelectionChanged(); +} + +void DeleteMenuBarCommand::undo() +{ + if (m_mainWindow) { + m_menuBar->setParent(m_mainWindow); + QDesignerContainerExtension *c; + c = qt_extension(core()->extensionManager(), m_mainWindow); + + c->addWidget(m_menuBar); + + core()->metaDataBase()->add(m_menuBar); + m_menuBar->show(); + formWindow()->emitSelectionChanged(); + } +} + +// ---- CreateStatusBarCommand ---- +CreateStatusBarCommand::CreateStatusBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Create Status Bar"), formWindow) +{ +} + +void CreateStatusBarCommand::init(QMainWindow *mainWindow) +{ + m_mainWindow = mainWindow; + QDesignerFormEditorInterface *core = formWindow()->core(); + m_statusBar = qobject_cast(core->widgetFactory()->createWidget(u"QStatusBar"_s, m_mainWindow)); + core->widgetFactory()->initialize(m_statusBar); +} + +void CreateStatusBarCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c; + c = qt_extension(core->extensionManager(), m_mainWindow); + c->addWidget(m_statusBar); + + m_statusBar->setObjectName(u"statusBar"_s); + formWindow()->ensureUniqueObjectName(m_statusBar); + core->metaDataBase()->add(m_statusBar); + formWindow()->emitSelectionChanged(); +} + +void CreateStatusBarCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == m_statusBar) { + c->remove(i); + break; + } + } + + core->metaDataBase()->remove(m_statusBar); + formWindow()->emitSelectionChanged(); +} + +// ---- DeleteStatusBarCommand ---- +DeleteStatusBarCommand::DeleteStatusBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Delete Status Bar"), formWindow) +{ +} + +void DeleteStatusBarCommand::init(QStatusBar *statusBar) +{ + m_statusBar = statusBar; + m_mainWindow = qobject_cast(statusBar->parentWidget()); +} + +void DeleteStatusBarCommand::redo() +{ + if (m_mainWindow) { + QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), m_mainWindow); + Q_ASSERT(c != nullptr); + for (int i=0; icount(); ++i) { + if (c->widget(i) == m_statusBar) { + c->remove(i); + break; + } + } + } + + core()->metaDataBase()->remove(m_statusBar); + m_statusBar->hide(); + m_statusBar->setParent(formWindow()); + formWindow()->emitSelectionChanged(); +} + +void DeleteStatusBarCommand::undo() +{ + if (m_mainWindow) { + m_statusBar->setParent(m_mainWindow); + QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), m_mainWindow); + + c->addWidget(m_statusBar); + + core()->metaDataBase()->add(m_statusBar); + m_statusBar->show(); + formWindow()->emitSelectionChanged(); + } +} + +// ---- AddToolBarCommand ---- +AddToolBarCommand::AddToolBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Add Tool Bar"), formWindow) +{ +} + +void AddToolBarCommand::init(QMainWindow *mainWindow, Qt::ToolBarArea area) +{ + m_mainWindow = mainWindow; + QDesignerWidgetFactoryInterface * wf = formWindow()->core()->widgetFactory(); + // Pass on 0 parent first to avoid reparenting flicker. + m_toolBar = qobject_cast(wf->createWidget(u"QToolBar"_s, nullptr)); + m_toolBar->setProperty("_q_desiredArea", QVariant(area)); + wf->initialize(m_toolBar); + m_toolBar->hide(); +} + +void AddToolBarCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->add(m_toolBar); + + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + c->addWidget(m_toolBar); + + m_toolBar->setObjectName(u"toolBar"_s); + formWindow()->ensureUniqueObjectName(m_toolBar); + setPropertySheetWindowTitle(core, m_toolBar, m_toolBar->objectName()); + formWindow()->emitSelectionChanged(); +} + +void AddToolBarCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->remove(m_toolBar); + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == m_toolBar) { + c->remove(i); + break; + } + } + formWindow()->emitSelectionChanged(); +} + +// ---- DockWidgetCommand:: ---- +DockWidgetCommand::DockWidgetCommand(const QString &description, QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(description, formWindow) +{ +} + +DockWidgetCommand::~DockWidgetCommand() = default; + +void DockWidgetCommand::init(QDockWidget *dockWidget) +{ + m_dockWidget = dockWidget; +} + +// ---- AddDockWidgetCommand ---- +AddDockWidgetCommand::AddDockWidgetCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Add Dock Window"), formWindow) +{ +} + +void AddDockWidgetCommand::init(QMainWindow *mainWindow, QDockWidget *dockWidget) +{ + m_mainWindow = mainWindow; + m_dockWidget = dockWidget; +} + +void AddDockWidgetCommand::init(QMainWindow *mainWindow) +{ + m_mainWindow = mainWindow; + QDesignerFormEditorInterface *core = formWindow()->core(); + m_dockWidget = qobject_cast(core->widgetFactory()->createWidget(u"QDockWidget"_s, m_mainWindow)); +} + +void AddDockWidgetCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + c->addWidget(m_dockWidget); + + m_dockWidget->setObjectName(u"dockWidget"_s); + formWindow()->ensureUniqueObjectName(m_dockWidget); + formWindow()->manageWidget(m_dockWidget); + formWindow()->emitSelectionChanged(); +} + +void AddDockWidgetCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == m_dockWidget) { + c->remove(i); + break; + } + } + + formWindow()->unmanageWidget(m_dockWidget); + formWindow()->emitSelectionChanged(); +} + +// ---- AdjustWidgetSizeCommand ---- +AdjustWidgetSizeCommand::AdjustWidgetSizeCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ +} + +void AdjustWidgetSizeCommand::init(QWidget *widget) +{ + m_widget = widget; + setText(QApplication::translate("Command", "Adjust Size of '%1'").arg(widget->objectName())); +} + +QWidget *AdjustWidgetSizeCommand::widgetForAdjust() const +{ + QDesignerFormWindowInterface *fw = formWindow(); + // Return the outer, embedding widget if it is the main container + if (Utils::isCentralWidget(fw, m_widget)) + return fw->core()->integration()->containerWindow(m_widget); + return m_widget; +} + +void AdjustWidgetSizeCommand::redo() +{ + QWidget *aw = widgetForAdjust(); + m_geometry = aw->geometry(); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + aw->adjustSize(); + const bool isMainContainer = aw != m_widget; + if (!isMainContainer) { + /* When doing adjustsize on a selected non-laid out child that has been enlarged + * and pushed partially over the top/left edge[s], it is possible that it "disappears" + * when shrinking. In that case, move it back so that it remains visible. */ + if (aw->parentWidget()->layout() == nullptr) { + const QRect contentsRect = aw->parentWidget()->contentsRect(); + const QRect newGeometry = aw->geometry(); + QPoint newPos = m_geometry.topLeft(); + if (newGeometry.bottom() <= contentsRect.y()) + newPos.setY(contentsRect.y()); + if (newGeometry.right() <= contentsRect.x()) + newPos.setX(contentsRect.x()); + if (newPos != m_geometry.topLeft()) + aw->move(newPos); + } + } + updatePropertyEditor(); +} + +void AdjustWidgetSizeCommand::undo() +{ + QWidget *aw = widgetForAdjust(); + aw->resize(m_geometry.size()); + if (m_geometry.topLeft() != aw->geometry().topLeft()) + aw->move(m_geometry.topLeft()); + updatePropertyEditor(); +} + +void AdjustWidgetSizeCommand::updatePropertyEditor() const +{ + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == m_widget) + propertyEditor->setPropertyValue(u"geometry"_s, m_widget->geometry(), true); + } +} +// ------------ ChangeFormLayoutItemRoleCommand + +ChangeFormLayoutItemRoleCommand::ChangeFormLayoutItemRoleCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Change Form Layout Item Geometry"), formWindow), + m_operation(SpanningToLabel) +{ +} + +void ChangeFormLayoutItemRoleCommand::init(QWidget *widget, Operation op) +{ + m_widget = widget; + m_operation = op; +} + +void ChangeFormLayoutItemRoleCommand::redo() +{ + doOperation(m_operation); +} + +void ChangeFormLayoutItemRoleCommand::undo() +{ + doOperation(reverseOperation(m_operation)); +} + +ChangeFormLayoutItemRoleCommand::Operation ChangeFormLayoutItemRoleCommand::reverseOperation(Operation op) +{ + switch (op) { + case SpanningToLabel: + return LabelToSpanning; + case SpanningToField: + return FieldToSpanning; + case LabelToSpanning: + return SpanningToLabel; + case FieldToSpanning: + return SpanningToField; + } + return SpanningToField; +} + +void ChangeFormLayoutItemRoleCommand::doOperation(Operation op) +{ + QFormLayout *fl = ChangeFormLayoutItemRoleCommand::managedFormLayoutOf(formWindow()->core(), m_widget); + const int index = fl->indexOf(m_widget); + Q_ASSERT(index != -1); + int row; + QFormLayout::ItemRole role; + fl->getItemPosition (index, &row, &role); + Q_ASSERT(index != -1); + QLayoutItem *item = fl->takeAt(index); + const QRect area = QRect(0, row, 2, 1); + switch (op) { + case SpanningToLabel: + fl->setItem(row, QFormLayout::LabelRole, item); + QLayoutSupport::createEmptyCells(fl); + break; + case SpanningToField: + fl->setItem(row, QFormLayout::FieldRole, item); + QLayoutSupport::createEmptyCells(fl); + break; + case LabelToSpanning: + case FieldToSpanning: + QLayoutSupport::removeEmptyCells(fl, area); + fl->setItem(row, QFormLayout::SpanningRole, item); + break; + } +} + +unsigned ChangeFormLayoutItemRoleCommand::possibleOperations(QDesignerFormEditorInterface *core, QWidget *w) +{ + QFormLayout *fl = managedFormLayoutOf(core, w); + if (!fl) + return 0; + const int index = fl->indexOf(w); + if (index == -1) + return 0; + int row, col, colspan; + getFormLayoutItemPosition(fl, index, &row, &col, nullptr, &colspan); + // Spanning item? + if (colspan > 1) + return SpanningToLabel|SpanningToField; + // Is the neighbouring column free, that is, can the current item be expanded? + const QFormLayout::ItemRole neighbouringRole = col == 0 ? QFormLayout::FieldRole : QFormLayout::LabelRole; + const bool empty = LayoutInfo::isEmptyItem(fl->itemAt(row, neighbouringRole)); + if (empty) + return col == 0 ? LabelToSpanning : FieldToSpanning; + return 0; +} + +QFormLayout *ChangeFormLayoutItemRoleCommand::managedFormLayoutOf(QDesignerFormEditorInterface *core, QWidget *w) +{ + if (QLayout *layout = LayoutInfo::managedLayout(core, w->parentWidget())) + if (QFormLayout *fl = qobject_cast(layout)) + return fl; + return nullptr; +} + +// ---- ChangeLayoutItemGeometry ---- +ChangeLayoutItemGeometry::ChangeLayoutItemGeometry(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Change Layout Item Geometry"), formWindow) +{ +} + +void ChangeLayoutItemGeometry::init(QWidget *widget, int row, int column, int rowspan, int colspan) +{ + m_widget = widget; + Q_ASSERT(m_widget->parentWidget() != nullptr); + + QLayout *layout = LayoutInfo::managedLayout(formWindow()->core(), m_widget->parentWidget()); + Q_ASSERT(layout != nullptr); + + QGridLayout *grid = qobject_cast(layout); + Q_ASSERT(grid != nullptr); + + const int itemIndex = grid->indexOf(m_widget); + Q_ASSERT(itemIndex != -1); + + int current_row, current_column, current_rowspan, current_colspan; + grid->getItemPosition(itemIndex, ¤t_row, ¤t_column, ¤t_rowspan, ¤t_colspan); + + m_oldInfo.setRect(current_column, current_row, current_colspan, current_rowspan); + m_newInfo.setRect(column, row, colspan, rowspan); +} + +void ChangeLayoutItemGeometry::changeItemPosition(const QRect &g) +{ + QLayout *layout = LayoutInfo::managedLayout(formWindow()->core(), m_widget->parentWidget()); + Q_ASSERT(layout != nullptr); + + QGridLayout *grid = qobject_cast(layout); + Q_ASSERT(grid != nullptr); + + const int itemIndex = grid->indexOf(m_widget); + Q_ASSERT(itemIndex != -1); + + QLayoutItem *item = grid->takeAt(itemIndex); + delete item; + + if (!QLayoutSupport::removeEmptyCells(grid, g)) + qWarning() << "ChangeLayoutItemGeometry::changeItemPosition: Nonempty cell at " << g << '.'; + + grid->addWidget(m_widget, g.top(), g.left(), g.height(), g.width()); + + grid->invalidate(); + grid->activate(); + + QLayoutSupport::createEmptyCells(grid); + + formWindow()->clearSelection(false); + formWindow()->selectWidget(m_widget, true); +} + +void ChangeLayoutItemGeometry::redo() +{ + changeItemPosition(m_newInfo); +} + +void ChangeLayoutItemGeometry::undo() +{ + changeItemPosition(m_oldInfo); +} + +// ---- ContainerWidgetCommand ---- +ContainerWidgetCommand::ContainerWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_index(-1) +{ +} + +ContainerWidgetCommand::~ContainerWidgetCommand() = default; + +QDesignerContainerExtension *ContainerWidgetCommand::containerExtension() const +{ + QExtensionManager *mgr = core()->extensionManager(); + return qt_extension(mgr, m_containerWidget); +} + +void ContainerWidgetCommand::init(QWidget *containerWidget) +{ + m_containerWidget = containerWidget; + + if (QDesignerContainerExtension *c = containerExtension()) { + m_index = c->currentIndex(); + m_widget = c->widget(m_index); + } +} + +void ContainerWidgetCommand::removePage() +{ + if (QDesignerContainerExtension *c = containerExtension()) { + if (const int count = c->count()) { + // Undo add after last? + const int deleteIndex = m_index >= 0 ? m_index : count - 1; + c->remove(deleteIndex); + m_widget->hide(); + m_widget->setParent(formWindow()); + } + } +} + +void ContainerWidgetCommand::addPage() +{ + if (QDesignerContainerExtension *c = containerExtension()) { + int newCurrentIndex; + if (m_index >= 0) { + c->insertWidget(m_index, m_widget); + newCurrentIndex = m_index; + } else { + c->addWidget(m_widget); + newCurrentIndex = c->count() -1 ; + } + m_widget->show(); + c->setCurrentIndex(newCurrentIndex); + } +} + +// ---- DeleteContainerWidgetPageCommand ---- +DeleteContainerWidgetPageCommand::DeleteContainerWidgetPageCommand(QDesignerFormWindowInterface *formWindow) + : ContainerWidgetCommand(formWindow) +{ +} + +DeleteContainerWidgetPageCommand::~DeleteContainerWidgetPageCommand() = default; + +void DeleteContainerWidgetPageCommand::init(QWidget *containerWidget, ContainerType ct) +{ + ContainerWidgetCommand::init(containerWidget); + switch (ct) { + case WizardContainer: + case PageContainer: + setText(QApplication::translate("Command", "Delete Page")); + break; + case MdiContainer: + setText(QApplication::translate("Command", "Delete Subwindow")); + break; + } +} + +void DeleteContainerWidgetPageCommand::redo() +{ + removePage(); + cheapUpdate(); +} + +void DeleteContainerWidgetPageCommand::undo() +{ + addPage(); + cheapUpdate(); +} + +// ---- AddContainerWidgetPageCommand ---- +AddContainerWidgetPageCommand::AddContainerWidgetPageCommand(QDesignerFormWindowInterface *formWindow) + : ContainerWidgetCommand(formWindow) +{ +} + +AddContainerWidgetPageCommand::~AddContainerWidgetPageCommand() = default; + +void AddContainerWidgetPageCommand::init(QWidget *containerWidget, ContainerType ct, InsertionMode mode) +{ + m_containerWidget = containerWidget; + + if (QDesignerContainerExtension *c = containerExtension()) { + m_index = c->currentIndex(); + if (m_index >= 0 && mode == InsertAfter) + m_index++; + m_widget = nullptr; + const QDesignerFormEditorInterface *core = formWindow()->core(); + switch (ct) { + case PageContainer: + setText(QApplication::translate("Command", "Insert Page")); + m_widget = new QDesignerWidget(formWindow(), m_containerWidget); + m_widget->setObjectName(u"page"_s); + break; + case MdiContainer: + setText(QApplication::translate("Command", "Insert Subwindow")); + m_widget = new QDesignerWidget(formWindow(), m_containerWidget); + m_widget->setObjectName(u"subwindow"_s); + setPropertySheetWindowTitle(core, m_widget, QApplication::translate("Command", "Subwindow")); + break; + case WizardContainer: // Apply style, don't manage + m_widget = core->widgetFactory()->createWidget(u"QWizardPage"_s, nullptr); + break; + } + formWindow()->ensureUniqueObjectName(m_widget); + core->metaDataBase()->add(m_widget); + } +} + +void AddContainerWidgetPageCommand::redo() +{ + addPage(); + cheapUpdate(); +} + +void AddContainerWidgetPageCommand::undo() +{ + removePage(); + cheapUpdate(); +} + +ChangeCurrentPageCommand::ChangeCurrentPageCommand(QDesignerFormWindowInterface *formWindow) + : + QDesignerFormWindowCommand(QString(), formWindow), m_oldIndex(0), m_newIndex(0) +{ +} + +ChangeCurrentPageCommand::~ChangeCurrentPageCommand() = default; + +QDesignerContainerExtension *ChangeCurrentPageCommand::containerExtension() const +{ + QExtensionManager *mgr = core()->extensionManager(); + return qt_extension(mgr, m_containerWidget); +} + +void ChangeCurrentPageCommand::init(QWidget *containerWidget, int newIndex) +{ + m_containerWidget = containerWidget; + + if (QDesignerContainerExtension *c = containerExtension()) { + m_newIndex = newIndex; + m_oldIndex = c->currentIndex(); + m_widget = c->widget(m_oldIndex); + } +} + +void ChangeCurrentPageCommand::redo() +{ + containerExtension()->setCurrentIndex(m_newIndex); +} + +void ChangeCurrentPageCommand::undo() +{ + containerExtension()->setCurrentIndex(m_oldIndex); +} + +static const int itemRoles[] = { + Qt::DecorationPropertyRole, + Qt::DisplayPropertyRole, + Qt::ToolTipPropertyRole, + Qt::StatusTipPropertyRole, + Qt::WhatsThisPropertyRole, + Qt::FontRole, + Qt::TextAlignmentRole, + Qt::BackgroundRole, + Qt::ForegroundRole, + Qt::CheckStateRole +}; + +template +static void copyRoleFromItem(ItemData *id, int role, const T *item) +{ + QVariant v = item->data(role); + if (v.isValid()) + id->m_properties.insert(role, v); +} + +template +static void copyRolesFromItem(ItemData *id, const T *item, bool editor) +{ + static const Qt::ItemFlags defaultFlags = T().flags(); + + for (int i : itemRoles) + copyRoleFromItem(id, i, item); + + if (editor) + copyRoleFromItem(id, ItemFlagsShadowRole, item); + else if (item->flags() != defaultFlags) + id->m_properties.insert(ItemFlagsShadowRole, QVariant::fromValue(int(item->flags()))); +} + +template +static void copyRolesToItem(const ItemData *id, T *item, DesignerIconCache *iconCache, bool editor) +{ + for (auto it = id->m_properties.cbegin(), end = id->m_properties.cend(); it != end; ++it) { + if (it.value().isValid()) { + if (!editor && it.key() == ItemFlagsShadowRole) { + item->setFlags((Qt::ItemFlags)it.value().toInt()); + } else { + item->setData(it.key(), it.value()); + switch (it.key()) { + case Qt::DecorationPropertyRole: + if (iconCache) + item->setIcon(iconCache->icon(qvariant_cast(it.value()))); + break; + case Qt::DisplayPropertyRole: + item->setText(qvariant_cast(it.value()).value()); + break; + case Qt::ToolTipPropertyRole: + item->setToolTip(qvariant_cast(it.value()).value()); + break; + case Qt::StatusTipPropertyRole: + item->setStatusTip(qvariant_cast(it.value()).value()); + break; + case Qt::WhatsThisPropertyRole: + item->setWhatsThis(qvariant_cast(it.value()).value()); + break; + } + } + } + } + + if (editor) + item->setFlags(item->flags() | Qt::ItemIsEditable); +} + +ItemData::ItemData(const QListWidgetItem *item, bool editor) +{ + copyRolesFromItem(this, item, editor); +} + +QListWidgetItem *ItemData::createListItem(DesignerIconCache *iconCache, bool editor) const +{ + QListWidgetItem *item = new QListWidgetItem(); + copyRolesToItem(this, item, iconCache, editor); + return item; +} + +ItemData::ItemData(const QTableWidgetItem *item, bool editor) +{ + copyRolesFromItem(this, item, editor); +} + +QTableWidgetItem *ItemData::createTableItem(DesignerIconCache *iconCache, bool editor) const +{ + QTableWidgetItem *item = new QTableWidgetItem; + copyRolesToItem(this, item, iconCache, editor); + return item; +} + +static void copyRoleFromItem(ItemData *id, int role, const QTreeWidgetItem *item, int column) +{ + QVariant v = item->data(column, role); + if (v.isValid()) + id->m_properties.insert(role, v); +} + +ItemData::ItemData(const QTreeWidgetItem *item, int column) +{ + copyRoleFromItem(this, Qt::EditRole, item, column); + PropertySheetStringValue str(item->text(column)); + m_properties.insert(Qt::DisplayPropertyRole, QVariant::fromValue(str)); + + for (int i : itemRoles) + copyRoleFromItem(this, i, item, column); +} + +void ItemData::fillTreeItemColumn(QTreeWidgetItem *item, int column, DesignerIconCache *iconCache) const +{ + for (auto it = m_properties.cbegin(), end = m_properties.cend(); it != end; ++it) { + if (it.value().isValid()) { + item->setData(column, it.key(), it.value()); + switch (it.key()) { + case Qt::DecorationPropertyRole: + if (iconCache) + item->setIcon(column, iconCache->icon(qvariant_cast(it.value()))); + break; + case Qt::DisplayPropertyRole: + item->setText(column, qvariant_cast(it.value()).value()); + break; + case Qt::ToolTipPropertyRole: + item->setToolTip(column, qvariant_cast(it.value()).value()); + break; + case Qt::StatusTipPropertyRole: + item->setStatusTip(column, qvariant_cast(it.value()).value()); + break; + case Qt::WhatsThisPropertyRole: + item->setWhatsThis(column, qvariant_cast(it.value()).value()); + break; + } + } + } +} + +ListContents::ListContents(const QTreeWidgetItem *item) +{ + for (int i = 0; i < item->columnCount(); i++) + m_items.append(ItemData(item, i)); +} + +QTreeWidgetItem *ListContents::createTreeItem(DesignerIconCache *iconCache) const +{ + QTreeWidgetItem *item = new QTreeWidgetItem; + int i = 0; + for (const ItemData &id : m_items) + id.fillTreeItemColumn(item, i++, iconCache); + return item; +} + +void ListContents::createFromListWidget(const QListWidget *listWidget, bool editor) +{ + m_items.clear(); + + for (int i = 0; i < listWidget->count(); i++) + m_items.append(ItemData(listWidget->item(i), editor)); +} + +void ListContents::applyToListWidget(QListWidget *listWidget, DesignerIconCache *iconCache, + bool editor, Qt::Alignment alignmentDefault) const +{ + listWidget->clear(); + + int i = 0; + for (const ItemData &entry : m_items) { + auto *item = entry.isValid() + ? entry.createListItem(iconCache, editor) + : new QListWidgetItem(TableWidgetContents::defaultHeaderText(i)); + if (item->textAlignment() == 0) + item->setTextAlignment(alignmentDefault); + listWidget->addItem(item); + i++; + } +} + +void ListContents::createFromComboBox(const QComboBox *comboBox) +{ + m_items.clear(); + + const int count = comboBox->count(); + for (int i = 0; i < count; i++) { + // We might encounter items added in a custom combo + // constructor. Ignore those. + const QVariant textValue = comboBox->itemData(i, Qt::DisplayPropertyRole); + if (!textValue.isNull()) { + ItemData entry; + entry.m_properties.insert(Qt::DisplayPropertyRole, textValue); + const QVariant iconValue = comboBox->itemData(i, Qt::DecorationPropertyRole); + if (!iconValue.isNull()) + entry.m_properties.insert(Qt::DecorationPropertyRole, iconValue); + m_items.append(entry); + } + } +} + +void ListContents::applyToComboBox(QComboBox *comboBox, DesignerIconCache *iconCache) const +{ + comboBox->clear(); + + for (const ItemData &hash : m_items) { + QIcon icon; + if (iconCache) + icon = iconCache->icon(qvariant_cast( + hash.m_properties[Qt::DecorationPropertyRole])); + QVariant var = hash.m_properties[Qt::DisplayPropertyRole]; + PropertySheetStringValue str = qvariant_cast(var); + comboBox->addItem(icon, str.value()); + comboBox->setItemData(comboBox->count() - 1, + var, + Qt::DisplayPropertyRole); + comboBox->setItemData(comboBox->count() - 1, + hash.m_properties[Qt::DecorationPropertyRole], + Qt::DecorationPropertyRole); + } +} + +// --------- TableWidgetContents + +TableWidgetContents::TableWidgetContents() = default; + +void TableWidgetContents::clear() +{ + m_horizontalHeader.m_items.clear(); + m_verticalHeader.m_items.clear(); + m_items.clear(); + m_columnCount = 0; + m_rowCount = 0; +} + +QString TableWidgetContents::defaultHeaderText(int i) +{ + return QString::number(i + 1); +} + +bool TableWidgetContents::nonEmpty(const QTableWidgetItem *item, int headerColumn) +{ + static const Qt::ItemFlags defaultFlags = QTableWidgetItem().flags(); + + if (item->flags() != defaultFlags) + return true; + + QString text = qvariant_cast(item->data(Qt::DisplayPropertyRole)).value(); + if (!text.isEmpty()) { + if (headerColumn < 0 || text != defaultHeaderText(headerColumn)) + return true; + } else { + // FIXME: This doesn't seem to make sense + return true; + } + + for (int i : itemRoles) { + if (i != Qt::DisplayPropertyRole && item->data(i).isValid()) + return true; + } + + return false; +} + +void TableWidgetContents::insertHeaderItem(const QTableWidgetItem *item, int i, ListContents *header, bool editor) +{ + if (nonEmpty(item, i)) + header->m_items.append(ItemData(item, editor)); + else + header->m_items.append(ItemData()); +} + +void TableWidgetContents::fromTableWidget(const QTableWidget *tableWidget, bool editor) +{ + clear(); + m_columnCount = tableWidget->columnCount(); + m_rowCount = tableWidget->rowCount(); + // horiz header: Legacy behaviour: auto-generate number for empty items + for (int col = 0; col < m_columnCount; col++) + if (const QTableWidgetItem *item = tableWidget->horizontalHeaderItem(col)) + insertHeaderItem(item, col, &m_horizontalHeader, editor); + // vertical header: Legacy behaviour: auto-generate number for empty items + for (int row = 0; row < m_rowCount; row++) + if (const QTableWidgetItem *item = tableWidget->verticalHeaderItem(row)) + insertHeaderItem(item, row, &m_verticalHeader, editor); + // cell data + for (int col = 0; col < m_columnCount; col++) + for (int row = 0; row < m_rowCount; row++) + if (const QTableWidgetItem *item = tableWidget->item(row, col)) + if (nonEmpty(item, -1)) + m_items.insert(CellRowColumnAddress(row, col), ItemData(item, editor)); +} + +void TableWidgetContents::applyToTableWidget(QTableWidget *tableWidget, DesignerIconCache *iconCache, bool editor) const +{ + tableWidget->clear(); + + tableWidget->setColumnCount(m_columnCount); + tableWidget->setRowCount(m_rowCount); + + // horiz header + int col = 0; + for (const ItemData &id : m_horizontalHeader.m_items) { + if (id.isValid()) + tableWidget->setHorizontalHeaderItem(col, id.createTableItem(iconCache, editor)); + col++; + } + // vertical header + int row = 0; + for (const ItemData &id : m_verticalHeader.m_items) { + if (id.isValid()) + tableWidget->setVerticalHeaderItem(row, id.createTableItem(iconCache, editor)); + row++; + } + // items + for (auto it = m_items.cbegin(), icend = m_items.cend(); it != icend; ++ it) { + tableWidget->setItem(it.key().first, it.key().second, + it.value().createTableItem(iconCache, editor)); + } +} + +bool comparesEqual(const TableWidgetContents &lhs, + const TableWidgetContents &rhs) noexcept +{ + return lhs.m_columnCount == rhs.m_columnCount && lhs.m_rowCount == rhs.m_rowCount && + lhs.m_horizontalHeader.m_items == rhs.m_horizontalHeader.m_items && + lhs.m_verticalHeader.m_items == rhs.m_verticalHeader.m_items && + lhs.m_items == rhs.m_items; +} + +// ---- ChangeTableContentsCommand ---- +ChangeTableContentsCommand::ChangeTableContentsCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Change Table Contents"), + formWindow), m_iconCache(nullptr) +{ + FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + m_iconCache = fwb->iconCache(); +} + +void ChangeTableContentsCommand::init(QTableWidget *tableWidget, + const TableWidgetContents &oldCont, const TableWidgetContents &newCont) +{ + m_tableWidget = tableWidget; + m_oldContents = oldCont; + m_newContents = newCont; +} + +void ChangeTableContentsCommand::redo() +{ + m_newContents.applyToTableWidget(m_tableWidget, m_iconCache, false); + QMetaObject::invokeMethod(m_tableWidget, "updateGeometries"); +} + +void ChangeTableContentsCommand::undo() +{ + m_oldContents.applyToTableWidget(m_tableWidget, m_iconCache, false); + QMetaObject::invokeMethod(m_tableWidget, "updateGeometries"); +} + +// --------- TreeWidgetContents +TreeWidgetContents::ItemContents::ItemContents(const QTreeWidgetItem *item, bool editor) : + ListContents(item) +{ + static const Qt::ItemFlags defaultFlags = QTreeWidgetItem().flags(); + + if (editor) { + QVariant v = item->data(0, ItemFlagsShadowRole); + m_itemFlags = v.isValid() ? v.toInt() : -1; + } else { + m_itemFlags = (item->flags() != defaultFlags) ? int(item->flags()) : -1; + } + + for (int i = 0; i < item->childCount(); i++) + m_children.append(ItemContents(item->child(i), editor)); +} + +QTreeWidgetItem *TreeWidgetContents::ItemContents::createTreeItem(DesignerIconCache *iconCache, bool editor) const +{ + QTreeWidgetItem *item = ListContents::createTreeItem(iconCache); + + if (editor) + item->setFlags(item->flags() | Qt::ItemIsEditable); + + if (m_itemFlags != -1) { + if (editor) + item->setData(0, ItemFlagsShadowRole, QVariant::fromValue(m_itemFlags)); + else + item->setFlags((Qt::ItemFlags)m_itemFlags); + } + + for (const ItemContents &ic : m_children) + item->addChild(ic.createTreeItem(iconCache, editor)); + + return item; +} + +bool comparesEqual(const TreeWidgetContents::ItemContents &lhs, + const TreeWidgetContents::ItemContents &rhs) noexcept +{ + return lhs.m_itemFlags == rhs.m_itemFlags && lhs.m_items == rhs.m_items + && lhs.m_children == rhs.m_children; +} + +void TreeWidgetContents::clear() +{ + m_headerItem.m_items.clear(); + m_rootItems.clear(); +} + +void TreeWidgetContents::fromTreeWidget(const QTreeWidget *treeWidget, bool editor) +{ + clear(); + m_headerItem = ListContents(treeWidget->headerItem()); + for (int col = 0; col < treeWidget->topLevelItemCount(); col++) + m_rootItems.append(ItemContents(treeWidget->topLevelItem(col), editor)); +} + +void TreeWidgetContents::applyToTreeWidget(QTreeWidget *treeWidget, DesignerIconCache *iconCache, bool editor) const +{ + treeWidget->clear(); + + treeWidget->setColumnCount(m_headerItem.m_items.size()); + treeWidget->setHeaderItem(m_headerItem.createTreeItem(iconCache)); + for (const ItemContents &ic : m_rootItems) + treeWidget->addTopLevelItem(ic.createTreeItem(iconCache, editor)); + treeWidget->expandAll(); +} + +// ---- ChangeTreeContentsCommand ---- +ChangeTreeContentsCommand::ChangeTreeContentsCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Change Tree Contents"), formWindow), + m_iconCache(nullptr) +{ + FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + m_iconCache = fwb->iconCache(); +} + +void ChangeTreeContentsCommand::init(QTreeWidget *treeWidget, + const TreeWidgetContents &oldState, const TreeWidgetContents &newState) +{ + m_treeWidget = treeWidget; + m_oldState = oldState; + m_newState = newState; +} + +void ChangeTreeContentsCommand::redo() +{ + m_newState.applyToTreeWidget(m_treeWidget, m_iconCache, false); +} + +void ChangeTreeContentsCommand::undo() +{ + m_oldState.applyToTreeWidget(m_treeWidget, m_iconCache, false); +} + +// ---- ChangeListContentsCommand ---- +ChangeListContentsCommand::ChangeListContentsCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow), m_iconCache(nullptr) +{ + FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + m_iconCache = fwb->iconCache(); +} + +void ChangeListContentsCommand::init(QListWidget *listWidget, + const ListContents &oldItems, const ListContents &items) +{ + m_listWidget = listWidget; + m_comboBox = nullptr; + + m_newItemsState = items; + m_oldItemsState = oldItems; +} + +void ChangeListContentsCommand::init(QComboBox *comboBox, + const ListContents &oldItems, const ListContents &items) +{ + m_listWidget = nullptr; + m_comboBox = comboBox; + + m_newItemsState = items; + m_oldItemsState = oldItems; +} + +void ChangeListContentsCommand::redo() +{ + if (m_listWidget) + m_newItemsState.applyToListWidget(m_listWidget, m_iconCache, false); + else if (m_comboBox) + m_newItemsState.applyToComboBox(m_comboBox, m_iconCache); +} + +void ChangeListContentsCommand::undo() +{ + if (m_listWidget) + m_oldItemsState.applyToListWidget(m_listWidget, m_iconCache, false); + else if (m_comboBox) + m_oldItemsState.applyToComboBox(m_comboBox, m_iconCache); +} + +// ---- AddActionCommand ---- + +AddActionCommand::AddActionCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Add action"), formWindow) +{ + m_action = nullptr; +} + +void AddActionCommand::init(QAction *action) +{ + Q_ASSERT(m_action == nullptr); + m_action = action; +} + +void AddActionCommand::redo() +{ + core()->actionEditor()->setFormWindow(formWindow()); + core()->actionEditor()->manageAction(m_action); +} + +void AddActionCommand::undo() +{ + core()->actionEditor()->setFormWindow(formWindow()); + core()->actionEditor()->unmanageAction(m_action); +} + +// ---- RemoveActionCommand ---- + +RemoveActionCommand::RemoveActionCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Remove action"), formWindow), + m_action(0) +{ +} + +static RemoveActionCommand::ActionData findActionIn(QAction *action) +{ + RemoveActionCommand::ActionData result; + // We only want menus and toolbars, no toolbuttons. + const QObjectList associatedObjects = action->associatedObjects(); + for (QObject *obj : associatedObjects) { + if (!qobject_cast(obj) && !qobject_cast(obj)) + continue; + QWidget *widget = static_cast(obj); + const auto actionList = widget->actions(); + for (qsizetype i = 0, size = actionList.size(); i < size; ++i) { + if (actionList.at(i) == action) { + QAction *before = nullptr; + if (i + 1 < size) + before = actionList.at(i + 1); + result.append(RemoveActionCommand::ActionDataItem(before, widget)); + break; + } + } + } + return result; +} + +void RemoveActionCommand::init(QAction *action) +{ + Q_ASSERT(m_action == nullptr); + m_action = action; + + m_actionData = findActionIn(action); +} + +void RemoveActionCommand::redo() +{ + QDesignerFormWindowInterface *fw = formWindow(); + for (const ActionDataItem &item : std::as_const(m_actionData)) { + item.widget->removeAction(m_action); + } + // Notify components (for example, signal slot editor) + if (qdesigner_internal::FormWindowBase *fwb = qobject_cast(fw)) + fwb->emitObjectRemoved(m_action); + + core()->actionEditor()->setFormWindow(fw); + core()->actionEditor()->unmanageAction(m_action); + if (!m_actionData.isEmpty()) + core()->objectInspector()->setFormWindow(fw); +} + +void RemoveActionCommand::undo() +{ + core()->actionEditor()->setFormWindow(formWindow()); + core()->actionEditor()->manageAction(m_action); + for (const ActionDataItem &item : std::as_const(m_actionData)) + item.widget->insertAction(item.before, m_action); + if (!m_actionData.isEmpty()) + core()->objectInspector()->setFormWindow(formWindow()); +} + +// ---- ActionInsertionCommand ---- + +ActionInsertionCommand::ActionInsertionCommand(const QString &text, QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(text, formWindow), + m_parentWidget(nullptr), + m_action(nullptr), + m_beforeAction(nullptr), + m_update(false) +{ +} + +void ActionInsertionCommand::init(QWidget *parentWidget, QAction *action, QAction *beforeAction, bool update) +{ + Q_ASSERT(m_parentWidget == nullptr); + Q_ASSERT(m_action == nullptr); + + m_parentWidget = parentWidget; + m_action = action; + m_beforeAction = beforeAction; + m_update = update; +} + +void ActionInsertionCommand::insertAction() +{ + Q_ASSERT(m_action != nullptr); + Q_ASSERT(m_parentWidget != nullptr); + + if (m_beforeAction) + m_parentWidget->insertAction(m_beforeAction, m_action); + else + m_parentWidget->addAction(m_action); + + if (m_update) { + cheapUpdate(); + if (QMenu *menu = m_action->menu()) + selectUnmanagedObject(menu); + else + selectUnmanagedObject(m_action); + PropertyHelper::triggerActionChanged(m_action); // Update Used column in action editor. + } +} +void ActionInsertionCommand::removeAction() +{ + Q_ASSERT(m_action != nullptr); + Q_ASSERT(m_parentWidget != nullptr); + + if (QDesignerMenu *menu = qobject_cast(m_parentWidget)) + menu->hideSubMenu(); + + m_parentWidget->removeAction(m_action); + + if (m_update) { + cheapUpdate(); + selectUnmanagedObject(m_parentWidget); + PropertyHelper::triggerActionChanged(m_action); // Update Used column in action editor. + } +} + +InsertActionIntoCommand::InsertActionIntoCommand(QDesignerFormWindowInterface *formWindow) : + ActionInsertionCommand(QApplication::translate("Command", "Add action"), formWindow) +{ +} +// ---- RemoveActionFromCommand ---- + +RemoveActionFromCommand::RemoveActionFromCommand(QDesignerFormWindowInterface *formWindow) : + ActionInsertionCommand(QApplication::translate("Command", "Remove action"), formWindow) +{ +} + +// ---- AddMenuActionCommand ---- + +MenuActionCommand::MenuActionCommand(const QString &text, QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(text, formWindow), + m_action(nullptr), + m_actionBefore(nullptr), + m_menuParent(nullptr), + m_associatedWidget(nullptr), + m_objectToSelect(nullptr) +{ +} + +void MenuActionCommand::init(QAction *action, QAction *actionBefore, + QWidget *associatedWidget, QWidget *objectToSelect) +{ + QMenu *menu = action->menu(); + Q_ASSERT(menu); + m_menuParent = menu->parentWidget(); + m_action = action; + m_actionBefore = actionBefore; + m_associatedWidget = associatedWidget; + m_objectToSelect = objectToSelect; +} + +void MenuActionCommand::insertMenu() +{ + core()->metaDataBase()->add(m_action); + QMenu *menu = m_action->menu(); + if (m_menuParent && menu->parentWidget() != m_menuParent) + menu->setParent(m_menuParent); + core()->metaDataBase()->add(menu); + m_associatedWidget->insertAction(m_actionBefore, m_action); + cheapUpdate(); + selectUnmanagedObject(menu); +} + +void MenuActionCommand::removeMenu() +{ + m_action->menu()->setParent(nullptr); + QMenu *menu = m_action->menu(); + core()->metaDataBase()->remove(menu); + menu->setParent(nullptr); + core()->metaDataBase()->remove(m_action); + m_associatedWidget->removeAction(m_action); + cheapUpdate(); + selectUnmanagedObject(m_objectToSelect); +} + +AddMenuActionCommand::AddMenuActionCommand(QDesignerFormWindowInterface *formWindow) : + MenuActionCommand(QApplication::translate("Command", "Add menu"), formWindow) +{ +} + +// ---- RemoveMenuActionCommand ---- +RemoveMenuActionCommand::RemoveMenuActionCommand(QDesignerFormWindowInterface *formWindow) : + MenuActionCommand(QApplication::translate("Command", "Remove menu"), formWindow) +{ +} + +// ---- CreateSubmenuCommand ---- +CreateSubmenuCommand::CreateSubmenuCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Create submenu"), formWindow), + m_action(nullptr), + m_menu(nullptr), + m_objectToSelect(nullptr) +{ +} + +void CreateSubmenuCommand::init(QDesignerMenu *menu, QAction *action, QObject *objectToSelect) +{ + m_menu = menu; + m_action = action; + m_objectToSelect = objectToSelect; +} + +void CreateSubmenuCommand::redo() +{ + m_menu->createRealMenuAction(m_action); + cheapUpdate(); + if (m_objectToSelect) + selectUnmanagedObject(m_objectToSelect); +} + +void CreateSubmenuCommand::undo() +{ + m_menu->removeRealMenu(m_action); + cheapUpdate(); + selectUnmanagedObject(m_menu); +} + +// ---- DeleteToolBarCommand ---- +DeleteToolBarCommand::DeleteToolBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Delete Tool Bar"), formWindow) +{ +} + +void DeleteToolBarCommand::init(QToolBar *toolBar) +{ + m_toolBar = toolBar; + m_mainWindow = qobject_cast(toolBar->parentWidget()); +} + +void DeleteToolBarCommand::redo() +{ + if (m_mainWindow) { + QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), m_mainWindow); + Q_ASSERT(c != nullptr); + for (int i=0; icount(); ++i) { + if (c->widget(i) == m_toolBar) { + c->remove(i); + break; + } + } + } + + core()->metaDataBase()->remove(m_toolBar); + m_toolBar->hide(); + m_toolBar->setParent(formWindow()); + formWindow()->emitSelectionChanged(); +} + +void DeleteToolBarCommand::undo() +{ + if (m_mainWindow) { + m_toolBar->setParent(m_mainWindow); + QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), m_mainWindow); + + c->addWidget(m_toolBar); + + core()->metaDataBase()->add(m_toolBar); + m_toolBar->show(); + formWindow()->emitSelectionChanged(); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_command2.cpp b/src/designer/src/lib/shared/qdesigner_command2.cpp new file mode 100644 index 0000000..cc8cff4 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_command2.cpp @@ -0,0 +1,183 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_command2_p.h" +#include "formwindowbase_p.h" +#include "layoutinfo_p.h" +#include "qdesigner_command_p.h" +#include "widgetfactory_p.h" +#include "qlayout_widget_p.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +MorphLayoutCommand::MorphLayoutCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_breakLayoutCommand(new BreakLayoutCommand(formWindow)), + m_layoutCommand(new LayoutCommand(formWindow)), + m_newType(LayoutInfo::VBox), + m_layoutBase(nullptr) +{ +} + +MorphLayoutCommand::~MorphLayoutCommand() +{ + delete m_layoutCommand; + delete m_breakLayoutCommand; +} + +bool MorphLayoutCommand::init(QWidget *w, int newType) +{ + int oldType; + QDesignerFormWindowInterface *fw = formWindow(); + if (!canMorph(fw, w, &oldType) || oldType == newType) + return false; + m_layoutBase = w; + m_newType = newType; + // Find all managed widgets + m_widgets.clear(); + const QLayout *layout = LayoutInfo::managedLayout(fw->core(), w); + const int count = layout->count(); + for (int i = 0; i < count ; i++) { + if (QWidget *w = layout->itemAt(i)->widget()) + if (fw->isManaged(w)) + m_widgets.push_back(w); + } + const bool reparentLayoutWidget = false; // leave QLayoutWidget intact + m_breakLayoutCommand->init(m_widgets, m_layoutBase, reparentLayoutWidget); + m_layoutCommand->init(m_layoutBase, m_widgets, static_cast(m_newType), m_layoutBase, reparentLayoutWidget); + setText(formatDescription(core(), m_layoutBase, oldType, newType)); + return true; +} + +bool MorphLayoutCommand::canMorph(const QDesignerFormWindowInterface *formWindow, QWidget *w, int *ptrToCurrentType) +{ + if (ptrToCurrentType) + *ptrToCurrentType = LayoutInfo::NoLayout; + // We want a managed widget or a container page + // with a level-0 managed layout + QDesignerFormEditorInterface *core = formWindow->core(); + QLayout *layout = LayoutInfo::managedLayout(core, w); + if (!layout) + return false; + const LayoutInfo::Type type = LayoutInfo::layoutType(core, layout); + if (ptrToCurrentType) + *ptrToCurrentType = type; + switch (type) { + case LayoutInfo::HBox: + case LayoutInfo::VBox: + case LayoutInfo::Grid: + case LayoutInfo::Form: + return true; + break; + case LayoutInfo::NoLayout: + case LayoutInfo::HSplitter: // Nothing doing + case LayoutInfo::VSplitter: + case LayoutInfo::UnknownLayout: + break; + } + return false; +} + +void MorphLayoutCommand::redo() +{ + m_breakLayoutCommand->redo(); + m_layoutCommand->redo(); + /* Transfer applicable properties which is a cross-section of the modified + * properties except object name. */ + if (const LayoutProperties *properties = m_breakLayoutCommand->layoutProperties()) { + const int oldMask = m_breakLayoutCommand->propertyMask(); + QLayout *newLayout = LayoutInfo::managedLayout(core(), m_layoutBase); + const int newMask = LayoutProperties::visibleProperties(newLayout); + const int applicableMask = (oldMask & newMask) & ~LayoutProperties::ObjectNameProperty; + if (applicableMask) + properties->toPropertySheet(core(), newLayout, applicableMask); + } +} + +void MorphLayoutCommand::undo() +{ + m_layoutCommand->undo(); + m_breakLayoutCommand->undo(); +} + +QString MorphLayoutCommand::formatDescription(QDesignerFormEditorInterface * /* core*/, const QWidget *w, int oldType, int newType) +{ + const QString oldName = LayoutInfo::layoutName(static_cast(oldType)); + const QString newName = LayoutInfo::layoutName(static_cast(newType)); + const QString widgetName = qobject_cast(w) ? w->layout()->objectName() : w->objectName(); + return QApplication::translate("Command", "Change layout of '%1' from %2 to %3").arg(widgetName, oldName, newName); +} + +LayoutAlignmentCommand::LayoutAlignmentCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Change layout alignment"), formWindow), + m_widget(nullptr) +{ +} + +bool LayoutAlignmentCommand::init(QWidget *w, Qt::Alignment alignment) +{ + bool enabled; + m_newAlignment = alignment; + m_oldAlignment = LayoutAlignmentCommand::alignmentOf(core(), w, &enabled); + m_widget = w; + return enabled; +} + +void LayoutAlignmentCommand::redo() +{ + LayoutAlignmentCommand::applyAlignment(core(), m_widget, m_newAlignment); +} + +void LayoutAlignmentCommand::undo() +{ + LayoutAlignmentCommand::applyAlignment(core(), m_widget, m_oldAlignment); +} + +// Find out alignment and return whether command is enabled. +Qt::Alignment LayoutAlignmentCommand::alignmentOf(const QDesignerFormEditorInterface *core, QWidget *w, bool *enabledIn) +{ + bool managed; + QLayout *layout; + + if (enabledIn) + *enabledIn = false; + // Can only work on a managed layout + const LayoutInfo::Type type = LayoutInfo::laidoutWidgetType(core, w, &managed, &layout); + const bool enabled = layout && managed && + (type == LayoutInfo::HBox || type == LayoutInfo::VBox + || type == LayoutInfo::Grid); + if (!enabled) + return {}; + // Get alignment + const int index = layout->indexOf(w); + Q_ASSERT(index >= 0); + if (enabledIn) + *enabledIn = true; + return layout->itemAt(index)->alignment(); +} + +void LayoutAlignmentCommand::applyAlignment(const QDesignerFormEditorInterface *core, QWidget *w, Qt::Alignment a) +{ + // Find layout and apply to item + QLayout *layout; + LayoutInfo::laidoutWidgetType(core, w, nullptr, &layout); + if (layout) { + const int index = layout->indexOf(w); + if (index >= 0) { + layout->itemAt(index)->setAlignment(a); + layout->update(); + } + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_command2_p.h b/src/designer/src/lib/shared/qdesigner_command2_p.h new file mode 100644 index 0000000..bfe32cd --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_command2_p.h @@ -0,0 +1,85 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_COMMAND2_H +#define QDESIGNER_COMMAND2_H + +#include "shared_global_p.h" +#include "qdesigner_formwindowcommand_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class LayoutCommand; +class BreakLayoutCommand; + +/* This command changes the type of a managed layout on a widget (including + * red layouts of type 'QLayoutWidget') into another type, maintaining the + * applicable properties. It does this by chaining BreakLayoutCommand and + * LayoutCommand, parametrizing them not to actually delete/reparent + * QLayoutWidget's. */ + +class QDESIGNER_SHARED_EXPORT MorphLayoutCommand : public QDesignerFormWindowCommand { + Q_DISABLE_COPY_MOVE(MorphLayoutCommand) +public: + explicit MorphLayoutCommand(QDesignerFormWindowInterface *formWindow); + ~MorphLayoutCommand() override; + + bool init(QWidget *w, int newType); + + static bool canMorph(const QDesignerFormWindowInterface *formWindow, QWidget *w, int *ptrToCurrentType = nullptr); + + void redo() override; + void undo() override; + +private: + static QString formatDescription(QDesignerFormEditorInterface *core, const QWidget *w, int oldType, int newType); + + BreakLayoutCommand *m_breakLayoutCommand; + LayoutCommand *m_layoutCommand; + int m_newType; + QWidgetList m_widgets; + QWidget *m_layoutBase; +}; + +// Change the alignment of a widget in a managed grid/box layout cell. +class LayoutAlignmentCommand : public QDesignerFormWindowCommand { + Q_DISABLE_COPY_MOVE(LayoutAlignmentCommand) +public: + explicit LayoutAlignmentCommand(QDesignerFormWindowInterface *formWindow); + + bool init(QWidget *w, Qt::Alignment alignment); + + void redo() override; + void undo() override; + + // Find out alignment and return whether command is enabled. + static Qt::Alignment alignmentOf(const QDesignerFormEditorInterface *core, QWidget *w, bool *enabled = nullptr); + +private: + static void applyAlignment(const QDesignerFormEditorInterface *core, QWidget *w, Qt::Alignment a); + + Qt::Alignment m_newAlignment; + Qt::Alignment m_oldAlignment; + QWidget *m_widget; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_COMMAND2_H diff --git a/src/designer/src/lib/shared/qdesigner_command_p.h b/src/designer/src/lib/shared/qdesigner_command_p.h new file mode 100644 index 0000000..e3239ce --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_command_p.h @@ -0,0 +1,1116 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_COMMAND_H +#define QDESIGNER_COMMAND_H + +#include "layoutinfo_p.h" +#include "qdesigner_formeditorcommand_p.h" +#include "qdesigner_formwindowcommand_p.h" +#include "qdesigner_utils_p.h" +#include "shared_enums_p.h" +#include "shared_global_p.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerContainerExtension; +class QDesignerMetaDataBaseItemInterface; +class QDesignerMenu; + +class QMenuBar; +class QStatusBar; +class QToolBar; +class QToolBox; +class QTabWidget; +class QTableWidget; +class QTableWidgetItem; +class QTreeWidget; +class QTreeWidgetItem; +class QListWidget; +class QListWidgetItem; +class QComboBox; +class QStackedWidget; +class QDockWidget; +class QMainWindow; +class QFormLayout; + +namespace qdesigner_internal { + +class Layout; +class LayoutHelper; +class PropertySheetIconValue; +class DesignerIconCache; +struct LayoutProperties; + +class QDESIGNER_SHARED_EXPORT InsertWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit InsertWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~InsertWidgetCommand() override; + + void init(QWidget *widget, bool already_in_form = false, int layoutRow = -1, int layoutColumn = -1); + + void redo() override; + void undo() override; + +private: + void refreshBuddyLabels(); + + QPointer m_widget; + QDesignerLayoutDecorationExtension::InsertMode m_insertMode; + std::pair m_cell; + LayoutHelper* m_layoutHelper; + bool m_widgetWasManaged; +}; + +class QDESIGNER_SHARED_EXPORT ChangeZOrderCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeZOrderCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget); + + void redo() override; + void undo() override; +protected: + virtual QWidgetList reorderWidget(const QWidgetList &list, QWidget *widget) const = 0; + virtual void reorder(QWidget *widget) const = 0; + +private: + QPointer m_widget; + QPointer m_oldPreceding; + QWidgetList m_oldParentZOrder; +}; + +class QDESIGNER_SHARED_EXPORT RaiseWidgetCommand: public ChangeZOrderCommand +{ + +public: + explicit RaiseWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget); + +protected: + QWidgetList reorderWidget(const QWidgetList &list, QWidget *widget) const override; + void reorder(QWidget *widget) const override; +}; + +class QDESIGNER_SHARED_EXPORT LowerWidgetCommand: public ChangeZOrderCommand +{ + +public: + explicit LowerWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget); + +protected: + QWidgetList reorderWidget(const QWidgetList &list, QWidget *widget) const override; + void reorder(QWidget *widget) const override; +}; + +class QDESIGNER_SHARED_EXPORT AdjustWidgetSizeCommand: public QDesignerFormWindowCommand +{ + +public: + explicit AdjustWidgetSizeCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget); + + void redo() override; + void undo() override; + +private: + QWidget *widgetForAdjust() const; + bool adjustNonLaidOutMainContainer(QWidget *integrationContainer); + void updatePropertyEditor() const; + + QPointer m_widget; + QRect m_geometry; +}; + +// Helper to correctly unmanage a widget and its children for delete operations +class QDESIGNER_SHARED_EXPORT ManageWidgetCommandHelper { +public: + ManageWidgetCommandHelper(); + void init(const QDesignerFormWindowInterface *fw, QWidget *widget); + void init(QWidget *widget, const QWidgetList &managedChildren); + + void manage(QDesignerFormWindowInterface *fw); + void unmanage(QDesignerFormWindowInterface *fw); + + const QWidgetList &managedChildren() const { return m_managedChildren; } +private: + QWidget *m_widget = nullptr; + QWidgetList m_managedChildren; +}; + +class QDESIGNER_SHARED_EXPORT DeleteWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DeleteWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteWidgetCommand() override; + + enum DeleteFlags { DoNotUnmanage = 0x1, DoNotSimplifyLayout = 0x2 }; + + void init(QWidget *widget, unsigned flags = 0); + + void redo() override; + void undo() override; + +private: + QPointer m_widget; + QPointer m_parentWidget; + QRect m_geometry; + LayoutInfo::Type m_layoutType; + LayoutHelper* m_layoutHelper; + unsigned m_flags; + QRect m_layoutPosition; + int m_splitterIndex; + bool m_layoutSimplified; + QDesignerMetaDataBaseItemInterface *m_formItem; + int m_tabOrderIndex; + int m_widgetOrderIndex; + int m_zOrderIndex; + ManageWidgetCommandHelper m_manageHelper; +}; + +class QDESIGNER_SHARED_EXPORT ReparentWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ReparentWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget, QWidget *parentWidget); + + void redo() override; + void undo() override; + +private: + QPointer m_widget; + QPoint m_oldPos; + QPoint m_newPos; + QPointer m_oldParentWidget; + QPointer m_newParentWidget; + QWidgetList m_oldParentList; + QWidgetList m_oldParentZOrder; +}; + +class QDESIGNER_SHARED_EXPORT ChangeFormLayoutItemRoleCommand : public QDesignerFormWindowCommand +{ +public: + enum Operation { SpanningToLabel = 0x1, SpanningToField = 0x2, LabelToSpanning = 0x4, FieldToSpanning =0x8 }; + + explicit ChangeFormLayoutItemRoleCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget, Operation op); + + void redo() override; + void undo() override; + + // Return a mask of possible operations of that item + static unsigned possibleOperations(QDesignerFormEditorInterface *core, QWidget *w); + +private: + static QFormLayout *managedFormLayoutOf(QDesignerFormEditorInterface *core, QWidget *w); + static Operation reverseOperation(Operation op); + void doOperation(Operation op); + + QPointer m_widget; + Operation m_operation; +}; + +class QDESIGNER_SHARED_EXPORT ChangeLayoutItemGeometry: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeLayoutItemGeometry(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget, int row, int column, int rowspan, int colspan); + + void redo() override; + void undo() override; + +protected: + void changeItemPosition(const QRect &g); + +private: + QPointer m_widget; + QRect m_oldInfo; + QRect m_newInfo; +}; + +class QDESIGNER_SHARED_EXPORT TabOrderCommand: public QDesignerFormWindowCommand +{ + +public: + explicit TabOrderCommand(QDesignerFormWindowInterface *formWindow); + + void init(const QWidgetList &newTabOrder); + + inline QWidgetList oldTabOrder() const + { return m_oldTabOrder; } + + inline QWidgetList newTabOrder() const + { return m_newTabOrder; } + + void redo() override; + void undo() override; + +private: + QDesignerMetaDataBaseItemInterface *m_widgetItem; + QWidgetList m_oldTabOrder; + QWidgetList m_newTabOrder; +}; + +class QDESIGNER_SHARED_EXPORT PromoteToCustomWidgetCommand : public QDesignerFormWindowCommand +{ +public: + using WidgetPointerList = QList >; + + explicit PromoteToCustomWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(const WidgetPointerList &widgets, const QString &customClassName); + void redo() override; + void undo() override; + +private: + void updateSelection(); + WidgetPointerList m_widgets; + QString m_customClassName; +}; + +class QDESIGNER_SHARED_EXPORT DemoteFromCustomWidgetCommand : public QDesignerFormWindowCommand +{ +public: + using WidgetList = PromoteToCustomWidgetCommand::WidgetPointerList; + + explicit DemoteFromCustomWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(const WidgetList &promoted); + void redo() override; + void undo() override; +private: + PromoteToCustomWidgetCommand m_promote_cmd; +}; + +// Mixin class for storing the selection state +class QDESIGNER_SHARED_EXPORT CursorSelectionState { + Q_DISABLE_COPY_MOVE(CursorSelectionState) +public: + CursorSelectionState(); + + void save(const QDesignerFormWindowInterface *formWindow); + void restore(QDesignerFormWindowInterface *formWindow) const; + +private: + using WidgetPointerList = QList >; + WidgetPointerList m_selection; + QPointer m_current; +}; + +class QDESIGNER_SHARED_EXPORT LayoutCommand: public QDesignerFormWindowCommand +{ + +public: + explicit LayoutCommand(QDesignerFormWindowInterface *formWindow); + ~LayoutCommand() override; + + inline QWidgetList widgets() const { return m_widgets; } + + void init(QWidget *parentWidget, const QWidgetList &widgets, LayoutInfo::Type layoutType, + QWidget *layoutBase = nullptr, + // Reparent/Hide instances of QLayoutWidget. + bool reparentLayoutWidget = true); + + void redo() override; + void undo() override; + +private: + QPointer m_parentWidget; + QWidgetList m_widgets; + QPointer m_layoutBase; + QPointer m_layout; + CursorSelectionState m_cursorSelectionState; + bool m_setup; +}; + +class QDESIGNER_SHARED_EXPORT BreakLayoutCommand: public QDesignerFormWindowCommand +{ + +public: + explicit BreakLayoutCommand(QDesignerFormWindowInterface *formWindow); + ~BreakLayoutCommand() override; + + inline QWidgetList widgets() const { return m_widgets; } + + void init(const QWidgetList &widgets, QWidget *layoutBase, + // Reparent/Hide instances of QLayoutWidget. + bool reparentLayoutWidget = true); + + void redo() override; + void undo() override; + + // Access the properties of the layout, 0 in case of splitters. + const LayoutProperties *layoutProperties() const; + int propertyMask() const; + +private: + QWidgetList m_widgets; + QPointer m_layoutBase; + QPointer m_layout; + LayoutHelper* m_layoutHelper; + LayoutProperties *m_properties; + int m_propertyMask; + CursorSelectionState m_cursorSelectionState; +}; + +class QDESIGNER_SHARED_EXPORT SimplifyLayoutCommand: public QDesignerFormWindowCommand +{ +public: + explicit SimplifyLayoutCommand(QDesignerFormWindowInterface *formWindow); + ~SimplifyLayoutCommand() override; + + bool init(QWidget *layoutBase); + + // Quick check + static bool canSimplify(QDesignerFormEditorInterface *core, const QWidget *w, int *layoutType = nullptr); + + void redo() override; + void undo() override; + +private: + const QRect m_area; + QWidget *m_layoutBase; + LayoutHelper* m_layoutHelper; + bool m_layoutSimplified; +}; + +class ToolBoxCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ToolBoxCommand(QDesignerFormWindowInterface *formWindow); + ~ToolBoxCommand() override; + + void init(QToolBox *toolBox); + +protected: + void removePage(); + void addPage(); + + QPointer m_toolBox; + QPointer m_widget; + int m_index; + QString m_itemText; + QIcon m_itemIcon; +}; + +class MoveToolBoxPageCommand: public ToolBoxCommand +{ + +public: + explicit MoveToolBoxPageCommand(QDesignerFormWindowInterface *formWindow); + ~MoveToolBoxPageCommand() override; + + void init(QToolBox *toolBox, QWidget *page, int newIndex); + + void redo() override; + void undo() override; + +private: + int m_newIndex; + int m_oldIndex; +}; + +class DeleteToolBoxPageCommand: public ToolBoxCommand +{ + +public: + explicit DeleteToolBoxPageCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteToolBoxPageCommand() override; + + void init(QToolBox *toolBox); + + void redo() override; + void undo() override; +}; + +class AddToolBoxPageCommand: public ToolBoxCommand +{ + +public: + enum InsertionMode { + InsertBefore, + InsertAfter + }; + explicit AddToolBoxPageCommand(QDesignerFormWindowInterface *formWindow); + ~AddToolBoxPageCommand() override; + + void init(QToolBox *toolBox); + void init(QToolBox *toolBox, InsertionMode mode); + + void redo() override; + void undo() override; +}; + +class TabWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit TabWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~TabWidgetCommand() override; + + void init(QTabWidget *tabWidget); + +protected: + void removePage(); + void addPage(); + + QPointer m_tabWidget; + QPointer m_widget; + int m_index; + QString m_itemText; + QIcon m_itemIcon; +}; + +class DeleteTabPageCommand: public TabWidgetCommand +{ + +public: + explicit DeleteTabPageCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteTabPageCommand() override; + + void init(QTabWidget *tabWidget); + + void redo() override; + void undo() override; +}; + +class AddTabPageCommand: public TabWidgetCommand +{ + +public: + enum InsertionMode { + InsertBefore, + InsertAfter + }; + explicit AddTabPageCommand(QDesignerFormWindowInterface *formWindow); + ~AddTabPageCommand() override; + + void init(QTabWidget *tabWidget); + void init(QTabWidget *tabWidget, InsertionMode mode); + + void redo() override; + void undo() override; +}; + +class MoveTabPageCommand: public TabWidgetCommand +{ + +public: + explicit MoveTabPageCommand(QDesignerFormWindowInterface *formWindow); + ~MoveTabPageCommand() override; + + void init(QTabWidget *tabWidget, QWidget *page, + const QIcon &icon, const QString &label, + int index, int newIndex); + + void redo() override; + void undo() override; + +private: + int m_newIndex; + int m_oldIndex; + QPointer m_page; + QString m_label; + QIcon m_icon; +}; + +class StackedWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit StackedWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~StackedWidgetCommand() override; + + void init(QStackedWidget *stackedWidget); + +protected: + void removePage(); + void addPage(); + + QPointer m_stackedWidget; + QPointer m_widget; + int m_index; +}; + +class MoveStackedWidgetCommand: public StackedWidgetCommand +{ + +public: + explicit MoveStackedWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~MoveStackedWidgetCommand() override; + + void init(QStackedWidget *stackedWidget, QWidget *page, int newIndex); + + void redo() override; + void undo() override; + +private: + int m_newIndex; + int m_oldIndex; +}; + +class DeleteStackedWidgetPageCommand: public StackedWidgetCommand +{ + +public: + explicit DeleteStackedWidgetPageCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteStackedWidgetPageCommand() override; + + void init(QStackedWidget *stackedWidget); + + void redo() override; + void undo() override; +}; + +class AddStackedWidgetPageCommand: public StackedWidgetCommand +{ + +public: + enum InsertionMode { + InsertBefore, + InsertAfter + }; + explicit AddStackedWidgetPageCommand(QDesignerFormWindowInterface *formWindow); + ~AddStackedWidgetPageCommand() override; + + void init(QStackedWidget *stackedWidget); + void init(QStackedWidget *stackedWidget, InsertionMode mode); + + void redo() override; + void undo() override; +}; + +class CreateMenuBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit CreateMenuBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMainWindow *mainWindow); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_menuBar; +}; + +class DeleteMenuBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DeleteMenuBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMenuBar *menuBar); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_menuBar; +}; + +class CreateStatusBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit CreateStatusBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMainWindow *mainWindow); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_statusBar; +}; + +class QDESIGNER_SHARED_EXPORT DeleteStatusBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DeleteStatusBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QStatusBar *statusBar); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_statusBar; +}; + +class AddToolBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit AddToolBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMainWindow *mainWindow, Qt::ToolBarArea area); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_toolBar; +}; + +class DeleteToolBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DeleteToolBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QToolBar *toolBar); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_toolBar; +}; + +class DockWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DockWidgetCommand(const QString &description, QDesignerFormWindowInterface *formWindow); + ~DockWidgetCommand() override; + + void init(QDockWidget *dockWidget); + +protected: + QPointer m_dockWidget; +}; + +class QDESIGNER_SHARED_EXPORT AddDockWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit AddDockWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMainWindow *mainWindow, QDockWidget *dockWidget); + void init(QMainWindow *mainWindow); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_dockWidget; +}; + +class QDESIGNER_SHARED_EXPORT ContainerWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ContainerWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~ContainerWidgetCommand() override; + + QDesignerContainerExtension *containerExtension() const; + + void init(QWidget *containerWidget); + +protected: + void removePage(); + void addPage(); + + QPointer m_containerWidget; + QPointer m_widget; + int m_index; +}; + +class QDESIGNER_SHARED_EXPORT DeleteContainerWidgetPageCommand: public ContainerWidgetCommand +{ + +public: + explicit DeleteContainerWidgetPageCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteContainerWidgetPageCommand() override; + + void init(QWidget *containerWidget, ContainerType ct); + + void redo() override; + void undo() override; +}; + +class QDESIGNER_SHARED_EXPORT AddContainerWidgetPageCommand: public ContainerWidgetCommand +{ + +public: + enum InsertionMode { + InsertBefore, + InsertAfter + }; + explicit AddContainerWidgetPageCommand(QDesignerFormWindowInterface *formWindow); + ~AddContainerWidgetPageCommand() override; + + void init(QWidget *containerWidget, ContainerType ct, InsertionMode mode); + + void redo() override; + void undo() override; +}; + +class QDESIGNER_SHARED_EXPORT ChangeCurrentPageCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeCurrentPageCommand(QDesignerFormWindowInterface *formWindow); + ~ChangeCurrentPageCommand() override; + + QDesignerContainerExtension *containerExtension() const; + + void init(QWidget *containerWidget, int newIndex); + + void redo() override; + void undo() override; + +protected: + QPointer m_containerWidget; + QPointer m_widget; + int m_oldIndex; + int m_newIndex; +}; + +struct QDESIGNER_SHARED_EXPORT ItemData { + ItemData() = default; + + ItemData(const QListWidgetItem *item, bool editor); + ItemData(const QTableWidgetItem *item, bool editor); + ItemData(const QTreeWidgetItem *item, int column); + QListWidgetItem *createListItem(DesignerIconCache *iconCache, bool editor) const; + QTableWidgetItem *createTableItem(DesignerIconCache *iconCache, bool editor) const; + void fillTreeItemColumn(QTreeWidgetItem *item, int column, DesignerIconCache *iconCache) const; + + bool isValid() const { return !m_properties.isEmpty(); } + + QHash m_properties; + + friend bool comparesEqual(const ItemData &lhs, const ItemData &rhs) noexcept + { + return lhs.m_properties == rhs.m_properties; + } + Q_DECLARE_EQUALITY_COMPARABLE(ItemData) +}; + +struct QDESIGNER_SHARED_EXPORT ListContents { + ListContents() = default; + + ListContents(const QTreeWidgetItem *item); + QTreeWidgetItem *createTreeItem(DesignerIconCache *iconCache) const; + + void createFromListWidget(const QListWidget *listWidget, bool editor); + void applyToListWidget(QListWidget *listWidget, DesignerIconCache *iconCache, + bool editor, + Qt::Alignment alignmentDefault = Qt::AlignLeading | Qt::AlignVCenter) const; + void createFromComboBox(const QComboBox *listWidget); + void applyToComboBox(QComboBox *listWidget, DesignerIconCache *iconCache) const; + + QList m_items; + + friend bool comparesEqual(const ListContents &lhs, const ListContents &rhs) noexcept + { + return lhs.m_items == rhs.m_items; + } + Q_DECLARE_EQUALITY_COMPARABLE(ListContents) +}; + +// Data structure representing the contents of a QTableWidget with +// methods to retrieve and apply for ChangeTableContentsCommand +struct QDESIGNER_SHARED_EXPORT TableWidgetContents { + + using CellRowColumnAddress = std::pair; + + TableWidgetContents(); + void clear(); + + void fromTableWidget(const QTableWidget *tableWidget, bool editor); + void applyToTableWidget(QTableWidget *tableWidget, DesignerIconCache *iconCache, bool editor) const; + + static bool nonEmpty(const QTableWidgetItem *item, int headerColumn); + static QString defaultHeaderText(int i); + static void insertHeaderItem(const QTableWidgetItem *item, int i, ListContents *header, bool editor); + + int m_columnCount = 0; + int m_rowCount = 0; + ListContents m_horizontalHeader; + ListContents m_verticalHeader; + QMap m_items; + + friend QDESIGNER_SHARED_EXPORT + bool comparesEqual(const TableWidgetContents &lhs, + const TableWidgetContents &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(TableWidgetContents) +}; + +class QDESIGNER_SHARED_EXPORT ChangeTableContentsCommand: public QDesignerFormWindowCommand +{ +public: + explicit ChangeTableContentsCommand(QDesignerFormWindowInterface *formWindow); + + void init(QTableWidget *tableWidget, const TableWidgetContents &oldCont, const TableWidgetContents &newCont); + void redo() override; + void undo() override; + +private: + QPointer m_tableWidget; + TableWidgetContents m_oldContents; + TableWidgetContents m_newContents; + DesignerIconCache *m_iconCache; +}; + +// Data structure representing the contents of a QTreeWidget with +// methods to retrieve and apply for ChangeTreeContentsCommand +struct QDESIGNER_SHARED_EXPORT TreeWidgetContents { + + struct ItemContents : public ListContents { + ItemContents() = default; + ItemContents(const QTreeWidgetItem *item, bool editor); + QTreeWidgetItem *createTreeItem(DesignerIconCache *iconCache, bool editor) const; + + int m_itemFlags = -1; + //bool m_firstColumnSpanned:1; + //bool m_hidden:1; + //bool m_expanded:1; + QList m_children; + + friend QDESIGNER_SHARED_EXPORT + bool comparesEqual(const ItemContents &lhs, + const ItemContents &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(ItemContents) + }; + + void clear(); + + void fromTreeWidget(const QTreeWidget *treeWidget, bool editor); + void applyToTreeWidget(QTreeWidget *treeWidget, DesignerIconCache *iconCache, bool editor) const; + + ListContents m_headerItem; + QList m_rootItems; + + friend bool comparesEqual(const TreeWidgetContents &lhs, + const TreeWidgetContents &rhs) noexcept + { + return lhs.m_headerItem == rhs.m_headerItem && lhs.m_rootItems == rhs.m_rootItems; + } + Q_DECLARE_EQUALITY_COMPARABLE(TreeWidgetContents) +}; + +class QDESIGNER_SHARED_EXPORT ChangeTreeContentsCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeTreeContentsCommand(QDesignerFormWindowInterface *formWindow); + + void init(QTreeWidget *treeWidget, const TreeWidgetContents &oldState, const TreeWidgetContents &newState); + void redo() override; + void undo() override; + enum ApplyIconStrategy { + SetIconStrategy, + ResetIconStrategy + }; +private: + QPointer m_treeWidget; + TreeWidgetContents m_oldState; + TreeWidgetContents m_newState; + DesignerIconCache *m_iconCache; +}; + +class QDESIGNER_SHARED_EXPORT ChangeListContentsCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeListContentsCommand(QDesignerFormWindowInterface *formWindow); + + void init(QListWidget *listWidget, const ListContents &oldItems, const ListContents &items); + void init(QComboBox *comboBox, const ListContents &oldItems, const ListContents &items); + void redo() override; + void undo() override; +private: + QPointer m_listWidget; + QPointer m_comboBox; + ListContents m_oldItemsState; + ListContents m_newItemsState; + DesignerIconCache *m_iconCache; +}; + +class QDESIGNER_SHARED_EXPORT AddActionCommand : public QDesignerFormWindowCommand +{ + +public: + explicit AddActionCommand(QDesignerFormWindowInterface *formWindow); + void init(QAction *action); + void redo() override; + void undo() override; +private: + QAction *m_action; +}; + +// Note: This command must be executed within a macro since it +// makes the form emit objectRemoved() which might cause other components +// to add commands (for example, removal of signals and slots +class QDESIGNER_SHARED_EXPORT RemoveActionCommand : public QDesignerFormWindowCommand +{ + +public: + explicit RemoveActionCommand(QDesignerFormWindowInterface *formWindow); + void init(QAction *action); + void redo() override; + void undo() override; + + struct ActionDataItem { + ActionDataItem(QAction *_before = nullptr, QWidget *_widget = nullptr) + : before(_before), widget(_widget) {} + QAction *before; + QWidget *widget; + }; + using ActionData = QList; + +private: + QAction *m_action; + + ActionData m_actionData; +}; + +class ActionInsertionCommand : public QDesignerFormWindowCommand +{ + +protected: + ActionInsertionCommand(const QString &text, QDesignerFormWindowInterface *formWindow); + +public: + void init(QWidget *parentWidget, QAction *action, QAction *beforeAction = nullptr, bool update = true); + +protected: + void insertAction(); + void removeAction(); + +private: + QWidget *m_parentWidget; + QAction *m_action; + QAction *m_beforeAction; + bool m_update; +}; + +class InsertActionIntoCommand : public ActionInsertionCommand +{ + +public: + explicit InsertActionIntoCommand(QDesignerFormWindowInterface *formWindow); + + void redo() override { insertAction(); } + void undo() override { removeAction(); } +}; + +class RemoveActionFromCommand : public ActionInsertionCommand +{ + +public: + explicit RemoveActionFromCommand(QDesignerFormWindowInterface *formWindow); + + void redo() override { removeAction(); } + void undo() override { insertAction(); } +}; + +class MenuActionCommand : public QDesignerFormWindowCommand +{ +public: + void init(QAction *action, QAction *actionBefore, QWidget *associatedWidget, QWidget *objectToSelect); + +protected: + MenuActionCommand(const QString &text, QDesignerFormWindowInterface *formWindow); + void insertMenu(); + void removeMenu(); + +private: + QAction *m_action; + QAction *m_actionBefore; + QWidget *m_menuParent; + QWidget *m_associatedWidget; + QWidget *m_objectToSelect; +}; + +class AddMenuActionCommand : public MenuActionCommand +{ + +public: + explicit AddMenuActionCommand(QDesignerFormWindowInterface *formWindow); + + void redo() override { insertMenu(); } + void undo() override { removeMenu(); } +}; + +class RemoveMenuActionCommand : public MenuActionCommand +{ + +public: + explicit RemoveMenuActionCommand(QDesignerFormWindowInterface *formWindow); + + void redo() override { removeMenu(); } + void undo() override { insertMenu(); } +}; + +class CreateSubmenuCommand : public QDesignerFormWindowCommand +{ + +public: + explicit CreateSubmenuCommand(QDesignerFormWindowInterface *formWindow); + void init(QDesignerMenu *menu, QAction *action, QObject *m_objectToSelect = nullptr); + void redo() override; + void undo() override; +private: + QAction *m_action; + QDesignerMenu *m_menu; + QObject *m_objectToSelect; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_COMMAND_H diff --git a/src/designer/src/lib/shared/qdesigner_dnditem.cpp b/src/designer/src/lib/shared/qdesigner_dnditem.cpp new file mode 100644 index 0000000..d7875f5 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_dnditem.cpp @@ -0,0 +1,265 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_dnditem_p.h" +#include "formwindowbase_p.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +QDesignerDnDItem::QDesignerDnDItem(DropType type, QWidget *source) : + m_source(source), + m_type(type), + m_dom_ui(nullptr), + m_widget(nullptr), + m_decoration(nullptr) +{ +} + +void QDesignerDnDItem::init(DomUI *ui, QWidget *widget, QWidget *decoration, + const QPoint &global_mouse_pos) +{ + Q_ASSERT(widget != nullptr || ui != nullptr); + Q_ASSERT(decoration != nullptr); + + m_dom_ui = ui; + m_widget = widget; + m_decoration = decoration; + + m_hot_spot = global_mouse_pos - m_decoration->geometry().topLeft(); +} + +QDesignerDnDItem::~QDesignerDnDItem() +{ + if (m_decoration != nullptr) + m_decoration->deleteLater(); + delete m_dom_ui; +} + +DomUI *QDesignerDnDItem::domUi() const +{ + return m_dom_ui; +} + +QWidget *QDesignerDnDItem::decoration() const +{ + return m_decoration; +} + +QPoint QDesignerDnDItem::hotSpot() const +{ + return m_hot_spot; +} + +QWidget *QDesignerDnDItem::widget() const +{ + return m_widget; +} + +QDesignerDnDItem::DropType QDesignerDnDItem::type() const +{ + return m_type; +} + +QWidget *QDesignerDnDItem::source() const +{ + return m_source; +} + +void QDesignerDnDItem::setDomUi(DomUI *dom_ui) +{ + delete m_dom_ui; + m_dom_ui = dom_ui; +} + +// ---------- QDesignerMimeData + +// Make pixmap transparent on Windows only. Mac is transparent by default, Unix usually does not work. +#ifdef Q_OS_WIN +# define TRANSPARENT_DRAG_PIXMAP +#endif + +QDesignerMimeData::QDesignerMimeData(const QDesignerDnDItems &items, QDrag *drag) : + m_items(items) +{ + enum { Alpha = 200 }; + QPoint decorationTopLeft; + switch (m_items.size()) { + case 0: + break; + case 1: { + QWidget *deco = m_items.first()->decoration(); + decorationTopLeft = deco->pos(); + const QPixmap widgetPixmap = deco->grab(QRect(0, 0, -1, -1)); +#ifdef TRANSPARENT_DRAG_PIXMAP + QImage image(widgetPixmap.size(), QImage::Format_ARGB32); + image.setDevicePixelRatio(widgetPixmap.devicePixelRatio()); + image.fill(QColor(Qt::transparent).rgba()); + QPainter painter(&image); + painter.drawPixmap(QPoint(0, 0), widgetPixmap); + painter.end(); + setImageTransparency(image, Alpha); + drag->setPixmap(QPixmap::fromImage(image)); +#else + drag->setPixmap(widgetPixmap); +#endif + } + break; + default: { + // determine size of drag decoration by uniting all geometries + const auto cend = m_items.cend(); + auto it = m_items.cbegin(); + QRect unitedGeometry = (*it)->decoration()->geometry(); + const qreal devicePixelRatio = (*it)->decoration()->devicePixelRatioF(); + for (++it; it != cend; ++it ) + unitedGeometry = unitedGeometry .united((*it)->decoration()->geometry()); + + // paint with offset. At the same time, create a mask bitmap, containing widget rectangles. + const QSize imageSize = (QSizeF(unitedGeometry.size()) * devicePixelRatio).toSize(); + QImage image(imageSize, QImage::Format_ARGB32); + image.setDevicePixelRatio(devicePixelRatio); + image.fill(QColor(Qt::transparent).rgba()); + QBitmap mask(imageSize); + mask.setDevicePixelRatio(devicePixelRatio); + mask.clear(); + // paint with offset, determine action + QPainter painter(&image); + QPainter maskPainter(&mask); + decorationTopLeft = unitedGeometry.topLeft(); + for (auto *item : std::as_const(m_items)) { + QWidget *w = item->decoration(); + const QPixmap wp = w->grab(QRect(0, 0, -1, -1)); + const QPoint pos = w->pos() - decorationTopLeft; + painter.drawPixmap(pos, wp); + maskPainter.fillRect(QRect(pos, w->size()), Qt::color1); + } + painter.end(); + maskPainter.end(); +#ifdef TRANSPARENT_DRAG_PIXMAP + setImageTransparency(image, Alpha); +#endif + QPixmap pixmap = QPixmap::fromImage(image); + pixmap.setMask(mask); + drag->setPixmap(pixmap); + } + break; + } + // determine hot spot and reconstruct the exact starting position as form window + // introduces some offset when detecting DnD + m_globalStartPos = m_items.first()->decoration()->pos() + m_items.first()->hotSpot(); + m_hotSpot = m_globalStartPos - decorationTopLeft; + drag->setHotSpot(m_hotSpot); + + drag->setMimeData(this); +} + +QDesignerMimeData::~QDesignerMimeData() +{ + qDeleteAll(m_items); +} + +Qt::DropAction QDesignerMimeData::proposedDropAction() const +{ + return m_items.first()->type() == QDesignerDnDItemInterface::CopyDrop ? Qt::CopyAction : Qt::MoveAction; +} + +Qt::DropAction QDesignerMimeData::execDrag(const QDesignerDnDItems &items, QWidget * dragSource) +{ + if (items.isEmpty()) + return Qt::IgnoreAction; + + QDrag *drag = new QDrag(dragSource); + QDesignerMimeData *mimeData = new QDesignerMimeData(items, drag); + + // Store pointers to widgets that are to be re-shown if a move operation is canceled + QWidgetList reshowWidgets; + for (auto *item : items) { + if (QWidget *w = item->widget()) { + if (item->type() == QDesignerDnDItemInterface::MoveDrop) + reshowWidgets.push_back(w); + } + } + + const Qt::DropAction executedAction = drag->exec(Qt::CopyAction|Qt::MoveAction, mimeData->proposedDropAction()); + + if (executedAction == Qt::IgnoreAction) { + for (QWidget *w : std::as_const(reshowWidgets)) + w->show(); + } + + return executedAction; +} + + +void QDesignerMimeData::moveDecoration(const QPoint &globalPos) const +{ + const QPoint relativeDistance = globalPos - m_globalStartPos; + for (auto *item : m_items) { + QWidget *w = item->decoration(); + w->move(w->pos() + relativeDistance); + } +} + +void QDesignerMimeData::removeMovedWidgetsFromSourceForm(const QDesignerDnDItems &items) +{ + QMultiMap formWidgetMap; + // Find moved widgets per form + for (auto *item : items) { + if (item->type() == QDesignerDnDItemInterface::MoveDrop) { + if (QWidget *w = item->widget()) { + if (FormWindowBase *fb = qobject_cast(item->source())) + formWidgetMap.insert(fb, w); + } + } + } + + const auto &formWindows = formWidgetMap.uniqueKeys(); + for (FormWindowBase *fb : formWindows) + fb->deleteWidgetList(formWidgetMap.values(fb)); +} + +void QDesignerMimeData::acceptEventWithAction(Qt::DropAction desiredAction, QDropEvent *e) +{ + if (e->proposedAction() == desiredAction) { + e->acceptProposedAction(); + } else { + e->setDropAction(desiredAction); + e->accept(); + } +} + +void QDesignerMimeData::acceptEvent(QDropEvent *e) const +{ + acceptEventWithAction(proposedDropAction(), e); +} + +void QDesignerMimeData::setImageTransparency(QImage &image, int alpha) +{ + const int height = image.height(); + for (int l = 0; l < height; l++) { + QRgb *line = reinterpret_cast(image.scanLine(l)); + QRgb *lineEnd = line + image.width(); + for ( ; line < lineEnd; line++) { + const QRgb rgba = *line; + *line = qRgba(qRed(rgba), qGreen(rgba), qBlue(rgba), alpha); + } + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_dnditem_p.h b/src/designer/src/lib/shared/qdesigner_dnditem_p.h new file mode 100644 index 0000000..aecd626 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_dnditem_p.h @@ -0,0 +1,109 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_DNDITEM_H +#define QDESIGNER_DNDITEM_H + +#include "shared_global_p.h" +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDrag; +class QImage; +class QDropEvent; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT QDesignerDnDItem: public QDesignerDnDItemInterface +{ +public: + explicit QDesignerDnDItem(DropType type, QWidget *source = nullptr); + ~QDesignerDnDItem() override; + + DomUI *domUi() const override; + QWidget *decoration() const override; + QWidget *widget() const override; + QPoint hotSpot() const override; + QWidget *source() const override; + + DropType type() const override; + +protected: + void setDomUi(DomUI *dom_ui); + void init(DomUI *ui, QWidget *widget, QWidget *decoration, const QPoint &global_mouse_pos); + +private: + QWidget *m_source; + const DropType m_type; + const QPoint m_globalStartPos; + DomUI *m_dom_ui; + QWidget *m_widget; + QWidget *m_decoration; + QPoint m_hot_spot; + + Q_DISABLE_COPY_MOVE(QDesignerDnDItem) +}; + +// Mime data for use with designer drag and drop operations. + +class QDESIGNER_SHARED_EXPORT QDesignerMimeData : public QMimeData { + Q_OBJECT + +public: + using QDesignerDnDItems = QList; + + ~QDesignerMimeData() override; + + const QDesignerDnDItems &items() const { return m_items; } + + // Execute a drag and drop operation. + static Qt::DropAction execDrag(const QDesignerDnDItems &items, QWidget * dragSource); + + QPoint hotSpot() const { return m_hotSpot; } + + // Move the decoration. Required for drops over form windows as the position + // is derived from the decoration position. + void moveDecoration(const QPoint &globalPos) const; + + // For a move operation, create the undo command sequence to remove + // the widgets from the source form. + static void removeMovedWidgetsFromSourceForm(const QDesignerDnDItems &items); + + // Accept an event with the proper action. + void acceptEvent(QDropEvent *e) const; + + // Helper to accept an event with the desired action. + static void acceptEventWithAction(Qt::DropAction desiredAction, QDropEvent *e); + +private: + QDesignerMimeData(const QDesignerDnDItems &items, QDrag *drag); + Qt::DropAction proposedDropAction() const; + + static void setImageTransparency(QImage &image, int alpha); + + const QDesignerDnDItems m_items; + QPoint m_globalStartPos; + QPoint m_hotSpot; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_DNDITEM_H diff --git a/src/designer/src/lib/shared/qdesigner_dockwidget.cpp b/src/designer/src/lib/shared/qdesigner_dockwidget.cpp new file mode 100644 index 0000000..9b857ef --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_dockwidget.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_dockwidget_p.h" +#include "layoutinfo_p.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +bool QDockWidgetPropertySheet::isEnabled(int index) const +{ + const QString &name = propertyName(index); + if (name == "dockWidgetArea"_L1) + return static_cast(object())->docked(); + if (name == "docked"_L1) + return static_cast(object())->inMainWindow(); + return QDesignerPropertySheet::isEnabled(index); +} + +QDesignerDockWidget::QDesignerDockWidget(QWidget *parent) + : QDockWidget(parent) +{ +} + +QDesignerDockWidget::~QDesignerDockWidget() = default; + +bool QDesignerDockWidget::docked() const +{ + return qobject_cast(parentWidget()) != 0; +} + +void QDesignerDockWidget::setDocked(bool b) +{ + if (QMainWindow *mainWindow = findMainWindow()) { + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c; + c = qt_extension(core->extensionManager(), mainWindow); + if (b && !docked()) { + // Dock it + // ### undo/redo stack + setParent(nullptr); + c->addWidget(this); + formWindow()->selectWidget(this, formWindow()->cursor()->isWidgetSelected(this)); + } else if (!b && docked()) { + // Undock it + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == this) { + c->remove(i); + break; + } + } + // #### restore the position + setParent(mainWindow->centralWidget()); + show(); + formWindow()->selectWidget(this, formWindow()->cursor()->isWidgetSelected(this)); + } + } +} + +Qt::DockWidgetArea QDesignerDockWidget::dockWidgetArea() const +{ + if (QMainWindow *mainWindow = qobject_cast(parentWidget())) + return mainWindow->dockWidgetArea(const_cast(this)); + + return Qt::LeftDockWidgetArea; +} + +void QDesignerDockWidget::setDockWidgetArea(Qt::DockWidgetArea dockWidgetArea) +{ + if (QMainWindow *mainWindow = qobject_cast(parentWidget())) { + if ((dockWidgetArea != Qt::NoDockWidgetArea) + && isAreaAllowed(dockWidgetArea)) { + mainWindow->addDockWidget(dockWidgetArea, this); + } + } +} + +bool QDesignerDockWidget::inMainWindow() const +{ + QMainWindow *mw = findMainWindow(); + if (mw && !mw->centralWidget()->layout()) { + if (mw == parentWidget()) + return true; + if (mw->centralWidget() == parentWidget()) + return true; + } + return false; +} + +QDesignerFormWindowInterface *QDesignerDockWidget::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(const_cast(this)); +} + +QMainWindow *QDesignerDockWidget::findMainWindow() const +{ + if (QDesignerFormWindowInterface *fw = formWindow()) + return qobject_cast(fw->mainContainer()); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_dockwidget_p.h b/src/designer/src/lib/shared/qdesigner_dockwidget_p.h new file mode 100644 index 0000000..f8031c0 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_dockwidget_p.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_DOCKWIDGET_H +#define QDESIGNER_DOCKWIDGET_H + +#include "shared_global_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +class QDESIGNER_SHARED_EXPORT QDesignerDockWidget: public QDockWidget +{ + Q_OBJECT + Q_PROPERTY(Qt::DockWidgetArea dockWidgetArea READ dockWidgetArea WRITE setDockWidgetArea DESIGNABLE true STORED false) + Q_PROPERTY(bool docked READ docked WRITE setDocked DESIGNABLE true STORED false) +public: + QDesignerDockWidget(QWidget *parent = nullptr); + ~QDesignerDockWidget() override; + + bool docked() const; + void setDocked(bool b); + + Qt::DockWidgetArea dockWidgetArea() const; + void setDockWidgetArea(Qt::DockWidgetArea dockWidgetArea); + + bool inMainWindow() const; + +private: + QDesignerFormWindowInterface *formWindow() const; + QMainWindow *findMainWindow() const; +}; + +class QDESIGNER_SHARED_EXPORT QDockWidgetPropertySheet : public QDesignerPropertySheet +{ + Q_OBJECT +public: + using QDesignerPropertySheet::QDesignerPropertySheet; + + bool isEnabled(int index) const override; +}; + +using QDockWidgetPropertySheetFactory = + QDesignerPropertySheetFactory; + +QT_END_NAMESPACE + +#endif // QDESIGNER_DOCKWIDGET_H diff --git a/src/designer/src/lib/shared/qdesigner_formbuilder.cpp b/src/designer/src/lib/shared/qdesigner_formbuilder.cpp new file mode 100644 index 0000000..ff52bfd --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_formbuilder.cpp @@ -0,0 +1,385 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_formbuilder_p.h" +#include "dynamicpropertysheet.h" +#include "qsimpleresource_p.h" +#include "widgetfactory_p.h" +#include "qdesigner_introspection_p.h" + +#include +#include +// sdk +#include +#include +#include +#include +#include +#include +#include + +#include + +// shared +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +QDesignerFormBuilder::QDesignerFormBuilder(QDesignerFormEditorInterface *core, + const DeviceProfile &deviceProfile) : + m_core(core), + m_deviceProfile(deviceProfile), + m_pixmapCache(nullptr), + m_iconCache(nullptr), + m_ignoreCreateResources(false), + m_tempResourceSet(nullptr), + m_mainWidget(true) +{ + Q_ASSERT(m_core); +} + +QString QDesignerFormBuilder::systemStyle() const +{ + return m_deviceProfile.isEmpty() ? + QString::fromUtf8(QApplication::style()->metaObject()->className()) : + m_deviceProfile.style(); +} + +QWidget *QDesignerFormBuilder::create(DomUI *ui, QWidget *parentWidget) +{ + m_mainWidget = true; + QtResourceSet *resourceSet = core()->resourceModel()->currentResourceSet(); + + // reload resource properties; + createResources(ui->elementResources()); + core()->resourceModel()->setCurrentResourceSet(m_tempResourceSet); + + m_ignoreCreateResources = true; + DesignerPixmapCache pixmapCache; + DesignerIconCache iconCache(&pixmapCache); + m_pixmapCache = &pixmapCache; + m_iconCache = &iconCache; + + QWidget *widget = QFormBuilder::create(ui, parentWidget); + + core()->resourceModel()->setCurrentResourceSet(resourceSet); + core()->resourceModel()->removeResourceSet(m_tempResourceSet); + m_tempResourceSet = nullptr; + m_ignoreCreateResources = false; + m_pixmapCache = nullptr; + m_iconCache = nullptr; + + m_customWidgetsWithScript.clear(); + return widget; +} + +QWidget *QDesignerFormBuilder::createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) +{ + QWidget *widget = nullptr; + + if (widgetName == "QToolBar"_L1) { + widget = new QToolBar(parentWidget); + } else if (widgetName == "QMenu"_L1) { + widget = new QMenu(parentWidget); + } else if (widgetName == "QMenuBar"_L1) { + widget = new QMenuBar(parentWidget); + } else { + widget = core()->widgetFactory()->createWidget(widgetName, parentWidget); + } + + if (widget) { + widget->setObjectName(name); + if (QSimpleResource::hasCustomWidgetScript(m_core, widget)) + m_customWidgetsWithScript.insert(widget); + } + + if (m_mainWidget) { // We need to apply the DPI here to take effect on size hints, etc. + m_deviceProfile.apply(m_core, widget, DeviceProfile::ApplyPreview); + m_mainWidget = false; + } + return widget; +} + +bool QDesignerFormBuilder::addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) +{ + // Use container extension or rely on scripts unless main window. + if (QFormBuilder::addItem(ui_widget, widget, parentWidget)) + return true; + + if (QDesignerContainerExtension *container = qt_extension(m_core->extensionManager(), parentWidget)) { + container->addWidget(widget); + return true; + } + return false; +} + +bool QDesignerFormBuilder::addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) +{ + return QFormBuilder::addItem(ui_item, item, layout); +} + +QIcon QDesignerFormBuilder::nameToIcon(const QString &filePath, const QString &qrcPath) +{ + Q_UNUSED(filePath); + Q_UNUSED(qrcPath); + qWarning() << "QDesignerFormBuilder::nameToIcon() is obsoleted"; + return QIcon(); +} + +QPixmap QDesignerFormBuilder::nameToPixmap(const QString &filePath, const QString &qrcPath) +{ + Q_UNUSED(filePath); + Q_UNUSED(qrcPath); + qWarning() << "QDesignerFormBuilder::nameToPixmap() is obsoleted"; + return QPixmap(); +} + +/* If the property is a enum or flag value, retrieve + * the existing enum/flag type via property sheet and use it to convert */ + +static bool readDomEnumerationValue(const DomProperty *p, + const QDesignerPropertySheetExtension* sheet, + QVariant &v) +{ + switch (p->kind()) { + case DomProperty::Set: { + const int index = sheet->indexOf(p->attributeName()); + if (index == -1) + return false; + const QVariant sheetValue = sheet->property(index); + if (sheetValue.canConvert()) { + const PropertySheetFlagValue f = qvariant_cast(sheetValue); + bool ok = false; + v = f.metaFlags.parseFlags(p->elementSet(), &ok); + if (!ok) + designerWarning(f.metaFlags.messageParseFailed(p->elementSet())); + return true; + } + } + break; + case DomProperty::Enum: { + const int index = sheet->indexOf(p->attributeName()); + if (index == -1) + return false; + const QVariant sheetValue = sheet->property(index); + if (sheetValue.canConvert()) { + const PropertySheetEnumValue e = qvariant_cast(sheetValue); + bool ok = false; + v = e.metaEnum.parseEnum(p->elementEnum(), &ok); + if (!ok) + designerWarning(e.metaEnum.messageParseFailed(p->elementEnum())); + return true; + } + } + break; + default: + break; + } + return false; +} + +void QDesignerFormBuilder::applyProperties(QObject *o, const QList &properties) +{ + if (properties.isEmpty()) + return; + + const QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), o); + const QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core()->extensionManager(), o); + const bool changingMetaObject = WidgetFactory::classNameOf(core(), o) == "QAxWidget"_L1; + const QDesignerMetaObjectInterface *meta = core()->introspection()->metaObject(o); + const bool dynamicPropertiesAllowed = dynamicSheet && dynamicSheet->dynamicPropertiesAllowed(); + + QDesignerPropertySheet *designerPropertySheet = qobject_cast( + core()->extensionManager()->extension(o, Q_TYPEID(QDesignerPropertySheetExtension))); + + if (designerPropertySheet) { + if (designerPropertySheet->pixmapCache()) + designerPropertySheet->setPixmapCache(m_pixmapCache); + if (designerPropertySheet->iconCache()) + designerPropertySheet->setIconCache(m_iconCache); + } + + for (DomProperty *p : properties) { + QVariant v; + if (!readDomEnumerationValue(p, sheet, v)) + v = toVariant(o->metaObject(), p); + + if (v.isNull()) + continue; + + const QString attributeName = p->attributeName(); + if (d->applyPropertyInternally(o, attributeName, v)) + continue; + + // refuse fake properties like current tab name (weak test) + if (!dynamicPropertiesAllowed) { + if (changingMetaObject) // Changes after setting control of QAxWidget + meta = core()->introspection()->metaObject(o); + if (meta->indexOfProperty(attributeName) == -1) + continue; + } + + QObject *obj = o; + QAbstractScrollArea *scroll = qobject_cast(o); + if (scroll && attributeName == "cursor"_L1 && scroll->viewport()) + obj = scroll->viewport(); + + // a real property + obj->setProperty(attributeName.toUtf8(), v); + } +} + +DomWidget *QDesignerFormBuilder::createDom(QWidget *widget, DomWidget *ui_parentWidget, bool recursive) +{ + DomWidget *ui_widget = QFormBuilder::createDom(widget, ui_parentWidget, recursive); + QSimpleResource::addExtensionDataToDOM(this, m_core, ui_widget, widget); + return ui_widget; +} + +QWidget *QDesignerFormBuilder::create(DomWidget *ui_widget, QWidget *parentWidget) +{ + QWidget *widget = QFormBuilder::create(ui_widget, parentWidget); + // Do not apply state if scripts are to be run in preview mode + QSimpleResource::applyExtensionDataFromDOM(this, m_core, ui_widget, widget); + return widget; +} + +void QDesignerFormBuilder::createResources(DomResources *resources) +{ + if (m_ignoreCreateResources) + return; + QStringList paths; + if (resources != nullptr) { + const auto &dom_include = resources->elementInclude(); + for (DomResource *res : dom_include) { + QString path = QDir::cleanPath(workingDirectory().absoluteFilePath(res->attributeLocation())); + paths << path; + } + } + + m_tempResourceSet = core()->resourceModel()->addResourceSet(paths); +} + +QLayout *QDesignerFormBuilder::create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) +{ + return QFormBuilder::create(ui_layout, layout, parentWidget); +} + +void QDesignerFormBuilder::loadExtraInfo(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) +{ + QFormBuilder::loadExtraInfo(ui_widget, widget, parentWidget); +} + +QWidget *QDesignerFormBuilder::createPreview(const QDesignerFormWindowInterface *fw, + const QString &styleName, + const QString &appStyleSheet, + const DeviceProfile &deviceProfile, + QString *errorMessage) +{ + // load + QDesignerFormBuilder builder(fw->core(), deviceProfile); + builder.setWorkingDirectory(fw->absoluteDir()); + + QByteArray bytes = fw->contents().toUtf8(); + + QBuffer buffer(&bytes); + buffer.open(QIODevice::ReadOnly); + + QWidget *widget = builder.load(&buffer, nullptr); + if (!widget) { // Shouldn't happen + *errorMessage = QCoreApplication::translate("QDesignerFormBuilder", "The preview failed to build."); + return nullptr; + } + // Make sure palette is applied + const QString styleToUse = styleName.isEmpty() ? builder.deviceProfile().style() : styleName; + if (!styleToUse.isEmpty()) { + if (WidgetFactory *wf = qobject_cast(fw->core()->widgetFactory())) { + if (styleToUse != wf->styleName()) + WidgetFactory::applyStyleToTopLevel(wf->getStyle(styleToUse), widget); + } + } + // Fake application style sheet by prepending. (If this doesn't work, fake by nesting + // into parent widget). + if (!appStyleSheet.isEmpty()) + widget->setStyleSheet(appStyleSheet + u'\n' + widget->styleSheet()); + return widget; +} + +QWidget *QDesignerFormBuilder::createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName) +{ + return createPreview(fw, styleName, QString()); +} + +QWidget *QDesignerFormBuilder::createPreview(const QDesignerFormWindowInterface *fw, + const QString &styleName, + const QString &appStyleSheet, + QString *errorMessage) +{ + return createPreview(fw, styleName, appStyleSheet, DeviceProfile(), errorMessage); +} + +QWidget *QDesignerFormBuilder::createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName, const QString &appStyleSheet) +{ + QString errorMessage; + QWidget *widget = createPreview(fw, styleName, appStyleSheet, DeviceProfile(), &errorMessage); + if (!widget && !errorMessage.isEmpty()) { + // Display Script errors or message box + QWidget *dialogParent = fw->core()->topLevel(); + fw->core()->dialogGui()->message(dialogParent, QDesignerDialogGuiInterface::PreviewFailureMessage, + QMessageBox::Warning, QCoreApplication::translate("QDesignerFormBuilder", "Designer"), + errorMessage, QMessageBox::Ok); + return nullptr; + } + return widget; +} + +QPixmap QDesignerFormBuilder::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &styleName, const QString &appStyleSheet) +{ + QWidget *widget = createPreview(fw, styleName, appStyleSheet); + if (!widget) + return QPixmap(); + + const QPixmap rc = widget->grab(QRect(0, 0, -1, -1)); + widget->deleteLater(); + return rc; +} + +// ---------- NewFormWidgetFormBuilder + +NewFormWidgetFormBuilder::NewFormWidgetFormBuilder(QDesignerFormEditorInterface *core, + const DeviceProfile &deviceProfile) : + QDesignerFormBuilder(core, deviceProfile) +{ +} + +void NewFormWidgetFormBuilder::createCustomWidgets(DomCustomWidgets *dc) +{ + QSimpleResource::handleDomCustomWidgets(core(), dc); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_formbuilder_p.h b/src/designer/src/lib/shared/qdesigner_formbuilder_p.h new file mode 100644 index 0000000..a4652f6 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_formbuilder_p.h @@ -0,0 +1,130 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_FORMBUILDER_H +#define QDESIGNER_FORMBUILDER_H + +#include "shared_global_p.h" +#include "deviceprofile_p.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class QPixmap; +class QtResourceSet; + +namespace qdesigner_internal { + +class DesignerPixmapCache; +class DesignerIconCache; + +/* Form builder used for previewing forms and widget box. + * It applies the system settings to its toplevel window. */ + +class QDESIGNER_SHARED_EXPORT QDesignerFormBuilder: public QFormBuilder +{ +public: + QDesignerFormBuilder(QDesignerFormEditorInterface *core, + const DeviceProfile &deviceProfile = DeviceProfile()); + + virtual QWidget *createWidget(DomWidget *ui_widget, QWidget *parentWidget = nullptr) + { return QFormBuilder::create(ui_widget, parentWidget); } + + inline QDesignerFormEditorInterface *core() const + { return m_core; } + + QString systemStyle() const; + + // Create a preview widget (for integrations) or return 0. The widget has to be embedded into a main window. + // Experimental, depending on script support. + static QWidget *createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName /* ="" */, + const QString &appStyleSheet /* ="" */, + const DeviceProfile &deviceProfile, + QString *errorMessage); + // Convenience that pops up message boxes in case of failures. + static QWidget *createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName = QString()); + // Create a preview widget (for integrations) or return 0. The widget has to be embedded into a main window. + static QWidget *createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName, const QString &appStyleSheet, QString *errorMessage); + // Convenience that pops up message boxes in case of failures. + static QWidget *createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName, const QString &appStyleSheet); + + // Create a preview image + static QPixmap createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &styleName = QString(), const QString &appStyleSheet = QString()); + +protected: + using QFormBuilder::createDom; + using QFormBuilder::create; + + QWidget *create(DomUI *ui, QWidget *parentWidget) override; + DomWidget *createDom(QWidget *widget, DomWidget *ui_parentWidget, bool recursive = true) override; + QWidget *create(DomWidget *ui_widget, QWidget *parentWidget) override; + QLayout *create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) override; + void createResources(DomResources *resources) override; + + QWidget *createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) override; + bool addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; + bool addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) override; + + virtual QIcon nameToIcon(const QString &filePath, const QString &qrcPath); + virtual QPixmap nameToPixmap(const QString &filePath, const QString &qrcPath); + + void applyProperties(QObject *o, const QList &properties) override; + + void loadExtraInfo(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; + + QtResourceSet *internalResourceSet() const { return m_tempResourceSet; } + + DeviceProfile deviceProfile() const { return m_deviceProfile; } + +private: + QDesignerFormEditorInterface *m_core; + + using WidgetSet = QSet; + WidgetSet m_customWidgetsWithScript; + + const DeviceProfile m_deviceProfile; + + DesignerPixmapCache *m_pixmapCache; + DesignerIconCache *m_iconCache; + bool m_ignoreCreateResources; + QtResourceSet *m_tempResourceSet; + bool m_mainWidget; +}; + +// Form builder for a new form widget (preview). To allow for promoted +// widgets in the template, it implements the handling of custom widgets +// (adding of them to the widget database). + +class QDESIGNER_SHARED_EXPORT NewFormWidgetFormBuilder: public QDesignerFormBuilder { +public: + NewFormWidgetFormBuilder(QDesignerFormEditorInterface *core, + const DeviceProfile &deviceProfile = DeviceProfile()); + +protected: + void createCustomWidgets(DomCustomWidgets *) override; +}; + + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_FORMBUILDER_H diff --git a/src/designer/src/lib/shared/qdesigner_formeditorcommand.cpp b/src/designer/src/lib/shared/qdesigner_formeditorcommand.cpp new file mode 100644 index 0000000..8972710 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_formeditorcommand.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "qdesigner_formeditorcommand_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ---- QDesignerFormEditorCommand ---- +QDesignerFormEditorCommand::QDesignerFormEditorCommand(const QString &description, QDesignerFormEditorInterface *core) + : QUndoCommand(description), + m_core(core) +{ +} + +QDesignerFormEditorInterface *QDesignerFormEditorCommand::core() const +{ + return m_core; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_formeditorcommand_p.h b/src/designer/src/lib/shared/qdesigner_formeditorcommand_p.h new file mode 100644 index 0000000..8b7d8c9 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_formeditorcommand_p.h @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_FORMEDITORCOMMAND_H +#define QDESIGNER_FORMEDITORCOMMAND_H + +#include "shared_global_p.h" + +#include + +#include + + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT QDesignerFormEditorCommand: public QUndoCommand +{ + +public: + QDesignerFormEditorCommand(const QString &description, QDesignerFormEditorInterface *core); + +protected: + QDesignerFormEditorInterface *core() const; + +private: + QPointer m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_FORMEDITORCOMMAND_H diff --git a/src/designer/src/lib/shared/qdesigner_formwindowcommand.cpp b/src/designer/src/lib/shared/qdesigner_formwindowcommand.cpp new file mode 100644 index 0000000..4868727 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_formwindowcommand.cpp @@ -0,0 +1,113 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "qdesigner_formwindowcommand_p.h" +#include "qdesigner_objectinspector_p.h" +#include "layout_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// ---- QDesignerFormWindowCommand ---- +QDesignerFormWindowCommand::QDesignerFormWindowCommand(const QString &description, + QDesignerFormWindowInterface *formWindow, + QUndoCommand *parent) + : QUndoCommand(description, parent), + m_formWindow(formWindow) +{ +} + +QDesignerFormWindowInterface *QDesignerFormWindowCommand::formWindow() const +{ + return m_formWindow; +} + +QDesignerFormEditorInterface *QDesignerFormWindowCommand::core() const +{ + if (QDesignerFormWindowInterface *fw = formWindow()) + return fw->core(); + + return nullptr; +} + +void QDesignerFormWindowCommand::undo() +{ + cheapUpdate(); +} + +void QDesignerFormWindowCommand::redo() +{ + cheapUpdate(); +} + +void QDesignerFormWindowCommand::cheapUpdate() +{ + if (core()->objectInspector()) + core()->objectInspector()->setFormWindow(formWindow()); + + if (core()->actionEditor()) + core()->actionEditor()->setFormWindow(formWindow()); +} + +QDesignerPropertySheetExtension* QDesignerFormWindowCommand::propertySheet(QObject *object) const +{ + return qt_extension(formWindow()->core()->extensionManager(), object); +} + +void QDesignerFormWindowCommand::updateBuddies(QDesignerFormWindowInterface *form, + const QString &old_name, + const QString &new_name) +{ + QExtensionManager* extensionManager = form->core()->extensionManager(); + + const auto label_list = form->findChildren(); + if (label_list.isEmpty()) + return; + + const QString buddyProperty = u"buddy"_s; + const QByteArray oldNameU8 = old_name.toUtf8(); + const QByteArray newNameU8 = new_name.toUtf8(); + + for (QLabel *label : label_list) { + if (QDesignerPropertySheetExtension* sheet = + qt_extension(extensionManager, label)) { + const int idx = sheet->indexOf(buddyProperty); + if (idx != -1) { + const QByteArray oldBuddy = sheet->property(idx).toByteArray(); + if (oldBuddy == oldNameU8) + sheet->setProperty(idx, newNameU8); + } + } + } +} + +void QDesignerFormWindowCommand::selectUnmanagedObject(QObject *unmanagedObject) +{ + // Keep selection in sync + if (QDesignerObjectInspector *oi = qobject_cast(core()->objectInspector())) { + oi->clearSelection(); + oi->selectObject(unmanagedObject); + } + core()->propertyEditor()->setObject(unmanagedObject); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_formwindowcommand_p.h b/src/designer/src/lib/shared/qdesigner_formwindowcommand_p.h new file mode 100644 index 0000000..a2d2ad1 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_formwindowcommand_p.h @@ -0,0 +1,61 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_FORMWINDOWCOMMAND_H +#define QDESIGNER_FORMWINDOWCOMMAND_H + +#include "shared_global_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QDesignerPropertySheetExtension; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT QDesignerFormWindowCommand: public QUndoCommand +{ + +public: + QDesignerFormWindowCommand(const QString &description, + QDesignerFormWindowInterface *formWindow, + QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + static void updateBuddies(QDesignerFormWindowInterface *form, + const QString &old_name, const QString &new_name); +protected: + QDesignerFormWindowInterface *formWindow() const; + QDesignerFormEditorInterface *core() const; + QDesignerPropertySheetExtension* propertySheet(QObject *object) const; + + void cheapUpdate(); + + void selectUnmanagedObject(QObject *unmanagedObject); +private: + QPointer m_formWindow; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_COMMAND_H diff --git a/src/designer/src/lib/shared/qdesigner_formwindowmanager.cpp b/src/designer/src/lib/shared/qdesigner_formwindowmanager.cpp new file mode 100644 index 0000000..4491619 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_formwindowmanager.cpp @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_formwindowmanager_p.h" +#include "plugindialog_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +/*! + \class qdesigner_internal::QDesignerFormWindowManager + \inmodule QtDesigner + \internal + + Extends QDesignerFormWindowManagerInterface with methods to control + the preview and printing of forms. It provides a facade that simplifies + the complexity of the more general PreviewConfiguration & PreviewManager + interfaces. + + \since 4.5 + */ + + +QDesignerFormWindowManager::QDesignerFormWindowManager(QObject *parent) + : QDesignerFormWindowManagerInterface(parent) +{ +} + +QDesignerFormWindowManager::~QDesignerFormWindowManager() = default; + +/*! + \fn PreviewManager *qdesigner_internal::QDesignerFormWindowManager::previewManager() const + + Accesses the previewmanager implementation. + + \since 4.5 + \internal + */ + +void QDesignerFormWindowManager::showPluginDialog() +{ + PluginDialog dlg(core(), core()->topLevel()); + dlg.exec(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_formwindowmanager_p.h b/src/designer/src/lib/shared/qdesigner_formwindowmanager_p.h new file mode 100644 index 0000000..e4864d7 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_formwindowmanager_p.h @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_FORMWINDOMANAGER_H +#define QDESIGNER_FORMWINDOMANAGER_H + +#include "shared_global_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class PreviewManager; + +// +// Convenience methods to manage form previews (ultimately forwarded to PreviewManager). +// +class QDESIGNER_SHARED_EXPORT QDesignerFormWindowManager + : public QDesignerFormWindowManagerInterface +{ + Q_OBJECT +public: + explicit QDesignerFormWindowManager(QObject *parent = nullptr); + ~QDesignerFormWindowManager() override; + + virtual PreviewManager *previewManager() const = 0; + + void showPluginDialog() override; + +private: + void *m_unused = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_FORMWINDOMANAGER_H diff --git a/src/designer/src/lib/shared/qdesigner_introspection.cpp b/src/designer/src/lib/shared/qdesigner_introspection.cpp new file mode 100644 index 0000000..60a4272 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_introspection.cpp @@ -0,0 +1,331 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_introspection_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Qt Implementation +static QStringList byteArrayListToStringList(const QByteArrayList &l) +{ + if (l.isEmpty()) + return QStringList(); + QStringList rc; + for (const QByteArray &b : l) + rc += QString::fromUtf8(b); + return rc; +} + +static inline QString charToQString(const char *c) +{ + if (!c) + return QString(); + return QString::fromUtf8(c); +} + +namespace { + // ------- QDesignerMetaEnum + class QDesignerMetaEnum : public QDesignerMetaEnumInterface { + public: + QDesignerMetaEnum(const QMetaEnum &qEnum); + bool isFlag() const override { return m_enum.isFlag(); } + QString key(int index) const override { return charToQString(m_enum.key(index)); } + int keyCount() const override { return m_enum.keyCount(); } + int keyToValue(const QString &key) const override { return m_enum.keyToValue(key.toUtf8()); } + int keysToValue(const QString &keys) const override { return m_enum.keysToValue(keys.toUtf8()); } + QString name() const override { return m_name; } + QString enumName() const override { return charToQString(m_enum.enumName()); } + QString scope() const override { return m_scope; } + QString separator() const override; + int value(int index) const override { return m_enum.value(index); } + QString valueToKey(int value) const override { return charToQString(m_enum.valueToKey(value)); } + QString valueToKeys(int value) const override { return charToQString(m_enum.valueToKeys(value)); } + + private: + const QMetaEnum m_enum; + const QString m_name; + const QString m_scope; + }; + + QDesignerMetaEnum::QDesignerMetaEnum(const QMetaEnum &qEnum) : + m_enum(qEnum), + m_name(charToQString(m_enum.name())), + m_scope(charToQString(m_enum.scope())) + { + } + + QString QDesignerMetaEnum::separator() const + { + return u"::"_s; + } + + // ------- QDesignerMetaProperty + class QDesignerMetaProperty : public QDesignerMetaPropertyInterface { + public: + QDesignerMetaProperty(const QMetaProperty &property); + ~QDesignerMetaProperty() override; + + const QDesignerMetaEnumInterface *enumerator() const override { return m_enumerator; } + + Kind kind() const override { return m_kind; } + + AccessFlags accessFlags() const override { return m_access; } + Attributes attributes() const override; + + int type() const override { return m_property.metaType().id(); } + QString name() const override { return m_name; } + QString typeName() const override { return m_typeName; } + int userType() const override { return m_property.userType(); } + bool hasSetter() const override { return m_property.hasStdCppSet(); } + + QVariant read(const QObject *object) const override { return m_property.read(object); } + bool reset(QObject *object) const override { return m_property.reset(object); } + bool write(QObject *object, const QVariant &value) const override { return m_property.write(object, value); } + + private: + const QMetaProperty m_property; + const QString m_name; + const QString m_typeName; + Kind m_kind; + AccessFlags m_access; + Attributes m_defaultAttributes; + QDesignerMetaEnumInterface *m_enumerator; + }; + + QDesignerMetaProperty::QDesignerMetaProperty(const QMetaProperty &property) : + m_property(property), + m_name(charToQString(m_property.name())), + m_typeName(charToQString(m_property.typeName())), + m_kind(OtherKind), + m_enumerator(nullptr) + { + if (m_property.isFlagType() || m_property.isEnumType()) { + const QMetaEnum metaEnum = m_property.enumerator(); + Q_ASSERT(metaEnum.isValid()); + m_enumerator = new QDesignerMetaEnum(metaEnum); + } + // kind + if (m_property.isFlagType()) + m_kind = FlagKind; + else + if (m_property.isEnumType()) + m_kind = EnumKind; + // flags and attributes + if (m_property.isReadable()) + m_access |= ReadAccess; + if (m_property.isWritable()) + m_access |= WriteAccess; + if (m_property.isResettable()) + m_access |= ResetAccess; + + if (m_property.isDesignable()) + m_defaultAttributes |= DesignableAttribute; + if (m_property.isScriptable()) + m_defaultAttributes |= ScriptableAttribute; + if (m_property.isStored()) + m_defaultAttributes |= StoredAttribute; + if (m_property.isUser()) + m_defaultAttributes |= UserAttribute; + } + + QDesignerMetaProperty::~QDesignerMetaProperty() + { + delete m_enumerator; + } + + QDesignerMetaProperty::Attributes QDesignerMetaProperty::attributes() const + { + return m_defaultAttributes; + } + + // -------------- QDesignerMetaMethod + + class QDesignerMetaMethod : public QDesignerMetaMethodInterface { + public: + QDesignerMetaMethod(const QMetaMethod &method); + + Access access() const override { return m_access; } + MethodType methodType() const override { return m_methodType; } + QStringList parameterNames() const override { return m_parameterNames; } + QStringList parameterTypes() const override { return m_parameterTypes; } + QString signature() const override { return m_signature; } + QString normalizedSignature() const override { return m_normalizedSignature; } + QString tag() const override { return m_tag; } + QString typeName() const override { return m_typeName; } + + private: + Access m_access; + MethodType m_methodType; + const QStringList m_parameterNames; + const QStringList m_parameterTypes; + const QString m_signature; + const QString m_normalizedSignature; + const QString m_tag; + const QString m_typeName; + }; + + QDesignerMetaMethod::QDesignerMetaMethod(const QMetaMethod &method) : + m_parameterNames(byteArrayListToStringList(method.parameterNames())), + m_parameterTypes(byteArrayListToStringList(method.parameterTypes())), + m_signature(QString::fromLatin1(method.methodSignature())), + m_normalizedSignature(QString::fromLatin1(QMetaObject::normalizedSignature(method.methodSignature().constData()))), + m_tag(charToQString(method.tag())), + m_typeName(charToQString(method.typeName())) + { + switch (method.access()) { + case QMetaMethod::Public: + m_access = Public; + break; + case QMetaMethod::Protected: + m_access = Protected; + break; + case QMetaMethod::Private: + m_access = Private; + break; + + } + switch (method.methodType()) { + case QMetaMethod::Constructor: + m_methodType = Constructor; + break; + case QMetaMethod::Method: + m_methodType = Method; + break; + case QMetaMethod::Signal: + m_methodType = Signal; + break; + + case QMetaMethod::Slot: + m_methodType = Slot; + break; + } + } + + // ------------- QDesignerMetaObject + class QDesignerMetaObject : public QDesignerMetaObjectInterface { + public: + QDesignerMetaObject(const qdesigner_internal::QDesignerIntrospection *introspection, const QMetaObject *metaObject); + ~QDesignerMetaObject() override; + + QString className() const override { return m_className; } + const QDesignerMetaEnumInterface *enumerator(int index) const override + { return m_enumerators[index]; } + int enumeratorCount() const override { return m_enumerators.size(); } + int enumeratorOffset() const override { return m_metaObject->enumeratorOffset(); } + + int indexOfEnumerator(const QString &name) const override + { return m_metaObject->indexOfEnumerator(name.toUtf8()); } + int indexOfMethod(const QString &method) const override + { return m_metaObject->indexOfMethod(method.toUtf8()); } + int indexOfProperty(const QString &name) const override + { return m_metaObject->indexOfProperty(name.toUtf8()); } + int indexOfSignal(const QString &signal) const override + { return m_metaObject->indexOfSignal(signal.toUtf8()); } + int indexOfSlot(const QString &slot) const override + { return m_metaObject->indexOfSlot(slot.toUtf8()); } + + const QDesignerMetaMethodInterface *method(int index) const override + { return m_methods[index]; } + int methodCount() const override { return m_methods.size(); } + int methodOffset() const override { return m_metaObject->methodOffset(); } + + const QDesignerMetaPropertyInterface *property(int index) const override + { return m_properties[index]; } + int propertyCount() const override { return m_properties.size(); } + int propertyOffset() const override { return m_metaObject->propertyOffset(); } + + const QDesignerMetaObjectInterface *superClass() const override; + const QDesignerMetaPropertyInterface *userProperty() const override + { return m_userProperty; } + + private: + const QString m_className; + const qdesigner_internal::QDesignerIntrospection *m_introspection; + const QMetaObject *m_metaObject; + + using Enumerators = QList; + Enumerators m_enumerators; + + using Methods = QList; + Methods m_methods; + + using Properties = QList; + Properties m_properties; + + QDesignerMetaPropertyInterface *m_userProperty; + }; + + QDesignerMetaObject::QDesignerMetaObject(const qdesigner_internal::QDesignerIntrospection *introspection, const QMetaObject *metaObject) : + m_className(charToQString(metaObject->className())), + m_introspection(introspection), + m_metaObject(metaObject), + m_userProperty(nullptr) + { + const int numEnumerators = metaObject->enumeratorCount(); + m_enumerators.reserve(numEnumerators); + for (int i = 0; i < numEnumerators; i++) + m_enumerators.push_back(new QDesignerMetaEnum(metaObject->enumerator(i))); + const int numMethods = metaObject->methodCount(); + m_methods.reserve(numMethods); + for (int i = 0; i < numMethods; i++) + m_methods.push_back(new QDesignerMetaMethod(metaObject->method(i))); + + const int numProperties = metaObject->propertyCount(); + m_properties.reserve(numProperties); + for (int i = 0; i < numProperties; i++) + m_properties.push_back(new QDesignerMetaProperty(metaObject->property(i))); + + const QMetaProperty userProperty = metaObject->userProperty(); + if (userProperty.isValid()) + m_userProperty = new QDesignerMetaProperty(userProperty); + } + + QDesignerMetaObject::~QDesignerMetaObject() + { + qDeleteAll(m_enumerators); + qDeleteAll(m_methods); + qDeleteAll(m_properties); + delete m_userProperty; + } + + const QDesignerMetaObjectInterface *QDesignerMetaObject::superClass() const + { + const QMetaObject *qSuperClass = m_metaObject->superClass(); + if (!qSuperClass) + return nullptr; + return m_introspection->metaObjectForQMetaObject(qSuperClass); + } + +} + +namespace qdesigner_internal { + + QDesignerIntrospection::QDesignerIntrospection() = default; + + QDesignerIntrospection::~QDesignerIntrospection() + { + qDeleteAll(m_metaObjectMap.values()); + } + + const QDesignerMetaObjectInterface* QDesignerIntrospection::metaObject(const QObject *object) const + { + return metaObjectForQMetaObject(object->metaObject()); + } + + const QDesignerMetaObjectInterface* QDesignerIntrospection::metaObjectForQMetaObject(const QMetaObject *metaObject) const + { + auto it = m_metaObjectMap.find(metaObject); + if (it == m_metaObjectMap.end()) + it = m_metaObjectMap.insert(metaObject, new QDesignerMetaObject(this, metaObject)); + return it.value(); + } +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_introspection_p.h b/src/designer/src/lib/shared/qdesigner_introspection_p.h new file mode 100644 index 0000000..1cdf0aa --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_introspection_p.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DESIGNERINTROSPECTION +#define DESIGNERINTROSPECTION + +#include "shared_global_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +struct QMetaObject; +class QWidget; + +namespace qdesigner_internal { + // Qt C++ introspection with helpers to find core and meta object for an object + class QDESIGNER_SHARED_EXPORT QDesignerIntrospection : public QDesignerIntrospectionInterface { + public: + QDesignerIntrospection(); + ~QDesignerIntrospection() override; + + const QDesignerMetaObjectInterface* metaObject(const QObject *object) const override; + + const QDesignerMetaObjectInterface* metaObjectForQMetaObject(const QMetaObject *metaObject) const; + + private: + mutable QHash m_metaObjectMap; + }; +} + +QT_END_NAMESPACE + +#endif // DESIGNERINTROSPECTION diff --git a/src/designer/src/lib/shared/qdesigner_membersheet.cpp b/src/designer/src/lib/shared/qdesigner_membersheet.cpp new file mode 100644 index 0000000..5451222 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_membersheet.cpp @@ -0,0 +1,212 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_membersheet_p.h" +#include "qdesigner_propertysheet_p.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QByteArrayList stringListToByteArray(const QStringList &l) +{ + QByteArrayList rc; + for (const auto &s : l) + rc += s.toUtf8(); + return rc; +} + +// ------------ QDesignerMemberSheetPrivate +class QDesignerMemberSheetPrivate { +public: + explicit QDesignerMemberSheetPrivate(QObject *object, QObject *sheetParent); + + QDesignerFormEditorInterface *m_core; + const QDesignerMetaObjectInterface *m_meta; + + class Info { + public: + QString group; + bool visible{true}; + }; + + Info &ensureInfo(int index); + + QHash m_info; +}; + +QDesignerMemberSheetPrivate::QDesignerMemberSheetPrivate(QObject *object, QObject *sheetParent) : + m_core(QDesignerPropertySheet::formEditorForObject(sheetParent)), + m_meta(m_core->introspection()->metaObject(object)) +{ +} + +QDesignerMemberSheetPrivate::Info &QDesignerMemberSheetPrivate::ensureInfo(int index) +{ + auto it = m_info.find(index); + if (it == m_info.end()) + it = m_info.insert(index, Info()); + return it.value(); +} + +// --------- QDesignerMemberSheet + +QDesignerMemberSheet::QDesignerMemberSheet(QObject *object, QObject *parent) : + QObject(parent), + d(new QDesignerMemberSheetPrivate(object, parent)) +{ +} + +QDesignerMemberSheet::~QDesignerMemberSheet() +{ + delete d; +} + +int QDesignerMemberSheet::count() const +{ + return d->m_meta->methodCount(); +} + +int QDesignerMemberSheet::indexOf(const QString &name) const +{ + return d->m_meta->indexOfMethod(name); +} + +QString QDesignerMemberSheet::memberName(int index) const +{ + return d->m_meta->method(index)->tag(); +} + +QString QDesignerMemberSheet::declaredInClass(int index) const +{ + const QString member = d->m_meta->method(index)->signature(); + + // Find class whose superclass does not contain the method. + const QDesignerMetaObjectInterface *meta_obj = d->m_meta; + + for (;;) { + const QDesignerMetaObjectInterface *tmp = meta_obj->superClass(); + if (tmp == nullptr) + break; + if (tmp->indexOfMethod(member) == -1) + break; + meta_obj = tmp; + } + return meta_obj->className(); +} + +QString QDesignerMemberSheet::memberGroup(int index) const +{ + return d->m_info.value(index).group; +} + +void QDesignerMemberSheet::setMemberGroup(int index, const QString &group) +{ + d->ensureInfo(index).group = group; +} + +QString QDesignerMemberSheet::signature(int index) const +{ + return d->m_meta->method(index)->normalizedSignature(); +} + +bool QDesignerMemberSheet::isVisible(int index) const +{ + const auto it = d->m_info.constFind(index); + if (it != d->m_info.constEnd()) + return it.value().visible; + + return d->m_meta->method(index)->methodType() == QDesignerMetaMethodInterface::Signal + || d->m_meta->method(index)->access() == QDesignerMetaMethodInterface::Public; +} + +void QDesignerMemberSheet::setVisible(int index, bool visible) +{ + d->ensureInfo(index).visible = visible; +} + +bool QDesignerMemberSheet::isSignal(int index) const +{ + return d->m_meta->method(index)->methodType() == QDesignerMetaMethodInterface::Signal; +} + +bool QDesignerMemberSheet::isSlot(int index) const +{ + return d->m_meta->method(index)->methodType() == QDesignerMetaMethodInterface::Slot; +} + +bool QDesignerMemberSheet::inheritedFromWidget(int index) const +{ + return declaredInClass(index) == "QWidget"_L1 || declaredInClass(index) == "QObject"_L1; +} + + +QList QDesignerMemberSheet::parameterTypes(int index) const +{ + return stringListToByteArray(d->m_meta->method(index)->parameterTypes()); +} + +QList QDesignerMemberSheet::parameterNames(int index) const +{ + return stringListToByteArray(d->m_meta->method(index)->parameterNames()); +} + +bool QDesignerMemberSheet::signalMatchesSlot(const QString &signal, const QString &slot) +{ + bool result = true; + + do { + qsizetype signal_idx = signal.indexOf(u'('); + qsizetype slot_idx = slot.indexOf(u'('); + if (signal_idx == -1 || slot_idx == -1) + break; + + ++signal_idx; ++slot_idx; + + if (slot.at(slot_idx) == u')') + break; + + while (signal_idx < signal.size() && slot_idx < slot.size()) { + const QChar signal_c = signal.at(signal_idx); + const QChar slot_c = slot.at(slot_idx); + + if (signal_c == u',' && slot_c == u')') + break; + + if (signal_c == u')' && slot_c == u')') + break; + + if (signal_c != slot_c) { + result = false; + break; + } + + ++signal_idx; ++slot_idx; + } + } while (false); + + return result; +} + +// ------------ QDesignerMemberSheetFactory + +QDesignerMemberSheetFactory::QDesignerMemberSheetFactory(QExtensionManager *parent) + : QExtensionFactory(parent) +{ +} + +QObject *QDesignerMemberSheetFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const +{ + if (iid == Q_TYPEID(QDesignerMemberSheetExtension)) { + return new QDesignerMemberSheet(object, parent); + } + + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_membersheet_p.h b/src/designer/src/lib/shared/qdesigner_membersheet_p.h new file mode 100644 index 0000000..b8df5e2 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_membersheet_p.h @@ -0,0 +1,79 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_MEMBERSHEET_H +#define QDESIGNER_MEMBERSHEET_H + +#include "shared_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerMemberSheetPrivate; + +class QDESIGNER_SHARED_EXPORT QDesignerMemberSheet: public QObject, public QDesignerMemberSheetExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerMemberSheetExtension) + +public: + explicit QDesignerMemberSheet(QObject *object, QObject *parent = nullptr); + ~QDesignerMemberSheet() override; + + int indexOf(const QString &name) const override; + + int count() const override; + QString memberName(int index) const override; + + QString memberGroup(int index) const override; + void setMemberGroup(int index, const QString &group) override; + + bool isVisible(int index) const override; + void setVisible(int index, bool b) override; + + bool isSignal(int index) const override; + bool isSlot(int index) const override; + + bool inheritedFromWidget(int index) const override; + + static bool signalMatchesSlot(const QString &signal, const QString &slot); + + QString declaredInClass(int index) const override; + + QString signature(int index) const override; + QList parameterTypes(int index) const override; + QList parameterNames(int index) const override; + +private: + QDesignerMemberSheetPrivate *d; +}; + +class QDESIGNER_SHARED_EXPORT QDesignerMemberSheetFactory: public QExtensionFactory +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionFactory) + +public: + QDesignerMemberSheetFactory(QExtensionManager *parent = nullptr); + +protected: + QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_MEMBERSHEET_H diff --git a/src/designer/src/lib/shared/qdesigner_menu.cpp b/src/designer/src/lib/shared/qdesigner_menu.cpp new file mode 100644 index 0000000..c19f98d --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_menu.cpp @@ -0,0 +1,1363 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_menu_p.h" +#include "qdesigner_menubar_p.h" +#include "qdesigner_toolbar_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "actionrepository_p.h" +#include "actionprovider_p.h" +#include "actioneditor_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_objectinspector_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// give the user a little more space to click on the sub menu rectangle +static inline void extendClickableArea(QRect *subMenuRect, Qt::LayoutDirection dir) +{ + switch (dir) { + case Qt::LayoutDirectionAuto: // Should never happen + case Qt::LeftToRight: + subMenuRect->setLeft(subMenuRect->left() - 20); + break; + case Qt::RightToLeft: + subMenuRect->setRight(subMenuRect->right() + 20); + break; + } +} + +QDesignerMenu::QDesignerMenu(QWidget *parent) : + QMenu(parent), + m_subMenuPixmap(QPixmap(u":/qt-project.org/formeditor/images/submenu.png"_s)), + m_currentIndex(0), + m_addItem(new qdesigner_internal::SpecialMenuAction(this)), + m_addSeparator(new qdesigner_internal::SpecialMenuAction(this)), + m_showSubMenuTimer(new QTimer(this)), + m_deactivateWindowTimer(new QTimer(this)), + m_adjustSizeTimer(new QTimer(this)), + m_editor(new QLineEdit(this)), + m_dragging(false), + m_lastSubMenuIndex(-1) +{ + setContextMenuPolicy(Qt::DefaultContextMenu); + setAcceptDrops(true); // ### fake + setSeparatorsCollapsible(false); + + connect(m_adjustSizeTimer, &QTimer::timeout, this, &QDesignerMenu::slotAdjustSizeNow); + m_addItem->setText(tr("Type Here")); + addAction(m_addItem); + + m_addSeparator->setText(tr("Add Separator")); + addAction(m_addSeparator); + + connect(m_showSubMenuTimer, &QTimer::timeout, this, &QDesignerMenu::slotShowSubMenuNow); + + connect(m_deactivateWindowTimer, &QTimer::timeout, this, &QDesignerMenu::slotDeactivateNow); + + m_editor->setObjectName(u"__qt__passive_editor"_s); + m_editor->hide(); + + m_editor->installEventFilter(this); + installEventFilter(this); +} + +QDesignerMenu::~QDesignerMenu() = default; + +void QDesignerMenu::slotAdjustSizeNow() +{ + // Not using a single-shot, since we want to compress the timers if many items are being + // adjusted + m_adjustSizeTimer->stop(); + adjustSize(); +} + +bool QDesignerMenu::handleEvent(QWidget *widget, QEvent *event) +{ + if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) { + update(); + + if (widget == m_editor) + return false; + } + + switch (event->type()) { + default: break; + + case QEvent::MouseButtonPress: + return handleMousePressEvent(widget, static_cast(event)); + case QEvent::MouseButtonRelease: + return handleMouseReleaseEvent(widget, static_cast(event)); + case QEvent::MouseButtonDblClick: + return handleMouseDoubleClickEvent(widget, static_cast(event)); + case QEvent::MouseMove: + return handleMouseMoveEvent(widget, static_cast(event)); + case QEvent::ContextMenu: + return handleContextMenuEvent(widget, static_cast(event)); + case QEvent::KeyPress: + return handleKeyPressEvent(widget, static_cast(event)); + } + + return true; +} + +void QDesignerMenu::startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers) +{ + using namespace qdesigner_internal; + + const int index = findAction(pos); + if (index >= realActionCount()) + return; + + QAction *action = safeActionAt(index); + + QDesignerFormWindowInterface *fw = formWindow(); + const Qt::DropAction dropAction = (modifiers & Qt::ControlModifier) ? Qt::CopyAction : Qt::MoveAction; + if (dropAction == Qt::MoveAction) { + auto *cmd = new RemoveActionFromCommand(fw); + cmd->init(this, action, actions().at(index + 1)); + fw->commandHistory()->push(cmd); + } + + QDrag *drag = new QDrag(this); + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(action)); + drag->setMimeData(new ActionRepositoryMimeData(action, dropAction)); + + const int old_index = m_currentIndex; + m_currentIndex = -1; + + if (drag->exec(dropAction) == Qt::IgnoreAction) { + if (dropAction == Qt::MoveAction) { + QAction *previous = safeActionAt(index); + auto *cmd = new InsertActionIntoCommand(fw); + cmd->init(this, action, previous); + fw->commandHistory()->push(cmd); + } + + m_currentIndex = old_index; + } +} + +bool QDesignerMenu::handleKeyPressEvent(QWidget * /*widget*/, QKeyEvent *e) +{ + m_showSubMenuTimer->stop(); + + if (m_editor->isHidden() && hasFocus()) { // In navigation mode + switch (e->key()) { + + case Qt::Key_Delete: + if (m_currentIndex == -1 || m_currentIndex >= realActionCount()) + break; + hideSubMenu(); + deleteAction(); + break; + + case Qt::Key_Left: + e->accept(); + moveLeft(); + return true; + + case Qt::Key_Right: + e->accept(); + moveRight(); + return true; // no update + + case Qt::Key_Up: + e->accept(); + moveUp(e->modifiers() & Qt::ControlModifier); + return true; + + case Qt::Key_Down: + e->accept(); + moveDown(e->modifiers() & Qt::ControlModifier); + return true; + + case Qt::Key_PageUp: + m_currentIndex = 0; + break; + + case Qt::Key_PageDown: + m_currentIndex = actions().size() - 1; + break; + + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_F2: + e->accept(); + enterEditMode(); + return true; // no update + + case Qt::Key_Escape: + e->ignore(); + setFocus(); + hide(); + closeMenuChain(); + return true; + + case Qt::Key_Alt: + case Qt::Key_Shift: + case Qt::Key_Control: + e->ignore(); + setFocus(); // FIXME: this is because some other widget get the focus when CTRL is pressed + return true; // no update + + default: { + QAction *action = currentAction(); + if (!action || action->isSeparator() || action == m_addSeparator) { + e->ignore(); + return true; + } + if (!e->text().isEmpty() && e->text().at(0).toLatin1() >= 32) { + showLineEdit(); + QApplication::sendEvent(m_editor, e); + e->accept(); + } else { + e->ignore(); + } + } + return true; + } + } else if (m_editor->hasFocus()) { // In edit mode + switch (e->key()) { + default: + e->ignore(); + return false; + + case Qt::Key_Enter: + case Qt::Key_Return: + if (!m_editor->text().isEmpty()) { + leaveEditMode(ForceAccept); + m_editor->hide(); + setFocus(); + moveDown(false); + break; + } + Q_FALLTHROUGH(); + + case Qt::Key_Escape: + m_editor->hide(); + setFocus(); + break; + } + } + + e->accept(); + update(); + + return true; +} + +static void sendMouseEventTo(QWidget *target, const QPoint &targetPoint, const QMouseEvent *event) +{ + QMouseEvent e(event->type(), targetPoint, event->globalPosition().toPoint(), event->button(), event->buttons(), event->modifiers()); + QApplication::sendEvent(target, &e); +} + +bool QDesignerMenu::handleMouseDoubleClickEvent(QWidget *, QMouseEvent *event) +{ + event->accept(); + m_startPosition = QPoint(); + + if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) + return true; + + if (!rect().contains(event->position().toPoint())) { + // special case for menubar + QWidget *target = QApplication::widgetAt(event->globalPosition().toPoint()); + QMenuBar *mb = qobject_cast(target); + QDesignerMenu *menu = qobject_cast(target); + if (mb != nullptr || menu != nullptr) { + const QPoint pt = target->mapFromGlobal(event->globalPosition().toPoint()); + QAction *action = mb == nullptr ? menu->actionAt(pt) : mb->actionAt(pt); + if (action) + sendMouseEventTo(target, pt, event); + } + return true; + } + + m_currentIndex = findAction(event->position().toPoint()); + QAction *action = safeActionAt(m_currentIndex); + + QRect pm_rect; + if (action->menu() || hasSubMenuPixmap(action)) { + pm_rect = subMenuPixmapRect(action); + extendClickableArea(&pm_rect, layoutDirection()); + } + + if (!pm_rect.contains(event->position().toPoint()) && m_currentIndex != -1) + enterEditMode(); + + return true; +} + +bool QDesignerMenu::handleMousePressEvent(QWidget * /*widget*/, QMouseEvent *event) +{ + if (!rect().contains(event->position().toPoint())) { + QWidget *clickedWidget = QApplication::widgetAt(event->globalPosition().toPoint()); + if (QMenuBar *mb = qobject_cast(clickedWidget)) { + const QPoint pt = mb->mapFromGlobal(event->globalPosition().toPoint()); + if (QAction *action = mb->actionAt(pt)) { + QMenu * menu = action->menu(); + if (menu == findRootMenu()) { + // propagate the mouse press event (but don't close the popup) + sendMouseEventTo(mb, pt, event); + return true; + } + } + } + + if (QDesignerMenu *m = qobject_cast(clickedWidget)) { + m->hideSubMenu(); + sendMouseEventTo(m, m->mapFromGlobal(event->globalPosition().toPoint()), event); + } else { + QDesignerMenu *root = findRootMenu(); + root->hide(); + root->hideSubMenu(); + } + if (clickedWidget) { + if (QWidget *focusProxy = clickedWidget->focusProxy()) + clickedWidget = focusProxy; + if (clickedWidget->focusPolicy() != Qt::NoFocus) + clickedWidget->setFocus(Qt::OtherFocusReason); + } + return true; + } + + m_showSubMenuTimer->stop(); + m_startPosition = QPoint(); + event->accept(); + + if (event->button() != Qt::LeftButton) + return true; + + m_startPosition = mapFromGlobal(event->globalPosition().toPoint()); + + const int index = findAction(m_startPosition); + + QAction *action = safeActionAt(index); + QRect pm_rect = subMenuPixmapRect(action); + extendClickableArea(&pm_rect, layoutDirection()); + + const int old_index = m_currentIndex; + m_currentIndex = index; + if ((hasSubMenuPixmap(action) || action->menu() != nullptr) + && pm_rect.contains(m_startPosition)) { + if (m_currentIndex == m_lastSubMenuIndex) { + hideSubMenu(); + } else + slotShowSubMenuNow(); + } else { + if (index == old_index) { + if (m_currentIndex == m_lastSubMenuIndex) + hideSubMenu(); + } else { + hideSubMenu(); + } + } + + update(); + if (index != old_index) + selectCurrentAction(); + + return true; +} + +bool QDesignerMenu::handleMouseReleaseEvent(QWidget *, QMouseEvent *event) +{ + event->accept(); + m_startPosition = QPoint(); + + return true; +} + +bool QDesignerMenu::handleMouseMoveEvent(QWidget *, QMouseEvent *event) +{ + if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) + return true; + + if (!rect().contains(event->position().toPoint())) { + + if (QMenuBar *mb = qobject_cast(QApplication::widgetAt(event->globalPosition().toPoint()))) { + const QPoint pt = mb->mapFromGlobal(event->globalPosition().toPoint()); + QAction *action = mb->actionAt(pt); + if (action && action->menu() == findRootMenu()) { + // propagate the mouse press event (but don't close the popup) + sendMouseEventTo(mb, pt, event); + return true; + } + // hide the popup Qt will replay the event + slotDeactivateNow(); + } + return true; + } + + if (m_startPosition.isNull()) + return true; + + event->accept(); + + const QPoint pos = mapFromGlobal(event->globalPosition().toPoint()); + + if ((pos - m_startPosition).manhattanLength() < qApp->startDragDistance()) + return true; + + startDrag(m_startPosition, event->modifiers()); + m_startPosition = QPoint(); + + return true; +} + +bool QDesignerMenu::handleContextMenuEvent(QWidget *, QContextMenuEvent *event) +{ + event->accept(); + + const int index = findAction(mapFromGlobal(event->globalPos())); + QAction *action = safeActionAt(index); + if (qobject_cast(action)) + return true; + + QMenu menu; + QVariant itemData; + itemData.setValue(action); + + QAction *addSeparatorAction = menu.addAction(tr("Insert separator")); + addSeparatorAction->setData(itemData); + + QAction *removeAction = nullptr; + if (action->isSeparator()) + removeAction = menu.addAction(tr("Remove separator")); + else + removeAction = menu.addAction(tr("Remove action '%1'").arg(action->objectName())); + removeAction->setData(itemData); + + connect(addSeparatorAction, &QAction::triggered, this, &QDesignerMenu::slotAddSeparator); + connect(removeAction, &QAction::triggered, this, &QDesignerMenu::slotRemoveSelectedAction); + menu.exec(event->globalPos()); + + return true; +} + +void QDesignerMenu::slotAddSeparator() +{ + QAction *action = qobject_cast(sender()); + if (!action) + return; + + QAction *a = qvariant_cast(action->data()); + Q_ASSERT(a != nullptr); + + const int pos = actions().indexOf(a); + QAction *action_before = nullptr; + if (pos != -1) + action_before = safeActionAt(pos); + + QDesignerFormWindowInterface *fw = formWindow(); + fw->beginCommand(tr("Add separator")); + QAction *sep = createAction(QString(), true); + + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, sep, action_before); + fw->commandHistory()->push(cmd); + + if (parentMenu()) { + QAction *parent_action = parentMenu()->currentAction(); + if (parent_action->menu() == nullptr) { + auto *cmd = new qdesigner_internal::CreateSubmenuCommand(fw); + cmd->init(parentMenu(), parentMenu()->currentAction()); + fw->commandHistory()->push(cmd); + } + } + + fw->endCommand(); +} + +void QDesignerMenu::slotRemoveSelectedAction() +{ + if (QAction *action = qobject_cast(sender())) + if (QAction *a = qvariant_cast(action->data())) + deleteAction(a); +} + +void QDesignerMenu::deleteAction(QAction *a) +{ + const int pos = actions().indexOf(a); + QAction *action_before = nullptr; + if (pos != -1) + action_before = safeActionAt(pos + 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd->init(this, a, action_before); + fw->commandHistory()->push(cmd); +} + +QRect QDesignerMenu::subMenuPixmapRect(QAction *action) const +{ + const QRect g = actionGeometry(action); + const int x = layoutDirection() == Qt::LeftToRight ? (g.right() - m_subMenuPixmap.width() - 2) : 2; + const int y = g.top() + (g.height() - m_subMenuPixmap.height())/2 + 1; + return QRect(x, y, m_subMenuPixmap.width(), m_subMenuPixmap.height()); +} + +bool QDesignerMenu::hasSubMenuPixmap(QAction *action) const +{ + return action != nullptr + && qobject_cast(action) == nullptr + && !action->isSeparator() + && !action->menu() + && canCreateSubMenu(action); +} + +void QDesignerMenu::showEvent ( QShowEvent * event ) +{ + selectCurrentAction(); + QMenu::showEvent (event); +} + +void QDesignerMenu::paintEvent(QPaintEvent *event) +{ + using namespace qdesigner_internal; + + QMenu::paintEvent(event); + + QPainter p(this); + + QAction *current = currentAction(); + + const auto &actionList = actions(); + for (QAction *a : actionList) { + const QRect g = actionGeometry(a); + + if (qobject_cast(a)) { + QLinearGradient lg(g.left(), g.top(), g.left(), g.bottom()); + lg.setColorAt(0.0, Qt::transparent); + lg.setColorAt(0.7, QColor(0, 0, 0, 32)); + lg.setColorAt(1.0, Qt::transparent); + + p.fillRect(g, lg); + } else if (hasSubMenuPixmap(a)) { + p.drawPixmap(subMenuPixmapRect(a).topLeft(), m_subMenuPixmap); + } + } + + if (!hasFocus() || !current || m_dragging) + return; + + if (QDesignerMenu *menu = parentMenu()) { + if (menu->dragging()) + return; + } + + if (QDesignerMenuBar *menubar = qobject_cast(parentWidget())) { + if (menubar->dragging()) + return; + } + + const QRect g = actionGeometry(current); + drawSelection(&p, g.adjusted(1, 1, -3, -3)); +} + +bool QDesignerMenu::dragging() const +{ + return m_dragging; +} + +QDesignerMenu *QDesignerMenu::findRootMenu() const +{ + if (parentMenu()) + return parentMenu()->findRootMenu(); + + return const_cast(this); +} + +QDesignerMenu *QDesignerMenu::findActivatedMenu() const +{ + if (QDesignerMenu *activeDesignerMenu = qobject_cast(QApplication::activeWindow())) { + if (activeDesignerMenu == this || findChildren().contains(activeDesignerMenu)) + return activeDesignerMenu; + } + + return nullptr; +} + +bool QDesignerMenu::eventFilter(QObject *object, QEvent *event) +{ + if (object != this && object != m_editor) { + return false; + } + + if (!m_editor->isHidden() && object == m_editor && event->type() == QEvent::FocusOut) { + leaveEditMode(Default); + m_editor->hide(); + update(); + return false; + } + + bool dispatch = true; + + switch (event->type()) { + default: break; + + case QEvent::WindowDeactivate: + deactivateMenu(); + break; + case QEvent::ContextMenu: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + + while (QApplication::activePopupWidget() && !qobject_cast(QApplication::activePopupWidget())) { + QApplication::activePopupWidget()->close(); + } + + Q_FALLTHROUGH(); // fall through + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::MouseMove: + dispatch = (object != m_editor); + Q_FALLTHROUGH(); // no break + + case QEvent::Enter: + case QEvent::Leave: + case QEvent::FocusIn: + case QEvent::FocusOut: + if (dispatch) + if (QWidget *widget = qobject_cast(object)) + if (widget == this || isAncestorOf(widget)) + return handleEvent(widget, event); + break; + } + + return false; +}; + +int QDesignerMenu::findAction(const QPoint &pos) const +{ + const int index = actionIndexAt(this, pos, Qt::Vertical); + if (index == -1) + return realActionCount(); + + return index; +} + +void QDesignerMenu::adjustIndicator(const QPoint &pos) +{ + if (QDesignerActionProviderExtension *a = actionProvider()) { + a->adjustIndicator(pos); + } +} + +QDesignerMenu::ActionDragCheck QDesignerMenu::checkAction(QAction *action) const +{ + if (!action || (action->menu() && action->menu()->parentWidget() != const_cast(this))) + return NoActionDrag; // menu action!! nothing to do + + if (!qdesigner_internal::Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) + return NoActionDrag; // the action belongs to another form window + + if (actions().contains(action)) + return ActionDragOnSubMenu; // we already have the action in the menu + + return AcceptActionDrag; +} + +void QDesignerMenu::dragEnterEvent(QDragEnterEvent *event) +{ + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + + QAction *action = d->actionList().first(); + + switch (checkAction(action)) { + case NoActionDrag: + event->ignore(); + break; + case ActionDragOnSubMenu: + d->accept(event); + m_dragging = true; + break; + case AcceptActionDrag: + d->accept(event); + m_dragging = true; + adjustIndicator(event->position().toPoint()); + break; + } +} + +void QDesignerMenu::dragMoveEvent(QDragMoveEvent *event) +{ + if (actionGeometry(m_addSeparator).contains(event->position().toPoint())) { + event->ignore(); + adjustIndicator(QPoint(-1, -1)); + return; + } + + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + + QAction *action = d->actionList().first(); + const ActionDragCheck dc = checkAction(action); + switch (dc) { + case NoActionDrag: + event->ignore(); + break; + case ActionDragOnSubMenu: + case AcceptActionDrag: { // Do not pop up submenu of action being dragged + const int newIndex = findAction(event->position().toPoint()); + if (safeActionAt(newIndex) != action) { + m_currentIndex = newIndex; + if (m_lastSubMenuIndex != m_currentIndex) + m_showSubMenuTimer->start(300); + } + if (dc == AcceptActionDrag) { + adjustIndicator(event->position().toPoint()); + d->accept(event); + } else { + event->ignore(); + } + } + break; + } +} + +void QDesignerMenu::dragLeaveEvent(QDragLeaveEvent *) +{ + m_dragging = false; + adjustIndicator(QPoint(-1, -1)); + m_showSubMenuTimer->stop(); +} + +void QDesignerMenu::dropEvent(QDropEvent *event) +{ + m_showSubMenuTimer->stop(); + hideSubMenu(); + m_dragging = false; + + QDesignerFormWindowInterface *fw = formWindow(); + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + QAction *action = d->actionList().first(); + if (action && checkAction(action) == AcceptActionDrag) { + event->acceptProposedAction(); + int index = findAction(event->position().toPoint()); + index = qMin(index, actions().size() - 1); + + fw->beginCommand(tr("Insert action")); + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, action, safeActionAt(index)); + fw->commandHistory()->push(cmd); + + m_currentIndex = index; + + if (parentMenu()) { + QAction *parent_action = parentMenu()->currentAction(); + if (parent_action->menu() == nullptr) { + auto *cmd = new qdesigner_internal::CreateSubmenuCommand(fw); + cmd->init(parentMenu(), parentMenu()->currentAction(), action); + fw->commandHistory()->push(cmd); + } + } + update(); + fw->endCommand(); + } else { + event->ignore(); + } + adjustIndicator(QPoint(-1, -1)); +} + +void QDesignerMenu::actionEvent(QActionEvent *event) +{ + QMenu::actionEvent(event); + m_adjustSizeTimer->start(0); +} + +QDesignerFormWindowInterface *QDesignerMenu::formWindow() const +{ + if (parentMenu()) + return parentMenu()->formWindow(); + + return QDesignerFormWindowInterface::findFormWindow(parentWidget()); +} + +QDesignerActionProviderExtension *QDesignerMenu::actionProvider() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QDesignerFormEditorInterface *core = fw->core(); + return qt_extension(core->extensionManager(), this); + } + + return nullptr; +} + +void QDesignerMenu::closeMenuChain() +{ + m_showSubMenuTimer->stop(); + + QWidget *w = this; + while (w && qobject_cast(w)) + w = w->parentWidget(); + + if (w) { + const auto &menus = w->findChildren(); + for (QMenu *subMenu : menus) + subMenu->hide(); + } + + m_lastSubMenuIndex = -1; +} + +// Close submenu using the left/right keys according to layoutDirection(). +// Return false to indicate the event must be propagated to the menu bar. +bool QDesignerMenu::hideSubMenuOnCursorKey() +{ + if (parentMenu()) { + hide(); + return true; + } + closeMenuChain(); + update(); + return parentMenuBar() == nullptr; +} + +// Open a submenu using the left/right keys according to layoutDirection(). +// Return false to indicate the event must be propagated to the menu bar. +bool QDesignerMenu::showSubMenuOnCursorKey() +{ + using namespace qdesigner_internal; + + const QAction *action = currentAction(); + + if (qobject_cast(action) || action->isSeparator()) { + closeMenuChain(); + if (parentMenuBar()) + return false; + return true; + } + m_lastSubMenuIndex = -1; // force a refresh + slotShowSubMenuNow(); + return true; +} + +void QDesignerMenu::moveLeft() +{ + const bool handled = layoutDirection() == Qt::LeftToRight ? + hideSubMenuOnCursorKey() : showSubMenuOnCursorKey(); + if (!handled) + parentMenuBar()->moveLeft(); +} + +void QDesignerMenu::moveRight() +{ + const bool handled = layoutDirection() == Qt::LeftToRight ? + showSubMenuOnCursorKey() : hideSubMenuOnCursorKey(); + if (!handled) + parentMenuBar()->moveRight(); +} + +void QDesignerMenu::moveUp(bool ctrl) +{ + if (m_currentIndex == 0) { + hide(); + return; + } + + if (ctrl) + (void) swap(m_currentIndex, m_currentIndex - 1); + --m_currentIndex; + m_currentIndex = qMax(0, m_currentIndex); + // Always re-select, swapping destroys order + update(); + selectCurrentAction(); +} + +void QDesignerMenu::moveDown(bool ctrl) +{ + if (m_currentIndex == actions().size() - 1) { + return; + } + + if (ctrl) + (void) swap(m_currentIndex + 1, m_currentIndex); + + ++m_currentIndex; + m_currentIndex = qMin(actions().size() - 1, m_currentIndex); + update(); + if (!ctrl) + selectCurrentAction(); +} + +QAction *QDesignerMenu::currentAction() const +{ + if (m_currentIndex < 0 || m_currentIndex >= actions().size()) + return nullptr; + + return safeActionAt(m_currentIndex); +} + +int QDesignerMenu::realActionCount() const +{ + return actions().size() - 2; // 2 fake actions +} + +void QDesignerMenu::selectCurrentAction() +{ + using namespace qdesigner_internal; + + QAction *action = currentAction(); + if (!action || action == m_addSeparator || action == m_addItem) + return; + + QDesignerObjectInspector *oi = nullptr; + ActionEditor *ae = nullptr; + if (QDesignerFormWindowInterface *fw = formWindow()) { + auto core = fw->core(); + oi = qobject_cast(core->objectInspector()); + ae = qobject_cast(core->actionEditor()); + } + + if (!oi) + return; + + oi->clearSelection(); + if (QMenu *menu = action->menu()) { + oi->selectObject(menu); + if (ae) + ae->clearSelection(); + } else { + oi->selectObject(action); + if (ae) + ae->selectAction(action); + } +} + +void QDesignerMenu::createRealMenuAction(QAction *action) +{ + using namespace qdesigner_internal; + + if (action->menu()) + return; // nothing to do + + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = formWindow()->core(); + + QDesignerMenu *menu = findOrCreateSubMenu(action); + m_subMenus.remove(action); + + action->setMenu(menu); + menu->setTitle(action->text()); + + Q_ASSERT(fw); + + core->widgetFactory()->initialize(menu); + + const QString niceObjectName = ActionEditor::actionTextToName(menu->title(), u"menu"_s); + menu->setObjectName(niceObjectName); + + core->metaDataBase()->add(menu); + fw->ensureUniqueObjectName(menu); + + QAction *menuAction = menu->menuAction(); + core->metaDataBase()->add(menuAction); +} + +void QDesignerMenu::removeRealMenu(QAction *action) +{ + QDesignerMenu *menu = qobject_cast(action->menu()); + if (menu == nullptr) + return; + action->setMenu(nullptr); + m_subMenus.insert(action, menu); + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->remove(menu); +} + +QDesignerMenu *QDesignerMenu::findOrCreateSubMenu(QAction *action) +{ + if (action->menu()) + return qobject_cast(action->menu()); + + QDesignerMenu *menu = m_subMenus.value(action); + if (!menu) { + menu = new QDesignerMenu(this); + m_subMenus.insert(action, menu); + } + + return menu; +} + +bool QDesignerMenu::canCreateSubMenu(QAction *action) const // ### improve it's a bit too slow +{ + const QObjectList associatedObjects = action->associatedObjects(); + for (const QObject *ao : associatedObjects) { + if (ao != this) { + if (const QMenu *m = qobject_cast(ao)) { + if (m->actions().contains(action)) + return false; // sorry + } else if (const QToolBar *tb = qobject_cast(ao)) { + if (tb->actions().contains(action)) + return false; // sorry + } + } + } + return true; +} + +void QDesignerMenu::slotShowSubMenuNow() +{ + m_showSubMenuTimer->stop(); + + if (m_lastSubMenuIndex == m_currentIndex) + return; + + if (m_lastSubMenuIndex != -1) + hideSubMenu(); + + if (m_currentIndex >= realActionCount()) + return; + + QAction *action = currentAction(); + + if (action->isSeparator() || !canCreateSubMenu(action)) + return; + + if (QMenu *menu = findOrCreateSubMenu(action)) { + if (!menu->isVisible()) { + if ((menu->windowFlags() & Qt::Popup) != Qt::Popup) + menu->setWindowFlags(Qt::Popup); + const QRect g = actionGeometry(action); + if (layoutDirection() == Qt::LeftToRight) { + menu->move(mapToGlobal(g.topRight())); + } else { + // The position is not initially correct due to the unknown width, + // causing it to overlap a bit the first time it is invoked. + QPoint point = g.topLeft() - QPoint(menu->width() + 10, 0); + menu->move(mapToGlobal(point)); + } + menu->show(); + menu->setFocus(); + } else { + menu->raise(); + } + menu->setFocus(); + + m_lastSubMenuIndex = m_currentIndex; + } +} + +void QDesignerMenu::showSubMenu(QAction *action) +{ + using namespace qdesigner_internal; + + m_showSubMenuTimer->stop(); + + if (m_editor->isVisible() || !action || qobject_cast(action) + || action->isSeparator() || !isVisible()) + return; + + m_showSubMenuTimer->start(300); +} + +QDesignerMenu *QDesignerMenu::parentMenu() const +{ + return qobject_cast(parentWidget()); +} + +QDesignerMenuBar *QDesignerMenu::parentMenuBar() const +{ + if (QDesignerMenuBar *mb = qobject_cast(parentWidget())) + return mb; + if (QDesignerMenu *m = parentMenu()) + return m->parentMenuBar(); + + return nullptr; +} + +void QDesignerMenu::setVisible(bool visible) +{ + if (visible) + m_currentIndex = 0; + else + m_lastSubMenuIndex = -1; + + QMenu::setVisible(visible); + +} + +void QDesignerMenu::adjustSpecialActions() +{ + removeAction(m_addItem); + removeAction(m_addSeparator); + addAction(m_addItem); + addAction(m_addSeparator); +} + +void QDesignerMenu::enterEditMode() +{ + if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) { + showLineEdit(); + } else { + hideSubMenu(); + QDesignerFormWindowInterface *fw = formWindow(); + fw->beginCommand(tr("Add separator")); + QAction *sep = createAction(QString(), true); + + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, sep, safeActionAt(realActionCount())); + fw->commandHistory()->push(cmd); + + if (parentMenu()) { + QAction *parent_action = parentMenu()->currentAction(); + if (parent_action->menu() == nullptr) { + auto *cmd = new qdesigner_internal::CreateSubmenuCommand(fw); + cmd->init(parentMenu(), parentMenu()->currentAction()); + fw->commandHistory()->push(cmd); + } + } + + fw->endCommand(); + + m_currentIndex = actions().indexOf(m_addItem); + update(); + } +} + +void QDesignerMenu::leaveEditMode(LeaveEditMode mode) +{ + using namespace qdesigner_internal; + + if (mode == Default) + return; + + QAction *action = nullptr; + + QDesignerFormWindowInterface *fw = formWindow(); + if (m_currentIndex < realActionCount()) { + action = safeActionAt(m_currentIndex); + fw->beginCommand(QApplication::translate("Command", "Set action text")); + } else { + Q_ASSERT(fw != nullptr); + fw->beginCommand(QApplication::translate("Command", "Insert action")); + action = createAction(ActionEditor::actionTextToName(m_editor->text())); + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, action, currentAction()); + fw->commandHistory()->push(cmd); + } + + auto *cmd = new qdesigner_internal::SetPropertyCommand(fw); + cmd->init(action, u"text"_s, m_editor->text()); + fw->commandHistory()->push(cmd); + + if (parentMenu()) { + QAction *parent_action = parentMenu()->currentAction(); + if (parent_action->menu() == nullptr) { + auto *cmd = new qdesigner_internal::CreateSubmenuCommand(fw); + cmd->init(parentMenu(), parentMenu()->currentAction(), action); + fw->commandHistory()->push(cmd); + } + } + + update(); + fw->endCommand(); +} + +QAction *QDesignerMenu::safeMenuAction(QDesignerMenu *menu) const +{ + QAction *action = menu->menuAction(); + + if (!action) + action = m_subMenus.key(menu); + + return action; +} + +void QDesignerMenu::showLineEdit() +{ + m_showSubMenuTimer->stop(); + + QAction *action = nullptr; + + if (m_currentIndex < realActionCount()) + action = safeActionAt(m_currentIndex); + else + action = m_addItem; + + if (action->isSeparator()) + return; + + hideSubMenu(); + + // open edit field for item name + setFocus(); + + const QString text = action != m_addItem ? action->text() : QString(); + m_editor->setText(text); + m_editor->selectAll(); + m_editor->setGeometry(actionGeometry(action).adjusted(1, 1, -2, -2)); + m_editor->show(); + m_editor->setFocus(); +} + +QAction *QDesignerMenu::createAction(const QString &objectName, bool separator) +{ + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw); + return qdesigner_internal::ToolBarEventFilter::createAction(fw, objectName, separator); +} + +// ### share with QDesignerMenu::swap +bool QDesignerMenu::swap(int a, int b) +{ + using namespace qdesigner_internal; + + const int left = qMin(a, b); + int right = qMax(a, b); + + QAction *action_a = safeActionAt(left); + QAction *action_b = safeActionAt(right); + + if (action_a == action_b + || !action_a + || !action_b + || qobject_cast(action_a) + || qobject_cast(action_b)) + return false; // nothing to do + + right = qMin(right, realActionCount()); + if (right < 0) + return false; // nothing to do + + QDesignerFormWindowInterface *fw = formWindow(); + fw->beginCommand(QApplication::translate("Command", "Move action")); + + QAction *action_b_before = safeActionAt(right + 1); + + auto *cmd1 = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd1->init(this, action_b, action_b_before, false); + fw->commandHistory()->push(cmd1); + + QAction *action_a_before = safeActionAt(left + 1); + + auto *cmd2 = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd2->init(this, action_b, action_a_before, false); + fw->commandHistory()->push(cmd2); + + auto *cmd3 = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd3->init(this, action_a, action_b, false); + fw->commandHistory()->push(cmd3); + + auto *cmd4 = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd4->init(this, action_a, action_b_before, true); + fw->commandHistory()->push(cmd4); + + fw->endCommand(); + + return true; +} + +QAction *QDesignerMenu::safeActionAt(int index) const +{ + if (index < 0 || index >= actions().size()) + return nullptr; + + return actions().at(index); +} + +void QDesignerMenu::hideSubMenu() +{ + m_lastSubMenuIndex = -1; + const auto &menus = findChildren(); + for (QMenu *subMenu : menus) + subMenu->hide(); +} + +void QDesignerMenu::deleteAction() +{ + QAction *action = currentAction(); + const int pos = actions().indexOf(action); + QAction *action_before = nullptr; + if (pos != -1) + action_before = safeActionAt(pos + 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd->init(this, action, action_before); + fw->commandHistory()->push(cmd); + + update(); +} + +void QDesignerMenu::deactivateMenu() +{ + m_deactivateWindowTimer->start(10); +} + +void QDesignerMenu::slotDeactivateNow() +{ + m_deactivateWindowTimer->stop(); + + if (m_dragging) + return; + + QDesignerMenu *root = findRootMenu(); + + if (! root->findActivatedMenu()) { + root->hide(); + root->hideSubMenu(); + } +} + +void QDesignerMenu::drawSelection(QPainter *p, const QRect &r) +{ + p->save(); + + QColor c = Qt::blue; + p->setPen(QPen(c, 1)); + c.setAlpha(32); + p->setBrush(c); + p->drawRect(r); + + p->restore(); +} + +void QDesignerMenu::keyPressEvent(QKeyEvent *event) +{ + event->ignore(); +} + +void QDesignerMenu::keyReleaseEvent(QKeyEvent *event) +{ + event->ignore(); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_menu_p.h b/src/designer/src/lib/shared/qdesigner_menu_p.h new file mode 100644 index 0000000..a3212ad --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_menu_p.h @@ -0,0 +1,170 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_MENU_H +#define QDESIGNER_MENU_H + +#include "shared_global_p.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QTimer; +class QLineEdit; + +class QDesignerFormWindowInterface; +class QDesignerActionProviderExtension; +class QDesignerMenu; +class QDesignerMenuBar; +class QPainter; +class QMimeData; + +namespace qdesigner_internal { + class CreateSubmenuCommand; + class ActionInsertionCommand; +} + +class QDESIGNER_SHARED_EXPORT QDesignerMenu: public QMenu +{ + Q_OBJECT +public: + QDesignerMenu(QWidget *parent = nullptr); + ~QDesignerMenu() override; + + bool eventFilter(QObject *object, QEvent *event) override; + + QDesignerFormWindowInterface *formWindow() const; + QDesignerActionProviderExtension *actionProvider(); + + QDesignerMenu *parentMenu() const; + QDesignerMenuBar *parentMenuBar() const; + + void setVisible(bool visible) override; + + void adjustSpecialActions(); + + void createRealMenuAction(QAction *action); + void removeRealMenu(QAction *action); + + static void drawSelection(QPainter *p, const QRect &r); + + bool dragging() const; + + void closeMenuChain(); + + void moveLeft(); + void moveRight(); + void moveUp(bool ctrl); + void moveDown(bool ctrl); + + // Helper for MenuTaskMenu extension + void deleteAction(QAction *a); + +private slots: + void slotAddSeparator(); + void slotRemoveSelectedAction(); + void slotShowSubMenuNow(); + void slotDeactivateNow(); + void slotAdjustSizeNow(); + +protected: + void actionEvent(QActionEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void showEvent(QShowEvent *event) override; + + bool handleEvent(QWidget *widget, QEvent *event); + bool handleMouseDoubleClickEvent(QWidget *widget, QMouseEvent *event); + bool handleMousePressEvent(QWidget *widget, QMouseEvent *event); + bool handleMouseReleaseEvent(QWidget *widget, QMouseEvent *event); + bool handleMouseMoveEvent(QWidget *widget, QMouseEvent *event); + bool handleContextMenuEvent(QWidget *widget, QContextMenuEvent *event); + bool handleKeyPressEvent(QWidget *widget, QKeyEvent *event); + + void startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers); + + void adjustIndicator(const QPoint &pos); + int findAction(const QPoint &pos) const; + + QAction *currentAction() const; + int realActionCount() const; + enum ActionDragCheck { NoActionDrag, ActionDragOnSubMenu, AcceptActionDrag }; + ActionDragCheck checkAction(QAction *action) const; + + void showSubMenu(QAction *action); + + enum LeaveEditMode { + Default = 0, + ForceAccept + }; + + void enterEditMode(); + void leaveEditMode(LeaveEditMode mode); + void showLineEdit(); + + QAction *createAction(const QString &text, bool separator = false); + QDesignerMenu *findOrCreateSubMenu(QAction *action); + + QAction *safeActionAt(int index) const; + QAction *safeMenuAction(QDesignerMenu *menu) const; + bool swap(int a, int b); + + void hideSubMenu(); + void deleteAction(); + void deactivateMenu(); + + bool canCreateSubMenu(QAction *action) const; + QDesignerMenu *findRootMenu() const; + QDesignerMenu *findActivatedMenu() const; + + QRect subMenuPixmapRect(QAction *action) const; + bool hasSubMenuPixmap(QAction *action) const; + + void selectCurrentAction(); + +private: + bool hideSubMenuOnCursorKey(); + bool showSubMenuOnCursorKey(); + const QPixmap m_subMenuPixmap; + + QPoint m_startPosition; + int m_currentIndex = 0; + QAction *m_addItem; + QAction *m_addSeparator; + QHash m_subMenus; + QTimer *m_showSubMenuTimer; + QTimer *m_deactivateWindowTimer; + QTimer *m_adjustSizeTimer; + QLineEdit *m_editor; + bool m_dragging = false; + int m_lastSubMenuIndex = -1; + + friend class qdesigner_internal::CreateSubmenuCommand; + friend class qdesigner_internal::ActionInsertionCommand; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_MENU_H diff --git a/src/designer/src/lib/shared/qdesigner_menubar.cpp b/src/designer/src/lib/shared/qdesigner_menubar.cpp new file mode 100644 index 0000000..7b442ed --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_menubar.cpp @@ -0,0 +1,932 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_menubar_p.h" +#include "qdesigner_menu_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "actionrepository_p.h" +#include "actionprovider_p.h" +#include "actioneditor_p.h" +#include "qdesigner_utils_p.h" +#include "promotiontaskmenu_p.h" +#include "qdesigner_objectinspector_p.h" + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using ActionList = QList; + +namespace qdesigner_internal +{ + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +SpecialMenuAction::SpecialMenuAction(QObject *parent) + : QAction(parent) +{ +} + +SpecialMenuAction::~SpecialMenuAction() = default; + +} // namespace qdesigner_internal + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +QDesignerMenuBar::QDesignerMenuBar(QWidget *parent) : + QMenuBar(parent), + m_addMenu(new qdesigner_internal::SpecialMenuAction(this)), + m_editor(new QLineEdit(this)), + m_promotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(this, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this)) +{ + setContextMenuPolicy(Qt::DefaultContextMenu); + + setAcceptDrops(true); // ### fake + // Fake property: Keep the menu bar editable in the form even if a native menu bar is used. + setNativeMenuBar(false); + + m_addMenu->setText(tr("Type Here")); + addAction(m_addMenu); + + QFont italic; + italic.setItalic(true); + m_addMenu->setFont(italic); + + m_editor->setObjectName(u"__qt__passive_editor"_s); + m_editor->hide(); + m_editor->installEventFilter(this); + installEventFilter(this); +} + +QDesignerMenuBar::~QDesignerMenuBar() = default; + +void QDesignerMenuBar::paintEvent(QPaintEvent *event) +{ + QMenuBar::paintEvent(event); + + QPainter p(this); + + const auto &actionList = actions(); + for (QAction *a : actionList) { + if (qobject_cast(a)) { + const QRect g = actionGeometry(a); + QLinearGradient lg(g.left(), g.top(), g.left(), g.bottom()); + lg.setColorAt(0.0, Qt::transparent); + lg.setColorAt(0.7, QColor(0, 0, 0, 32)); + lg.setColorAt(1.0, Qt::transparent); + + p.fillRect(g, lg); + } + } + + QAction *action = currentAction(); + + if (m_dragging || !action) + return; + + if (hasFocus()) { + const QRect g = actionGeometry(action); + QDesignerMenu::drawSelection(&p, g.adjusted(1, 1, -1, -1)); + } else if (action->menu() && action->menu()->isVisible()) { + const QRect g = actionGeometry(action); + p.drawRect(g.adjusted(1, 1, -1, -1)); + } +} + +bool QDesignerMenuBar::handleEvent(QWidget *widget, QEvent *event) +{ + if (!formWindow()) + return false; + + if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) + update(); + + switch (event->type()) { + default: break; + + case QEvent::MouseButtonDblClick: + return handleMouseDoubleClickEvent(widget, static_cast(event)); + case QEvent::MouseButtonPress: + return handleMousePressEvent(widget, static_cast(event)); + case QEvent::MouseButtonRelease: + return handleMouseReleaseEvent(widget, static_cast(event)); + case QEvent::MouseMove: + return handleMouseMoveEvent(widget, static_cast(event)); + case QEvent::ContextMenu: + return handleContextMenuEvent(widget, static_cast(event)); + case QEvent::KeyPress: + return handleKeyPressEvent(widget, static_cast(event)); + case QEvent::FocusIn: + case QEvent::FocusOut: + return widget != m_editor; + } + + return true; +} + +bool QDesignerMenuBar::handleMouseDoubleClickEvent(QWidget *, QMouseEvent *event) +{ + if (!rect().contains(event->position().toPoint())) + return true; + + if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) + return true; + + event->accept(); + + m_startPosition = QPoint(); + + m_currentIndex = actionIndexAt(this, event->position().toPoint(), Qt::Horizontal); + if (m_currentIndex != -1) { + showLineEdit(); + } + + return true; +} + +bool QDesignerMenuBar::handleKeyPressEvent(QWidget *, QKeyEvent *e) +{ + if (m_editor->isHidden()) { // In navigation mode + switch (e->key()) { + + case Qt::Key_Delete: + if (m_currentIndex == -1 || m_currentIndex >= realActionCount()) + break; + hideMenu(); + deleteMenu(); + break; + + case Qt::Key_Left: + e->accept(); + moveLeft(e->modifiers() & Qt::ControlModifier); + return true; + + case Qt::Key_Right: + e->accept(); + moveRight(e->modifiers() & Qt::ControlModifier); + return true; // no update + + case Qt::Key_Up: + e->accept(); + moveUp(); + return true; + + case Qt::Key_Down: + e->accept(); + moveDown(); + return true; + + case Qt::Key_PageUp: + m_currentIndex = 0; + break; + + case Qt::Key_PageDown: + m_currentIndex = actions().size() - 1; + break; + + case Qt::Key_Enter: + case Qt::Key_Return: + e->accept(); + enterEditMode(); + return true; // no update + + case Qt::Key_Alt: + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Escape: + e->ignore(); + setFocus(); // FIXME: this is because some other widget get the focus when CTRL is pressed + return true; // no update + + default: + if (!e->text().isEmpty() && e->text().at(0).toLatin1() >= 32) { + showLineEdit(); + QApplication::sendEvent(m_editor, e); + e->accept(); + } else { + e->ignore(); + } + return true; + } + } else { // In edit mode + switch (e->key()) { + default: + return false; + + case Qt::Key_Control: + e->ignore(); + return true; + + case Qt::Key_Enter: + case Qt::Key_Return: + if (!m_editor->text().isEmpty()) { + leaveEditMode(ForceAccept); + if (m_lastFocusWidget) + m_lastFocusWidget->setFocus(); + + m_editor->hide(); + showMenu(); + break; + } + Q_FALLTHROUGH(); + + case Qt::Key_Escape: + update(); + setFocus(); + break; + } + } + + e->accept(); + update(); + + return true; +} + +void QDesignerMenuBar::startDrag(const QPoint &pos) +{ + using namespace qdesigner_internal; + + const int index = findAction(pos); + if (m_currentIndex == -1 || index >= realActionCount()) + return; + + QAction *action = safeActionAt(index); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd->init(this, action, actions().at(index + 1)); + fw->commandHistory()->push(cmd); + + adjustSize(); + + hideMenu(index); + + QDrag *drag = new QDrag(this); + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(action)); + drag->setMimeData(new ActionRepositoryMimeData(action, Qt::MoveAction)); + + const int old_index = m_currentIndex; + m_currentIndex = -1; + + if (drag->exec(Qt::MoveAction) == Qt::IgnoreAction) { + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, action, safeActionAt(index)); + fw->commandHistory()->push(cmd); + + m_currentIndex = old_index; + adjustSize(); + } +} + +bool QDesignerMenuBar::handleMousePressEvent(QWidget *, QMouseEvent *event) +{ + m_startPosition = QPoint(); + event->accept(); + + if (event->button() != Qt::LeftButton) + return true; + + m_startPosition = event->position().toPoint(); + const int newIndex = actionIndexAt(this, m_startPosition, Qt::Horizontal); + const bool changed = newIndex != m_currentIndex; + m_currentIndex = newIndex; + updateCurrentAction(changed); + + return true; +} + +bool QDesignerMenuBar::handleMouseReleaseEvent(QWidget *, QMouseEvent *event) +{ + m_startPosition = QPoint(); + + if (event->button() != Qt::LeftButton) + return true; + + event->accept(); + m_currentIndex = actionIndexAt(this, event->position().toPoint(), Qt::Horizontal); + if (!m_editor->isVisible() && m_currentIndex != -1 && m_currentIndex < realActionCount()) + showMenu(); + + return true; +} + +bool QDesignerMenuBar::handleMouseMoveEvent(QWidget *, QMouseEvent *event) +{ + if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) + return true; + + if (m_startPosition.isNull()) + return true; + + const QPoint pos = mapFromGlobal(event->globalPosition().toPoint()); + + if ((pos - m_startPosition).manhattanLength() < qApp->startDragDistance()) + return true; + + const int index = actionIndexAt(this, m_startPosition, Qt::Horizontal); + if (index < actions().size()) { + hideMenu(index); + update(); + } + + startDrag(m_startPosition); + m_startPosition = QPoint(); + + return true; +} + +ActionList QDesignerMenuBar::contextMenuActions() +{ + using namespace qdesigner_internal; + + ActionList rc; + if (QAction *action = safeActionAt(m_currentIndex)) { + if (!qobject_cast(action)) { + QVariant itemData; + itemData.setValue(action); + + QAction *remove_action = new QAction(tr("Remove Menu '%1'").arg(action->menu()->objectName()), nullptr); + remove_action->setData(itemData); + connect(remove_action, &QAction::triggered, this, &QDesignerMenuBar::deleteMenu); + rc.push_back(remove_action); + QAction *sep = new QAction(nullptr); + sep->setSeparator(true); + rc.push_back(sep); + } + } + + m_promotionTaskMenu->addActions(formWindow(), PromotionTaskMenu::TrailingSeparator, rc); + + QAction *remove_menubar = new QAction(tr("Remove Menu Bar"), nullptr); + connect(remove_menubar, &QAction::triggered, this, &QDesignerMenuBar::slotRemoveMenuBar); + rc.push_back(remove_menubar); + return rc; +} + +bool QDesignerMenuBar::handleContextMenuEvent(QWidget *, QContextMenuEvent *event) +{ + event->accept(); + + m_currentIndex = actionIndexAt(this, mapFromGlobal(event->globalPos()), Qt::Horizontal); + + update(); + + QMenu menu; + const ActionList al = contextMenuActions(); + for (auto *a : al) + menu.addAction(a); + menu.exec(event->globalPos()); + return true; +} + +void QDesignerMenuBar::slotRemoveMenuBar() +{ + Q_ASSERT(formWindow() != nullptr); + + QDesignerFormWindowInterface *fw = formWindow(); + + auto *cmd = new qdesigner_internal::DeleteMenuBarCommand(fw); + cmd->init(this); + fw->commandHistory()->push(cmd); +} + +void QDesignerMenuBar::focusOutEvent(QFocusEvent *event) +{ + QMenuBar::focusOutEvent(event); +} + +void QDesignerMenuBar::enterEditMode() +{ + if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) { + showLineEdit(); + } +} + +void QDesignerMenuBar::leaveEditMode(LeaveEditMode mode) +{ + using namespace qdesigner_internal; + + m_editor->releaseKeyboard(); + + if (mode == Default) + return; + + if (m_editor->text().isEmpty()) + return; + + QAction *action = nullptr; + + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw); + + if (m_currentIndex >= 0 && m_currentIndex < realActionCount()) { + action = safeActionAt(m_currentIndex); + fw->beginCommand(QApplication::translate("Command", "Change Title")); + } else { + fw->beginCommand(QApplication::translate("Command", "Insert Menu")); + const QString niceObjectName = ActionEditor::actionTextToName(m_editor->text(), u"menu"_s); + QMenu *menu = qobject_cast(fw->core()->widgetFactory()->createWidget(u"QMenu"_s, this)); + fw->core()->widgetFactory()->initialize(menu); + menu->setObjectName(niceObjectName); + menu->setTitle(tr("Menu")); + fw->ensureUniqueObjectName(menu); + action = menu->menuAction(); + auto *cmd = new qdesigner_internal::AddMenuActionCommand(fw); + cmd->init(action, m_addMenu, this, this); + fw->commandHistory()->push(cmd); + } + + auto *cmd = new qdesigner_internal::SetPropertyCommand(fw); + cmd->init(action, u"text"_s, m_editor->text()); + fw->commandHistory()->push(cmd); + fw->endCommand(); +} + +void QDesignerMenuBar::showLineEdit() +{ + QAction *action = nullptr; + + if (m_currentIndex >= 0 && m_currentIndex < realActionCount()) + action = safeActionAt(m_currentIndex); + else + action = m_addMenu; + + if (action->isSeparator()) + return; + + // hideMenu(); + + m_lastFocusWidget = qApp->focusWidget(); + + // open edit field for item name + const QString text = action != m_addMenu ? action->text() : QString(); + + m_editor->setText(text); + m_editor->selectAll(); + m_editor->setGeometry(actionGeometry(action)); + m_editor->show(); + m_editor->activateWindow(); + m_editor->setFocus(); + m_editor->grabKeyboard(); +} + +bool QDesignerMenuBar::eventFilter(QObject *object, QEvent *event) +{ + if (object != this && object != m_editor) + return false; + + if (!m_editor->isHidden() && object == m_editor && event->type() == QEvent::FocusOut) { + leaveEditMode(Default); + m_editor->hide(); + update(); + return true; + } + + bool dispatch = true; + + switch (event->type()) { + default: break; + + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::ContextMenu: + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + dispatch = (object != m_editor); + Q_FALLTHROUGH(); // no break + + case QEvent::Enter: + case QEvent::Leave: + case QEvent::FocusIn: + case QEvent::FocusOut: + { + QWidget *widget = qobject_cast(object); + + if (dispatch && widget && (widget == this || isAncestorOf(widget))) + return handleEvent(widget, event); + } break; + + case QEvent::Shortcut: + event->accept(); + return true; + } + + return false; +}; + +int QDesignerMenuBar::findAction(const QPoint &pos) const +{ + const int index = actionIndexAt(this, pos, Qt::Horizontal); + if (index == -1) + return realActionCount(); + + return index; +} + +void QDesignerMenuBar::adjustIndicator(const QPoint &pos) +{ + const int index = findAction(pos); + QAction *action = safeActionAt(index); + Q_ASSERT(action != nullptr); + + if (pos != QPoint(-1, -1)) { + QDesignerMenu *m = qobject_cast(action->menu()); + if (!m || m->parentMenu()) { + m_currentIndex = index; + showMenu(index); + } + } + + if (QDesignerActionProviderExtension *a = actionProvider()) { + a->adjustIndicator(pos); + } +} + +QDesignerMenuBar::ActionDragCheck QDesignerMenuBar::checkAction(QAction *action) const +{ + // action belongs to another form + if (!action || !qdesigner_internal::Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) + return NoActionDrag; + + if (!action->menu()) + return ActionDragOnSubMenu; // simple action only on sub menus + + QDesignerMenu *m = qobject_cast(action->menu()); + if (m && m->parentMenu()) + return ActionDragOnSubMenu; // it looks like a submenu + + if (actions().contains(action)) + return ActionDragOnSubMenu; // we already have the action in the menubar + + return AcceptActionDrag; +} + +void QDesignerMenuBar::dragEnterEvent(QDragEnterEvent *event) +{ + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + + QAction *action = d->actionList().first(); + switch (checkAction(action)) { + case NoActionDrag: + event->ignore(); + break; + case ActionDragOnSubMenu: + m_dragging = true; + d->accept(event); + break; + case AcceptActionDrag: + m_dragging = true; + d->accept(event); + adjustIndicator(event->position().toPoint()); + break; + } +} + +void QDesignerMenuBar::dragMoveEvent(QDragMoveEvent *event) +{ + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + QAction *action = d->actionList().first(); + + switch (checkAction(action)) { + case NoActionDrag: + event->ignore(); + break; + case ActionDragOnSubMenu: + event->ignore(); + showMenu(findAction(event->position().toPoint())); + break; + case AcceptActionDrag: + d->accept(event); + adjustIndicator(event->position().toPoint()); + break; + } +} + +void QDesignerMenuBar::dragLeaveEvent(QDragLeaveEvent *) +{ + m_dragging = false; + + adjustIndicator(QPoint(-1, -1)); +} + +void QDesignerMenuBar::dropEvent(QDropEvent *event) +{ + m_dragging = false; + + if (auto *d = qobject_cast(event->mimeData())) { + + QAction *action = d->actionList().first(); + if (checkAction(action) == AcceptActionDrag) { + event->acceptProposedAction(); + int index = findAction(event->position().toPoint()); + index = qMin(index, actions().size() - 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, action, safeActionAt(index)); + fw->commandHistory()->push(cmd); + + m_currentIndex = index; + update(); + adjustIndicator(QPoint(-1, -1)); + return; + } + } + event->ignore(); +} + +void QDesignerMenuBar::actionEvent(QActionEvent *event) +{ + QMenuBar::actionEvent(event); +} + +QDesignerFormWindowInterface *QDesignerMenuBar::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(const_cast(this)); +} + +QDesignerActionProviderExtension *QDesignerMenuBar::actionProvider() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QDesignerFormEditorInterface *core = fw->core(); + return qt_extension(core->extensionManager(), this); + } + + return nullptr; +} + +QAction *QDesignerMenuBar::currentAction() const +{ + if (m_currentIndex < 0 || m_currentIndex >= actions().size()) + return nullptr; + + return safeActionAt(m_currentIndex); +} + +int QDesignerMenuBar::realActionCount() const +{ + return actions().size() - 1; // 1 fake actions +} + +bool QDesignerMenuBar::dragging() const +{ + return m_dragging; +} + +void QDesignerMenuBar::moveLeft(bool ctrl) +{ + if (layoutDirection() == Qt::LeftToRight) { + movePrevious(ctrl); + } else { + moveNext(ctrl); + } +} + +void QDesignerMenuBar::moveRight(bool ctrl) +{ + if (layoutDirection() == Qt::LeftToRight) { + moveNext(ctrl); + } else { + movePrevious(ctrl); + } +} + +void QDesignerMenuBar::movePrevious(bool ctrl) +{ + const bool swapped = ctrl && swapActions(m_currentIndex, m_currentIndex - 1); + const int newIndex = qMax(0, m_currentIndex - 1); + // Always re-select, swapping destroys order + if (swapped || newIndex != m_currentIndex) { + m_currentIndex = newIndex; + updateCurrentAction(true); + } +} + +void QDesignerMenuBar::moveNext(bool ctrl) +{ + const bool swapped = ctrl && swapActions(m_currentIndex + 1, m_currentIndex); + const int newIndex = qMin(actions().size() - 1, m_currentIndex + 1); + if (swapped || newIndex != m_currentIndex) { + m_currentIndex = newIndex; + updateCurrentAction(!ctrl); + } +} + +void QDesignerMenuBar::moveUp() +{ + update(); +} + +void QDesignerMenuBar::moveDown() +{ + showMenu(); +} + +void QDesignerMenuBar::adjustSpecialActions() +{ + removeAction(m_addMenu); + addAction(m_addMenu); +} + +void QDesignerMenuBar::hideMenu(int index) +{ + if (index < 0 && m_currentIndex >= 0) + index = m_currentIndex; + + if (index < 0 || index >= realActionCount()) + return; + + QAction *action = safeActionAt(index); + + if (action && action->menu()) { + action->menu()->hide(); + + if (QDesignerMenu *menu = qobject_cast(action->menu())) { + menu->closeMenuChain(); + } + } +} + +void QDesignerMenuBar::deleteMenu() +{ + deleteMenuAction(currentAction()); +} + +void QDesignerMenuBar::deleteMenuAction(QAction *action) +{ + if (action && !qobject_cast(action)) { + const int pos = actions().indexOf(action); + QAction *action_before = nullptr; + if (pos != -1) + action_before = safeActionAt(pos + 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::RemoveMenuActionCommand(fw); + cmd->init(action, action_before, this, this); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerMenuBar::showMenu(int index) +{ + if (index < 0 && m_currentIndex >= 0) + index = m_currentIndex; + + if (index < 0 || index >= realActionCount()) + return; + + m_currentIndex = index; + QAction *action = currentAction(); + + if (action && action->menu()) { + if (m_lastMenuActionIndex != -1 && m_lastMenuActionIndex != index) { + hideMenu(m_lastMenuActionIndex); + } + + m_lastMenuActionIndex = index; + QMenu *menu = action->menu(); + const QRect g = actionGeometry(action); + + if (!menu->isVisible()) { + if ((menu->windowFlags() & Qt::Popup) != Qt::Popup) + menu->setWindowFlags(Qt::Popup); + menu->adjustSize(); + if (layoutDirection() == Qt::LeftToRight) { + menu->move(mapToGlobal(g.bottomLeft())); + } else { + // The position is not initially correct due to the unknown width, + // causing it to overlap a bit the first time it is invoked. + QPoint point = g.bottomRight() - QPoint(menu->width(), 0); + menu->move(mapToGlobal(point)); + } + menu->setFocus(Qt::MouseFocusReason); + menu->raise(); + menu->show(); + } else { + menu->raise(); + } + } +} + +QAction *QDesignerMenuBar::safeActionAt(int index) const +{ + if (index < 0 || index >= actions().size()) + return nullptr; + + return actions().at(index); +} + +bool QDesignerMenuBar::swapActions(int a, int b) +{ + using namespace qdesigner_internal; + + const int left = qMin(a, b); + int right = qMax(a, b); + + QAction *action_a = safeActionAt(left); + QAction *action_b = safeActionAt(right); + + if (action_a == action_b + || !action_a + || !action_b + || qobject_cast(action_a) + || qobject_cast(action_b)) + return false; // nothing to do + + right = qMin(right, realActionCount()); + if (right < 0) + return false; // nothing to do + + formWindow()->beginCommand(QApplication::translate("Command", "Move action")); + + QAction *action_b_before = safeActionAt(right + 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd1 = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd1->init(this, action_b, action_b_before, false); + fw->commandHistory()->push(cmd1); + + QAction *action_a_before = safeActionAt(left + 1); + + auto *cmd2 = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd2->init(this, action_b, action_a_before, false); + fw->commandHistory()->push(cmd2); + + auto *cmd3 = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd3->init(this, action_a, action_b, false); + fw->commandHistory()->push(cmd3); + + auto *cmd4 = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd4->init(this, action_a, action_b_before, true); + fw->commandHistory()->push(cmd4); + + fw->endCommand(); + + return true; +} + +void QDesignerMenuBar::keyPressEvent(QKeyEvent *event) +{ + event->ignore(); +} + +void QDesignerMenuBar::keyReleaseEvent(QKeyEvent *event) +{ + event->ignore(); +} + +void QDesignerMenuBar::updateCurrentAction(bool selectAction) +{ + using namespace qdesigner_internal; + + update(); + + if (!selectAction) + return; + + QAction *action = currentAction(); + if (!action || action == m_addMenu) + return; + + QMenu *menu = action->menu(); + if (!menu) + return; + + QDesignerObjectInspector *oi = nullptr; + if (QDesignerFormWindowInterface *fw = formWindow()) + oi = qobject_cast(fw->core()->objectInspector()); + + if (!oi) + return; + + oi->clearSelection(); + oi->selectObject(menu); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_menubar_p.h b/src/designer/src/lib/shared/qdesigner_menubar_p.h new file mode 100644 index 0000000..e1d02e3 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_menubar_p.h @@ -0,0 +1,140 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_MENUBAR_H +#define QDESIGNER_MENUBAR_H + +#include "shared_global_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerActionProviderExtension; + +class QLineEdit; +class QMimeData; + +namespace qdesigner_internal { +class PromotionTaskMenu; + +class SpecialMenuAction: public QAction +{ + Q_OBJECT +public: + SpecialMenuAction(QObject *parent = nullptr); + ~SpecialMenuAction() override; +}; + +} // namespace qdesigner_internal + +class QDESIGNER_SHARED_EXPORT QDesignerMenuBar: public QMenuBar +{ + Q_OBJECT +public: + QDesignerMenuBar(QWidget *parent = nullptr); + ~QDesignerMenuBar() override; + + bool eventFilter(QObject *object, QEvent *event) override; + + QDesignerFormWindowInterface *formWindow() const; + QDesignerActionProviderExtension *actionProvider(); + + void adjustSpecialActions(); + bool dragging() const; + + void moveLeft(bool ctrl = false); + void moveRight(bool ctrl = false); + void moveUp(); + void moveDown(); + + // Helpers for MenuTaskMenu/MenuBarTaskMenu extensions + QList contextMenuActions(); + void deleteMenuAction(QAction *action); + +private slots: + void deleteMenu(); + void slotRemoveMenuBar(); + +protected: + void actionEvent(QActionEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + + bool handleEvent(QWidget *widget, QEvent *event); + bool handleMouseDoubleClickEvent(QWidget *widget, QMouseEvent *event); + bool handleMousePressEvent(QWidget *widget, QMouseEvent *event); + bool handleMouseReleaseEvent(QWidget *widget, QMouseEvent *event); + bool handleMouseMoveEvent(QWidget *widget, QMouseEvent *event); + bool handleContextMenuEvent(QWidget *widget, QContextMenuEvent *event); + bool handleKeyPressEvent(QWidget *widget, QKeyEvent *event); + + void startDrag(const QPoint &pos); + + enum ActionDragCheck { NoActionDrag, ActionDragOnSubMenu, AcceptActionDrag }; + ActionDragCheck checkAction(QAction *action) const; + + void adjustIndicator(const QPoint &pos); + int findAction(const QPoint &pos) const; + + QAction *currentAction() const; + int realActionCount() const; + + enum LeaveEditMode { + Default = 0, + ForceAccept + }; + + void enterEditMode(); + void leaveEditMode(LeaveEditMode mode); + void showLineEdit(); + + void showMenu(int index = -1); + void hideMenu(int index = -1); + + QAction *safeActionAt(int index) const; + + bool swapActions(int a, int b); + +private: + void updateCurrentAction(bool selectAction); + void movePrevious(bool ctrl); + void moveNext(bool ctrl); + + QAction *m_addMenu; + QPointer m_activeMenu; + QPoint m_startPosition; + int m_currentIndex = 0; + QLineEdit *m_editor; + bool m_dragging = false; + int m_lastMenuActionIndex = -1; + QPointer m_lastFocusWidget; + qdesigner_internal::PromotionTaskMenu* m_promotionTaskMenu; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_MENUBAR_H diff --git a/src/designer/src/lib/shared/qdesigner_objectinspector.cpp b/src/designer/src/lib/shared/qdesigner_objectinspector.cpp new file mode 100644 index 0000000..9d54650 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_objectinspector.cpp @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_objectinspector_p.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +QDesignerObjectInspector::QDesignerObjectInspector(QWidget *parent, Qt::WindowFlags flags) : + QDesignerObjectInspectorInterface(parent, flags) +{ +} + +void QDesignerObjectInspector::mainContainerChanged() +{ +} + +void Selection::clear() +{ + managed.clear(); + unmanaged.clear(); + objects.clear(); +} + +bool Selection::empty() const +{ + return managed.isEmpty() && unmanaged.isEmpty() && objects.isEmpty(); +} + +QObjectList Selection::selection() const +{ + QObjectList rc(objects); + for (QObject *o : managed) + rc.push_back(o); + for (QObject *o : unmanaged) + rc.push_back(o); + return rc; +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_objectinspector_p.h b/src/designer/src/lib/shared/qdesigner_objectinspector_p.h new file mode 100644 index 0000000..e439543 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_objectinspector_p.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DESIGNEROBJECTINSPECTOR_H +#define DESIGNEROBJECTINSPECTOR_H + +#include "shared_global_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerDnDItemInterface; + +namespace qdesigner_internal { + +struct QDESIGNER_SHARED_EXPORT Selection { + bool empty() const; + void clear(); + + // Merge all lists + QObjectList selection() const; + + // Selection in cursor (managed widgets) + QWidgetList managed; + // Unmanaged widgets + QWidgetList unmanaged; + // Remaining selected objects (non-widgets) + QObjectList objects; +}; + +// Extends the QDesignerObjectInspectorInterface by functionality +// to access the selection + +class QDESIGNER_SHARED_EXPORT QDesignerObjectInspector: public QDesignerObjectInspectorInterface +{ + Q_OBJECT +public: + explicit QDesignerObjectInspector(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + + // Select a qobject unmanaged by form window + virtual bool selectObject(QObject *o) = 0; + virtual void getSelection(Selection &s) const = 0; + virtual void clearSelection() = 0; + +public slots: + virtual void mainContainerChanged(); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DESIGNEROBJECTINSPECTOR_H diff --git a/src/designer/src/lib/shared/qdesigner_promotion.cpp b/src/designer/src/lib/shared/qdesigner_promotion.cpp new file mode 100644 index 0000000..eb78dd4 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_promotion.cpp @@ -0,0 +1,359 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_promotion_p.h" +#include "widgetdatabase_p.h" +#include "metadatabase_p.h" +#include "widgetdatabase_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + // Return a set of on-promotable classes + const QSet &nonPromotableClasses() { + static const QSet rc = { + u"Line"_s, + u"QAction"_s, + u"Spacer"_s, + u"QMainWindow"_s, + u"QDialog"_s, + u"QMdiArea"_s, + u"QMdiSubWindow"_s + }; + return rc; + } + + // Return widget database index of a promoted class or -1 with error message + int promotedWidgetDataBaseIndex(const QDesignerWidgetDataBaseInterface *widgetDataBase, + const QString &className, + QString *errorMessage) { + const int index = widgetDataBase->indexOfClassName(className); + if (index == -1 || !widgetDataBase->item(index)->isPromoted()) { + *errorMessage = QCoreApplication::tr("%1 is not a promoted class.").arg(className); + return -1; + } + return index; + } + + // Return widget database item of a promoted class or 0 with error message + QDesignerWidgetDataBaseItemInterface *promotedWidgetDataBaseItem(const QDesignerWidgetDataBaseInterface *widgetDataBase, + const QString &className, + QString *errorMessage) { + + const int index = promotedWidgetDataBaseIndex(widgetDataBase, className, errorMessage); + if (index == -1) + return nullptr; + return widgetDataBase->item(index); + } + + // extract class name from xml "". Quite a hack. + QString classNameFromXml(QString xml) + { + constexpr auto tag = "class=\""_L1; + const int pos = xml.indexOf(tag); + if (pos == -1) + return QString(); + xml.remove(0, pos + tag.size()); + const auto closingPos = xml.indexOf(u'"'); + if (closingPos == -1) + return QString(); + xml.remove(closingPos, xml.size() - closingPos); + return xml; + } + + // return a list of class names in the scratch pad + QStringList getScratchPadClasses(const QDesignerWidgetBoxInterface *wb) { + QStringList rc; + const int catCount = wb->categoryCount(); + for (int c = 0; c < catCount; c++) { + const QDesignerWidgetBoxInterface::Category category = wb->category(c); + if (category.type() == QDesignerWidgetBoxInterface::Category::Scratchpad) { + const int widgetCount = category.widgetCount(); + for (int w = 0; w < widgetCount; w++) { + const QString className = classNameFromXml( category.widget(w).domXml()); + if (!className.isEmpty()) + rc += className; + } + } + } + return rc; + } +} + +static void markFormsDirty(const QDesignerFormEditorInterface *core) +{ + const QDesignerFormWindowManagerInterface *fwm = core->formWindowManager(); + for (int f = 0, count = fwm->formWindowCount(); f < count; ++f) + fwm->formWindow(f)->setDirty(true); +} + +namespace qdesigner_internal { + + QDesignerPromotion::QDesignerPromotion(QDesignerFormEditorInterface *core) : + m_core(core) { + } + + bool QDesignerPromotion::addPromotedClass(const QString &baseClass, + const QString &className, + const QString &includeFile, + QString *errorMessage) + { + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + const int baseClassIndex = widgetDataBase->indexOfClassName(baseClass); + + if (baseClassIndex == -1) { + *errorMessage = QCoreApplication::tr("The base class %1 is invalid.").arg(baseClass); + return false; + } + + const int existingClassIndex = widgetDataBase->indexOfClassName(className); + + if (existingClassIndex != -1) { + *errorMessage = QCoreApplication::tr("The class %1 already exists.").arg(className); + return false; + } + // Clone derived item. + QDesignerWidgetDataBaseItemInterface *promotedItem = WidgetDataBaseItem::clone(widgetDataBase->item(baseClassIndex)); + // Also inherit the container flag in case of QWidget-derived classes + // as it is most likely intended for stacked pages. + // set new props + promotedItem->setName(className); + promotedItem->setGroup(QCoreApplication::tr("Promoted Widgets")); + promotedItem->setCustom(true); + promotedItem->setPromoted(true); + promotedItem->setExtends(baseClass); + promotedItem->setIncludeFile(includeFile); + widgetDataBase->append(promotedItem); + markFormsDirty(m_core); + return true; + } + + QList QDesignerPromotion::promotionBaseClasses() const + { + using SortedDatabaseItemMap = QMap; + SortedDatabaseItemMap sortedDatabaseItemMap; + + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + + const int cnt = widgetDataBase->count(); + for (int i = 0; i < cnt; i++) { + QDesignerWidgetDataBaseItemInterface *dbItem = widgetDataBase->item(i); + if (canBePromoted(dbItem)) { + sortedDatabaseItemMap.insert(dbItem->name(), dbItem); + } + } + + return sortedDatabaseItemMap.values(); + } + + + bool QDesignerPromotion::canBePromoted(const QDesignerWidgetDataBaseItemInterface *dbItem) const + { + if (dbItem->isPromoted() || !dbItem->extends().isEmpty()) + return false; + + const QString name = dbItem->name(); + + if (nonPromotableClasses().contains(name)) + return false; + + if (name.startsWith("QDesigner"_L1) || name.startsWith("QLayout"_L1)) + return false; + + return true; + } + + QDesignerPromotion::PromotedClasses QDesignerPromotion::promotedClasses() const + { + using ClassNameItemMap = QMap; + // A map containing base classes and their promoted classes. + QMap baseClassPromotedMap; + + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + // Look for promoted classes and insert into map according to base class. + const int cnt = widgetDataBase->count(); + for (int i = 0; i < cnt; i++) { + QDesignerWidgetDataBaseItemInterface *dbItem = widgetDataBase->item(i); + if (dbItem->isPromoted()) { + const QString baseClassName = dbItem->extends(); + auto it = baseClassPromotedMap.find(baseClassName); + if (it == baseClassPromotedMap.end()) { + it = baseClassPromotedMap.insert(baseClassName, ClassNameItemMap()); + } + it.value().insert(dbItem->name(), dbItem); + } + } + // convert map into list. + PromotedClasses rc; + + if (baseClassPromotedMap.isEmpty()) + return rc; + + for (auto bit = baseClassPromotedMap.cbegin(), bcend = baseClassPromotedMap.cend(); bit != bcend; ++bit) { + const int baseIndex = widgetDataBase->indexOfClassName(bit.key()); + Q_ASSERT(baseIndex >= 0); + QDesignerWidgetDataBaseItemInterface *baseItem = widgetDataBase->item(baseIndex); + // promoted + for (auto pit = bit.value().cbegin(), pcend = bit.value().cend(); pit != pcend; ++pit) { + PromotedClass item; + item.baseItem = baseItem; + item.promotedItem = pit.value(); + rc.push_back(item); + } + } + + return rc; + } + + QSet QDesignerPromotion::referencedPromotedClassNames() const { + QSet rc; + const MetaDataBase *metaDataBase = qobject_cast(m_core->metaDataBase()); + if (!metaDataBase) + return rc; + + const QObjectList &objects = metaDataBase->objects(); + for (QObject *object : objects) { + const QString customClass = metaDataBase->metaDataBaseItem(object)->customClassName(); + if (!customClass.isEmpty()) + rc.insert(customClass); + + } + // check the scratchpad of the widget box + if (QDesignerWidgetBoxInterface *widgetBox = m_core->widgetBox()) { + const QStringList scratchPadClasses = getScratchPadClasses(widgetBox); + if (!scratchPadClasses.isEmpty()) { + // Check whether these are actually promoted + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + for (const auto &scItem : scratchPadClasses) { + const int index = widgetDataBase->indexOfClassName(scItem); + if (index != -1 && widgetDataBase->item(index)->isPromoted()) + rc.insert(scItem); + } + } + } + return rc; + } + + bool QDesignerPromotion::removePromotedClass(const QString &className, QString *errorMessage) { + // check if it exists and is promoted + WidgetDataBase *widgetDataBase = qobject_cast(m_core->widgetDataBase()); + if (!widgetDataBase) { + *errorMessage = QCoreApplication::tr("The class %1 cannot be removed").arg(className); + return false; + } + + const int index = promotedWidgetDataBaseIndex(widgetDataBase, className, errorMessage); + if (index == -1) + return false; + + if (referencedPromotedClassNames().contains(className)) { + *errorMessage = QCoreApplication::tr("The class %1 cannot be removed because it is still referenced.").arg(className); + return false; + } + // QTBUG-52963: Check for classes that specify the to-be-removed class as + // base class of a promoted class. This should not happen in the normal case + // as promoted classes cannot serve as base for further promotion. It is possible + // though if a class provided by a plugin (say Qt WebKit's QWebView) is used as + // a base class for a promoted widget B and the plugin is removed in the next + // launch. QWebView will then appear as promoted class itself and the promoted + // class B will depend on it. When removing QWebView, the base class of B will + // be changed to that of QWebView by the below code. + const PromotedClasses promotedList = promotedClasses(); + for (const auto &pc : promotedList) { + if (pc.baseItem->name() == className) { + const QString extends = widgetDataBase->item(index)->extends(); + qWarning().nospace() << "Warning: Promoted class " << pc.promotedItem->name() + << " extends " << className << ", changing its base class to " << extends << '.'; + pc.promotedItem->setExtends(extends); + } + } + widgetDataBase->remove(index); + markFormsDirty(m_core); + return true; + } + + bool QDesignerPromotion::changePromotedClassName(const QString &oldclassName, const QString &newClassName, QString *errorMessage) { + const MetaDataBase *metaDataBase = qobject_cast(m_core->metaDataBase()); + if (!metaDataBase) { + *errorMessage = QCoreApplication::tr("The class %1 cannot be renamed").arg(oldclassName); + return false; + } + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + + // check the new name + if (newClassName.isEmpty()) { + *errorMessage = QCoreApplication::tr("The class %1 cannot be renamed to an empty name.").arg(oldclassName); + return false; + } + const int existingIndex = widgetDataBase->indexOfClassName(newClassName); + if (existingIndex != -1) { + *errorMessage = QCoreApplication::tr("There is already a class named %1.").arg(newClassName); + return false; + } + // Check old class + QDesignerWidgetDataBaseItemInterface *dbItem = promotedWidgetDataBaseItem(widgetDataBase, oldclassName, errorMessage); + if (!dbItem) + return false; + + // Change the name in the data base and change all referencing objects in the meta database + dbItem->setName(newClassName); + bool foundReferences = false; + const QObjectList &dbObjects = metaDataBase->objects(); + for (QObject* object : dbObjects) { + MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(object); + Q_ASSERT(item); + if (item->customClassName() == oldclassName) { + item->setCustomClassName(newClassName); + foundReferences = true; + } + } + // set state + if (foundReferences) + refreshObjectInspector(); + + markFormsDirty(m_core); + return true; + } + + bool QDesignerPromotion::setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) { + // check file + if (includeFile.isEmpty()) { + *errorMessage = QCoreApplication::tr("Cannot set an empty include file."); + return false; + } + // check item + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + QDesignerWidgetDataBaseItemInterface *dbItem = promotedWidgetDataBaseItem(widgetDataBase, className, errorMessage); + if (!dbItem) + return false; + if (dbItem->includeFile() != includeFile) { + dbItem->setIncludeFile(includeFile); + markFormsDirty(m_core); + } + return true; + } + + void QDesignerPromotion::refreshObjectInspector() { + if (QDesignerFormWindowManagerInterface *fwm = m_core->formWindowManager()) { + if (QDesignerFormWindowInterface *fw = fwm->activeFormWindow()) + if ( QDesignerObjectInspectorInterface *oi = m_core->objectInspector()) { + oi->setFormWindow(fw); + } + } + } +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_promotion_p.h b/src/designer/src/lib/shared/qdesigner_promotion_p.h new file mode 100644 index 0000000..e268296 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_promotion_p.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNERPROMOTION_H +#define QDESIGNERPROMOTION_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + + class QDESIGNER_SHARED_EXPORT QDesignerPromotion : public QDesignerPromotionInterface + { + public: + explicit QDesignerPromotion(QDesignerFormEditorInterface *core); + + PromotedClasses promotedClasses() const override; + + QSet referencedPromotedClassNames() const override; + + bool addPromotedClass(const QString &baseClass, + const QString &className, + const QString &includeFile, + QString *errorMessage) override; + + bool removePromotedClass(const QString &className, QString *errorMessage) override; + + bool changePromotedClassName(const QString &oldclassName, const QString &newClassName, QString *errorMessage) override; + + bool setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) override; + + QList promotionBaseClasses() const override; + + private: + bool canBePromoted(const QDesignerWidgetDataBaseItemInterface *) const; + void refreshObjectInspector(); + + QDesignerFormEditorInterface *m_core; + }; +} + +QT_END_NAMESPACE + +#endif // QDESIGNERPROMOTION_H diff --git a/src/designer/src/lib/shared/qdesigner_promotiondialog.cpp b/src/designer/src/lib/shared/qdesigner_promotiondialog.cpp new file mode 100644 index 0000000..286ee35 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_promotiondialog.cpp @@ -0,0 +1,432 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_promotiondialog_p.h" +#include "promotionmodel_p.h" +#include "iconloader_p.h" +#include "widgetdatabase_p.h" +#include "signalslotdialog_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + // PromotionParameters + struct PromotionParameters { + QString m_baseClass; + QString m_className; + QString m_includeFile; + }; + + // NewPromotedClassPanel + NewPromotedClassPanel::NewPromotedClassPanel(const QStringList &baseClasses, + int selectedBaseClass, + QWidget *parent) : + QGroupBox(parent), + m_baseClassCombo(new QComboBox), + m_classNameEdit(new QLineEdit), + m_includeFileEdit(new QLineEdit), + m_globalIncludeCheckBox(new QCheckBox), + m_addButton(new QPushButton(tr("Add"))) + { + setTitle(tr("New Promoted Class")); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); + QHBoxLayout *hboxLayout = new QHBoxLayout(this); + + m_classNameEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(u"^[_a-zA-Z:][:_a-zA-Z0-9]*$"_s), m_classNameEdit)); + connect(m_classNameEdit, &QLineEdit::textChanged, + this, &NewPromotedClassPanel::slotNameChanged); + connect(m_includeFileEdit, &QLineEdit::textChanged, + this, &NewPromotedClassPanel::slotIncludeFileChanged); + + m_baseClassCombo->setEditable(false); + m_baseClassCombo->addItems(baseClasses); + if (selectedBaseClass != -1) + m_baseClassCombo->setCurrentIndex(selectedBaseClass); + + // Grid + QFormLayout *formLayout = new QFormLayout(); + formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); // Mac + formLayout->addRow(tr("Base class name:"), m_baseClassCombo); + formLayout->addRow(tr("Promoted class name:"), m_classNameEdit); + + QString toolTip = tr("Header file for C++ classes or module name for Qt for Python."); + auto *label = new QLabel(tr("Header file:")); + label->setToolTip(toolTip); + formLayout->addRow(label, m_includeFileEdit); + m_includeFileEdit->setToolTip(toolTip); + + toolTip = tr("Indicates that the header file is a global header file. Does not have any effect on Qt for Python."); + label = new QLabel(tr("Global include")); + label->setToolTip(toolTip); + formLayout->addRow(label, m_globalIncludeCheckBox); + m_globalIncludeCheckBox->setToolTip(toolTip); + + hboxLayout->addLayout(formLayout); + hboxLayout->addItem(new QSpacerItem(15, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + // Button box + QVBoxLayout *buttonLayout = new QVBoxLayout(); + + m_addButton->setAutoDefault(false); + connect(m_addButton, &QAbstractButton::clicked, this, &NewPromotedClassPanel::slotAdd); + m_addButton->setEnabled(false); + buttonLayout->addWidget(m_addButton); + + QPushButton *resetButton = new QPushButton(tr("Reset")); + resetButton->setAutoDefault(false); + connect(resetButton, &QAbstractButton::clicked, this, &NewPromotedClassPanel::slotReset); + + buttonLayout->addWidget(resetButton); + buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); + hboxLayout->addLayout(buttonLayout); + + enableButtons(); + } + + void NewPromotedClassPanel::slotAdd() { + bool ok = false; + emit newPromotedClass(promotionParameters(), &ok); + if (ok) + slotReset(); + } + + void NewPromotedClassPanel::slotReset() { + const QString empty; + m_classNameEdit->setText(empty); + m_includeFileEdit->setText(empty); + m_globalIncludeCheckBox->setCheckState(Qt::Unchecked); + } + + void NewPromotedClassPanel::grabFocus() { + m_classNameEdit->setFocus(Qt::OtherFocusReason); + } + + void NewPromotedClassPanel::slotNameChanged(const QString &className) { + // Suggest a name + if (!className.isEmpty()) { + QString suggestedHeader = m_promotedHeaderLowerCase ? + className.toLower() : className; + suggestedHeader.replace("::"_L1, "_"_L1); + if (!m_promotedHeaderSuffix.startsWith(u'.')) + suggestedHeader += u'.'; + suggestedHeader += m_promotedHeaderSuffix; + + const bool blocked = m_includeFileEdit->blockSignals(true); + m_includeFileEdit->setText(suggestedHeader); + m_includeFileEdit->blockSignals(blocked); + } + enableButtons(); + } + + void NewPromotedClassPanel::slotIncludeFileChanged(const QString &){ + enableButtons(); + } + + void NewPromotedClassPanel::enableButtons() { + const bool enabled = !m_classNameEdit->text().isEmpty() && !m_includeFileEdit->text().isEmpty(); + m_addButton->setEnabled(enabled); + m_addButton->setDefault(enabled); + } + + PromotionParameters NewPromotedClassPanel::promotionParameters() const { + PromotionParameters rc; + rc.m_baseClass = m_baseClassCombo->currentText(); + rc.m_className = m_classNameEdit->text(); + rc.m_includeFile = buildIncludeFile(m_includeFileEdit->text(), + m_globalIncludeCheckBox->checkState() == Qt::Checked ? IncludeGlobal : IncludeLocal); + return rc; + } + + void NewPromotedClassPanel::chooseBaseClass(const QString &baseClass) { + const int index = m_baseClassCombo->findText (baseClass); + if (index != -1) + m_baseClassCombo->setCurrentIndex (index); + } + + // --------------- QDesignerPromotionDialog + QDesignerPromotionDialog::QDesignerPromotionDialog(QDesignerFormEditorInterface *core, + QWidget *parent, + const QString &promotableWidgetClassName, + QString *promoteTo) : + QDialog(parent), + m_mode(promotableWidgetClassName.isEmpty() || promoteTo == nullptr ? ModeEdit : ModeEditChooseClass), + m_promotableWidgetClassName(promotableWidgetClassName), + m_core(core), + m_promoteTo(promoteTo), + m_promotion(core->promotion()), + m_model(new PromotionModel(core)), + m_treeView(new QTreeView), + m_buttonBox(nullptr), + m_removeButton(new QPushButton(createIconSet("minus.png"_L1), QString())) + { + m_buttonBox = createButtonBox(); + setModal(true); + setWindowTitle(tr("Promoted Widgets")); + + QVBoxLayout *vboxLayout = new QVBoxLayout(this); + + // tree view group + QGroupBox *treeViewGroup = new QGroupBox(); + treeViewGroup->setTitle(tr("Promoted Classes")); + QVBoxLayout *treeViewVBoxLayout = new QVBoxLayout(treeViewGroup); + // tree view + m_treeView->setModel (m_model); + m_treeView->setMinimumWidth(450); + m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &QDesignerPromotionDialog::slotSelectionChanged); + + connect(m_treeView, &QWidget::customContextMenuRequested, + this, &QDesignerPromotionDialog::slotTreeViewContextMenu); + + QHeaderView *headerView = m_treeView->header(); + headerView->setSectionResizeMode(QHeaderView::ResizeToContents); + treeViewVBoxLayout->addWidget(m_treeView); + // remove button + QHBoxLayout *hboxLayout = new QHBoxLayout(); + hboxLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + + m_removeButton->setAutoDefault(false); + connect(m_removeButton, &QAbstractButton::clicked, this, &QDesignerPromotionDialog::slotRemove); + m_removeButton->setEnabled(false); + hboxLayout->addWidget(m_removeButton); + treeViewVBoxLayout->addLayout(hboxLayout); + vboxLayout->addWidget(treeViewGroup); + // Create new panel: Try to be smart and preselect a base class. Default to QFrame + const QStringList &baseClassNameList = baseClassNames(m_promotion); + int preselectedBaseClass = -1; + if (m_mode == ModeEditChooseClass) { + preselectedBaseClass = baseClassNameList.indexOf(m_promotableWidgetClassName); + } + if (preselectedBaseClass == -1) + preselectedBaseClass = baseClassNameList.indexOf("QFrame"_L1); + + NewPromotedClassPanel *newPromotedClassPanel = new NewPromotedClassPanel(baseClassNameList, preselectedBaseClass); + newPromotedClassPanel->setPromotedHeaderSuffix(core->integration()->headerSuffix()); + newPromotedClassPanel->setPromotedHeaderLowerCase(core->integration()->isHeaderLowercase()); + connect(newPromotedClassPanel, &NewPromotedClassPanel::newPromotedClass, + this, &QDesignerPromotionDialog::slotNewPromotedClass); + connect(this, &QDesignerPromotionDialog::selectedBaseClassChanged, + newPromotedClassPanel, &NewPromotedClassPanel::chooseBaseClass); + vboxLayout->addWidget(newPromotedClassPanel); + // button box + vboxLayout->addWidget(m_buttonBox); + // connect model + connect(m_model, &PromotionModel::includeFileChanged, + this, &QDesignerPromotionDialog::slotIncludeFileChanged); + + connect(m_model, &PromotionModel::classNameChanged, + this, &QDesignerPromotionDialog::slotClassNameChanged); + + // focus + if (m_mode == ModeEditChooseClass) + newPromotedClassPanel->grabFocus(); + + slotUpdateFromWidgetDatabase(); + } + + QDialogButtonBox *QDesignerPromotionDialog::createButtonBox() { + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Close); + + connect(buttonBox, &QDialogButtonBox::accepted, + this, &QDesignerPromotionDialog::slotAcceptPromoteTo); + buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Promote")); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + return buttonBox; + } + + void QDesignerPromotionDialog::slotUpdateFromWidgetDatabase() { + m_model->updateFromWidgetDatabase(); + m_treeView->expandAll(); + m_removeButton->setEnabled(false); + } + + void QDesignerPromotionDialog::delayedUpdateFromWidgetDatabase() { + QTimer::singleShot(0, this, &QDesignerPromotionDialog::slotUpdateFromWidgetDatabase); + } + + const QStringList &QDesignerPromotionDialog::baseClassNames(const QDesignerPromotionInterface *promotion) { + using WidgetDataBaseItemList = QList; + static QStringList rc; + if (rc.isEmpty()) { + // Convert the item list into a string list. + const WidgetDataBaseItemList dbItems = promotion->promotionBaseClasses(); + for (auto *item : dbItems) + rc.append(item->name()); + } + return rc; + } + + void QDesignerPromotionDialog::slotAcceptPromoteTo() { + Q_ASSERT(m_mode == ModeEditChooseClass); + unsigned flags; + // Ok pressed: Promote to selected class + if (QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags)) { + if (flags & CanPromote) { + *m_promoteTo = dbItem ->name(); + accept(); + } + } + } + + void QDesignerPromotionDialog::slotRemove() { + unsigned flags; + QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); + if (!dbItem || (flags & Referenced)) + return; + + QString errorMessage; + if (m_promotion->removePromotedClass(dbItem->name(), &errorMessage)) { + slotUpdateFromWidgetDatabase(); + } else { + displayError(errorMessage); + } + } + + void QDesignerPromotionDialog::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &) { + // Enable deleting non-referenced items + unsigned flags; + const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(selected, flags); + m_removeButton->setEnabled(dbItem && !(flags & Referenced)); + // In choose mode, can we promote to the class? + if (m_mode == ModeEditChooseClass) { + const bool enablePromoted = flags & CanPromote; + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enablePromoted); + m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(enablePromoted); + } + // different base? + if (dbItem) { + const QString baseClass = dbItem->extends(); + if (baseClass != m_lastSelectedBaseClass) { + m_lastSelectedBaseClass = baseClass; + emit selectedBaseClassChanged(m_lastSelectedBaseClass); + } + } + } + + QDesignerWidgetDataBaseItemInterface *QDesignerPromotionDialog::databaseItemAt(const QItemSelection &selected, unsigned &flags) const { + flags = 0; + const QModelIndexList indexes = selected.indexes(); + if (indexes.isEmpty()) + return nullptr; + const PromotionModel::ModelData data = m_model->modelData(indexes.constFirst()); + QDesignerWidgetDataBaseItemInterface *dbItem = data.promotedItem; + + if (dbItem) { + if (data.referenced) + flags |= Referenced; + // In choose mode, can we promote to the class? + if (m_mode == ModeEditChooseClass && dbItem && dbItem->isPromoted() && dbItem->extends() == m_promotableWidgetClassName) + flags |= CanPromote; + + } + return dbItem; + } + + void QDesignerPromotionDialog::slotNewPromotedClass(const PromotionParameters &p, bool *ok) { + QString errorMessage; + *ok = m_promotion->addPromotedClass(p.m_baseClass, p.m_className, p.m_includeFile, &errorMessage); + if (*ok) { + // update and select + slotUpdateFromWidgetDatabase(); + const QModelIndex newClassIndex = m_model->indexOfClass(p.m_className); + if (newClassIndex.isValid()) { + m_treeView->selectionModel()->select(newClassIndex, QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows); + } + } else { + displayError(errorMessage); + } + } + + void QDesignerPromotionDialog::slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &includeFile) { + if (includeFile.isEmpty()) { + delayedUpdateFromWidgetDatabase(); + return; + } + + if (dbItem->includeFile() == includeFile) + return; + + QString errorMessage; + if (!m_promotion->setPromotedClassIncludeFile(dbItem->name(), includeFile, &errorMessage)) { + displayError(errorMessage); + delayedUpdateFromWidgetDatabase(); + } + } + + void QDesignerPromotionDialog::slotClassNameChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &newName) { + if (newName.isEmpty()) { + delayedUpdateFromWidgetDatabase(); + return; + } + const QString oldName = dbItem->name(); + if (newName == oldName) + return; + + QString errorMessage; + if (!m_promotion->changePromotedClassName(oldName , newName, &errorMessage)) { + displayError(errorMessage); + delayedUpdateFromWidgetDatabase(); + } + } + + void QDesignerPromotionDialog::slotTreeViewContextMenu(const QPoint &pos) { + unsigned flags; + const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); + if (!dbItem) + return; + + QMenu menu; + QAction *signalSlotAction = menu.addAction(tr("Change signals/slots...")); + connect(signalSlotAction, &QAction::triggered, + this, &QDesignerPromotionDialog::slotEditSignalsSlots); + + menu.exec(m_treeView->viewport()->mapToGlobal(pos)); + } + + void QDesignerPromotionDialog::slotEditSignalsSlots() { + unsigned flags; + const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); + if (!dbItem) + return; + + SignalSlotDialog::editPromotedClass(m_core, dbItem->name(), this); + } + + void QDesignerPromotionDialog::displayError(const QString &message) { + m_core->dialogGui()->message(this, QDesignerDialogGuiInterface::PromotionErrorMessage, QMessageBox::Warning, + tr("%1 - Error").arg(windowTitle()), message, QMessageBox::Close); + } +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_promotiondialog_p.h b/src/designer/src/lib/shared/qdesigner_promotiondialog_p.h new file mode 100644 index 0000000..529578f --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_promotiondialog_p.h @@ -0,0 +1,132 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PROMOTIONEDITORDIALOG_H +#define PROMOTIONEDITORDIALOG_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QDesignerPromotionInterface; +class QDesignerWidgetDataBaseItemInterface; + +class QTreeView; +class QPushButton; +class QItemSelection; +class QDialogButtonBox; +class QComboBox; +class QLineEdit; +class QCheckBox; + +namespace qdesigner_internal { + struct PromotionParameters; + class PromotionModel; + + + // Panel for adding a new promoted class. Separate class for code cleanliness. + class NewPromotedClassPanel : public QGroupBox { + Q_OBJECT + public: + explicit NewPromotedClassPanel(const QStringList &baseClasses, + int selectedBaseClass = -1, + QWidget *parent = nullptr); + + QString promotedHeaderSuffix() const { return m_promotedHeaderSuffix; } + void setPromotedHeaderSuffix(const QString &s) { m_promotedHeaderSuffix = s; } + + bool isPromotedHeaderLowerCase() const { return m_promotedHeaderLowerCase; } + void setPromotedHeaderLowerCase(bool l) { m_promotedHeaderLowerCase = l; } + + signals: + void newPromotedClass(const PromotionParameters &, bool *ok); + + public slots: + void grabFocus(); + void chooseBaseClass(const QString &); + private slots: + void slotNameChanged(const QString &); + void slotIncludeFileChanged(const QString &); + void slotAdd(); + void slotReset(); + + private: + PromotionParameters promotionParameters() const; + void enableButtons(); + + QString m_promotedHeaderSuffix; + bool m_promotedHeaderLowerCase = false; + + QComboBox *m_baseClassCombo; + QLineEdit *m_classNameEdit; + QLineEdit *m_includeFileEdit; + QCheckBox *m_globalIncludeCheckBox; + QPushButton *m_addButton; + }; + + // Dialog for editing promoted classes. + class QDesignerPromotionDialog : public QDialog { + Q_OBJECT + + public: + enum Mode { ModeEdit, ModeEditChooseClass }; + + explicit QDesignerPromotionDialog(QDesignerFormEditorInterface *core, + QWidget *parent = nullptr, + const QString &promotableWidgetClassName = QString(), + QString *promoteTo = nullptr); + // Return an alphabetically ordered list of base class names for adding new classes. + static const QStringList &baseClassNames(const QDesignerPromotionInterface *promotion); + + signals: + void selectedBaseClassChanged(const QString &); + private slots: + void slotRemove(); + void slotAcceptPromoteTo(); + void slotSelectionChanged(const QItemSelection &, const QItemSelection &); + void slotNewPromotedClass(const PromotionParameters &, bool *ok); + + void slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface *, const QString &includeFile); + void slotClassNameChanged(QDesignerWidgetDataBaseItemInterface *, const QString &newName); + void slotUpdateFromWidgetDatabase(); + void slotTreeViewContextMenu(const QPoint &); + void slotEditSignalsSlots(); + + private: + QDialogButtonBox *createButtonBox(); + void delayedUpdateFromWidgetDatabase(); + // Return item at model index and a combination of flags or 0. + enum { Referenced = 1, CanPromote = 2 }; + QDesignerWidgetDataBaseItemInterface *databaseItemAt(const QItemSelection &, unsigned &flags) const; + void displayError(const QString &message); + + const Mode m_mode; + const QString m_promotableWidgetClassName; + QDesignerFormEditorInterface *m_core; + QString *m_promoteTo; + QDesignerPromotionInterface *m_promotion; + PromotionModel *m_model; + QTreeView *m_treeView; + QDialogButtonBox *m_buttonBox; + QPushButton *m_removeButton; + QString m_lastSelectedBaseClass; + }; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PROMOTIONEDITORDIALOG_H diff --git a/src/designer/src/lib/shared/qdesigner_propertycommand.cpp b/src/designer/src/lib/shared/qdesigner_propertycommand.cpp new file mode 100644 index 0000000..f24675e --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_propertycommand.cpp @@ -0,0 +1,1528 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_propertycommand_p.h" +#include "qdesigner_utils_p.h" +#include "dynamicpropertysheet.h" +#include "qdesigner_propertyeditor_p.h" +#include "spacer_widget_p.h" +#include "qdesigner_propertysheet_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { +enum { debugPropertyCommands = 0 }; + +const unsigned QFontFamiliesResolved = (QFont::FamilyResolved | QFont::FamiliesResolved); + +// Debug resolve mask of font +QString fontMask(unsigned m) +{ + QString rc; + if (m & QFontFamiliesResolved) + rc += "Family"_L1; + if (m & QFont::SizeResolved) + rc += "Size "_L1; + if (m & QFont::WeightResolved) + rc += "Bold "_L1; + if (m & QFont::StyleResolved) + rc += "Style "_L1; + if (m & QFont::UnderlineResolved) + rc += "Underline "_L1; + if (m & QFont::StrikeOutResolved) + rc += "StrikeOut "_L1; + if (m & QFont::KerningResolved) + rc += "Kerning "_L1; + if (m & QFont::StyleStrategyResolved) + rc += "StyleStrategy"_L1; + return rc; +} + +// Debug font +QString fontString(const QFont &f) +{ + QString rc; { + QTextStream str(&rc); + str << "QFont(\"" << f.family() << ',' << f.pointSize(); + if (f.bold()) + str << ',' << "bold"; + if (f.italic()) + str << ',' << "italic"; + if (f.underline()) + str << ',' << "underline"; + if (f.strikeOut()) + str << ',' << "strikeOut"; + if (f.kerning()) + str << ',' << "kerning"; + str << ',' << f.styleStrategy() << " resolve: " + << fontMask(f.resolveMask()) << ')'; + } + return rc; +} +QSize checkSize(const QSize &size) +{ + return size.boundedTo(QSize(0xFFFFFF, 0xFFFFFF)); +} + +QSize diffSize(QDesignerFormWindowInterface *fw) +{ + const QWidget *container = fw->core()->integration()->containerWindow(fw); + if (!container) + return QSize(); + + const QSize diff = container->size() - fw->size(); // decoration offset of container window + return diff; +} + +void checkSizes(QDesignerFormWindowInterface *fw, const QSize &size, QSize *formSize, QSize *containerSize) +{ + const QWidget *container = fw->core()->integration()->containerWindow(fw); + if (!container) + return; + + const QSize diff = diffSize(fw); // decoration offset of container window + + QSize newFormSize = checkSize(size).expandedTo(fw->mainContainer()->minimumSizeHint()); // don't try to resize to smaller size than minimumSizeHint + QSize newContainerSize = newFormSize + diff; + + newContainerSize = newContainerSize.expandedTo(container->minimumSizeHint()); + newContainerSize = newContainerSize.expandedTo(container->minimumSize()); + + newFormSize = newContainerSize - diff; + + newContainerSize = checkSize(newContainerSize); + + if (formSize) + *formSize = newFormSize; + if (containerSize) + *containerSize = newContainerSize; +} + +/* SubProperties: When applying a changed property to a multiselection, it sometimes makes + * sense to apply only parts (subproperties) of the property. + * For example, if someone changes the x-value of a geometry in the property editor + * and applies it to a multi-selection, y should not be applied as this would cause all + * the widgets to overlap. + * The following routines can be used to find out the changed subproperties of a property, + * which are represented as a mask, and to apply them while leaving the others intact. */ + +enum RectSubPropertyMask { SubPropertyX=1, SubPropertyY = 2, SubPropertyWidth = 4, SubPropertyHeight = 8 }; +enum SizePolicySubPropertyMask { SubPropertyHSizePolicy = 1, SubPropertyHStretch = 2, SubPropertyVSizePolicy = 4, SubPropertyVStretch = 8 }; +enum AlignmentSubPropertyMask { SubPropertyHorizontalAlignment = 1, SubPropertyVerticalAlignment = 2 }; +enum StringSubPropertyMask { SubPropertyStringValue = 1, SubPropertyStringComment = 2, + SubPropertyStringTranslatable = 4, SubPropertyStringDisambiguation = 8, + SubPropertyStringId = 16 }; +enum StringListSubPropertyMask { SubPropertyStringListValue = 1, SubPropertyStringListComment = 2, + SubPropertyStringListTranslatable = 4, SubPropertyStringListDisambiguation = 8, + SubPropertyStringListId = 16 }; +enum KeySequenceSubPropertyMask { SubPropertyKeySequenceValue = 1, SubPropertyKeySequenceComment = 2, + SubPropertyKeySequenceTranslatable = 4, SubPropertyKeySequenceDisambiguation = 8, + SubPropertyKeySequenceId = 16 }; + +enum CommonSubPropertyMask : quint64 { SubPropertyAll = quint64(-1) }; + +// Set the mask flag in mask if the properties do not match. +#define COMPARE_SUBPROPERTY(object1, object2, getter, mask, maskFlag) \ + if (object1.getter() != object2.getter()) (mask) |= (maskFlag); + +// find changed subproperties of a rectangle +quint64 compareSubProperties(const QRect & r1, const QRect & r2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(r1, r2, x, rc, SubPropertyX) + COMPARE_SUBPROPERTY(r1, r2, y, rc, SubPropertyY) + COMPARE_SUBPROPERTY(r1, r2, width, rc, SubPropertyWidth) + COMPARE_SUBPROPERTY(r1, r2, height, rc, SubPropertyHeight) + return rc; +} + +// find changed subproperties of a QSize +quint64 compareSubProperties(const QSize & r1, const QSize & r2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(r1, r2, width, rc, SubPropertyWidth) + COMPARE_SUBPROPERTY(r1, r2, height, rc, SubPropertyHeight) + return rc; +} +// find changed subproperties of a QSizePolicy +quint64 compareSubProperties(const QSizePolicy & sp1, const QSizePolicy & sp2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(sp1, sp2, horizontalPolicy, rc, SubPropertyHSizePolicy) + COMPARE_SUBPROPERTY(sp1, sp2, horizontalStretch, rc, SubPropertyHStretch) + COMPARE_SUBPROPERTY(sp1, sp2, verticalPolicy, rc, SubPropertyVSizePolicy) + COMPARE_SUBPROPERTY(sp1, sp2, verticalStretch, rc, SubPropertyVStretch) + return rc; +} +// find changed subproperties of qdesigner_internal::PropertySheetStringValue +quint64 compareSubProperties(const qdesigner_internal::PropertySheetStringValue & str1, const qdesigner_internal::PropertySheetStringValue & str2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(str1, str2, value, rc, SubPropertyStringValue) + COMPARE_SUBPROPERTY(str1, str2, comment, rc, SubPropertyStringComment) + COMPARE_SUBPROPERTY(str1, str2, translatable, rc, SubPropertyStringTranslatable) + COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyStringDisambiguation) + COMPARE_SUBPROPERTY(str1, str2, id, rc, SubPropertyStringId) + return rc; +} +// find changed subproperties of qdesigner_internal::PropertySheetStringListValue +quint64 compareSubProperties(const qdesigner_internal::PropertySheetStringListValue & str1, const qdesigner_internal::PropertySheetStringListValue & str2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(str1, str2, value, rc, SubPropertyStringListValue) + COMPARE_SUBPROPERTY(str1, str2, comment, rc, SubPropertyStringListComment) + COMPARE_SUBPROPERTY(str1, str2, translatable, rc, SubPropertyStringListTranslatable) + COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyStringListDisambiguation) + COMPARE_SUBPROPERTY(str1, str2, id, rc, SubPropertyStringListId) + return rc; +} +// find changed subproperties of qdesigner_internal::PropertySheetKeySequenceValue +quint64 compareSubProperties(const qdesigner_internal::PropertySheetKeySequenceValue & str1, const qdesigner_internal::PropertySheetKeySequenceValue & str2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(str1, str2, value, rc, SubPropertyKeySequenceValue) + COMPARE_SUBPROPERTY(str1, str2, comment, rc, SubPropertyKeySequenceComment) + COMPARE_SUBPROPERTY(str1, str2, translatable, rc, SubPropertyKeySequenceTranslatable) + COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyKeySequenceDisambiguation) + COMPARE_SUBPROPERTY(str1, str2, id, rc, SubPropertyKeySequenceId) + return rc; +} + +// Compare font-subproperties taking the [undocumented] +// resolve flag into account +template +void compareFontSubProperty(const QFont & f1, + const QFont & f2, + Property (QFont::*getter) () const, + quint64 maskBit, + quint64 &mask) +{ + const bool f1Changed = f1.resolveMask() & maskBit; + const bool f2Changed = f2.resolveMask() & maskBit; + // Role has been set/reset in editor + if (f1Changed != f2Changed) { + mask |= maskBit; + } else { + // Was modified in both palettes: Compare values. + if (f1Changed && f2Changed && (f1.*getter)() != (f2.*getter)()) + mask |= maskBit; + } +} +// find changed subproperties of a QFont +quint64 compareSubProperties(const QFont & f1, const QFont & f2) +{ + quint64 rc = 0; + compareFontSubProperty(f1, f2, &QFont::family, QFontFamiliesResolved, rc); + compareFontSubProperty(f1, f2, &QFont::pointSize, QFont::SizeResolved, rc); + compareFontSubProperty(f1, f2, &QFont::weight, QFont::WeightResolved, rc); + compareFontSubProperty(f1, f2, &QFont::italic, QFont::StyleResolved, rc); + compareFontSubProperty(f1, f2, &QFont::underline, QFont::UnderlineResolved, rc); + compareFontSubProperty(f1, f2, &QFont::strikeOut, QFont::StrikeOutResolved, rc); + compareFontSubProperty(f1, f2, &QFont::kerning, QFont::KerningResolved, rc); + compareFontSubProperty(f1, f2, &QFont::styleStrategy, QFont::StyleStrategyResolved, rc); + compareFontSubProperty(f1, f2, &QFont::hintingPreference, QFont::HintingPreferenceResolved, rc); + if (debugPropertyCommands) + qDebug() << "compareSubProperties " << fontString(f1) << fontString(f2) << "\n\treturns " << fontMask(rc); + return rc; +} + +// find changed subproperties of a QPalette taking the [undocumented] resolve flags into account +quint64 compareSubProperties(const QPalette & p1, const QPalette & p2) +{ + quint64 rc = 0; + // generate a mask for each role + const auto p1Changed = p1.resolveMask(); + const auto p2Changed = p2.resolveMask(); + + for (int r = 0; r < static_cast(QPalette::NColorRoles); ++r) { + for (int g = 0; g < static_cast(QPalette::NColorGroups); ++g) { + const auto role = static_cast(r); + const auto group = static_cast(g); + const auto maskBit = qdesigner_internal::paletteResolveMask(group, role); + const bool p1RoleChanged = p1Changed & maskBit; + const bool p2RoleChanged = p2Changed & maskBit; + if (p1RoleChanged != p2RoleChanged // Role has been set/reset in editor + // Was modified in both palettes: Compare values. + || (p1RoleChanged && p2RoleChanged + && p1.brush(group, role).color() != p2.brush(group, role).color())) { + rc |= maskBit; + } + } + } + + return rc; +} + +// find changed subproperties of a QAlignment which is a flag combination of vertical and horizontal + +quint64 compareSubProperties(Qt::Alignment a1, Qt::Alignment a2) +{ + quint64 rc = 0; + if ((a1 & Qt::AlignHorizontal_Mask) != (a2 & Qt::AlignHorizontal_Mask)) + rc |= SubPropertyHorizontalAlignment; + if ((a1 & Qt::AlignVertical_Mask) != (a2 & Qt::AlignVertical_Mask)) + rc |= SubPropertyVerticalAlignment; + return rc; +} + +Qt::Alignment variantToAlignment(const QVariant & q) +{ + return Qt::Alignment(qdesigner_internal::Utils::valueOf(q)); +} +// find changed subproperties of a variant +quint64 compareSubProperties(const QVariant & q1, const QVariant & q2, qdesigner_internal::SpecialProperty specialProperty) +{ + // Do not clobber new value in the comparison function in + // case someone sets a QString on a PropertySheetStringValue. + const int t1 = q1.metaType().id(); + const int t2 = q2.metaType().id(); + if (t1 != t2) + return SubPropertyAll; + switch (t1) { + case QMetaType::QRect: + return compareSubProperties(q1.toRect(), q2.toRect()); + case QMetaType::QSize: + return compareSubProperties(q1.toSize(), q2.toSize()); + case QMetaType::QSizePolicy: + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + case QMetaType::QFont: + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + case QMetaType::QPalette: + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + default: + if (q1.userType() == qMetaTypeId()) + return qvariant_cast(q1).compare(qvariant_cast(q2)); + else if (q1.userType() == qMetaTypeId()) + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + else if (q1.userType() == qMetaTypeId()) + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + else if (q1.userType() == qMetaTypeId()) + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + // Enumerations, flags + switch (specialProperty) { + case qdesigner_internal::SP_Alignment: + return compareSubProperties(variantToAlignment(q1), variantToAlignment(q2)); + default: + break; + } + break; + } + return SubPropertyAll; +} + +// Apply the sub property if mask flag is set in mask +#define SET_SUBPROPERTY(rc, newValue, getter, setter, mask, maskFlag) \ + if ((mask) & (maskFlag)) rc.setter((newValue).getter()); + +// apply changed subproperties to a rectangle +QRect applyRectSubProperty(const QRect &oldValue, const QRect &newValue, unsigned mask) +{ + QRect rc = oldValue; + SET_SUBPROPERTY(rc, newValue, x, moveLeft, mask, SubPropertyX) + SET_SUBPROPERTY(rc, newValue, y, moveTop, mask, SubPropertyY) + SET_SUBPROPERTY(rc, newValue, width, setWidth, mask, SubPropertyWidth) + SET_SUBPROPERTY(rc, newValue, height, setHeight, mask, SubPropertyHeight) + return rc; +} + + +// apply changed subproperties to a rectangle QSize +QSize applySizeSubProperty(const QSize &oldValue, const QSize &newValue, unsigned mask) +{ + QSize rc = oldValue; + SET_SUBPROPERTY(rc, newValue, width, setWidth, mask, SubPropertyWidth) + SET_SUBPROPERTY(rc, newValue, height, setHeight, mask, SubPropertyHeight) + return rc; +} + + +// apply changed subproperties to a SizePolicy +QSizePolicy applySizePolicySubProperty(const QSizePolicy &oldValue, const QSizePolicy &newValue, unsigned mask) +{ + QSizePolicy rc = oldValue; + SET_SUBPROPERTY(rc, newValue, horizontalPolicy, setHorizontalPolicy, mask, SubPropertyHSizePolicy) + SET_SUBPROPERTY(rc, newValue, horizontalStretch, setHorizontalStretch, mask, SubPropertyHStretch) + SET_SUBPROPERTY(rc, newValue, verticalPolicy, setVerticalPolicy, mask, SubPropertyVSizePolicy) + SET_SUBPROPERTY(rc, newValue, verticalStretch, setVerticalStretch, mask, SubPropertyVStretch) + return rc; +} + +// apply changed subproperties to a qdesigner_internal::PropertySheetStringValue +qdesigner_internal::PropertySheetStringValue applyStringSubProperty(const qdesigner_internal::PropertySheetStringValue &oldValue, + const qdesigner_internal::PropertySheetStringValue &newValue, unsigned mask) +{ + qdesigner_internal::PropertySheetStringValue rc = oldValue; + SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyStringValue) + SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyStringComment) + SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyStringTranslatable) + SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyStringDisambiguation) + SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyStringId) + return rc; +} + +// apply changed subproperties to a qdesigner_internal::PropertySheetStringListValue +qdesigner_internal::PropertySheetStringListValue applyStringListSubProperty(const qdesigner_internal::PropertySheetStringListValue &oldValue, + const qdesigner_internal::PropertySheetStringListValue &newValue, unsigned mask) +{ + qdesigner_internal::PropertySheetStringListValue rc = oldValue; + SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyStringListValue) + SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyStringListComment) + SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyStringListTranslatable) + SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyStringListDisambiguation) + SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyStringListId) + return rc; +} + +// apply changed subproperties to a qdesigner_internal::PropertySheetKeySequenceValue +qdesigner_internal::PropertySheetKeySequenceValue applyKeySequenceSubProperty(const qdesigner_internal::PropertySheetKeySequenceValue &oldValue, + const qdesigner_internal::PropertySheetKeySequenceValue &newValue, unsigned mask) +{ + qdesigner_internal::PropertySheetKeySequenceValue rc = oldValue; + SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyKeySequenceValue) + SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyKeySequenceComment) + SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyKeySequenceTranslatable) + SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyKeySequenceDisambiguation) + SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyKeySequenceId) + return rc; +} + +// Apply the font-subproperties keeping the [undocumented] +// resolve flag in sync (note that PropertySetterType might be something like const T&). +template +inline void setFontSubProperty(unsigned mask, + const QFont &newValue, + unsigned maskBit, + PropertyReturnType (QFont::*getter) () const, + void (QFont::*setter) (PropertySetterType), + QFont &value) +{ + if (mask & maskBit) { + (value.*setter)((newValue.*getter)()); + // Set the resolve bit from NewValue in return value + uint r = value.resolveMask(); + const bool origFlag = newValue.resolveMask() & maskBit; + if (origFlag) + r |= maskBit; + else + r &= ~maskBit; + value.setResolveMask(r); + if (debugPropertyCommands) + qDebug() << "setFontSubProperty " << fontMask(maskBit) << " resolve=" << origFlag; + } +} +// apply changed subproperties to a QFont +QFont applyFontSubProperty(const QFont &oldValue, const QFont &newValue, unsigned mask) +{ + QFont rc = oldValue; + setFontSubProperty(mask, newValue, QFontFamiliesResolved, &QFont::family, &QFont::setFamily, rc); + setFontSubProperty(mask, newValue, QFont::SizeResolved, &QFont::pointSize, &QFont::setPointSize, rc); + setFontSubProperty(mask, newValue, QFont::WeightResolved, &QFont::weight, &QFont::setWeight, rc); + setFontSubProperty(mask, newValue, QFont::StyleResolved, &QFont::italic, &QFont::setItalic, rc); + setFontSubProperty(mask, newValue, QFont::UnderlineResolved, &QFont::underline, &QFont::setUnderline, rc); + setFontSubProperty(mask, newValue, QFont::StrikeOutResolved, &QFont::strikeOut, &QFont::setStrikeOut, rc); + setFontSubProperty(mask, newValue, QFont::KerningResolved, &QFont::kerning, &QFont::setKerning, rc); + setFontSubProperty(mask, newValue, QFont::StyleStrategyResolved, &QFont::styleStrategy, &QFont::setStyleStrategy, rc); + setFontSubProperty(mask, newValue, QFont::HintingPreferenceResolved, &QFont::hintingPreference, &QFont::setHintingPreference, rc); + if (debugPropertyCommands) + qDebug() << "applyFontSubProperty old " << fontMask(oldValue.resolveMask()) << " new " << fontMask(newValue.resolveMask()) << " return: " << fontMask(rc.resolveMask()); + return rc; +} + +// apply changed subproperties to a QPalette +QPalette applyPaletteSubProperty(const QPalette &oldValue, const QPalette &newValue, + quint64 mask) +{ + QPalette rc = oldValue; + // apply a mask for each role/group + for (int r = 0; r < static_cast(QPalette::NColorRoles); ++r) { + for (int g = 0; g < static_cast(QPalette::NColorGroups); ++g) { + const auto role = static_cast(r); + const auto group = static_cast(g); + const auto maskBit = qdesigner_internal::paletteResolveMask(group, role); + if (mask & maskBit) { + rc.setColor(group, role, newValue.color(group, role)); + // Set the resolve bit from NewValue in return value + auto resolveMask = rc.resolveMask(); + const bool origFlag = newValue.resolveMask() & maskBit; + if (origFlag) + resolveMask |= maskBit; + else + resolveMask &= ~maskBit; + rc.setResolveMask(resolveMask); + } + } + } + return rc; +} + +// apply changed subproperties to a QAlignment which is a flag combination of vertical and horizontal +Qt::Alignment applyAlignmentSubProperty(Qt::Alignment oldValue, Qt::Alignment newValue, unsigned mask) +{ + // easy: both changed. + if (mask == (SubPropertyHorizontalAlignment|SubPropertyVerticalAlignment)) + return newValue; + // Change subprop + const Qt::Alignment changeMask = (mask & SubPropertyHorizontalAlignment) ? Qt::AlignHorizontal_Mask : Qt::AlignVertical_Mask; + const Qt::Alignment takeOverMask = (mask & SubPropertyHorizontalAlignment) ? Qt::AlignVertical_Mask : Qt::AlignHorizontal_Mask; + return (oldValue & takeOverMask) | (newValue & changeMask); +} + +} + +namespace qdesigner_internal { + +// apply changed subproperties to a variant +PropertyHelper::Value applySubProperty(const QVariant &oldValue, const QVariant &newValue, + qdesigner_internal::SpecialProperty specialProperty, + quint64 mask, bool changed) +{ + if (mask == SubPropertyAll) + return PropertyHelper::Value(newValue, changed); + + switch (oldValue.metaType().id()) { + case QMetaType::QRect: + return PropertyHelper::Value(applyRectSubProperty(oldValue.toRect(), newValue.toRect(), mask), changed); + case QMetaType::QSize: + return PropertyHelper::Value(applySizeSubProperty(oldValue.toSize(), newValue.toSize(), mask), changed); + case QMetaType::QSizePolicy: + return PropertyHelper::Value(QVariant::fromValue(applySizePolicySubProperty(qvariant_cast(oldValue), qvariant_cast(newValue), mask)), changed); + case QMetaType::QFont: { + // Changed flag in case of font and palette depends on resolve mask only, not on the passed "changed" value. + + // The first case: the user changed bold subproperty and then pressed reset button for this subproperty (not for + // the whole font property). We instantiate SetPropertyCommand passing changed=true. But in this case no + // subproperty is changed and the whole property should be marked an unchanged. + + // The second case: there are 2 pushbuttons, for 1st the user set bold and italic subproperties, + // for the 2nd he set bold only. He does multiselection so that the current widget is the 2nd one. + // He press reset next to bold subproperty. In result the 2nd widget should have the whole + // font property marked as unchanged and the 1st widget should have the font property + // marked as changed and only italic subproperty should be marked as changed (the bold should be reset). + + // The third case: there are 2 pushbuttons, for 1st the user set bold and italic subproperties, + // for the 2nd he set bold only. He does multiselection so that the current widget is the 2nd one. + // He press reset button for the whole font property. In result whole font properties for both + // widgets should be marked as unchanged. + QFont font = applyFontSubProperty(qvariant_cast(oldValue), qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(font), font.resolveMask()); + } + case QMetaType::QPalette: { + QPalette palette = applyPaletteSubProperty(qvariant_cast(oldValue), qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(palette), palette.resolveMask()); + } + default: + if (oldValue.userType() == qMetaTypeId()) { + PropertySheetIconValue icon = qvariant_cast(oldValue); + icon.assign(qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(icon), icon.mask()); + } else if (oldValue.userType() == qMetaTypeId()) { + qdesigner_internal::PropertySheetStringValue str = applyStringSubProperty( + qvariant_cast(oldValue), + qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(str), changed); + } else if (oldValue.userType() == qMetaTypeId()) { + qdesigner_internal::PropertySheetStringListValue str = applyStringListSubProperty( + qvariant_cast(oldValue), + qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(str), changed); + } else if (oldValue.userType() == qMetaTypeId()) { + qdesigner_internal::PropertySheetKeySequenceValue key = applyKeySequenceSubProperty( + qvariant_cast(oldValue), + qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(key), changed); + } + // Enumerations, flags + switch (specialProperty) { + case qdesigner_internal::SP_Alignment: { + qdesigner_internal::PropertySheetFlagValue f = qvariant_cast(oldValue); + f.value = applyAlignmentSubProperty(variantToAlignment(oldValue), variantToAlignment(newValue), mask); + QVariant v; + v.setValue(f); + return PropertyHelper::Value(v, changed); + } + default: + break; + } + break; + } + return PropertyHelper::Value(newValue, changed); + +} +// figure out special property +enum SpecialProperty getSpecialProperty(const QString& propertyName) +{ + if (propertyName == "objectName"_L1) + return SP_ObjectName; + if (propertyName == "layoutName"_L1) + return SP_LayoutName; + if (propertyName == "spacerName"_L1) + return SP_SpacerName; + if (propertyName == "icon"_L1) + return SP_Icon; + if (propertyName == "currentTabName"_L1) + return SP_CurrentTabName; + if (propertyName == "currentItemName"_L1) + return SP_CurrentItemName; + if (propertyName == "currentPageName"_L1) + return SP_CurrentPageName; + if (propertyName == "geometry"_L1) + return SP_Geometry; + if (propertyName == "windowTitle"_L1) + return SP_WindowTitle; + if (propertyName == "minimumSize"_L1) + return SP_MinimumSize; + if (propertyName == "maximumSize"_L1) + return SP_MaximumSize; + if (propertyName == "alignment"_L1) + return SP_Alignment; + if (propertyName == "autoDefault"_L1) + return SP_AutoDefault; + if (propertyName == "shortcut"_L1) + return SP_Shortcut; + if (propertyName == "orientation"_L1) + return SP_Orientation; + return SP_None; +} + + +PropertyHelper::PropertyHelper(QObject* object, + SpecialProperty specialProperty, + QDesignerPropertySheetExtension *sheet, + int index) : + m_specialProperty(specialProperty), + m_object(object), + m_objectType(OT_Object), + m_propertySheet(sheet), m_index(index), + m_oldValue(m_propertySheet->property(m_index), m_propertySheet->isChanged(m_index)) +{ + if (object->isWidgetType()) { + m_parentWidget = (qobject_cast(object))->parentWidget(); + m_objectType = OT_Widget; + } else { + if (const QAction *action = qobject_cast(m_object)) { + const QObjectList associatedObjects = action->associatedObjects(); + auto it = std::find_if(associatedObjects.cbegin(), associatedObjects.cend(), + [](QObject *obj) { + return qobject_cast(obj) != nullptr; + }); + m_objectType = (it == associatedObjects.cend()) ? OT_FreeAction : OT_AssociatedAction; + } + } + + if(debugPropertyCommands) + qDebug() << "PropertyHelper on " << m_object->objectName() << " index= " << m_index << " type = " << m_objectType; +} + +QDesignerIntegration *PropertyHelper::integration(QDesignerFormWindowInterface *fw) const +{ + return qobject_cast(fw->core()->integration()); +} + +// Set widget value, apply corrections and checks in case of main window. +void PropertyHelper::checkApplyWidgetValue(QDesignerFormWindowInterface *fw, QWidget* w, + SpecialProperty specialProperty, QVariant &value) +{ + + bool isMainContainer = false; + if (QDesignerFormWindowCursorInterface *cursor = fw->cursor()) { + if (cursor->isWidgetSelected(w)) { + if (cursor->isWidgetSelected(fw->mainContainer())) { + isMainContainer = true; + } + } + } + if (!isMainContainer) + return; + + QWidget *container = fw->core()->integration()->containerWindow(fw); + if (!container) + return; + + + switch (specialProperty) { + case SP_MinimumSize: { + const QSize size = checkSize(value.toSize()); + value.setValue(size); + } + + break; + case SP_MaximumSize: { + QSize fs, cs; + checkSizes(fw, value.toSize(), &fs, &cs); + container->setMaximumSize(cs); + fw->mainContainer()->setMaximumSize(fs); + value.setValue(fs); + + } + break; + case SP_Geometry: { + QRect r = value.toRect(); + QSize fs, cs; + checkSizes(fw, r.size(), &fs, &cs); + container->resize(cs); + r.setSize(fs); + value.setValue(r); + } + break; + default: + break; + } +} + +unsigned PropertyHelper::updateMask() const +{ + unsigned rc = 0; + switch (m_specialProperty) { + case SP_ObjectName: + case SP_LayoutName: + case SP_SpacerName: + case SP_CurrentTabName: + case SP_CurrentItemName: + case SP_CurrentPageName: + if (m_objectType != OT_FreeAction) + rc |= UpdateObjectInspector; + break; + case SP_Icon: + if (m_objectType == OT_AssociatedAction) + rc |= UpdateObjectInspector; + break; + case SP_Orientation: // for updating splitter icon + rc |= UpdateObjectInspector; + break; + default: + break; + + } + return rc; +} + + +bool PropertyHelper::canMerge(const PropertyHelper &other) const +{ + return m_object == other.m_object && m_index == other.m_index; +} + +void PropertyHelper::triggerActionChanged(QAction *a) +{ + a->setData(QVariant(true)); // this triggers signal "changed" in QAction + a->setData(QVariant(false)); +} + +// Update the object to reflect the changes +void PropertyHelper::updateObject(QDesignerFormWindowInterface *fw, const QVariant &oldValue, const QVariant &newValue) +{ + if(debugPropertyCommands){ + qDebug() << "PropertyHelper::updateObject(" << m_object->objectName() << ") " << oldValue << " -> " << newValue; + } + switch (m_objectType) { + case OT_Widget: { + switch (m_specialProperty) { + case SP_ObjectName: { + const QString oldName = qvariant_cast(oldValue).value(); + const QString newName = qvariant_cast(newValue).value(); + QDesignerFormWindowCommand::updateBuddies(fw, oldName, newName); + } + break; + default: + break; + } + } break; + case OT_AssociatedAction: + case OT_FreeAction: + // SP_Shortcut is a fake property, so, QAction::changed does not trigger. + if (m_specialProperty == SP_ObjectName || m_specialProperty == SP_Shortcut) + triggerActionChanged(qobject_cast(m_object)); + break; + default: + break; + } + + switch (m_specialProperty) { + case SP_ObjectName: + case SP_LayoutName: + case SP_SpacerName: + if (QDesignerIntegration *integr = integration(fw)) { + const QString oldName = qvariant_cast(oldValue).value(); + const QString newName = qvariant_cast(newValue).value(); + integr->emitObjectNameChanged(fw, m_object, newName, oldName); + } + break; + default: + break; + } +} + +void PropertyHelper::ensureUniqueObjectName(QDesignerFormWindowInterface *fw, QObject *object) const +{ + switch (m_specialProperty) { + case SP_SpacerName: + if (object->isWidgetType()) { + if (Spacer *sp = qobject_cast(object)) { + fw->ensureUniqueObjectName(sp); + return; + } + } + fw->ensureUniqueObjectName(object); + break; + case SP_LayoutName: // Layout name is invoked on the parent widget. + if (object->isWidgetType()) { + const QWidget * w = qobject_cast(object); + if (QLayout *wlayout = w->layout()) { + fw->ensureUniqueObjectName(wlayout); + return; + } + } + fw->ensureUniqueObjectName(object); + break; + case SP_ObjectName: + fw->ensureUniqueObjectName(object); + break; + default: + break; + } +} + +PropertyHelper::Value PropertyHelper::setValue(QDesignerFormWindowInterface *fw, const QVariant &value, + bool changed, quint64 subPropertyMask) +{ + // Set new whole value + if (subPropertyMask == SubPropertyAll) + return applyValue(fw, m_oldValue.first, Value(value, changed)); + + // apply subproperties + const PropertyHelper::Value maskedNewValue = applySubProperty(m_oldValue.first, value, m_specialProperty, subPropertyMask, changed); + return applyValue(fw, m_oldValue.first, maskedNewValue); +} + +// Apply the value and update. Returns corrected value +PropertyHelper::Value PropertyHelper::applyValue(QDesignerFormWindowInterface *fw, const QVariant &oldValue, Value newValue) +{ + if(debugPropertyCommands){ + qDebug() << "PropertyHelper::applyValue(" << m_object << ") " << oldValue << " -> " << newValue.first << " changed=" << newValue.second; + } + + if (m_objectType == OT_Widget) { + checkApplyWidgetValue(fw, qobject_cast(m_object), m_specialProperty, newValue.first); + } + + m_propertySheet->setProperty(m_index, newValue.first); + m_propertySheet->setChanged(m_index, newValue.second); + + switch (m_specialProperty) { + case SP_LayoutName: + case SP_ObjectName: + case SP_SpacerName: + ensureUniqueObjectName(fw, m_object); + newValue.first = m_propertySheet->property(m_index); + break; + default: + break; + } + + updateObject(fw, oldValue, newValue.first); + return newValue; +} + +PropertyHelper::Value PropertyHelper::restoreOldValue(QDesignerFormWindowInterface *fw) +{ + return applyValue(fw, m_propertySheet->property(m_index), m_oldValue); +} + +// find the default value in widget DB in case PropertySheet::reset fails +QVariant PropertyHelper::findDefaultValue(QDesignerFormWindowInterface *fw) const +{ + if (m_specialProperty == SP_AutoDefault && qobject_cast(m_object)) { + // AutoDefault defaults to true on dialogs + const bool isDialog = qobject_cast(fw->mainContainer()); + return QVariant(isDialog); + } + + const int item_idx = fw->core()->widgetDataBase()->indexOfObject(m_object); + if (item_idx == -1) + return m_oldValue.first; // We simply don't know the value in this case + + const QDesignerWidgetDataBaseItemInterface *item = fw->core()->widgetDataBase()->item(item_idx); + const auto default_prop_values = item->defaultPropertyValues(); + if (m_index < default_prop_values.size()) + return default_prop_values.at(m_index); + + if (m_oldValue.first.metaType().id() == QMetaType::QColor) + return QColor(); + + return m_oldValue.first; // Again, we just don't know +} + +PropertyHelper::Value PropertyHelper::restoreDefaultValue(QDesignerFormWindowInterface *fw) +{ + + Value defaultValue{{}, false}; + const QVariant currentValue = m_propertySheet->property(m_index); + // try to reset sheet, else try to find default + if (m_propertySheet->reset(m_index)) { + defaultValue.first = m_propertySheet->property(m_index); + } else { + defaultValue.first = findDefaultValue(fw); + m_propertySheet->setProperty(m_index, defaultValue.first); + } + + m_propertySheet->setChanged(m_index, defaultValue.second); + + if (m_objectType == OT_Widget) { + checkApplyWidgetValue(fw, qobject_cast(m_object), m_specialProperty, defaultValue.first); + } + + switch (m_specialProperty) { + case SP_LayoutName: + case SP_ObjectName: + case SP_SpacerName: + ensureUniqueObjectName(fw, m_object); + defaultValue.first = m_propertySheet->property(m_index); + break; + default: + break; + } + + updateObject(fw, currentValue, defaultValue.first); + return defaultValue; +} + +// ---- PropertyListCommand::PropertyDescription( + + +PropertyListCommand::PropertyDescription::PropertyDescription(const QString &propertyName, + QDesignerPropertySheetExtension *propertySheet, + int index) : + m_propertyName(propertyName), + m_propertyGroup(propertySheet->propertyGroup(index)), + m_propertyType(propertySheet->property(index).metaType().id()), + m_specialProperty(getSpecialProperty(propertyName)) +{ +} + +void PropertyListCommand::PropertyDescription::debug() const +{ + qDebug() << m_propertyName << m_propertyGroup << m_propertyType << m_specialProperty; +} + +bool PropertyListCommand::PropertyDescription::equals(const PropertyDescription &p) const +{ + return m_propertyType == p.m_propertyType && m_specialProperty == p.m_specialProperty && + m_propertyName == p.m_propertyName && m_propertyGroup == p.m_propertyGroup; +} + + +// ---- PropertyListCommand +PropertyListCommand::PropertyListCommand(QDesignerFormWindowInterface *formWindow, + QUndoCommand *parent) : + QDesignerFormWindowCommand(QString(), formWindow, parent) +{ +} + +const QString PropertyListCommand::propertyName() const +{ + return m_propertyDescription.m_propertyName; +} + +SpecialProperty PropertyListCommand::specialProperty() const +{ + return m_propertyDescription.m_specialProperty; +} + +// add an object +bool PropertyListCommand::add(QObject *object, const QString &propertyName) +{ + QDesignerPropertySheetExtension* sheet = propertySheet(object); + Q_ASSERT(sheet); + + const int index = sheet->indexOf(propertyName); + if (index == -1) + return false; + + if (!sheet->isEnabled(index)) + return false; + + const PropertyDescription description(propertyName, sheet, index); + + if (m_propertyHelperList.empty()) { + // first entry + m_propertyDescription = description; + } else { + // checks: mismatch or only one object in case of name + const bool match = m_propertyDescription.equals(description); + if (!match || m_propertyDescription.m_specialProperty == SP_ObjectName) + return false; + } + + auto ph = createPropertyHelper(object, m_propertyDescription.m_specialProperty, sheet, index); + m_propertyHelperList.push_back(std::move(ph)); + return true; +} + +std::unique_ptr +PropertyListCommand::createPropertyHelper(QObject *object, SpecialProperty sp, + QDesignerPropertySheetExtension *sheet, int sheetIndex) const +{ + return std::make_unique(object, sp, sheet, sheetIndex); +} + +// Init from a list and make sure referenceObject is added first to obtain the right property group +bool PropertyListCommand::initList(const QObjectList &list, const QString &apropertyName, QObject *referenceObject) +{ + m_propertyHelperList.clear(); + + // Ensure the referenceObject (property editor) is first, so the right property group is chosen. + if (referenceObject) { + if (!add(referenceObject, apropertyName)) + return false; + } + for (QObject *o : list) { + if (o != referenceObject) + add(o, apropertyName); + } + + return !m_propertyHelperList.empty(); +} + + +QObject* PropertyListCommand::object(int index) const +{ + Q_ASSERT(size_t(index) < m_propertyHelperList.size()); + return m_propertyHelperList[index]->object(); +} + +QVariant PropertyListCommand::oldValue(int index) const +{ + Q_ASSERT(size_t(index) < m_propertyHelperList.size()); + return m_propertyHelperList[index]->oldValue(); +} + +void PropertyListCommand::setOldValue(const QVariant &oldValue, int index) +{ + Q_ASSERT(size_t(index) < m_propertyHelperList.size()); + m_propertyHelperList[index]->setOldValue(oldValue); +} +// ----- SetValueFunction: Set a new value when applied to a PropertyHelper. +class SetValueFunction { +public: + SetValueFunction(QDesignerFormWindowInterface *formWindow, const PropertyHelper::Value &newValue, + quint64 subPropertyMask); + + PropertyHelper::Value operator()(PropertyHelper&); +private: + QDesignerFormWindowInterface *m_formWindow; + const PropertyHelper::Value m_newValue; + const quint64 m_subPropertyMask; +}; + + +SetValueFunction::SetValueFunction(QDesignerFormWindowInterface *formWindow, + const PropertyHelper::Value &newValue, + quint64 subPropertyMask) : + m_formWindow(formWindow), + m_newValue(newValue), + m_subPropertyMask(subPropertyMask) +{ +} + +PropertyHelper::Value SetValueFunction::operator()(PropertyHelper &ph) { + return ph.setValue(m_formWindow, m_newValue.first, m_newValue.second, m_subPropertyMask); +} + +// ----- UndoSetValueFunction: Restore old value when applied to a PropertyHelper. +class UndoSetValueFunction { +public: + UndoSetValueFunction(QDesignerFormWindowInterface *formWindow) : m_formWindow(formWindow) {} + PropertyHelper::Value operator()(PropertyHelper& ph) { return ph.restoreOldValue(m_formWindow); } +private: + QDesignerFormWindowInterface *m_formWindow; +}; + +// ----- RestoreDefaultFunction: Restore default value when applied to a PropertyHelper. +class RestoreDefaultFunction { +public: + RestoreDefaultFunction(QDesignerFormWindowInterface *formWindow) : m_formWindow(formWindow) {} + PropertyHelper::Value operator()(PropertyHelper& ph) { return ph.restoreDefaultValue(m_formWindow); } +private: + QDesignerFormWindowInterface *m_formWindow; +}; + +// ----- changePropertyList: Iterates over a sequence of PropertyHelpers and +// applies a function to them. +// The function returns the corrected value which is then set in the property editor. +// Returns a combination of update flags. +template + unsigned changePropertyList(QDesignerFormEditorInterface *core, + const QString &propertyName, + PropertyListIterator begin, + PropertyListIterator end, + Function function) +{ + unsigned updateMask = 0; + QDesignerPropertyEditorInterface *propertyEditor = core->propertyEditor(); + bool updatedPropertyEditor = false; + + for (auto it = begin; it != end; ++it) { + PropertyHelper *ph = it->get(); + if (QObject* object = ph->object()) { // Might have been deleted in the meantime + const PropertyHelper::Value newValue = function( *ph ); + updateMask |= ph->updateMask(); + // Update property editor if it is the current object + if (!updatedPropertyEditor && propertyEditor && object == propertyEditor->object()) { + propertyEditor->setPropertyValue(propertyName, newValue.first, newValue.second); + updatedPropertyEditor = true; + } + } + } + if (!updatedPropertyEditor) updateMask |= PropertyHelper::UpdatePropertyEditor; + return updateMask; +} + + +// set a new value, return update mask +unsigned PropertyListCommand::setValue(const QVariant &value, bool changed, quint64 subPropertyMask) +{ + if(debugPropertyCommands) + qDebug() << "PropertyListCommand::setValue(" << value + << changed << subPropertyMask << ')'; + return changePropertyList(formWindow()->core(), + m_propertyDescription.m_propertyName, + m_propertyHelperList.begin(), m_propertyHelperList.end(), + SetValueFunction(formWindow(), PropertyHelper::Value(value, changed), subPropertyMask)); +} + +// restore old value, return update mask +unsigned PropertyListCommand::restoreOldValue() +{ + if(debugPropertyCommands) + qDebug() << "PropertyListCommand::restoreOldValue()"; + + return changePropertyList(formWindow()->core(), + m_propertyDescription.m_propertyName, m_propertyHelperList.begin(), m_propertyHelperList.end(), + UndoSetValueFunction(formWindow())); +} +// set default value, return update mask +unsigned PropertyListCommand::restoreDefaultValue() +{ + if(debugPropertyCommands) + qDebug() << "PropertyListCommand::restoreDefaultValue()"; + + return changePropertyList(formWindow()->core(), + m_propertyDescription.m_propertyName, m_propertyHelperList.begin(), m_propertyHelperList.end(), + RestoreDefaultFunction(formWindow())); +} + +// update +void PropertyListCommand::update(unsigned updateMask) +{ + if(debugPropertyCommands) + qDebug() << "PropertyListCommand::update(" << updateMask << ')'; + + if (updateMask & PropertyHelper::UpdateObjectInspector) { + if (QDesignerObjectInspectorInterface *oi = formWindow()->core()->objectInspector()) + oi->setFormWindow(formWindow()); + } + + if (updateMask & PropertyHelper::UpdatePropertyEditor) { + // this is needed when f.ex. undo, changes parent's palette, but + // the child is the active widget, + // TODO: current object? + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + propertyEditor->setObject(propertyEditor->object()); + } + } +} + +void PropertyListCommand::undo() +{ + update(restoreOldValue()); + QDesignerPropertyEditor *designerPropertyEditor = qobject_cast(core()->propertyEditor()); + if (designerPropertyEditor) + designerPropertyEditor->updatePropertySheet(); +} + +// check if lists are aequivalent for command merging (same widgets and props) +bool PropertyListCommand::canMergeLists(const PropertyHelperList& other) const +{ + if (m_propertyHelperList.size() != other.size()) + return false; + for (size_t i = 0; i < m_propertyHelperList.size(); ++i) { + if (!m_propertyHelperList[i]->canMerge(*other[i])) + return false; + } + return true; +} + +// ---- SetPropertyCommand ---- +SetPropertyCommand::SetPropertyCommand(QDesignerFormWindowInterface *formWindow, + QUndoCommand *parent) + : PropertyListCommand(formWindow, parent), + m_subPropertyMask(SubPropertyAll) +{ +} + +bool SetPropertyCommand::init(QObject *object, const QString &apropertyName, const QVariant &newValue) +{ + Q_ASSERT(object); + + m_newValue = newValue; + + propertyHelperList().clear(); + if (!add(object, apropertyName)) + return false; + + setDescription(); + return true; +} + +bool SetPropertyCommand::init(const QObjectList &list, const QString &apropertyName, const QVariant &newValue, + QObject *referenceObject, bool enableSubPropertyHandling) +{ + if (!initList(list, apropertyName, referenceObject)) + return false; + + m_newValue = newValue; + + if(debugPropertyCommands) + qDebug() << "SetPropertyCommand::init()" << propertyHelperList().size() << '/' << list.size() << " reference " << referenceObject; + + setDescription(); + + if (enableSubPropertyHandling) + m_subPropertyMask = subPropertyMask(newValue, referenceObject); + return true; +} + +quint64 SetPropertyCommand::subPropertyMask(const QVariant &newValue, QObject *referenceObject) +{ + // figure out the mask of changed sub properties when comparing newValue to the current value of the reference object. + if (!referenceObject) + return SubPropertyAll; + + QDesignerPropertySheetExtension* sheet = propertySheet(referenceObject); + Q_ASSERT(sheet); + + const int index = sheet->indexOf(propertyName()); + if (index == -1 || !sheet->isVisible(index)) + return SubPropertyAll; + + return compareSubProperties(sheet->property(index), newValue, specialProperty()); +} + +void SetPropertyCommand::setDescription() +{ + if (propertyHelperList().size() == 1) { + setText(QApplication::translate("Command", "Changed '%1' of '%2'") + .arg(propertyName(), propertyHelperList().front()->object()->objectName())); + } else { + int count = static_cast(propertyHelperList().size()); + setText(QCoreApplication::translate("Command", "Changed '%1' of %n objects", "", count).arg(propertyName())); + } +} + +void SetPropertyCommand::redo() +{ + update(setValue(m_newValue, true, m_subPropertyMask)); + QDesignerPropertyEditor *designerPropertyEditor = qobject_cast(core()->propertyEditor()); + if (designerPropertyEditor) + designerPropertyEditor->updatePropertySheet(); +} + + +int SetPropertyCommand::id() const +{ + return 1976; +} + +QVariant SetPropertyCommand::mergeValue(const QVariant &newValue) +{ + return newValue; +} + +bool SetPropertyCommand::mergeWith(const QUndoCommand *other) +{ + if (id() != other->id() || !formWindow()->isDirty()) + return false; + + // Merging: When for example when the user types ahead in an inplace-editor, + // it makes sense to merge all the generated commands containing the one-character changes. + // In the case of subproperties, if the user changes the font size from 10 to 30 via 20 + // and then changes to bold, it makes sense to merge the font size commands only. + // This is why the m_subPropertyMask is checked. + + const SetPropertyCommand *cmd = static_cast(other); + if (!propertyDescription().equals(cmd->propertyDescription()) || + m_subPropertyMask != cmd->m_subPropertyMask || + !canMergeLists(cmd->propertyHelperList())) + return false; + + const QVariant newValue = mergeValue(cmd->newValue()); + if (!newValue.isValid()) + return false; + m_newValue = newValue; + m_subPropertyMask |= cmd->m_subPropertyMask; + if(debugPropertyCommands) + qDebug() << "SetPropertyCommand::mergeWith() succeeded " << propertyName(); + + return true; +} + +// ---- ResetPropertyCommand ---- +ResetPropertyCommand::ResetPropertyCommand(QDesignerFormWindowInterface *formWindow) + : PropertyListCommand(formWindow) +{ +} + +bool ResetPropertyCommand::init(QObject *object, const QString &apropertyName) +{ + Q_ASSERT(object); + + propertyHelperList().clear(); + if (!add(object, apropertyName)) + return false; + + setDescription(); + return true; +} + +bool ResetPropertyCommand::init(const QObjectList &list, const QString &apropertyName, QObject *referenceObject) +{ + QObjectList modifiedList = list; // filter out modified properties + for (auto it = modifiedList.begin(); it != modifiedList.end() ; ) { + QDesignerPropertySheetExtension* sheet = propertySheet(*it); + Q_ASSERT(sheet); + const int index = sheet->indexOf(apropertyName); + if (index == -1 || !sheet->isChanged(index)) + it = modifiedList.erase(it); + else + ++it; + } + if (!modifiedList.contains(referenceObject)) + referenceObject = nullptr; + if (modifiedList.isEmpty() || !initList(modifiedList, apropertyName, referenceObject)) + return false; + + if(debugPropertyCommands) + qDebug() << "ResetPropertyCommand::init()" << propertyHelperList().size() << '/' << list.size(); + + setDescription(); + return true; +} + +void ResetPropertyCommand::setDescription() +{ + if (propertyHelperList().size() == 1) { + setText(QCoreApplication::translate("Command", "Reset '%1' of '%2'") + .arg(propertyName(), propertyHelperList().front()->object()->objectName())); + } else { + int count = static_cast(propertyHelperList().size()); + setText(QCoreApplication::translate("Command", "Reset '%1' of %n objects", "", count).arg(propertyName())); + } +} + +void ResetPropertyCommand::redo() +{ + update(restoreDefaultValue()); + QDesignerPropertyEditor *designerPropertyEditor = qobject_cast(core()->propertyEditor()); + if (designerPropertyEditor) + designerPropertyEditor->updatePropertySheet(); +} + +AddDynamicPropertyCommand::AddDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ + +} + +bool AddDynamicPropertyCommand::init(const QObjectList &selection, QObject *current, + const QString &propertyName, const QVariant &value) +{ + Q_ASSERT(current); + m_propertyName = propertyName; + + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), current); + Q_ASSERT(dynamicSheet); + + m_selection.clear(); + + if (!value.isValid()) + return false; + + if (!dynamicSheet->canAddDynamicProperty(m_propertyName)) + return false; + + m_selection.append(current); + + m_value = value; + + for (QObject *obj : selection) { + if (m_selection.contains(obj)) + continue; + dynamicSheet = qt_extension(core->extensionManager(), obj); + Q_ASSERT(dynamicSheet); + if (dynamicSheet->canAddDynamicProperty(m_propertyName)) + m_selection.append(obj); + } + + setDescription(); + return true; +} + +void AddDynamicPropertyCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + for (QObject *obj : std::as_const(m_selection)) { + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), obj); + dynamicSheet->addDynamicProperty(m_propertyName, m_value); + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == obj) + propertyEditor->setObject(obj); + } + } +} + +void AddDynamicPropertyCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + for (QObject *obj : std::as_const(m_selection)) { + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), obj); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), obj); + dynamicSheet->removeDynamicProperty(sheet->indexOf(m_propertyName)); + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == obj) + propertyEditor->setObject(obj); + } + } +} + +void AddDynamicPropertyCommand::setDescription() +{ + if (m_selection.size() == 1) { + setText(QApplication::translate("Command", "Add dynamic property '%1' to '%2'") + .arg(m_propertyName, m_selection.first()->objectName())); + } else { + int count = m_selection.size(); + setText(QCoreApplication::translate("Command", "Add dynamic property '%1' to %n objects", "", count) + .arg(m_propertyName)); + } +} + + +RemoveDynamicPropertyCommand::RemoveDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ + +} + +bool RemoveDynamicPropertyCommand::init(const QObjectList &selection, QObject *current, + const QString &propertyName) +{ + Q_ASSERT(current); + m_propertyName = propertyName; + + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerPropertySheetExtension *propertySheet = qt_extension(core->extensionManager(), current); + Q_ASSERT(propertySheet); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), current); + Q_ASSERT(dynamicSheet); + + m_objectToValueAndChanged.clear(); + + const int index = propertySheet->indexOf(m_propertyName); + if (!dynamicSheet->isDynamicProperty(index)) + return false; + + m_objectToValueAndChanged[current] = {propertySheet->property(index), + propertySheet->isChanged(index)}; + + for (QObject *obj : selection) { + if (m_objectToValueAndChanged.contains(obj)) + continue; + + propertySheet = qt_extension(core->extensionManager(), obj); + dynamicSheet = qt_extension(core->extensionManager(), obj); + const int idx = propertySheet->indexOf(m_propertyName); + if (dynamicSheet->isDynamicProperty(idx)) + m_objectToValueAndChanged[obj] = {propertySheet->property(idx), + propertySheet->isChanged(idx)}; + } + + setDescription(); + return true; +} + +void RemoveDynamicPropertyCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + for (auto it = m_objectToValueAndChanged.cbegin(), end = m_objectToValueAndChanged.cend(); it != end; ++it) { + QObject *obj = it.key(); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), obj); + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), obj); + dynamicSheet->removeDynamicProperty(sheet->indexOf(m_propertyName)); + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == obj) + propertyEditor->setObject(obj); + } + } +} + +void RemoveDynamicPropertyCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + for (auto it = m_objectToValueAndChanged.cbegin(), end = m_objectToValueAndChanged.cend(); it != end; ++it) { + QObject *obj = it.key(); + QDesignerPropertySheetExtension *propertySheet = qt_extension(core->extensionManager(), obj); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), obj); + const int index = dynamicSheet->addDynamicProperty(m_propertyName, it.value().first); + propertySheet->setChanged(index, it.value().second); + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == obj) + propertyEditor->setObject(obj); + } + } +} + +void RemoveDynamicPropertyCommand::setDescription() +{ + if (m_objectToValueAndChanged.size() == 1) { + setText(QApplication::translate("Command", + "Remove dynamic property '%1' from '%2'") + .arg(m_propertyName, m_objectToValueAndChanged.constBegin().key()->objectName())); + } else { + int count = m_objectToValueAndChanged.size(); + setText(QApplication::translate("Command", + "Remove dynamic property '%1' from %n objects", "", count) + .arg(m_propertyName)); + } +} + + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_propertycommand_p.h b/src/designer/src/lib/shared/qdesigner_propertycommand_p.h new file mode 100644 index 0000000..d069e76 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_propertycommand_p.h @@ -0,0 +1,272 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_PROPERTYCOMMAND_H +#define QDESIGNER_PROPERTYCOMMAND_H + +#include "qdesigner_formwindowcommand_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerPropertySheetExtension; +class QDesignerIntegration; + +namespace qdesigner_internal { + +enum SpecialProperty { + SP_None, SP_ObjectName, SP_LayoutName, SP_SpacerName,SP_WindowTitle, + SP_MinimumSize, SP_MaximumSize, SP_Geometry, SP_Icon, SP_CurrentTabName, SP_CurrentItemName, SP_CurrentPageName, + SP_AutoDefault, SP_Alignment, SP_Shortcut, SP_Orientation +}; + +//Determine special property +enum SpecialProperty getSpecialProperty(const QString& propertyName); + +// A helper class for applying properties to objects. +// Can be used for Set commands (setValue(), restoreOldValue()) or +// Reset Commands restoreDefaultValue(), restoreOldValue()). +// +class QDESIGNER_SHARED_EXPORT PropertyHelper { + Q_DISABLE_COPY(PropertyHelper) +public: + // A pair of Value and changed flag + using Value = std::pair; + + enum ObjectType {OT_Object, OT_FreeAction, OT_AssociatedAction, OT_Widget}; + + PropertyHelper(QObject* object, + SpecialProperty specialProperty, + QDesignerPropertySheetExtension *sheet, + int index); + virtual ~PropertyHelper() = default; + + QObject *object() const { return m_object; } + SpecialProperty specialProperty() const { return m_specialProperty; } + // set a new value. Can be overwritten to perform a transformation (see + // handling of Arrow key move in FormWindow class). + virtual Value setValue(QDesignerFormWindowInterface *fw, const QVariant &value, + bool changed, quint64 subPropertyMask); + + // restore old value + Value restoreOldValue(QDesignerFormWindowInterface *fw); + // set default value + Value restoreDefaultValue(QDesignerFormWindowInterface *fw); + + inline QVariant oldValue() const + { return m_oldValue.first; } + + inline void setOldValue(const QVariant &oldValue) + { m_oldValue.first = oldValue; } + + // required updates for this property (bit mask) + enum UpdateMask { UpdatePropertyEditor=1, UpdateObjectInspector=2 }; + unsigned updateMask() const; + + // can be merged into one command (that is, object and name match) + bool canMerge(const PropertyHelper &other) const; + QDesignerIntegration *integration(QDesignerFormWindowInterface *fw) const; + + static void triggerActionChanged(QAction *a); + +private: + // Apply the value and update. Returns corrected value + Value applyValue(QDesignerFormWindowInterface *fw, const QVariant &oldValue, Value newValue); + + static void checkApplyWidgetValue(QDesignerFormWindowInterface *fw, QWidget* w, + SpecialProperty specialProperty, QVariant &v); + + void updateObject(QDesignerFormWindowInterface *fw, const QVariant &oldValue, const QVariant &newValue); + QVariant findDefaultValue(QDesignerFormWindowInterface *fw) const; + void ensureUniqueObjectName(QDesignerFormWindowInterface *fw, QObject *object) const; + SpecialProperty m_specialProperty; + + QPointer m_object; + ObjectType m_objectType; + QPointer m_parentWidget; + + QDesignerPropertySheetExtension *m_propertySheet; + int m_index; + + Value m_oldValue; +}; + +// Base class for commands that can be applied to several widgets + +class QDESIGNER_SHARED_EXPORT PropertyListCommand : public QDesignerFormWindowCommand { +public: + explicit PropertyListCommand(QDesignerFormWindowInterface *formWindow, QUndoCommand *parent = nullptr); + + QObject* object(int index = 0) const; + + QVariant oldValue(int index = 0) const; + + void setOldValue(const QVariant &oldValue, int index = 0); + + // Calls restoreDefaultValue() and update() + void undo() override; + +protected: + using PropertyHelperPtr = std::unique_ptr; + using PropertyHelperList = std::vector; + + // add an object + bool add(QObject *object, const QString &propertyName); + + // Init from a list and make sure referenceObject is added first to obtain the right property group + bool initList(const QObjectList &list, const QString &apropertyName, QObject *referenceObject = nullptr); + + // set a new value, return update mask + unsigned setValue(const QVariant &value, bool changed, quint64 subPropertyMask); + + // restore old value, return update mask + unsigned restoreOldValue(); + // set default value, return update mask + unsigned restoreDefaultValue(); + + // update designer + void update(unsigned updateMask); + + // check if lists are aequivalent for command merging (same widgets and props) + bool canMergeLists(const PropertyHelperList& other) const; + + PropertyHelperList& propertyHelperList() { return m_propertyHelperList; } + const PropertyHelperList& propertyHelperList() const { return m_propertyHelperList; } + + const QString propertyName() const; + SpecialProperty specialProperty() const; + + // Helper struct describing a property used for checking whether + // properties of different widgets are equivalent + struct PropertyDescription { + public: + PropertyDescription() = default; + PropertyDescription(const QString &propertyName, QDesignerPropertySheetExtension *propertySheet, int index); + bool equals(const PropertyDescription &p) const; + void debug() const; + + QString m_propertyName; + QString m_propertyGroup; + int m_propertyType = QMetaType::UnknownType; + SpecialProperty m_specialProperty = SP_None; + }; + const PropertyDescription &propertyDescription() const { return m_propertyDescription; } + +protected: + virtual std::unique_ptr + createPropertyHelper(QObject *o, SpecialProperty sp, + QDesignerPropertySheetExtension *sheet, int sheetIndex) const; + +private: + PropertyDescription m_propertyDescription; + PropertyHelperList m_propertyHelperList; +}; + +class QDESIGNER_SHARED_EXPORT SetPropertyCommand: public PropertyListCommand +{ + +public: + explicit SetPropertyCommand(QDesignerFormWindowInterface *formWindow, QUndoCommand *parent = nullptr); + + bool init(QObject *object, const QString &propertyName, const QVariant &newValue); + bool init(const QObjectList &list, const QString &propertyName, const QVariant &newValue, + QObject *referenceObject = nullptr, bool enableSubPropertyHandling = true); + + + inline QVariant newValue() const + { return m_newValue; } + + inline void setNewValue(const QVariant &newValue) + { m_newValue = newValue; } + + int id() const override; + bool mergeWith(const QUndoCommand *other) override; + + void redo() override; + +protected: + virtual QVariant mergeValue(const QVariant &newValue); + +private: + quint64 subPropertyMask(const QVariant &newValue, QObject *referenceObject); + void setDescription(); + QVariant m_newValue; + quint64 m_subPropertyMask; +}; + +class QDESIGNER_SHARED_EXPORT ResetPropertyCommand: public PropertyListCommand +{ + +public: + explicit ResetPropertyCommand(QDesignerFormWindowInterface *formWindow); + + bool init(QObject *object, const QString &propertyName); + bool init(const QObjectList &list, const QString &propertyName, QObject *referenceObject = nullptr); + + void redo() override; + +protected: + bool mergeWith(const QUndoCommand *) override { return false; } + +private: + void setDescription(); + QString m_propertyName; +}; + + +class QDESIGNER_SHARED_EXPORT AddDynamicPropertyCommand: public QDesignerFormWindowCommand +{ + +public: + explicit AddDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow); + + bool init(const QObjectList &selection, QObject *current, const QString &propertyName, const QVariant &value); + + void redo() override; + void undo() override; +private: + void setDescription(); + QString m_propertyName; + QObjectList m_selection; + QVariant m_value; +}; + +class QDESIGNER_SHARED_EXPORT RemoveDynamicPropertyCommand: public QDesignerFormWindowCommand +{ + +public: + explicit RemoveDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow); + + bool init(const QObjectList &selection, QObject *current, const QString &propertyName); + + void redo() override; + void undo() override; +private: + void setDescription(); + QString m_propertyName; + QHash > m_objectToValueAndChanged; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_PROPERTYCOMMAND_H diff --git a/src/designer/src/lib/shared/qdesigner_propertyeditor.cpp b/src/designer/src/lib/shared/qdesigner_propertyeditor.cpp new file mode 100644 index 0000000..42a04b7 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_propertyeditor.cpp @@ -0,0 +1,155 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_propertyeditor_p.h" +#include "pluginmanager_p.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +using StringPropertyParameters = QDesignerPropertyEditor::StringPropertyParameters; +// A map of property name to type +using PropertyNameTypeMap = QHash; + +// Compile a map of hard-coded string property types +static const PropertyNameTypeMap &stringPropertyTypes() +{ + static PropertyNameTypeMap propertyNameTypeMap; + if (propertyNameTypeMap.isEmpty()) { + const StringPropertyParameters richtext(ValidationRichText, true); + // Accessibility. Both are texts the narrator reads + propertyNameTypeMap.insert(u"accessibleDescription"_s, richtext); + propertyNameTypeMap.insert(u"accessibleName"_s, richtext); + // object names + const StringPropertyParameters objectName(ValidationObjectName, false); + propertyNameTypeMap.insert(u"buddy"_s, objectName); + propertyNameTypeMap.insert(u"currentItemName"_s, objectName); + propertyNameTypeMap.insert(u"currentPageName"_s, objectName); + propertyNameTypeMap.insert(u"currentTabName"_s, objectName); + propertyNameTypeMap.insert(u"layoutName"_s, objectName); + propertyNameTypeMap.insert(u"spacerName"_s, objectName); + // Style sheet + propertyNameTypeMap.insert(u"styleSheet"_s, StringPropertyParameters(ValidationStyleSheet, false)); + // Buttons/ QCommandLinkButton + const StringPropertyParameters multiline(ValidationMultiLine, true); + propertyNameTypeMap.insert(u"description"_s, multiline); + propertyNameTypeMap.insert(u"iconText"_s, multiline); + // Tooltips, etc. + propertyNameTypeMap.insert(u"toolTip"_s, richtext); + propertyNameTypeMap.insert(u"whatsThis"_s, richtext); + propertyNameTypeMap.insert(u"windowIconText"_s, richtext); + propertyNameTypeMap.insert(u"html"_s, richtext); + // A QWizard page id + propertyNameTypeMap.insert(u"pageId"_s, StringPropertyParameters(ValidationSingleLine, false)); + // QPlainTextEdit + propertyNameTypeMap.insert(u"plainText"_s, StringPropertyParameters(ValidationMultiLine, true)); + } + return propertyNameTypeMap; +} + +QDesignerPropertyEditor::QDesignerPropertyEditor(QWidget *parent, Qt::WindowFlags flags) : + QDesignerPropertyEditorInterface(parent, flags) +{ + // Make old signal work for compatibility + connect(this, &QDesignerPropertyEditorInterface::propertyChanged, + this, &QDesignerPropertyEditor::slotPropertyChanged); +} + +static inline bool isDynamicProperty(QDesignerFormEditorInterface *core, QObject *object, + const QString &propertyName) +{ + if (const QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), object)) { + if (dynamicSheet->dynamicPropertiesAllowed()) { + if (QDesignerPropertySheetExtension *propertySheet = qt_extension(core->extensionManager(), object)) { + const int index = propertySheet->indexOf(propertyName); + return index >= 0 && dynamicSheet->isDynamicProperty(index); + } + } + } + return false; +} + +QDesignerPropertyEditor::StringPropertyParameters QDesignerPropertyEditor::textPropertyValidationMode( + QDesignerFormEditorInterface *core, const QObject *object, + const QString &propertyName, bool isMainContainer) +{ + // object name - no comment + if (propertyName == "objectName"_L1) { + const TextPropertyValidationMode vm = isMainContainer ? ValidationObjectNameScope : ValidationObjectName; + return StringPropertyParameters(vm, false); + } + + // Check custom widgets by class. + const QString className = WidgetFactory::classNameOf(core, object); + const QDesignerCustomWidgetData customData = core->pluginManager()->customWidgetData(className); + if (!customData.isNull()) { + StringPropertyParameters customType; + if (customData.xmlStringPropertyType(propertyName, &customType)) + return customType; + } + + if (isDynamicProperty(core, const_cast(object), propertyName)) + return StringPropertyParameters(ValidationMultiLine, true); + + // Check hardcoded property ames + const auto hit = stringPropertyTypes().constFind(propertyName); + if (hit != stringPropertyTypes().constEnd()) + return hit.value(); + + // text: Check according to widget type. + if (propertyName == "text"_L1) { + if (qobject_cast(object) || qobject_cast(object)) + return StringPropertyParameters(ValidationSingleLine, true); + if (qobject_cast(object)) + return StringPropertyParameters(ValidationMultiLine, true); + return StringPropertyParameters(ValidationRichText, true); + } + + // Fuzzy matching + if (propertyName.endsWith("Name"_L1)) + return StringPropertyParameters(ValidationSingleLine, true); + + if (propertyName.endsWith("ToolTip"_L1)) + return StringPropertyParameters(ValidationRichText, true); + +#ifdef Q_OS_WIN // No translation for the active X "control" property + if (propertyName == "control"_L1 && className == "QAxWidget"_L1) + return StringPropertyParameters(ValidationSingleLine, false); +#endif + + // default to single + return StringPropertyParameters(ValidationSingleLine, true); +} + +void QDesignerPropertyEditor::emitPropertyValueChanged(const QString &name, const QVariant &value, bool enableSubPropertyHandling) +{ + // Avoid duplicate signal emission - see below + m_propertyChangedForwardingBlocked = true; + emit propertyValueChanged(name, value, enableSubPropertyHandling); + emit propertyChanged(name, value); + m_propertyChangedForwardingBlocked = false; +} + +void QDesignerPropertyEditor::slotPropertyChanged(const QString &name, const QVariant &value) +{ + // Forward signal from Integration using the old interfaces. + if (!m_propertyChangedForwardingBlocked) + emit propertyValueChanged(name, value, true); +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_propertyeditor_p.h b/src/designer/src/lib/shared/qdesigner_propertyeditor_p.h new file mode 100644 index 0000000..0aa1feb --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_propertyeditor_p.h @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + + +#ifndef DESIGNERPROPERTYEDITOR_H +#define DESIGNERPROPERTYEDITOR_H + +#include "shared_global_p.h" +#include "shared_enums_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Extends the QDesignerPropertyEditorInterface by property comment handling and +// a signal for resetproperty. + +class QDESIGNER_SHARED_EXPORT QDesignerPropertyEditor: public QDesignerPropertyEditorInterface +{ + Q_OBJECT +public: + explicit QDesignerPropertyEditor(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + + // A pair . + using StringPropertyParameters = std::pair; + + // Return a pair of validation mode and flag indicating whether property is translatable + // for textual properties. + static StringPropertyParameters textPropertyValidationMode(QDesignerFormEditorInterface *core, + const QObject *object, const QString &propertyName, bool isMainContainer); + +Q_SIGNALS: + void propertyValueChanged(const QString &name, const QVariant &value, bool enableSubPropertyHandling); + void resetProperty(const QString &name); + void addDynamicProperty(const QString &name, const QVariant &value); + void removeDynamicProperty(const QString &name); + void editorOpened(); + void editorClosed(); + +public Q_SLOTS: + /* Quick update that assumes the actual count of properties has not changed + * (as opposed to setObject()). N/A when for example executing a + * layout command and margin properties appear. */ + virtual void updatePropertySheet() = 0; + virtual void reloadResourceProperties() = 0; + +private Q_SLOTS: + void slotPropertyChanged(const QString &name, const QVariant &value); + +protected: + void emitPropertyValueChanged(const QString &name, const QVariant &value, bool enableSubPropertyHandling); + +private: + bool m_propertyChangedForwardingBlocked = false; + +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DESIGNERPROPERTYEDITOR_H diff --git a/src/designer/src/lib/shared/qdesigner_propertysheet.cpp b/src/designer/src/lib/shared/qdesigner_propertysheet.cpp new file mode 100644 index 0000000..3173a1f --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_propertysheet.cpp @@ -0,0 +1,1721 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_propertysheet_p.h" +#include "qdesigner_utils_p.h" +#include "formwindowbase_p.h" +#include "layoutinfo_p.h" +#include "qlayout_widget_p.h" +#include "qdesigner_introspection_p.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static const QDesignerMetaObjectInterface *propertyIntroducedBy(const QDesignerMetaObjectInterface *meta, int index) +{ + if (index >= meta->propertyOffset()) + return meta; + + if (meta->superClass()) + return propertyIntroducedBy(meta->superClass(), index); + + return nullptr; +} + +// Layout fake properties (prefixed by 'layout' to distinguish them from other 'margins' +// that might be around. These are forwarded to the layout sheet (after name transformation). +// +// 'layoutObjectName' is new for 4.4. It is the name of the actual layout. +// Up to 4.3, QLayoutWidget's name was displayed in the objectinspector. +// This changes with 4.4; the layout name is displayed. This means that for +// old forms, QLayoutWidget will show up as ''; however, the uic code will +// still use 'verticalLayout' (in case someone accesses it). New Layouts get autogenerated names, +// legacy forms will keep their empty names (unless someone types in a new name). +static constexpr auto layoutObjectNameC = "layoutName"_L1; +static constexpr auto layoutLeftMarginC = "layoutLeftMargin"_L1; +static constexpr auto layoutTopMarginC = "layoutTopMargin"_L1; +static constexpr auto layoutRightMarginC = "layoutRightMargin"_L1; +static constexpr auto layoutBottomMarginC = "layoutBottomMargin"_L1; +static constexpr auto layoutSpacingC = "layoutSpacing"_L1; +static constexpr auto layoutHorizontalSpacingC = "layoutHorizontalSpacing"_L1; +static constexpr auto layoutVerticalSpacingC = "layoutVerticalSpacing"_L1; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +static constexpr auto layoutSizeConstraintC = "layoutSizeConstraint"_L1; +#else +static constexpr auto layoutHorizontalSizeConstraintC = "horizontalSizeConstraint"_L1; +static constexpr auto layoutVerticalSizeConstraintC = "verticalSizeConstraint"_L1; +#endif +// form layout +static constexpr auto layoutFieldGrowthPolicyC = "layoutFieldGrowthPolicy"_L1; +static constexpr auto layoutRowWrapPolicyC = "layoutRowWrapPolicy"_L1; +static constexpr auto layoutLabelAlignmentC = "layoutLabelAlignment"_L1; +static constexpr auto layoutFormAlignmentC = "layoutFormAlignment"_L1; +// stretches +static constexpr auto layoutboxStretchPropertyC = "layoutStretch"_L1; +static constexpr auto layoutGridRowStretchPropertyC = "layoutRowStretch"_L1; +static constexpr auto layoutGridColumnStretchPropertyC = "layoutColumnStretch"_L1; +static constexpr auto layoutGridRowMinimumHeightC = "layoutRowMinimumHeight"_L1; +static constexpr auto layoutGridColumnMinimumWidthC = "layoutColumnMinimumWidth"_L1; + +static bool hasLayoutAttributes(QDesignerFormEditorInterface *core, QObject *object) +{ + if (!object->isWidgetType()) + return false; + + QWidget *w = qobject_cast(object); + if (const QDesignerWidgetDataBaseInterface *db = core->widgetDataBase()) { + if (db->isContainer(w)) + return true; + } + return false; +} + +// Cache DesignerMetaEnum by scope/name of a QMetaEnum +static const qdesigner_internal::DesignerMetaEnum &designerMetaEnumFor(const QDesignerMetaEnumInterface *me) +{ + using ScopeNameKey = std::pair; + static QMap cache; + + const QString name = me->name(); + const QString scope = me->scope(); + + const ScopeNameKey key = ScopeNameKey(scope, name); + auto it = cache.find(key); + if (it == cache.end()) { + qdesigner_internal::DesignerMetaEnum dme = qdesigner_internal::DesignerMetaEnum(name, scope, me->separator()); + const int keyCount = me->keyCount(); + for (int i=0; i < keyCount; ++i) + dme.addKey(me->value(i), me->key(i)); + it = cache.insert(key, dme); + } + return it.value(); +} + +// Cache DesignerMetaFlags by scope/name of a QMetaEnum +static const qdesigner_internal::DesignerMetaFlags &designerMetaFlagsFor(const QDesignerMetaEnumInterface *me) +{ + using ScopeNameKey = std::pair; + static QMap cache; + + const QString name = me->name(); + const QString scope = me->scope(); + + const ScopeNameKey key = ScopeNameKey(scope, name); + auto it = cache.find(key); + if (it == cache.end()) { + auto dme = qdesigner_internal::DesignerMetaFlags(me->enumName(), scope, me->separator()); + const int keyCount = me->keyCount(); + for (int i=0; i < keyCount; ++i) + dme.addKey(me->value(i), me->key(i)); + it = cache.insert(key, dme); + } + return it.value(); +} + +// ------------ QDesignerMemberSheetPrivate +class QDesignerPropertySheetPrivate { +public: + using PropertyType = QDesignerPropertySheet::PropertyType; + using ObjectType = QDesignerPropertySheet::ObjectType; + using ObjectFlags = QDesignerPropertySheet::ObjectFlags; + + explicit QDesignerPropertySheetPrivate(QDesignerPropertySheet *sheetPublic, QObject *object, QObject *sheetParent); + + bool invalidIndex(const char *functionName, int index) const; + inline int count() const { return m_meta->propertyCount() + m_addProperties.size(); } + + PropertyType propertyType(int index) const; + QString transformLayoutPropertyName(int index) const; + QLayout* layout(QDesignerPropertySheetExtension **layoutPropertySheet = nullptr) const; + static ObjectType objectType(const QObject *o); + + bool isReloadableProperty(int index) const; + bool isResourceProperty(int index) const; + void addResourceProperty(int index, int type); + QVariant resourceProperty(int index) const; + void setResourceProperty(int index, const QVariant &value); + QVariant emptyResourceProperty(int index) const; // of type PropertySheetPixmapValue / PropertySheetIconValue + QVariant defaultResourceProperty(int index) const; // of type QPixmap / QIcon (maybe it can be generalized for all types, not resource only) + + bool isStringProperty(int index) const; + void addStringProperty(int index); + qdesigner_internal::PropertySheetStringValue stringProperty(int index) const; + void setStringProperty(int index, const qdesigner_internal::PropertySheetStringValue &value); + bool isStringListProperty(int index) const; + void addStringListProperty(int index); + qdesigner_internal::PropertySheetStringListValue stringListProperty(int index) const; + void setStringListProperty(int index, const qdesigner_internal::PropertySheetStringListValue &value); + + bool isKeySequenceProperty(int index) const; + void addKeySequenceProperty(int index); + qdesigner_internal::PropertySheetKeySequenceValue keySequenceProperty(int index) const; + void setKeySequenceProperty(int index, const qdesigner_internal::PropertySheetKeySequenceValue &value); + + enum PropertyKind { NormalProperty, FakeProperty, DynamicProperty, DefaultDynamicProperty }; + class Info { + public: + Info() = default; + + QString group; + QVariant defaultValue; + bool changed = false; + bool visible = true; + bool attribute = false; + bool reset = true; + PropertyType propertyType = QDesignerPropertySheet::PropertyNone; + PropertyKind kind = NormalProperty; + }; + + Info &ensureInfo(int index); + + QDesignerPropertySheet *q; + QDesignerFormEditorInterface *m_core; + const QDesignerMetaObjectInterface *m_meta; + const ObjectType m_objectType; + const ObjectFlags m_objectFlags; + + QHash m_info; + QHash m_fakeProperties; + QHash m_addProperties; + QHash m_addIndex; + QHash m_resourceProperties; // only PropertySheetPixmapValue snd PropertySheetIconValue here + QHash m_stringProperties; // only PropertySheetStringValue + QHash m_stringListProperties; // only PropertySheetStringListValue + QHash m_keySequenceProperties; // only PropertySheetKeySequenceValue + + const bool m_canHaveLayoutAttributes; + + // Variables used for caching the layout, access via layout(). + QPointer m_object; + mutable QPointer m_lastLayout; + mutable QDesignerPropertySheetExtension *m_lastLayoutPropertySheet; + mutable bool m_LastLayoutByDesigner; + + qdesigner_internal::DesignerPixmapCache *m_pixmapCache; + qdesigner_internal::DesignerIconCache *m_iconCache; + QPointer m_fwb; + + // Enable Qt's internal properties starting with prefix "_q_" + static bool m_internalDynamicPropertiesEnabled; +}; + +bool QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled = false; + +/* + The property is reloadable if its contents depends on resource. +*/ +bool QDesignerPropertySheetPrivate::isReloadableProperty(int index) const +{ + return isResourceProperty(index) + || propertyType(index) == QDesignerPropertySheet::PropertyStyleSheet + || propertyType(index) == QDesignerPropertySheet::PropertyText + || q->property(index).metaType().id() == QMetaType::QUrl; +} + +/* + Resource properties are those which: + 1) are reloadable + 2) their state is associated with a file which can be taken from resources + 3) we don't store them in Qt meta object system (because designer keeps different data structure for them) +*/ + +bool QDesignerPropertySheetPrivate::isResourceProperty(int index) const +{ + return m_resourceProperties.contains(index); +} + +void QDesignerPropertySheetPrivate::addResourceProperty(int index, int type) +{ + if (type == QMetaType::QPixmap) + m_resourceProperties.insert(index, QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue())); + else if (type == QMetaType::QIcon) + m_resourceProperties.insert(index, QVariant::fromValue(qdesigner_internal::PropertySheetIconValue())); +} + +QVariant QDesignerPropertySheetPrivate::emptyResourceProperty(int index) const +{ + QVariant v = m_resourceProperties.value(index); + if (v.canConvert()) + return QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue()); + if (v.canConvert()) + return QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()); + return v; +} + +QVariant QDesignerPropertySheetPrivate::defaultResourceProperty(int index) const +{ + return m_info.value(index).defaultValue; +} + +QVariant QDesignerPropertySheetPrivate::resourceProperty(int index) const +{ + return m_resourceProperties.value(index); +} + +void QDesignerPropertySheetPrivate::setResourceProperty(int index, const QVariant &value) +{ + Q_ASSERT(isResourceProperty(index)); + + QVariant &v = m_resourceProperties[index]; + if ((value.canConvert() && v.canConvert()) + || (value.canConvert() && v.canConvert())) + v = value; +} + +bool QDesignerPropertySheetPrivate::isStringProperty(int index) const +{ + return m_stringProperties.contains(index); +} + +void QDesignerPropertySheetPrivate::addStringProperty(int index) +{ + m_stringProperties.insert(index, qdesigner_internal::PropertySheetStringValue()); +} + +qdesigner_internal::PropertySheetStringValue QDesignerPropertySheetPrivate::stringProperty(int index) const +{ + return m_stringProperties.value(index); +} + +void QDesignerPropertySheetPrivate::setStringProperty(int index, const qdesigner_internal::PropertySheetStringValue &value) +{ + Q_ASSERT(isStringProperty(index)); + + m_stringProperties[index] = value; +} + +bool QDesignerPropertySheetPrivate::isStringListProperty(int index) const +{ + return m_stringListProperties.contains(index); +} + +void QDesignerPropertySheetPrivate::addStringListProperty(int index) +{ + m_stringListProperties.insert(index, qdesigner_internal::PropertySheetStringListValue()); +} + +qdesigner_internal::PropertySheetStringListValue QDesignerPropertySheetPrivate::stringListProperty(int index) const +{ + return m_stringListProperties.value(index); +} + +void QDesignerPropertySheetPrivate::setStringListProperty(int index, const qdesigner_internal::PropertySheetStringListValue &value) +{ + Q_ASSERT(isStringListProperty(index)); + + m_stringListProperties[index] = value; +} + +bool QDesignerPropertySheetPrivate::isKeySequenceProperty(int index) const +{ + return m_keySequenceProperties.contains(index); +} + +void QDesignerPropertySheetPrivate::addKeySequenceProperty(int index) +{ + m_keySequenceProperties.insert(index, qdesigner_internal::PropertySheetKeySequenceValue()); +} + +qdesigner_internal::PropertySheetKeySequenceValue QDesignerPropertySheetPrivate::keySequenceProperty(int index) const +{ + return m_keySequenceProperties.value(index); +} + +void QDesignerPropertySheetPrivate::setKeySequenceProperty(int index, const qdesigner_internal::PropertySheetKeySequenceValue &value) +{ + Q_ASSERT(isKeySequenceProperty(index)); + + m_keySequenceProperties[index] = value; +} + +QDesignerPropertySheetPrivate::QDesignerPropertySheetPrivate(QDesignerPropertySheet *sheetPublic, QObject *object, QObject *sheetParent) : + q(sheetPublic), + m_core(QDesignerPropertySheet::formEditorForObject(sheetParent)), + m_meta(m_core->introspection()->metaObject(object)), + m_objectType(QDesignerPropertySheet::objectTypeFromObject(object)), + m_objectFlags(QDesignerPropertySheet::objectFlagsFromObject(object)), + m_canHaveLayoutAttributes(hasLayoutAttributes(m_core, object)), + m_object(object), + m_lastLayout(nullptr), + m_lastLayoutPropertySheet(nullptr), + m_LastLayoutByDesigner(false), + m_pixmapCache(nullptr), + m_iconCache(nullptr) +{ +} + +qdesigner_internal::FormWindowBase *QDesignerPropertySheet::formWindowBase() const +{ + return d->m_fwb; +} + +bool QDesignerPropertySheetPrivate::invalidIndex(const char *functionName, int index) const +{ + if (index < 0 || index >= count()) { + qWarning() << "** WARNING " << functionName << " invoked for " << m_object->objectName() << " was passed an invalid index " << index << '.'; + return true; + } + return false; +} + +QLayout* QDesignerPropertySheetPrivate::layout(QDesignerPropertySheetExtension **layoutPropertySheet) const +{ + // Return the layout and its property sheet + // only if it is managed by designer and not one created on a custom widget. + // (attempt to cache the value as this requires some hoops). + if (layoutPropertySheet) + *layoutPropertySheet = nullptr; + + if (!m_object->isWidgetType() || !m_canHaveLayoutAttributes) + return nullptr; + + QWidget *widget = qobject_cast(m_object); + QLayout *widgetLayout = qdesigner_internal::LayoutInfo::internalLayout(widget); + if (!widgetLayout) { + m_lastLayout = nullptr; + m_lastLayoutPropertySheet = nullptr; + return nullptr; + } + // Smart logic to avoid retrieving the meta DB from the widget every time. + if (widgetLayout != m_lastLayout) { + m_lastLayout = widgetLayout; + m_LastLayoutByDesigner = false; + m_lastLayoutPropertySheet = nullptr; + // Is this a layout managed by designer or some layout on a custom widget? + if (qdesigner_internal::LayoutInfo::managedLayout(m_core ,widgetLayout)) { + m_LastLayoutByDesigner = true; + m_lastLayoutPropertySheet = qt_extension(m_core->extensionManager(), m_lastLayout); + } + } + if (!m_LastLayoutByDesigner) + return nullptr; + + if (layoutPropertySheet) + *layoutPropertySheet = m_lastLayoutPropertySheet; + + return m_lastLayout; +} + +QDesignerPropertySheetPrivate::Info &QDesignerPropertySheetPrivate::ensureInfo(int index) +{ + auto it = m_info.find(index); + if (it == m_info.end()) + it = m_info.insert(index, Info()); + return it.value(); +} + +QDesignerPropertySheet::PropertyType QDesignerPropertySheetPrivate::propertyType(int index) const +{ + const auto it = m_info.constFind(index); + if (it == m_info.constEnd()) + return QDesignerPropertySheet::PropertyNone; + return it.value().propertyType; +} + +QString QDesignerPropertySheetPrivate::transformLayoutPropertyName(int index) const +{ + using TypeNameMap = QMap; + static const TypeNameMap typeNameMap = { + {QDesignerPropertySheet::PropertyLayoutObjectName, u"objectName"_s}, + {QDesignerPropertySheet::PropertyLayoutLeftMargin, u"leftMargin"_s}, + {QDesignerPropertySheet::PropertyLayoutTopMargin, u"topMargin"_s}, + {QDesignerPropertySheet::PropertyLayoutRightMargin, u"rightMargin"_s}, + {QDesignerPropertySheet::PropertyLayoutBottomMargin, u"bottomMargin"_s}, + {QDesignerPropertySheet::PropertyLayoutSpacing, u"spacing"_s}, + {QDesignerPropertySheet::PropertyLayoutHorizontalSpacing, u"horizontalSpacing"_s}, + {QDesignerPropertySheet::PropertyLayoutVerticalSpacing, u"verticalSpacing"_s}, +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + {QDesignerPropertySheet::PropertyLayoutSizeConstraint, u"sizeConstraint"_s}, +#else + {QDesignerPropertySheet::PropertyLayoutHorizontalSizeConstraint, layoutHorizontalSizeConstraintC}, + {QDesignerPropertySheet::PropertyLayoutVerticalSizeConstraint, layoutVerticalSizeConstraintC}, +#endif + {QDesignerPropertySheet::PropertyLayoutFieldGrowthPolicy, u"fieldGrowthPolicy"_s}, + {QDesignerPropertySheet::PropertyLayoutRowWrapPolicy, u"rowWrapPolicy"_s}, + {QDesignerPropertySheet::PropertyLayoutLabelAlignment, u"labelAlignment"_s}, + {QDesignerPropertySheet::PropertyLayoutFormAlignment, u"formAlignment"_s}, + {QDesignerPropertySheet::PropertyLayoutBoxStretch, u"stretch"_s}, + {QDesignerPropertySheet::PropertyLayoutGridRowStretch, u"rowStretch"_s}, + {QDesignerPropertySheet::PropertyLayoutGridColumnStretch, u"columnStretch"_s}, + {QDesignerPropertySheet::PropertyLayoutGridRowMinimumHeight, u"rowMinimumHeight"_s}, + {QDesignerPropertySheet::PropertyLayoutGridColumnMinimumWidth, u"columnMinimumWidth"_s} + }; + const auto it = typeNameMap.constFind(propertyType(index)); + if (it != typeNameMap.constEnd()) + return it.value(); + return QString(); +} + +// ----------- QDesignerPropertySheet + +QDesignerPropertySheet::ObjectType QDesignerPropertySheet::objectTypeFromObject(const QObject *o) +{ + if (qobject_cast(o)) + return ObjectLayout; + + if (!o->isWidgetType()) + return ObjectNone; + + if (qobject_cast(o)) + return ObjectLayoutWidget; + + if (qobject_cast(o)) + return ObjectLabel; + + return ObjectNone; +} + +QDesignerPropertySheet::ObjectFlags QDesignerPropertySheet::objectFlagsFromObject(const QObject *o) +{ + ObjectFlags result; + if ((o->isWidgetType() && (qobject_cast(o) + || qobject_cast(o))) + || qobject_cast(o)) { + result |= CheckableProperty; + } + return result; +} + +QDesignerPropertySheet::PropertyType QDesignerPropertySheet::propertyTypeFromName(const QString &name) +{ + static const QHash propertyTypeHash = { + {layoutObjectNameC, PropertyLayoutObjectName}, + {layoutLeftMarginC, PropertyLayoutLeftMargin}, + {layoutTopMarginC, PropertyLayoutTopMargin}, + {layoutRightMarginC, PropertyLayoutRightMargin}, + {layoutBottomMarginC, PropertyLayoutBottomMargin}, + {layoutSpacingC, PropertyLayoutSpacing}, + {layoutHorizontalSpacingC, PropertyLayoutHorizontalSpacing}, + {layoutVerticalSpacingC, PropertyLayoutVerticalSpacing}, +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + {layoutSizeConstraintC, PropertyLayoutSizeConstraint}, +#else + {layoutHorizontalSizeConstraintC, PropertyLayoutHorizontalSizeConstraint}, + {layoutVerticalSizeConstraintC, PropertyLayoutVerticalSizeConstraint}, +#endif + {layoutFieldGrowthPolicyC, PropertyLayoutFieldGrowthPolicy}, + {layoutRowWrapPolicyC, PropertyLayoutRowWrapPolicy}, + {layoutLabelAlignmentC, PropertyLayoutLabelAlignment}, + {layoutFormAlignmentC, PropertyLayoutFormAlignment}, + {layoutboxStretchPropertyC, PropertyLayoutBoxStretch}, + {layoutGridRowStretchPropertyC, PropertyLayoutGridRowStretch}, + {layoutGridColumnStretchPropertyC, PropertyLayoutGridColumnStretch}, + {layoutGridRowMinimumHeightC, PropertyLayoutGridRowMinimumHeight}, + {layoutGridColumnMinimumWidthC, PropertyLayoutGridColumnMinimumWidth}, + {u"buddy"_s, PropertyBuddy}, + {u"geometry"_s, PropertyGeometry}, + {u"checked"_s, PropertyChecked}, + {u"checkable"_s, PropertyCheckable}, + {u"accessibleName"_s, PropertyAccessibility}, + {u"accessibleDescription"_s, PropertyAccessibility}, + {u"visible"_s, PropertyVisible}, + {u"windowTitle"_s, PropertyWindowTitle}, + {u"windowIcon"_s, PropertyWindowIcon}, + {u"windowFilePath"_s, PropertyWindowFilePath}, + {u"windowOpacity"_s, PropertyWindowOpacity}, + {u"windowIconText"_s, PropertyWindowIconText}, + {u"windowModality"_s, PropertyWindowModality}, + {u"windowModified"_s, PropertyWindowModified}, + {u"styleSheet"_s, PropertyStyleSheet}, + {u"text"_s, PropertyText} + }; + return propertyTypeHash.value(name, PropertyNone); +} + +QDesignerPropertySheet::QDesignerPropertySheet(QObject *object, QObject *parent) : + QObject(parent), + d(new QDesignerPropertySheetPrivate(this, object, parent)) +{ + using Info = QDesignerPropertySheetPrivate::Info; + const QDesignerMetaObjectInterface *baseMeta = d->m_meta; + + while (baseMeta &&baseMeta->className().startsWith("QDesigner"_L1)) { + baseMeta = baseMeta->superClass(); + } + Q_ASSERT(baseMeta != nullptr); + + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(d->m_object); + d->m_fwb = qobject_cast(formWindow); + if (d->m_fwb) { + d->m_pixmapCache = d->m_fwb->pixmapCache(); + d->m_iconCache = d->m_fwb->iconCache(); + d->m_fwb->addReloadablePropertySheet(this, object); + } + + for (int index=0; indexm_meta->property(index); + const QString name = p->name(); + if (p->type() == QMetaType::QKeySequence) { + createFakeProperty(name); + } else { + setVisible(index, false); // use the default for `real' properties + } + + QString pgroup = baseMeta->className(); + + if (const QDesignerMetaObjectInterface *pmeta = propertyIntroducedBy(baseMeta, index)) { + pgroup = pmeta->className(); + } + + Info &info = d->ensureInfo(index); + info.group = pgroup; + info.propertyType = propertyTypeFromName(name); + + const int type = p->type(); + switch (type) { + case QMetaType::QCursor: + case QMetaType::QIcon: + case QMetaType::QPixmap: + info.defaultValue = p->read(d->m_object); + if (type == QMetaType::QIcon || type == QMetaType::QPixmap) + d->addResourceProperty(index, type); + break; + case QMetaType::QString: + d->addStringProperty(index); + break; + case QMetaType::QStringList: + d->addStringListProperty(index); + break; + case QMetaType::QKeySequence: + d->addKeySequenceProperty(index); + break; + default: + break; + } + } + + if (object->isWidgetType()) { + createFakeProperty(u"focusPolicy"_s); + createFakeProperty(u"cursor"_s); + createFakeProperty(u"toolTip"_s); + createFakeProperty(u"whatsThis"_s); + createFakeProperty(u"acceptDrops"_s); + createFakeProperty(u"dragEnabled"_s); + // windowModality/Opacity is visible only for the main container, in which case the form windows enables it on loading + setVisible(createFakeProperty(u"windowModality"_s), false); + setVisible(createFakeProperty(u"windowOpacity"_s, double(1.0)), false); + if (qobject_cast(d->m_object)) { // prevent toolbars from being dragged off + createFakeProperty(u"floatable"_s, QVariant(true)); + } else { + if (qobject_cast(d->m_object)) { + // Keep the menu bar editable in the form even if a native menu bar is used. + const bool nativeMenuBarDefault = + !QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar); + createFakeProperty(u"nativeMenuBar"_s, QVariant(nativeMenuBarDefault)); + } + } + if (d->m_canHaveLayoutAttributes) { + const QString layoutGroup = u"Layout"_s; + static constexpr QLatin1StringView fakeLayoutProperties[] = { + layoutObjectNameC, layoutLeftMarginC, layoutTopMarginC, layoutRightMarginC, layoutBottomMarginC, layoutSpacingC, layoutHorizontalSpacingC, layoutVerticalSpacingC, + layoutFieldGrowthPolicyC, layoutRowWrapPolicyC, layoutLabelAlignmentC, layoutFormAlignmentC, + layoutboxStretchPropertyC, layoutGridRowStretchPropertyC, layoutGridColumnStretchPropertyC, + layoutGridRowMinimumHeightC, layoutGridColumnMinimumWidthC, +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + layoutSizeConstraintC +#else + layoutHorizontalSizeConstraintC, layoutVerticalSizeConstraintC +#endif + }; + static constexpr int fakeLayoutPropertyCount = sizeof(fakeLayoutProperties)/sizeof(fakeLayoutProperties[0]); + const int size = count(); + for (int i = 0; i < fakeLayoutPropertyCount; i++) { + createFakeProperty(fakeLayoutProperties[i], 0); + setAttribute(size + i, true); + setPropertyGroup(size + i, layoutGroup); + } + } + + if (d->m_objectType == ObjectLabel) + createFakeProperty(u"buddy"_s, QVariant(QByteArray())); + /* We need to create a fake property since the property does not work + * for non-toplevel windows or on other systems than Mac and only if + * it is above a certain Mac OS version. */ + if (qobject_cast(d->m_object)) + createFakeProperty(u"unifiedTitleAndToolBarOnMac"_s, false); + } + + if (qobject_cast(object)) { + createFakeProperty(u"modal"_s); + } + if (qobject_cast(object)) { + createFakeProperty(u"floating"_s); + } + + const QByteArrayList names = object->dynamicPropertyNames(); + for (const auto &nameB : names) { + const QString name = QString::fromLatin1(nameB); + const int idx = addDynamicProperty(name, object->property(nameB.constData())); + if (idx != -1) + d->ensureInfo(idx).kind = QDesignerPropertySheetPrivate::DefaultDynamicProperty; + } +} + +QDesignerPropertySheet::~QDesignerPropertySheet() +{ + delete d; +} + +QObject *QDesignerPropertySheet::object() const +{ + return d->m_object; +} + +bool QDesignerPropertySheet::dynamicPropertiesAllowed() const +{ + return true; +} + +bool QDesignerPropertySheet::canAddDynamicProperty(const QString &propName) const +{ + // used internally + if (propName == "database"_L1 || propName == "buttonGroupId"_L1) + return false; + const int index = d->m_meta->indexOfProperty(propName); + if (index != -1) + return false; // property already exists and is not a dynamic one + if (d->m_addIndex.contains(propName)) { + const int idx = d->m_addIndex.value(propName); + return !isVisible(idx); // dynamic property already exists + } + return QDesignerPropertySheet::internalDynamicPropertiesEnabled() + || !propName.startsWith("_q_"_L1); +} + +int QDesignerPropertySheet::addDynamicProperty(const QString &propName, const QVariant &value) +{ + using Info = QDesignerPropertySheetPrivate::Info; + if (!value.isValid()) + return -1; // property has invalid type + if (!canAddDynamicProperty(propName)) + return -1; + + QVariant v = value; + switch (value.metaType().id()) { + case QMetaType::QIcon: + v = QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()); + break; + case QMetaType::QPixmap: + v = QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue()); + break; + case QMetaType::QString: + v = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue(value.toString())); + break; + case QMetaType::QStringList: + v = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue(value.toStringList())); + break; + case QMetaType::QKeySequence: { + const QKeySequence keySequence = qvariant_cast(value); + v = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue(keySequence)); + } + break; + } + + if (d->m_addIndex.contains(propName)) { + const int idx = d->m_addIndex.value(propName); + // have to be invisible, this was checked in canAddDynamicProperty() method + setVisible(idx, true); + d->m_addProperties.insert(idx, v); + setChanged(idx, false); + const int index = d->m_meta->indexOfProperty(propName); + Info &info = d->ensureInfo(index); + info.defaultValue = value; + info.kind = QDesignerPropertySheetPrivate::DynamicProperty; + switch (value.metaType().id()) { + case QMetaType::QIcon: + case QMetaType::QPixmap: + d->addResourceProperty(idx, value.metaType().id()); + break; + case QMetaType::QString: + d->addStringProperty(idx); + break; + case QMetaType::QKeySequence: + d->addKeySequenceProperty(idx); + break; + } + return idx; + } + + const int index = count(); + d->m_addIndex.insert(propName, index); + d->m_addProperties.insert(index, v); + Info &info = d->ensureInfo(index); + info.visible = true; + info.changed = false; + info.defaultValue = value; + info.kind = QDesignerPropertySheetPrivate::DynamicProperty; + setPropertyGroup(index, tr("Dynamic Properties")); + switch (value.metaType().id()) { + case QMetaType::QIcon: + case QMetaType::QPixmap: + d->addResourceProperty(index, value.metaType().id()); + break; + case QMetaType::QString: + d->addStringProperty(index); + break; + case QMetaType::QStringList: + d->addStringListProperty(index); + break; + case QMetaType::QKeySequence: + d->addKeySequenceProperty(index); + break; + default: + break; + } + return index; +} + +bool QDesignerPropertySheet::removeDynamicProperty(int index) +{ + if (!d->m_addIndex.contains(propertyName(index))) + return false; + + setVisible(index, false); + return true; +} + +bool QDesignerPropertySheet::isDynamic(int index) const +{ + if (!d->m_addProperties.contains(index)) + return false; + + switch (propertyType(index)) { + case PropertyBuddy: + if (d->m_objectType == ObjectLabel) + return false; + break; + case PropertyLayoutLeftMargin: + case PropertyLayoutTopMargin: + case PropertyLayoutRightMargin: + case PropertyLayoutBottomMargin: + case PropertyLayoutSpacing: + case PropertyLayoutHorizontalSpacing: + case PropertyLayoutVerticalSpacing: + case PropertyLayoutObjectName: +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + case PropertyLayoutSizeConstraint: +#else + case PropertyLayoutHorizontalSizeConstraint: + case PropertyLayoutVerticalSizeConstraint: +#endif + case PropertyLayoutFieldGrowthPolicy: + case PropertyLayoutRowWrapPolicy: + case PropertyLayoutLabelAlignment: + case PropertyLayoutFormAlignment: + case PropertyLayoutBoxStretch: + case PropertyLayoutGridRowStretch: + case PropertyLayoutGridColumnStretch: + case PropertyLayoutGridRowMinimumHeight: + case PropertyLayoutGridColumnMinimumWidth: + if (d->m_object->isWidgetType() && d->m_canHaveLayoutAttributes) + return false; + break; + default: + break; + } + return true; +} + +bool QDesignerPropertySheet::isDynamicProperty(int index) const +{ + // Do not complain here, as an invalid index might be encountered + // if someone implements a property sheet only, omitting the dynamic sheet. + if (index < 0 || index >= count()) + return false; + return d->m_info.value(index).kind == QDesignerPropertySheetPrivate::DynamicProperty; +} + +bool QDesignerPropertySheet::isDefaultDynamicProperty(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + return d->m_info.value(index).kind == QDesignerPropertySheetPrivate::DefaultDynamicProperty; +} + +bool QDesignerPropertySheet::isResourceProperty(int index) const +{ + return d->isResourceProperty(index); +} + +QVariant QDesignerPropertySheet::defaultResourceProperty(int index) const +{ + return d->defaultResourceProperty(index); +} + +qdesigner_internal::DesignerPixmapCache *QDesignerPropertySheet::pixmapCache() const +{ + return d->m_pixmapCache; +} + +void QDesignerPropertySheet::setPixmapCache(qdesigner_internal::DesignerPixmapCache *cache) +{ + d->m_pixmapCache = cache; +} + +qdesigner_internal::DesignerIconCache *QDesignerPropertySheet::iconCache() const +{ + return d->m_iconCache; +} + +void QDesignerPropertySheet::setIconCache(qdesigner_internal::DesignerIconCache *cache) +{ + d->m_iconCache = cache; +} + +int QDesignerPropertySheet::createFakeProperty(const QString &propertyName, const QVariant &value) +{ + using Info = QDesignerPropertySheetPrivate::Info; + // fake properties + const int index = d->m_meta->indexOfProperty(propertyName); + if (index != -1) { + if (!(d->m_meta->property(index)->attributes() & QDesignerMetaPropertyInterface::DesignableAttribute)) + return -1; + Info &info = d->ensureInfo(index); + info.visible = false; + info.kind = QDesignerPropertySheetPrivate::FakeProperty; + QVariant v = value.isValid() ? value : metaProperty(index); + switch (v.metaType().id()) { + case QMetaType::QString: + v = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + break; + case QMetaType::QStringList: + v = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue()); + break; + case QMetaType::QKeySequence: + v = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue()); + break; + } + d->m_fakeProperties.insert(index, v); + return index; + } + if (!value.isValid()) + return -1; + + const int newIndex = count(); + d->m_addIndex.insert(propertyName, newIndex); + d->m_addProperties.insert(newIndex, value); + Info &info = d->ensureInfo(newIndex); + info.propertyType = propertyTypeFromName(propertyName); + info.kind = QDesignerPropertySheetPrivate::FakeProperty; + return newIndex; +} + +bool QDesignerPropertySheet::isAdditionalProperty(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + return d->m_addProperties.contains(index); +} + +bool QDesignerPropertySheet::isFakeProperty(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + // additional properties must be fake + return (d->m_fakeProperties.contains(index) || isAdditionalProperty(index)); +} + +int QDesignerPropertySheet::count() const +{ + return d->count(); +} + +int QDesignerPropertySheet::indexOf(const QString &name) const +{ + int index = d->m_meta->indexOfProperty(name); + + if (index == -1) + index = d->m_addIndex.value(name, -1); + + return index; +} + +QDesignerPropertySheet::PropertyType QDesignerPropertySheet::propertyType(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return PropertyNone; + return d->propertyType(index); +} + +QDesignerPropertySheet::ObjectType QDesignerPropertySheet::objectType() const +{ + return d->m_objectType; +} + +QString QDesignerPropertySheet::propertyName(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return QString(); + if (isAdditionalProperty(index)) + return d->m_addIndex.key(index); + + return d->m_meta->property(index)->name(); +} + +QString QDesignerPropertySheet::propertyGroup(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return QString(); + const QString g = d->m_info.value(index).group; + + if (!g.isEmpty()) + return g; + + if (propertyType(index) == PropertyAccessibility) + return u"Accessibility"_s; + + if (isAdditionalProperty(index)) + return d->m_meta->className(); + + return g; +} + +void QDesignerPropertySheet::setPropertyGroup(int index, const QString &group) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + d->ensureInfo(index).group = group; +} + +QVariant QDesignerPropertySheet::property(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return QVariant(); + if (isAdditionalProperty(index)) { + if (isFakeLayoutProperty(index)) { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) { + const QString newPropName = d->transformLayoutPropertyName(index); + if (!newPropName.isEmpty()) { + const int newIndex = layoutPropertySheet->indexOf(newPropName); + if (newIndex != -1) + return layoutPropertySheet->property(newIndex); + return QVariant(); + } + } + } + return d->m_addProperties.value(index); + } + + if (isFakeProperty(index)) { + return d->m_fakeProperties.value(index); + } + + if (d->isResourceProperty(index)) + return d->resourceProperty(index); + + if (d->isStringProperty(index)) { + QString strValue = metaProperty(index).toString(); + qdesigner_internal::PropertySheetStringValue value = d->stringProperty(index); + if (strValue != value.value()) { + value.setValue(strValue); + d->setStringProperty(index, value); // cache it + } + return QVariant::fromValue(value); + } + + if (d->isStringListProperty(index)) { + const QStringList listValue = metaProperty(index).toStringList(); + qdesigner_internal::PropertySheetStringListValue value = d->stringListProperty(index); + if (listValue != value.value()) { + value.setValue(listValue); + d->setStringListProperty(index, value); // cache it + } + return QVariant::fromValue(value); + } + + if (d->isKeySequenceProperty(index)) { + QKeySequence keyValue = qvariant_cast(metaProperty(index)); + qdesigner_internal::PropertySheetKeySequenceValue value = d->keySequenceProperty(index); + if (keyValue != value.value()) { + value.setValue(keyValue); + d->setKeySequenceProperty(index, value); // cache it + } + return QVariant::fromValue(value); + } + + QVariant result = metaProperty(index); + // QTBUG-49591: "visible" is only exposed for QHeaderView as a fake + // property ("headerVisible") for the item view. If the item view is not + // visible (on a page based container), check the WA_WState_Hidden instead, + // since otherwise false is returned when saving. + if (result.typeId() == QMetaType::Bool && !result.toBool() + && d->m_object->isWidgetType() + && propertyType(index) == PropertyVisible) { + if (auto *hv = qobject_cast(d->m_object)) { + if (auto *parent = hv->parentWidget()) { + if (!parent->isVisible()) + result = QVariant(!hv->testAttribute(Qt::WA_WState_Hidden)); + } + } + } + return result; +} + +QVariant QDesignerPropertySheet::metaProperty(int index) const +{ + Q_ASSERT(!isFakeProperty(index)); + + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + QVariant v = p->read(d->m_object); + switch (p->kind()) { + case QDesignerMetaPropertyInterface::FlagKind: { + qdesigner_internal::PropertySheetFlagValue psflags = qdesigner_internal::PropertySheetFlagValue(v.toInt(), designerMetaFlagsFor(p->enumerator())); + v.setValue(psflags); + } + break; + case QDesignerMetaPropertyInterface::EnumKind: { + qdesigner_internal::PropertySheetEnumValue pse = qdesigner_internal::PropertySheetEnumValue(v.toInt(), designerMetaEnumFor(p->enumerator())); + v.setValue(pse); + } + break; + case QDesignerMetaPropertyInterface::OtherKind: + break; + } + return v; +} + +QVariant QDesignerPropertySheet::resolvePropertyValue(int index, const QVariant &value) const +{ + if (value.canConvert()) + return qvariant_cast(value).value; + + if (value.canConvert()) + return qvariant_cast(value).value; + + if (value.canConvert()) + return qvariant_cast(value).value(); + + if (value.canConvert()) + return qvariant_cast(value).value(); + + if (value.canConvert()) + return QVariant::fromValue(qvariant_cast(value).value()); + + if (value.canConvert()) { + const QString path = qvariant_cast(value).path(); + if (path.isEmpty()) + return defaultResourceProperty(index); + if (d->m_pixmapCache) { + return d->m_pixmapCache->pixmap(qvariant_cast(value)); + } + } + + if (value.canConvert()) { + const unsigned mask = qvariant_cast(value).mask(); + if (mask == 0) + return defaultResourceProperty(index); + if (d->m_iconCache) + return d->m_iconCache->icon(qvariant_cast(value)); + } + + return value; +} + +void QDesignerPropertySheet::setFakeProperty(int index, const QVariant &value) +{ + Q_ASSERT(isFakeProperty(index)); + + QVariant &v = d->m_fakeProperties[index]; + + // set resource properties also (if we are going to have fake resource properties) + if (value.canConvert() || value.canConvert()) { + v = value; + } else if (v.canConvert()) { + qdesigner_internal::PropertySheetFlagValue f = qvariant_cast(v); + f.value = value.toInt(); + v.setValue(f); + Q_ASSERT(value.metaType().id() == QMetaType::Int); + } else if (v.canConvert()) { + qdesigner_internal::PropertySheetEnumValue e = qvariant_cast(v); + e.value = value.toInt(); + v.setValue(e); + Q_ASSERT(value.metaType().id() == QMetaType::Int); + } else { + v = value; + } +} + +void QDesignerPropertySheet::clearFakeProperties() +{ + d->m_fakeProperties.clear(); +} + +// Buddy needs to be byte array, else uic won't work +static QVariant toByteArray(const QVariant &value) { + if (value.metaType().id() == QMetaType::QByteArray) + return value; + const QByteArray ba = value.toString().toUtf8(); + return QVariant(ba); +} + +void QDesignerPropertySheet::setProperty(int index, const QVariant &value) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + if (isAdditionalProperty(index)) { + if (d->m_objectType == ObjectLabel && propertyType(index) == PropertyBuddy) { + QFormBuilderExtra::applyBuddy(value.toString(), QFormBuilderExtra::BuddyApplyVisibleOnly, qobject_cast(d->m_object)); + d->m_addProperties[index] = toByteArray(value); + return; + } + + if (isFakeLayoutProperty(index)) { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) { + const QString newPropName = d->transformLayoutPropertyName(index); + if (!newPropName.isEmpty()) { + const int newIndex = layoutPropertySheet->indexOf(newPropName); + if (newIndex != -1) + layoutPropertySheet->setProperty(newIndex, value); + } + } + } + + if (isDynamicProperty(index) || isDefaultDynamicProperty(index)) { + if (d->isResourceProperty(index)) + d->setResourceProperty(index, value); + if (d->isStringProperty(index)) + d->setStringProperty(index, qvariant_cast(value)); + if (d->isStringListProperty(index)) + d->setStringListProperty(index, qvariant_cast(value)); + if (d->isKeySequenceProperty(index)) + d->setKeySequenceProperty(index, qvariant_cast(value)); + d->m_object->setProperty(propertyName(index).toUtf8(), resolvePropertyValue(index, value)); + if (d->m_object->isWidgetType()) { + QWidget *w = qobject_cast(d->m_object); + w->setStyleSheet(w->styleSheet()); + } + } + d->m_addProperties[index] = value; + } else if (isFakeProperty(index)) { + setFakeProperty(index, value); + } else { + if (d->isResourceProperty(index)) + d->setResourceProperty(index, value); + if (d->isStringProperty(index)) + d->setStringProperty(index, qvariant_cast(value)); + if (d->isStringListProperty(index)) + d->setStringListProperty(index, qvariant_cast(value)); + if (d->isKeySequenceProperty(index)) + d->setKeySequenceProperty(index, qvariant_cast(value)); + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + p->write(d->m_object, resolvePropertyValue(index, value)); + if (qobject_cast(d->m_object) && propertyType(index) == PropertyCheckable) { + const int idx = indexOf(u"focusPolicy"_s); + if (!isChanged(idx)) { + qdesigner_internal::PropertySheetEnumValue e = qvariant_cast(property(idx)); + if (value.toBool()) { + const QDesignerMetaPropertyInterface *p = d->m_meta->property(idx); + p->write(d->m_object, Qt::NoFocus); + e.value = Qt::StrongFocus; + QVariant v; + v.setValue(e); + setFakeProperty(idx, v); + } else { + e.value = Qt::NoFocus; + QVariant v; + v.setValue(e); + setFakeProperty(idx, v); + } + } + } + } +} + +bool QDesignerPropertySheet::hasReset(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (isAdditionalProperty(index)) + return d->m_info.value(index).reset; + return true; +} + +bool QDesignerPropertySheet::reset(int index) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (d->isStringProperty(index)) { + qdesigner_internal::PropertySheetStringValue value; + // Main container: Reset to stored class name as not to change the file names generated by uic. + if (propertyName(index) == "objectName"_L1) { + const QVariant classNameDefaultV = d->m_object->property("_q_classname"); + if (classNameDefaultV.isValid()) + value.setValue(classNameDefaultV.toString()); + } else if (!isAdditionalProperty(index)) { + const QDesignerMetaPropertyInterface *property = d->m_meta->property(index); + if ((property->accessFlags() & QDesignerMetaPropertyInterface::ResetAccess) && property->reset(d->m_object)) + value.setValue(property->read(d->m_object).toString()); + else + return false; + } + setProperty(index, QVariant::fromValue(value)); + return true; + } + if (d->isStringListProperty(index)) + setProperty(index, QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue())); + if (d->isKeySequenceProperty(index)) + setProperty(index, QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue())); + if (d->isResourceProperty(index)) { + setProperty(index, d->emptyResourceProperty(index)); + return true; + } + if (isDynamic(index)) { + const QString propName = propertyName(index); + const QVariant oldValue = d->m_addProperties.value(index); + const QVariant defaultValue = d->m_info.value(index).defaultValue; + QVariant newValue = defaultValue; + if (d->isStringProperty(index)) { + newValue = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue(newValue.toString())); + } else if (d->isStringListProperty(index)) { + newValue = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue(newValue.toStringList())); + } else if (d->isKeySequenceProperty(index)) { + const QKeySequence keySequence = qvariant_cast(newValue); + newValue = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue(keySequence)); + } + if (oldValue == newValue) + return true; + d->m_object->setProperty(propName.toUtf8(), defaultValue); + d->m_addProperties[index] = newValue; + return true; + } else if (!d->m_info.value(index).defaultValue.isNull()) { + setProperty(index, d->m_info.value(index).defaultValue); + return true; + } + if (isAdditionalProperty(index)) { + const PropertyType pType = propertyType(index); + if (d->m_objectType == ObjectLabel && pType == PropertyBuddy) { + setProperty(index, QVariant(QByteArray())); + return true; + } + if (isFakeLayoutProperty(index)) { + // special properties + switch (pType) { + case PropertyLayoutObjectName: + setProperty(index, QString()); + return true; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + case PropertyLayoutSizeConstraint: +#else + case PropertyLayoutHorizontalSizeConstraint: + case PropertyLayoutVerticalSizeConstraint: +#endif + setProperty(index, QVariant(QLayout::SetDefaultConstraint)); + return true; + case PropertyLayoutBoxStretch: + case PropertyLayoutGridRowStretch: + case PropertyLayoutGridColumnStretch: + case PropertyLayoutGridRowMinimumHeight: + case PropertyLayoutGridColumnMinimumWidth: + case PropertyLayoutFieldGrowthPolicy: + case PropertyLayoutRowWrapPolicy: + case PropertyLayoutLabelAlignment: + case PropertyLayoutFormAlignment: { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) + return layoutPropertySheet->reset(layoutPropertySheet->indexOf(d->transformLayoutPropertyName(index))); + } + break; + default: + break; + } + // special margins + int value = -1; + switch (d->m_objectType) { + case ObjectLayoutWidget: + if (pType == PropertyLayoutLeftMargin || + pType == PropertyLayoutTopMargin || + pType == PropertyLayoutRightMargin || + pType == PropertyLayoutBottomMargin) + value = 0; + break; + default: + break; + } + setProperty(index, value); + return true; + } + return false; + } + if (isFakeProperty(index)) { + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + const bool result = p->reset(d->m_object); + d->m_fakeProperties[index] = p->read(d->m_object); + return result; + } + if (propertyType(index) == PropertyGeometry && d->m_object->isWidgetType()) { + if (QWidget *w = qobject_cast(d->m_object)) { + QWidget *widget = w; + if (qdesigner_internal::Utils::isCentralWidget(d->m_fwb, widget) && d->m_fwb->parentWidget()) + widget = d->m_fwb->parentWidget(); + + if (widget != w && widget->parentWidget()) { + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + widget->parentWidget()->adjustSize(); + } + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + widget->adjustSize(); + return true; + } + } + // ### TODO: reset for fake properties. + + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + return p->reset(d->m_object); +} + +bool QDesignerPropertySheet::isChanged(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (isAdditionalProperty(index)) { + if (isFakeLayoutProperty(index)) { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) { + const QString newPropName = d->transformLayoutPropertyName(index); + if (!newPropName.isEmpty()) { + const int newIndex = layoutPropertySheet->indexOf(newPropName); + if (newIndex != -1) + return layoutPropertySheet->isChanged(newIndex); + return false; + } + } + } + } + return d->m_info.value(index).changed; +} + +void QDesignerPropertySheet::setChanged(int index, bool changed) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + if (isAdditionalProperty(index)) { + if (isFakeLayoutProperty(index)) { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) { + const QString newPropName = d->transformLayoutPropertyName(index); + if (!newPropName.isEmpty()) { + const int newIndex = layoutPropertySheet->indexOf(newPropName); + if (newIndex != -1) + layoutPropertySheet->setChanged(newIndex, changed); + } + } + } + } + if (d->isReloadableProperty(index)) { + if (d->m_fwb) { + if (changed) + d->m_fwb->addReloadableProperty(this, index); + else + d->m_fwb->removeReloadableProperty(this, index); + } + } + d->ensureInfo(index).changed = changed; +} + +bool QDesignerPropertySheet::isFakeLayoutProperty(int index) const +{ + if (!isAdditionalProperty(index)) + return false; + + switch (propertyType(index)) { + case PropertyLayoutObjectName: +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + case PropertyLayoutSizeConstraint: +#else + case PropertyLayoutHorizontalSizeConstraint: + case PropertyLayoutVerticalSizeConstraint: +#endif + return true; + case PropertyLayoutLeftMargin: + case PropertyLayoutTopMargin: + case PropertyLayoutRightMargin: + case PropertyLayoutBottomMargin: + case PropertyLayoutSpacing: + case PropertyLayoutHorizontalSpacing: + case PropertyLayoutVerticalSpacing: + case PropertyLayoutFieldGrowthPolicy: + case PropertyLayoutRowWrapPolicy: + case PropertyLayoutLabelAlignment: + case PropertyLayoutFormAlignment: + case PropertyLayoutBoxStretch: + case PropertyLayoutGridRowStretch: + case PropertyLayoutGridColumnStretch: + case PropertyLayoutGridRowMinimumHeight: + case PropertyLayoutGridColumnMinimumWidth: + return d->m_canHaveLayoutAttributes; + default: + break; + } + return false; +} + +// Visible vs. Enabled: In Qt 5, it was possible to define a boolean function +// for the DESIGNABLE attribute of Q_PROPERTY. Qt Widgets Designer would use that to +// determine isEnabled() for the property and return isVisible() = false +// for properties that specified 'false' for DESIGNABLE. +// This was used for example for the "checked" property of QAbstractButton, +// QGroupBox and QAction, where "checkable" would determine isEnabled(). +// This is now implemented by querying the property directly. + +bool QDesignerPropertySheet::isVisible(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + + const PropertyType type = propertyType(index); + if (isAdditionalProperty(index)) { + if (isFakeLayoutProperty(index) && d->m_object->isWidgetType()) { + const QLayout *currentLayout = d->layout(); + if (!currentLayout) + return false; + const int visibleMask = qdesigner_internal::LayoutProperties::visibleProperties(currentLayout); + switch (type) { + case PropertyLayoutSpacing: + return visibleMask & qdesigner_internal::LayoutProperties::SpacingProperty; + case PropertyLayoutHorizontalSpacing: + case PropertyLayoutVerticalSpacing: + return visibleMask & qdesigner_internal::LayoutProperties::HorizSpacingProperty; + case PropertyLayoutFieldGrowthPolicy: + return visibleMask & qdesigner_internal::LayoutProperties::FieldGrowthPolicyProperty; + case PropertyLayoutRowWrapPolicy: + return visibleMask & qdesigner_internal::LayoutProperties::RowWrapPolicyProperty; + case PropertyLayoutLabelAlignment: + return visibleMask & qdesigner_internal::LayoutProperties::LabelAlignmentProperty; + case PropertyLayoutFormAlignment: + return visibleMask & qdesigner_internal::LayoutProperties::FormAlignmentProperty; + case PropertyLayoutBoxStretch: + return visibleMask & qdesigner_internal::LayoutProperties::BoxStretchProperty; + case PropertyLayoutGridRowStretch: + return visibleMask & qdesigner_internal::LayoutProperties::GridRowStretchProperty; + case PropertyLayoutGridColumnStretch: + return visibleMask & qdesigner_internal::LayoutProperties::GridColumnStretchProperty; + case PropertyLayoutGridRowMinimumHeight: + return visibleMask & qdesigner_internal::LayoutProperties::GridRowMinimumHeightProperty; + case PropertyLayoutGridColumnMinimumWidth: + return visibleMask & qdesigner_internal::LayoutProperties::GridColumnMinimumWidthProperty; + default: + break; + } + return true; + } + return d->m_info.value(index).visible; + } + + if (isFakeProperty(index)) { + switch (type) { + case PropertyWindowModality: // Hidden for child widgets + case PropertyWindowOpacity: + return d->m_info.value(index).visible; + default: + break; + } + return true; + } + + const bool visible = d->m_info.value(index).visible; + switch (type) { + case PropertyWindowTitle: + case PropertyWindowIcon: + case PropertyWindowFilePath: + case PropertyWindowOpacity: + case PropertyWindowIconText: + case PropertyWindowModified: + return visible; + default: + if (visible) + return true; + break; + } + + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + if (!(p->accessFlags() & QDesignerMetaPropertyInterface::WriteAccess)) + return false; + + return p->attributes().testFlag(QDesignerMetaPropertyInterface::DesignableAttribute); +} + +void QDesignerPropertySheet::setVisible(int index, bool visible) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + d->ensureInfo(index).visible = visible; +} + +bool QDesignerPropertySheet::isEnabled(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (isAdditionalProperty(index)) + return true; + + if (isFakeProperty(index)) + return true; + + // Grey out geometry of laid-out widgets (including splitter) + if (propertyType(index) == PropertyGeometry && d->m_object->isWidgetType()) { + bool isManaged; + const qdesigner_internal::LayoutInfo::Type lt = qdesigner_internal::LayoutInfo::laidoutWidgetType(d->m_core, qobject_cast(d->m_object), &isManaged); + return !isManaged || lt == qdesigner_internal::LayoutInfo::NoLayout; + } + + if (d->m_info.value(index).visible) + return true; + + // Enable setting of properties for statically non-designable properties + // as this might be done via TaskMenu/Cursor::setProperty. Note that those + // properties are not visible. + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + if (!p->accessFlags().testFlag(QDesignerMetaPropertyInterface::WriteAccess)) + return false; + + if (!p->attributes().testFlag(QDesignerMetaPropertyInterface::DesignableAttribute)) + return false; + + const PropertyType type = propertyType(index); + if (type == PropertyChecked && d->m_objectFlags.testFlag(CheckableProperty)) + return d->m_object->property("checkable").toBool(); + return true; +} + +bool QDesignerPropertySheet::isAttribute(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (isAdditionalProperty(index)) + return d->m_info.value(index).attribute; + + if (isFakeProperty(index)) + return false; + + return d->m_info.value(index).attribute; +} + +void QDesignerPropertySheet::setAttribute(int index, bool attribute) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + d->ensureInfo(index).attribute = attribute; +} + +QDesignerFormEditorInterface *QDesignerPropertySheet::core() const +{ + return d->m_core; +} + +bool QDesignerPropertySheet::internalDynamicPropertiesEnabled() +{ + return QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled; +} + +void QDesignerPropertySheet::setInternalDynamicPropertiesEnabled(bool v) +{ + QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled = v; +} + +// Find the form editor in the hierarchy. +// We know that the parent of the sheet is the extension manager +// whose parent is the core. +QDesignerFormEditorInterface *QDesignerPropertySheet::formEditorForObject(QObject *o) +{ + do { + if (auto *core = qobject_cast(o)) + return core; + o = o->parent(); + } while (o); + Q_ASSERT(o); + return nullptr; +} + +// ---------- QDesignerAbstractPropertySheetFactory + +struct QDesignerAbstractPropertySheetFactory::PropertySheetFactoryPrivate { + PropertySheetFactoryPrivate(); + const QString m_propertySheetId; + const QString m_dynamicPropertySheetId; + + QHash m_extensions; +}; + +QDesignerAbstractPropertySheetFactory::PropertySheetFactoryPrivate::PropertySheetFactoryPrivate() : + m_propertySheetId(Q_TYPEID(QDesignerPropertySheetExtension)), + m_dynamicPropertySheetId(Q_TYPEID(QDesignerDynamicPropertySheetExtension)) +{ +} + +// ---------- QDesignerAbstractPropertySheetFactory + + +QDesignerAbstractPropertySheetFactory::QDesignerAbstractPropertySheetFactory(QExtensionManager *parent) : + QExtensionFactory(parent), + m_impl(new PropertySheetFactoryPrivate) +{ +} + +QDesignerAbstractPropertySheetFactory::~QDesignerAbstractPropertySheetFactory() +{ + delete m_impl; +} + +QObject *QDesignerAbstractPropertySheetFactory::extension(QObject *object, const QString &iid) const +{ + if (!object) + return nullptr; + + if (iid != m_impl->m_propertySheetId && iid != m_impl->m_dynamicPropertySheetId) + return nullptr; + + QObject *ext = m_impl->m_extensions.value(object, 0); + if (!ext && (ext = createPropertySheet(object, const_cast(this)))) { + connect(ext, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed); + connect(object, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed); + m_impl->m_extensions.insert(object, ext); + } + + return ext; +} + +void QDesignerAbstractPropertySheetFactory::objectDestroyed(QObject *object) +{ + for (auto it = m_impl->m_extensions.begin(), end = m_impl->m_extensions.end(); it != end; /*erasing*/) { + if (it.key() == object || it.value() == object) { + if (it.key() == object) { + QObject *ext = it.value(); + disconnect(ext, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed); + delete ext; + } + it = m_impl->m_extensions.erase(it); + } else { + ++it; + } + } +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_propertysheet_p.h b/src/designer/src/lib/shared/qdesigner_propertysheet_p.h new file mode 100644 index 0000000..8c4eac8 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_propertysheet_p.h @@ -0,0 +1,245 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_PROPERTYSHEET_H +#define QDESIGNER_PROPERTYSHEET_H + +#include "shared_global_p.h" +#include "dynamicpropertysheet.h" +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QLayout; +class QDesignerFormEditorInterface; +class QDesignerPropertySheetPrivate; + +namespace qdesigner_internal +{ + class DesignerPixmapCache; + class DesignerIconCache; + class FormWindowBase; +} + +class QDESIGNER_SHARED_EXPORT QDesignerPropertySheet: public QObject, public QDesignerPropertySheetExtension, public QDesignerDynamicPropertySheetExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension QDesignerDynamicPropertySheetExtension) +public: + explicit QDesignerPropertySheet(QObject *object, QObject *parent = nullptr); + ~QDesignerPropertySheet() override; + + int indexOf(const QString &name) const override; + + int count() const override; + QString propertyName(int index) const override; + + QString propertyGroup(int index) const override; + void setPropertyGroup(int index, const QString &group) override; + + bool hasReset(int index) const override; + bool reset(int index) override; + + bool isAttribute(int index) const override; + void setAttribute(int index, bool b) override; + + bool isVisible(int index) const override; + void setVisible(int index, bool b) override; + + QVariant property(int index) const override; + void setProperty(int index, const QVariant &value) override; + + bool isChanged(int index) const override; + + void setChanged(int index, bool changed) override; + + bool dynamicPropertiesAllowed() const override; + int addDynamicProperty(const QString &propertyName, const QVariant &value) override; + bool removeDynamicProperty(int index) override; + bool isDynamicProperty(int index) const override; + bool canAddDynamicProperty(const QString &propertyName) const override; + + bool isDefaultDynamicProperty(int index) const; + + bool isResourceProperty(int index) const; + QVariant defaultResourceProperty(int index) const; + + qdesigner_internal::DesignerPixmapCache *pixmapCache() const; + void setPixmapCache(qdesigner_internal::DesignerPixmapCache *cache); + qdesigner_internal::DesignerIconCache *iconCache() const; + void setIconCache(qdesigner_internal::DesignerIconCache *cache); + int createFakeProperty(const QString &propertyName, const QVariant &value = QVariant()); + + bool isEnabled(int index) const override; + QObject *object() const; + + static bool internalDynamicPropertiesEnabled(); + static void setInternalDynamicPropertiesEnabled(bool v); + + static QDesignerFormEditorInterface *formEditorForObject(QObject *o); + +protected: + bool isAdditionalProperty(int index) const; + bool isFakeProperty(int index) const; + QVariant resolvePropertyValue(int index, const QVariant &value) const; + QVariant metaProperty(int index) const; + void setFakeProperty(int index, const QVariant &value); + void clearFakeProperties(); + + bool isFakeLayoutProperty(int index) const; + bool isDynamic(int index) const; + qdesigner_internal::FormWindowBase *formWindowBase() const; + QDesignerFormEditorInterface *core() const; + +public: + enum PropertyType { PropertyNone, + PropertyLayoutObjectName, + PropertyLayoutLeftMargin, + PropertyLayoutTopMargin, + PropertyLayoutRightMargin, + PropertyLayoutBottomMargin, + PropertyLayoutSpacing, + PropertyLayoutHorizontalSpacing, + PropertyLayoutVerticalSpacing, +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + PropertyLayoutSizeConstraint, +#else + PropertyLayoutHorizontalSizeConstraint, + PropertyLayoutVerticalSizeConstraint, +#endif + PropertyLayoutFieldGrowthPolicy, + PropertyLayoutRowWrapPolicy, + PropertyLayoutLabelAlignment, + PropertyLayoutFormAlignment, + PropertyLayoutBoxStretch, + PropertyLayoutGridRowStretch, + PropertyLayoutGridColumnStretch, + PropertyLayoutGridRowMinimumHeight, + PropertyLayoutGridColumnMinimumWidth, + PropertyBuddy, + PropertyAccessibility, + PropertyGeometry, + PropertyChecked, + PropertyCheckable, + PropertyVisible, + PropertyWindowTitle, + PropertyWindowIcon, + PropertyWindowFilePath, + PropertyWindowOpacity, + PropertyWindowIconText, + PropertyWindowModality, + PropertyWindowModified, + PropertyStyleSheet, + PropertyText + }; + + enum ObjectType { ObjectNone, ObjectLabel, ObjectLayout, ObjectLayoutWidget }; + enum ObjectFlag + { + CheckableProperty = 0x1 // Has a "checked" property depending on "checkable" + }; + Q_DECLARE_FLAGS(ObjectFlags, ObjectFlag) + + static ObjectType objectTypeFromObject(const QObject *o); + static ObjectFlags objectFlagsFromObject(const QObject *o); + static PropertyType propertyTypeFromName(const QString &name); + +protected: + PropertyType propertyType(int index) const; + ObjectType objectType() const; + +private: + QDesignerPropertySheetPrivate *d; +}; + +/* Abstract base class for factories that register a property sheet that implements + * both QDesignerPropertySheetExtension and QDesignerDynamicPropertySheetExtension + * by multiple inheritance. The factory maintains ownership of + * the extension and returns it for both id's. */ + +class QDESIGNER_SHARED_EXPORT QDesignerAbstractPropertySheetFactory: public QExtensionFactory +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionFactory) +public: + explicit QDesignerAbstractPropertySheetFactory(QExtensionManager *parent = nullptr); + ~QDesignerAbstractPropertySheetFactory() override; + + QObject *extension(QObject *object, const QString &iid) const override; + +private slots: + void objectDestroyed(QObject *object); + +private: + virtual QObject *createPropertySheet(QObject *qObject, QObject *parent) const = 0; + + struct PropertySheetFactoryPrivate; + PropertySheetFactoryPrivate *m_impl; +}; + +/* Convenience factory template for property sheets that implement + * QDesignerPropertySheetExtension and QDesignerDynamicPropertySheetExtension + * by multiple inheritance. */ + +template +class QDesignerPropertySheetFactory : public QDesignerAbstractPropertySheetFactory { +public: + explicit QDesignerPropertySheetFactory(QExtensionManager *parent = nullptr); + + static void registerExtension(QExtensionManager *mgr); + +private: + // Does a qobject_cast on the object. + QObject *createPropertySheet(QObject *qObject, QObject *parent) const override; +}; + +template +QDesignerPropertySheetFactory::QDesignerPropertySheetFactory(QExtensionManager *parent) : + QDesignerAbstractPropertySheetFactory(parent) +{ +} + +template +QObject *QDesignerPropertySheetFactory::createPropertySheet(QObject *qObject, QObject *parent) const +{ + Object *object = qobject_cast(qObject); + if (!object) + return nullptr; + return new PropertySheet(object, parent); +} + +template +void QDesignerPropertySheetFactory::registerExtension(QExtensionManager *mgr) +{ + QDesignerPropertySheetFactory *factory = new QDesignerPropertySheetFactory(mgr); + mgr->registerExtensions(factory, Q_TYPEID(QDesignerPropertySheetExtension)); + mgr->registerExtensions(factory, Q_TYPEID(QDesignerDynamicPropertySheetExtension)); +} + + +// Standard property sheet +using QDesignerDefaultPropertySheetFactory = QDesignerPropertySheetFactory; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDesignerPropertySheet::ObjectFlags) + +QT_END_NAMESPACE + +#endif // QDESIGNER_PROPERTYSHEET_H diff --git a/src/designer/src/lib/shared/qdesigner_qsettings.cpp b/src/designer/src/lib/shared/qdesigner_qsettings.cpp new file mode 100644 index 0000000..3105132 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_qsettings.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_qsettings_p.h" + +#include +#include +#include +#include +#include + +QDesignerQSettings::QDesignerQSettings() : + m_settings(qApp->organizationName(), settingsApplicationName()) +{ +} + +QString QDesignerQSettings::settingsApplicationName() +{ + return qApp->applicationName(); +} + +void QDesignerQSettings::beginGroup(const QString &prefix) +{ + m_settings.beginGroup(prefix); +} + +void QDesignerQSettings::endGroup() +{ + m_settings.endGroup(); +} + +bool QDesignerQSettings::contains(const QString &key) const +{ + return m_settings.contains(key); +} + +void QDesignerQSettings::setValue(const QString &key, const QVariant &value) +{ + m_settings.setValue(key, value); +} + +QVariant QDesignerQSettings::value(const QString &key, const QVariant &defaultValue) const +{ + return m_settings.value(key, defaultValue); +} + +void QDesignerQSettings::remove(const QString &key) +{ + m_settings.remove(key); +} diff --git a/src/designer/src/lib/shared/qdesigner_qsettings_p.h b/src/designer/src/lib/shared/qdesigner_qsettings_p.h new file mode 100644 index 0000000..12c6efd --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_qsettings_p.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_QSETTINGS_H +#define QDESIGNER_QSETTINGS_H + +#include "shared_global_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +// Implements QDesignerSettingsInterface by calls to QSettings +class QDESIGNER_SHARED_EXPORT QDesignerQSettings : public QDesignerSettingsInterface +{ +public: + QDesignerQSettings(); + + void beginGroup(const QString &prefix) override; + void endGroup() override; + + bool contains(const QString &key) const override; + void setValue(const QString &key, const QVariant &value) override; + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const override; + void remove(const QString &key) override; + + // The application name to be used for settings. Allows for including + // the Qt version to prevent settings of different Qt versions from + // interfering. + static QString settingsApplicationName(); + +private: + QSettings m_settings; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_QSETTINGS_H diff --git a/src/designer/src/lib/shared/qdesigner_stackedbox.cpp b/src/designer/src/lib/shared/qdesigner_stackedbox.cpp new file mode 100644 index 0000000..993dd60 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_stackedbox.cpp @@ -0,0 +1,366 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_stackedbox_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "orderdialog_p.h" +#include "promotiontaskmenu_p.h" +#include "widgetfactory_p.h" + +#include + +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QToolButton *createToolButton(QWidget *parent, Qt::ArrowType at, const QString &name) +{ + QToolButton *rc = new QToolButton(); + rc->setAttribute(Qt::WA_NoChildEventsForParent, true); + rc->setParent(parent); + rc->setObjectName(name); + rc->setArrowType(at); + rc->setAutoRaise(true); + rc->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + rc->setFixedSize(QSize(15, 15)); + return rc; +} + +// --------------- QStackedWidgetPreviewEventFilter +QStackedWidgetPreviewEventFilter::QStackedWidgetPreviewEventFilter(QStackedWidget *parent) : + QObject(parent), + m_buttonToolTipEnabled(false), // Not on preview + m_stackedWidget(parent), + m_prev(createToolButton(m_stackedWidget, Qt::LeftArrow, u"__qt__passive_prev"_s)), + m_next(createToolButton(m_stackedWidget, Qt::RightArrow, u"__qt__passive_next"_s)) +{ + connect(m_prev, &QAbstractButton::clicked, this, &QStackedWidgetPreviewEventFilter::prevPage); + connect(m_next, &QAbstractButton::clicked, this, &QStackedWidgetPreviewEventFilter::nextPage); + + updateButtons(); + m_stackedWidget->installEventFilter(this); + m_prev->installEventFilter(this); + m_next->installEventFilter(this); +} + +void QStackedWidgetPreviewEventFilter::install(QStackedWidget *stackedWidget) +{ + new QStackedWidgetPreviewEventFilter(stackedWidget); +} + +void QStackedWidgetPreviewEventFilter::updateButtons() +{ + m_prev->move(m_stackedWidget->width() - 31, 1); + m_prev->show(); + m_prev->raise(); + + m_next->move(m_stackedWidget->width() - 16, 1); + m_next->show(); + m_next->raise(); +} + +void QStackedWidgetPreviewEventFilter::prevPage() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + fw->clearSelection(); + fw->selectWidget(stackedWidget(), true); + } + const int count = m_stackedWidget->count(); + if (count > 1) { + int newIndex = m_stackedWidget->currentIndex() - 1; + if (newIndex < 0) + newIndex = count - 1; + gotoPage(newIndex); + } +} + +void QStackedWidgetPreviewEventFilter::nextPage() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + fw->clearSelection(); + fw->selectWidget(stackedWidget(), true); + } + const int count = m_stackedWidget->count(); + if (count > 1) + gotoPage((m_stackedWidget->currentIndex() + 1) % count); +} + +bool QStackedWidgetPreviewEventFilter::eventFilter(QObject *watched, QEvent *event) +{ + if (watched->isWidgetType()) { + if (watched == m_stackedWidget) { + switch (event->type()) { + case QEvent::LayoutRequest: + updateButtons(); + break; + case QEvent::ChildAdded: + case QEvent::ChildRemoved: + case QEvent::Resize: + case QEvent::Show: + updateButtons(); + break; + default: + break; + } + } + if (m_buttonToolTipEnabled && (watched == m_next || watched == m_prev)) { + switch (event->type()) { + case QEvent::ToolTip: + updateButtonToolTip(watched); // Tooltip includes page number, so, refresh on demand + break; + default: + break; + } + } + } + return QObject::eventFilter(watched, event); +} + +void QStackedWidgetPreviewEventFilter::gotoPage(int page) +{ + m_stackedWidget->setCurrentIndex(page); + updateButtons(); +} + +static inline QString stackedClassName(QStackedWidget *w) +{ + if (const QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w)) + return qdesigner_internal::WidgetFactory::classNameOf(fw->core(), w); + return u"Stacked widget"_s; +} + +void QStackedWidgetPreviewEventFilter::updateButtonToolTip(QObject *o) +{ + if (o == m_prev) { + const QString msg = tr("Go to previous page of %1 '%2' (%3/%4).") + .arg(stackedClassName(m_stackedWidget), m_stackedWidget->objectName()) + .arg(m_stackedWidget->currentIndex() + 1) + .arg(m_stackedWidget->count()); + m_prev->setToolTip(msg); + } else { + if (o == m_next) { + const QString msg = tr("Go to next page of %1 '%2' (%3/%4).") + .arg(stackedClassName(m_stackedWidget), m_stackedWidget->objectName()) + .arg(m_stackedWidget->currentIndex() + 1) + .arg(m_stackedWidget->count()); + m_next->setToolTip(msg); + } + } +} + +// --------------- QStackedWidgetEventFilter +QStackedWidgetEventFilter::QStackedWidgetEventFilter(QStackedWidget *parent) : + QStackedWidgetPreviewEventFilter(parent), + m_actionPreviousPage(new QAction(tr("Previous Page"), this)), + m_actionNextPage(new QAction(tr("Next Page"), this)), + m_actionDeletePage(new QAction(tr("Delete"), this)), + m_actionInsertPage(new QAction(tr("Before Current Page"), this)), + m_actionInsertPageAfter(new QAction(tr("After Current Page"), this)), + m_actionChangePageOrder(new QAction(tr("Change Page Order..."), this)), + m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this)) +{ + setButtonToolTipEnabled(true); + connect(m_actionPreviousPage, &QAction::triggered, this, &QStackedWidgetEventFilter::prevPage); + connect(m_actionNextPage, &QAction::triggered, this, &QStackedWidgetEventFilter::nextPage); + connect(m_actionDeletePage, &QAction::triggered, this, &QStackedWidgetEventFilter::removeCurrentPage); + connect(m_actionInsertPage, &QAction::triggered, this, &QStackedWidgetEventFilter::addPage); + connect(m_actionInsertPageAfter, &QAction::triggered, this, &QStackedWidgetEventFilter::addPageAfter); + connect(m_actionChangePageOrder, &QAction::triggered, this, &QStackedWidgetEventFilter::changeOrder); +} + +void QStackedWidgetEventFilter::install(QStackedWidget *stackedWidget) +{ + new QStackedWidgetEventFilter(stackedWidget); +} + +QStackedWidgetEventFilter *QStackedWidgetEventFilter::eventFilterOf(const QStackedWidget *stackedWidget) +{ + // Look for 1st order children only..otherwise, we might get filters of nested widgets + for (QObject *o : stackedWidget->children()) { + if (!o->isWidgetType()) + if (QStackedWidgetEventFilter *ef = qobject_cast(o)) + return ef; + } + return nullptr; +} + +QMenu *QStackedWidgetEventFilter::addStackedWidgetContextMenuActions(const QStackedWidget *stackedWidget, QMenu *popup) +{ + QStackedWidgetEventFilter *filter = eventFilterOf(stackedWidget); + if (!filter) + return nullptr; + return filter->addContextMenuActions(popup); +} + +void QStackedWidgetEventFilter::removeCurrentPage() +{ + if (stackedWidget()->currentIndex() == -1) + return; + + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + qdesigner_internal::DeleteStackedWidgetPageCommand *cmd = new qdesigner_internal::DeleteStackedWidgetPageCommand(fw); + cmd->init(stackedWidget()); + fw->commandHistory()->push(cmd); + } +} + +void QStackedWidgetEventFilter::changeOrder() +{ + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget()); + + if (!fw) + return; + + const QWidgetList oldPages = qdesigner_internal::OrderDialog::pagesOfContainer(fw->core(), stackedWidget()); + const int pageCount = oldPages.size(); + if (pageCount < 2) + return; + + qdesigner_internal::OrderDialog dlg(fw); + dlg.setPageList(oldPages); + if (dlg.exec() == QDialog::Rejected) + return; + + const QWidgetList newPages = dlg.pageList(); + if (newPages == oldPages) + return; + + fw->beginCommand(tr("Change Page Order")); + for(int i=0; i < pageCount; ++i) { + if (newPages.at(i) == stackedWidget()->widget(i)) + continue; + qdesigner_internal::MoveStackedWidgetCommand *cmd = new qdesigner_internal::MoveStackedWidgetCommand(fw); + cmd->init(stackedWidget(), newPages.at(i), i); + fw->commandHistory()->push(cmd); + } + fw->endCommand(); +} + +void QStackedWidgetEventFilter::addPage() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + qdesigner_internal::AddStackedWidgetPageCommand *cmd = new qdesigner_internal::AddStackedWidgetPageCommand(fw); + cmd->init(stackedWidget(), qdesigner_internal::AddStackedWidgetPageCommand::InsertBefore); + fw->commandHistory()->push(cmd); + } +} + +void QStackedWidgetEventFilter::addPageAfter() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + qdesigner_internal::AddStackedWidgetPageCommand *cmd = new qdesigner_internal::AddStackedWidgetPageCommand(fw); + cmd->init(stackedWidget(), qdesigner_internal::AddStackedWidgetPageCommand::InsertAfter); + fw->commandHistory()->push(cmd); + } +} + +void QStackedWidgetEventFilter::gotoPage(int page) { + // Are we on a form or in a preview? + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + qdesigner_internal::SetPropertyCommand *cmd = new qdesigner_internal::SetPropertyCommand(fw); + cmd->init(stackedWidget(), u"currentIndex"_s, page); + fw->commandHistory()->push(cmd); + fw->emitSelectionChanged(); // Magically prevent an endless loop triggered by auto-repeat. + updateButtons(); + } else { + QStackedWidgetPreviewEventFilter::gotoPage(page); + } +} + +QMenu *QStackedWidgetEventFilter::addContextMenuActions(QMenu *popup) +{ + QMenu *pageMenu = nullptr; + const int count = stackedWidget()->count(); + const bool hasSeveralPages = count > 1; + m_actionDeletePage->setEnabled(count); + if (count) { + const QString pageSubMenuLabel = tr("Page %1 of %2").arg(stackedWidget()->currentIndex() + 1).arg(count); + pageMenu = popup->addMenu(pageSubMenuLabel); + pageMenu->addAction(m_actionDeletePage); + // Set up promotion menu for current widget. + if (QWidget *page = stackedWidget()->currentWidget ()) { + m_pagePromotionTaskMenu->setWidget(page); + m_pagePromotionTaskMenu->addActions(QDesignerFormWindowInterface::findFormWindow(stackedWidget()), + qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit, + pageMenu); + } + QMenu *insertPageMenu = popup->addMenu(tr("Insert Page")); + insertPageMenu->addAction(m_actionInsertPageAfter); + insertPageMenu->addAction(m_actionInsertPage); + } else { + QAction *insertPageAction = popup->addAction(tr("Insert Page")); + connect(insertPageAction, &QAction::triggered, this, &QStackedWidgetEventFilter::addPage); + } + popup->addAction(m_actionNextPage); + m_actionNextPage->setEnabled(hasSeveralPages); + popup->addAction(m_actionPreviousPage); + m_actionPreviousPage->setEnabled(hasSeveralPages); + popup->addAction(m_actionChangePageOrder); + m_actionChangePageOrder->setEnabled(hasSeveralPages); + popup->addSeparator(); + return pageMenu; +} + +// -------- QStackedWidgetPropertySheet + +static constexpr auto pagePropertyName = "currentPageName"_L1; + +QStackedWidgetPropertySheet::QStackedWidgetPropertySheet(QStackedWidget *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_stackedWidget(object) +{ + createFakeProperty(pagePropertyName, QString()); +} + +bool QStackedWidgetPropertySheet::isEnabled(int index) const +{ + if (propertyName(index) != pagePropertyName) + return QDesignerPropertySheet::isEnabled(index); + return m_stackedWidget->currentWidget() != nullptr; +} + +void QStackedWidgetPropertySheet::setProperty(int index, const QVariant &value) +{ + if (propertyName(index) == pagePropertyName) { + if (QWidget *w = m_stackedWidget->currentWidget()) + w->setObjectName(value.toString()); + } else { + QDesignerPropertySheet::setProperty(index, value); + } +} + +QVariant QStackedWidgetPropertySheet::property(int index) const +{ + if (propertyName(index) == pagePropertyName) { + if (const QWidget *w = m_stackedWidget->currentWidget()) + return w->objectName(); + return QString(); + } + return QDesignerPropertySheet::property(index); +} + +bool QStackedWidgetPropertySheet::reset(int index) +{ + if (propertyName(index) == pagePropertyName) { + setProperty(index, QString()); + return true; + } + return QDesignerPropertySheet::reset(index); +} + +bool QStackedWidgetPropertySheet::checkProperty(const QString &propertyName) +{ + return propertyName != pagePropertyName; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_stackedbox_p.h b/src/designer/src/lib/shared/qdesigner_stackedbox_p.h new file mode 100644 index 0000000..9ef21fc --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_stackedbox_p.h @@ -0,0 +1,126 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_STACKEDBOX_H +#define QDESIGNER_STACKEDBOX_H + +#include "shared_global_p.h" +#include "qdesigner_propertysheet_p.h" + +QT_BEGIN_NAMESPACE + +class QStackedWidget; +class QWidget; +class QAction; +class QMenu; +class QToolButton; + +namespace qdesigner_internal { + class PromotionTaskMenu; +} + +// Event filter to be installed on a QStackedWidget in preview mode. +// Create two buttons to switch pages. + +class QDESIGNER_SHARED_EXPORT QStackedWidgetPreviewEventFilter : public QObject +{ + Q_OBJECT +public: + explicit QStackedWidgetPreviewEventFilter(QStackedWidget *parent); + + // Install helper on QStackedWidget + static void install(QStackedWidget *stackedWidget); + bool eventFilter(QObject *watched, QEvent *event) override; + + void setButtonToolTipEnabled(bool v) { m_buttonToolTipEnabled = v; } + bool buttonToolTipEnabled() const { return m_buttonToolTipEnabled; } + +public slots: + void updateButtons(); + void prevPage(); + void nextPage(); + +protected: + QStackedWidget *stackedWidget() const { return m_stackedWidget; } + virtual void gotoPage(int page); + +private: + void updateButtonToolTip(QObject *o); + + bool m_buttonToolTipEnabled; + QStackedWidget *m_stackedWidget; + QToolButton *m_prev; + QToolButton *m_next; +}; + +// Event filter to be installed on a QStackedWidget in editing mode. +// In addition to the browse buttons, handles context menu and everything + +class QDESIGNER_SHARED_EXPORT QStackedWidgetEventFilter : public QStackedWidgetPreviewEventFilter +{ + Q_OBJECT +public: + explicit QStackedWidgetEventFilter(QStackedWidget *parent); + + // Install helper on QStackedWidget + static void install(QStackedWidget *stackedWidget); + static QStackedWidgetEventFilter *eventFilterOf(const QStackedWidget *stackedWidget); + // Convenience to add a menu on a tackedWidget + static QMenu *addStackedWidgetContextMenuActions(const QStackedWidget *stackedWidget, QMenu *popup); + + // Add context menu and return page submenu or 0. + QMenu *addContextMenuActions(QMenu *popup); + +private slots: + void removeCurrentPage(); + void addPage(); + void addPageAfter(); + void changeOrder(); + +protected: + void gotoPage(int page) override; + +private: + QAction *m_actionPreviousPage; + QAction *m_actionNextPage; + QAction *m_actionDeletePage; + QAction *m_actionInsertPage; + QAction *m_actionInsertPageAfter; + QAction *m_actionChangePageOrder; + qdesigner_internal::PromotionTaskMenu* m_pagePromotionTaskMenu; +}; + +// PropertySheet to handle the "currentPageName" property +class QDESIGNER_SHARED_EXPORT QStackedWidgetPropertySheet : public QDesignerPropertySheet { +public: + explicit QStackedWidgetPropertySheet(QStackedWidget *object, QObject *parent = nullptr); + + void setProperty(int index, const QVariant &value) override; + QVariant property(int index) const override; + bool reset(int index) override; + bool isEnabled(int index) const override; + + // Check whether the property is to be saved. Returns false for the page + // properties (as the property sheet has no concept of 'stored') + static bool checkProperty(const QString &propertyName); + +private: + QStackedWidget *m_stackedWidget; +}; + +using QStackedWidgetPropertySheetFactory = QDesignerPropertySheetFactory; + +QT_END_NAMESPACE + +#endif // QDESIGNER_STACKEDBOX_H diff --git a/src/designer/src/lib/shared/qdesigner_tabwidget.cpp b/src/designer/src/lib/shared/qdesigner_tabwidget.cpp new file mode 100644 index 0000000..212ee44 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_tabwidget.cpp @@ -0,0 +1,531 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_tabwidget_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "promotiontaskmenu_p.h" +#include "formwindowbase_p.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +// Store tab widget as drag source +class MyMimeData : public QMimeData +{ + Q_OBJECT +public: + MyMimeData(const QTabWidget *tab) : m_tab(tab) {} + static bool fromMyTab(const QMimeData *mimeData, const QTabWidget *tab) { + if (!mimeData) + return false; + const MyMimeData *m = qobject_cast(mimeData); + return m && m->m_tab == tab; + } +private: + const QTabWidget *m_tab; +}; + +} // namespace qdesigner_internal + +// ------------- QTabWidgetEventFilter + +QTabWidgetEventFilter::QTabWidgetEventFilter(QTabWidget *parent) : + QObject(parent), + m_tabWidget(parent), + m_actionDeletePage(new QAction(tr("Delete"), this)), + m_actionInsertPage(new QAction(tr("Before Current Page"), this)), + m_actionInsertPageAfter(new QAction(tr("After Current Page"), this)), + m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this)) +{ + tabBar()->setAcceptDrops(true); + tabBar()->installEventFilter(this); + + connect(m_actionInsertPage, &QAction::triggered, this, &QTabWidgetEventFilter::addPage); + connect(m_actionInsertPageAfter, &QAction::triggered, this, &QTabWidgetEventFilter::addPageAfter); + connect(m_actionDeletePage, &QAction::triggered, this, &QTabWidgetEventFilter::removeCurrentPage); +} + +QTabWidgetEventFilter::~QTabWidgetEventFilter() = default; + +void QTabWidgetEventFilter::install(QTabWidget *tabWidget) +{ + new QTabWidgetEventFilter(tabWidget); +} + +QTabWidgetEventFilter *QTabWidgetEventFilter::eventFilterOf(const QTabWidget *tabWidget) +{ + // Look for 1st order children only..otherwise, we might get filters of nested tab widgets + for (QObject *o : tabWidget->children()) { + if (!o->isWidgetType()) + if (QTabWidgetEventFilter *ef = qobject_cast(o)) + return ef; + } + return nullptr; +} + +QMenu *QTabWidgetEventFilter::addTabWidgetContextMenuActions(const QTabWidget *tabWidget, QMenu *popup) +{ + QTabWidgetEventFilter *filter = eventFilterOf(tabWidget); + if (!filter) + return nullptr; + return filter->addContextMenuActions(popup); +} + +QTabBar *QTabWidgetEventFilter::tabBar() const +{ + // QTabWidget::tabBar() accessor is protected, grmbl... + if (!m_cachedTabBar) { + const auto tabBars = m_tabWidget->findChildren(); + Q_ASSERT(tabBars.size() == 1); + m_cachedTabBar = tabBars.constFirst(); + } + return m_cachedTabBar; + +} + +static bool canMove(const QPoint &pressPoint, const QMouseEvent *e) +{ + const QPoint pt = pressPoint - e->position().toPoint(); + return pt.manhattanLength() > QApplication::startDragDistance(); +} + +bool QTabWidgetEventFilter::eventFilter(QObject *o, QEvent *e) +{ + const QEvent::Type type = e->type(); + // Do not try to locate tab bar and form window, etc. for uninteresting events and + // avoid asserts about missing tab bars when being destroyed + switch (type) { + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + case QEvent::DragLeave: + case QEvent::DragEnter: + case QEvent::DragMove: + case QEvent::Drop: + break; + default: + return false; + } + + if (o != tabBar()) + return false; + + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return false; + + bool handled = true; + switch (type) { + case QEvent::MouseButtonDblClick: + break; + case QEvent::MouseButtonPress: { + QMouseEvent *mev = static_cast(e); + if (QDesignerFormWindowInterface *fw = formWindow()) { + fw->clearSelection(); + fw->selectWidget(m_tabWidget, true); + } + if (mev->button() & Qt::LeftButton) { + m_mousePressed = true; + m_pressPoint = mev->position().toPoint(); + + QTabBar *tabbar = tabBar(); + const int count = tabbar->count(); + for (int i = 0; i < count; ++i) { + if (tabbar->tabRect(i).contains(m_pressPoint)) { + if (i != tabbar->currentIndex()) { + qdesigner_internal::SetPropertyCommand *cmd = new qdesigner_internal::SetPropertyCommand(fw); + cmd->init(m_tabWidget, u"currentIndex"_s, i); + fw->commandHistory()->push(cmd); + } + break; + } + } + } + } break; + + case QEvent::MouseButtonRelease: + m_mousePressed = false; + break; + + case QEvent::MouseMove: { + QMouseEvent *mouseEvent = static_cast(e); + if (m_mousePressed && canMove(m_pressPoint, mouseEvent)) { + const int index = m_tabWidget->currentIndex(); + if (index == -1) + break; + + m_mousePressed = false; + QDrag *drg = new QDrag(m_tabWidget); + drg->setMimeData(new qdesigner_internal::MyMimeData(m_tabWidget)); + + m_dragIndex = index; + m_dragPage = m_tabWidget->currentWidget(); + m_dragLabel = m_tabWidget->tabText(index); + m_dragIcon = m_tabWidget->tabIcon(index); + if (m_dragIcon.isNull()) { + QLabel *label = new QLabel(m_dragLabel); + label->adjustSize(); + drg->setPixmap(label->grab(QRect(0, 0, -1, -1))); + label->deleteLater(); + } else { + drg->setPixmap(m_dragIcon.pixmap(22, 22)); + } + + m_tabWidget->removeTab(m_dragIndex); + + const Qt::DropActions dropAction = drg->exec(Qt::MoveAction); + + if (dropAction == Qt::IgnoreAction) { + // abort + m_tabWidget->insertTab(m_dragIndex, m_dragPage, m_dragIcon, m_dragLabel); + m_tabWidget->setCurrentIndex(m_dragIndex); + } + + if (m_dropIndicator) + m_dropIndicator->hide(); + } + } break; + + case QEvent::DragLeave: { + if (m_dropIndicator) + m_dropIndicator->hide(); + } break; + + case QEvent::DragEnter: + case QEvent::DragMove: { + QDragMoveEvent *de = static_cast(e); + if (!qdesigner_internal::MyMimeData::fromMyTab(de->mimeData(), m_tabWidget)) + return false; + + if (de->proposedAction() == Qt::MoveAction) + de->acceptProposedAction(); + else { + de->setDropAction(Qt::MoveAction); + de->accept(); + } + + QRect rect; + const int index = pageFromPosition(de->position().toPoint(), rect); + + if (!m_dropIndicator) { + m_dropIndicator = new QWidget(m_tabWidget); + QPalette p = m_dropIndicator->palette(); + p.setColor(m_tabWidget->backgroundRole(), Qt::red); + m_dropIndicator->setPalette(p); + } + + QPoint pos; + if (index == m_tabWidget->count()) + pos = tabBar()->mapToParent(QPoint(rect.x() + rect.width(), rect.y())); + else + pos = tabBar()->mapToParent(QPoint(rect.x(), rect.y())); + + m_dropIndicator->setGeometry(pos.x(), pos.y() , 3, rect.height()); + m_dropIndicator->show(); + } break; + + case QEvent::Drop: { + QDropEvent *de = static_cast(e); + if (!qdesigner_internal::MyMimeData::fromMyTab(de->mimeData(), m_tabWidget)) + return false; + de->acceptProposedAction(); + de->accept(); + + QRect rect; + const int newIndex = pageFromPosition(de->position().toPoint(), rect); + + qdesigner_internal::MoveTabPageCommand *cmd = new qdesigner_internal::MoveTabPageCommand(fw); + m_tabWidget->insertTab(m_dragIndex, m_dragPage, m_dragIcon, m_dragLabel); + cmd->init(m_tabWidget, m_dragPage, m_dragIcon, m_dragLabel, m_dragIndex, newIndex); + fw->commandHistory()->push(cmd); + } break; + + default: + handled = false; + break; + } + + return handled; +} + +void QTabWidgetEventFilter::removeCurrentPage() +{ + if (!m_tabWidget->currentWidget()) + return; + + if (QDesignerFormWindowInterface *fw = formWindow()) { + qdesigner_internal::DeleteTabPageCommand *cmd = new qdesigner_internal::DeleteTabPageCommand(fw); + cmd->init(m_tabWidget); + fw->commandHistory()->push(cmd); + } +} + +void QTabWidgetEventFilter::addPage() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + qdesigner_internal::AddTabPageCommand *cmd = new qdesigner_internal::AddTabPageCommand(fw); + cmd->init(m_tabWidget, qdesigner_internal::AddTabPageCommand::InsertBefore); + fw->commandHistory()->push(cmd); + } +} + +void QTabWidgetEventFilter::addPageAfter() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + qdesigner_internal::AddTabPageCommand *cmd = new qdesigner_internal::AddTabPageCommand(fw); + cmd->init(m_tabWidget, qdesigner_internal::AddTabPageCommand::InsertAfter); + fw->commandHistory()->push(cmd); + } +} + +QDesignerFormWindowInterface *QTabWidgetEventFilter::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(const_cast(m_tabWidget)); +} + +// Get page from mouse position. Default to new page if in right half of last page? +int QTabWidgetEventFilter::pageFromPosition(const QPoint &pos, QRect &rect) const +{ + int index = 0; + const QTabBar *tabbar = tabBar(); + const int count = m_tabWidget->count(); + for (; index < count; index++) { + const QRect rc = tabbar->tabRect(index); + if (rc.contains(pos)) { + rect = rc; + break; + } + } + + if (index == count -1) { + QRect rect2 = rect; + rect2.setLeft(rect2.left() + rect2.width() / 2); + if (rect2.contains(pos)) + index++; + } + return index; +} + +QMenu *QTabWidgetEventFilter::addContextMenuActions(QMenu *popup) +{ + QMenu *pageMenu = nullptr; + const int count = m_tabWidget->count(); + m_actionDeletePage->setEnabled(count); + if (count) { + const int currentIndex = m_tabWidget->currentIndex(); + const QString pageSubMenuLabel = tr("Page %1 of %2").arg(currentIndex + 1).arg(count); + pageMenu = popup->addMenu(pageSubMenuLabel); + pageMenu->addAction(m_actionDeletePage); + // Set up promotion menu for current widget. + if (QWidget *page = m_tabWidget->currentWidget ()) { + m_pagePromotionTaskMenu->setWidget(page); + m_pagePromotionTaskMenu->addActions(QDesignerFormWindowInterface::findFormWindow(m_tabWidget), + qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit, + pageMenu); + } + QMenu *insertPageMenu = popup->addMenu(tr("Insert Page")); + insertPageMenu->addAction(m_actionInsertPageAfter); + insertPageMenu->addAction(m_actionInsertPage); + } else { + QAction *insertPageAction = popup->addAction(tr("Insert Page")); + connect(insertPageAction, &QAction::triggered, this, &QTabWidgetEventFilter::addPage); + } + popup->addSeparator(); + return pageMenu; +} + +// ----------- QTabWidgetPropertySheet + +static constexpr auto currentTabTextKey = "currentTabText"_L1; +static constexpr auto currentTabNameKey = "currentTabName"_L1; +static constexpr auto currentTabIconKey = "currentTabIcon"_L1; +static constexpr auto currentTabToolTipKey = "currentTabToolTip"_L1; +static constexpr auto currentTabWhatsThisKey = "currentTabWhatsThis"_L1; +static constexpr auto tabMovableKey = "movable"_L1; + +QTabWidgetPropertySheet::QTabWidgetPropertySheet(QTabWidget *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_tabWidget(object) +{ + createFakeProperty(currentTabTextKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + createFakeProperty(currentTabNameKey, QString()); + createFakeProperty(currentTabIconKey, QVariant::fromValue(qdesigner_internal::PropertySheetIconValue())); + if (formWindowBase()) + formWindowBase()->addReloadableProperty(this, indexOf(currentTabIconKey)); + createFakeProperty(currentTabToolTipKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + createFakeProperty(currentTabWhatsThisKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + // Prevent the tab widget's drag and drop handling from interfering with Designer's + createFakeProperty(tabMovableKey, QVariant(false)); +} + +QTabWidgetPropertySheet::TabWidgetProperty QTabWidgetPropertySheet::tabWidgetPropertyFromName(const QString &name) +{ + static const QHash tabWidgetPropertyHash = { + {currentTabTextKey, PropertyCurrentTabText}, + {currentTabNameKey, PropertyCurrentTabName}, + {currentTabIconKey, PropertyCurrentTabIcon}, + {currentTabToolTipKey, PropertyCurrentTabToolTip}, + {currentTabWhatsThisKey, PropertyCurrentTabWhatsThis} + }; + return tabWidgetPropertyHash.value(name, PropertyTabWidgetNone); +} + +void QTabWidgetPropertySheet::setProperty(int index, const QVariant &value) +{ + const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(propertyName(index)); + if (tabWidgetProperty == PropertyTabWidgetNone) { + QDesignerPropertySheet::setProperty(index, value); + return; + } + + // index-dependent + const int currentIndex = m_tabWidget->currentIndex(); + QWidget *currentWidget = m_tabWidget->currentWidget(); + if (!currentWidget) + return; + + switch (tabWidgetProperty) { + case PropertyCurrentTabText: + m_tabWidget->setTabText(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].text = qvariant_cast(value); + break; + case PropertyCurrentTabName: + currentWidget->setObjectName(value.toString()); + break; + case PropertyCurrentTabIcon: + m_tabWidget->setTabIcon(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].icon = qvariant_cast(value); + break; + case PropertyCurrentTabToolTip: + m_tabWidget->setTabToolTip(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].tooltip = qvariant_cast(value); + break; + case PropertyCurrentTabWhatsThis: + m_tabWidget->setTabWhatsThis(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].whatsthis = qvariant_cast(value); + break; + case PropertyTabWidgetNone: + break; + } +} + +bool QTabWidgetPropertySheet::isEnabled(int index) const +{ + if (tabWidgetPropertyFromName(propertyName(index)) == PropertyTabWidgetNone) + return QDesignerPropertySheet::isEnabled(index); + return m_tabWidget->currentIndex() != -1; +} + +QVariant QTabWidgetPropertySheet::property(int index) const +{ + const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(propertyName(index)); + if (tabWidgetProperty == PropertyTabWidgetNone) + return QDesignerPropertySheet::property(index); + + // index-dependent + QWidget *currentWidget = m_tabWidget->currentWidget(); + if (!currentWidget) { + if (tabWidgetProperty == PropertyCurrentTabIcon) + return QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()); + if (tabWidgetProperty == PropertyCurrentTabText) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + if (tabWidgetProperty == PropertyCurrentTabToolTip) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + if (tabWidgetProperty == PropertyCurrentTabWhatsThis) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + return QVariant(QString()); + } + + // index-dependent + switch (tabWidgetProperty) { + case PropertyCurrentTabText: + return QVariant::fromValue(m_pageToData.value(currentWidget).text); + case PropertyCurrentTabName: + return currentWidget->objectName(); + case PropertyCurrentTabIcon: + return QVariant::fromValue(m_pageToData.value(currentWidget).icon); + case PropertyCurrentTabToolTip: + return QVariant::fromValue(m_pageToData.value(currentWidget).tooltip); + case PropertyCurrentTabWhatsThis: + return QVariant::fromValue(m_pageToData.value(currentWidget).whatsthis); + case PropertyTabWidgetNone: + break; + } + return QVariant(); +} + +bool QTabWidgetPropertySheet::reset(int index) +{ + const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(propertyName(index)); + if (tabWidgetProperty == PropertyTabWidgetNone) + return QDesignerPropertySheet::reset(index); + + // index-dependent + QWidget *currentWidget = m_tabWidget->currentWidget(); + if (!currentWidget) + return false; + + // index-dependent + switch (tabWidgetProperty) { + case PropertyCurrentTabName: + setProperty(index, QString()); + break; + case PropertyCurrentTabToolTip: + m_pageToData[currentWidget].tooltip = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentTabWhatsThis: + m_pageToData[currentWidget].whatsthis = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentTabText: + m_pageToData[currentWidget].text = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentTabIcon: + m_pageToData[currentWidget].icon = qdesigner_internal::PropertySheetIconValue(); + setProperty(index, QIcon()); + break; + case PropertyTabWidgetNone: + break; + } + return true; +} + +bool QTabWidgetPropertySheet::checkProperty(const QString &propertyName) +{ + switch (tabWidgetPropertyFromName(propertyName)) { + case PropertyCurrentTabText: + case PropertyCurrentTabName: + case PropertyCurrentTabToolTip: + case PropertyCurrentTabWhatsThis: + case PropertyCurrentTabIcon: + return false; + default: + break; + } + return true; +} + +QT_END_NAMESPACE + +#include "qdesigner_tabwidget.moc" // required for MyMimeData diff --git a/src/designer/src/lib/shared/qdesigner_tabwidget_p.h b/src/designer/src/lib/shared/qdesigner_tabwidget_p.h new file mode 100644 index 0000000..430c695 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_tabwidget_p.h @@ -0,0 +1,116 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_TABWIDGET_H +#define QDESIGNER_TABWIDGET_H + +#include "shared_global_p.h" +#include "qdesigner_propertysheet_p.h" +#include "qdesigner_utils_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QTabWidget; +class QTabBar; +class QMenu; +class QAction; + +namespace qdesigner_internal { + class PromotionTaskMenu; +} + +class QDESIGNER_SHARED_EXPORT QTabWidgetEventFilter : public QObject +{ + Q_OBJECT +public: + explicit QTabWidgetEventFilter(QTabWidget *parent); + ~QTabWidgetEventFilter(); + + // Install helper on QTabWidget + static void install(QTabWidget *tabWidget); + static QTabWidgetEventFilter *eventFilterOf(const QTabWidget *tabWidget); + // Convenience to add a menu on a tackedWidget + static QMenu *addTabWidgetContextMenuActions(const QTabWidget *tabWidget, QMenu *popup); + + // Add context menu and return page submenu or 0. + QMenu *addContextMenuActions(QMenu *popup); + + bool eventFilter(QObject *o, QEvent *e) override; + + QDesignerFormWindowInterface *formWindow() const; + +private slots: + void removeCurrentPage(); + void addPage(); + void addPageAfter(); + +private: + int pageFromPosition(const QPoint &pos, QRect &rect) const; + QTabBar *tabBar() const; + + QTabWidget *m_tabWidget; + mutable QPointer m_cachedTabBar; + QPoint m_pressPoint; + QWidget *m_dropIndicator = nullptr; + int m_dragIndex = -1; + QWidget *m_dragPage = nullptr; + QString m_dragLabel; + QIcon m_dragIcon; + bool m_mousePressed = false; + QAction *m_actionDeletePage; + QAction *m_actionInsertPage; + QAction *m_actionInsertPageAfter; + qdesigner_internal::PromotionTaskMenu* m_pagePromotionTaskMenu; +}; + +// PropertySheet to handle the page properties +class QDESIGNER_SHARED_EXPORT QTabWidgetPropertySheet : public QDesignerPropertySheet { +public: + explicit QTabWidgetPropertySheet(QTabWidget *object, QObject *parent = nullptr); + + void setProperty(int index, const QVariant &value) override; + QVariant property(int index) const override; + bool reset(int index) override; + bool isEnabled(int index) const override; + + // Check whether the property is to be saved. Returns false for the page + // properties (as the property sheet has no concept of 'stored') + static bool checkProperty(const QString &propertyName); + +private: + enum TabWidgetProperty { PropertyCurrentTabText, PropertyCurrentTabName, PropertyCurrentTabIcon, + PropertyCurrentTabToolTip, PropertyCurrentTabWhatsThis, PropertyTabWidgetNone }; + + static TabWidgetProperty tabWidgetPropertyFromName(const QString &name); + QTabWidget *m_tabWidget; + struct PageData + { + qdesigner_internal::PropertySheetStringValue text; + qdesigner_internal::PropertySheetStringValue tooltip; + qdesigner_internal::PropertySheetStringValue whatsthis; + qdesigner_internal::PropertySheetIconValue icon; + }; + QHash m_pageToData; +}; + +using QTabWidgetPropertySheetFactory = QDesignerPropertySheetFactory; + +QT_END_NAMESPACE + +#endif // QDESIGNER_TABWIDGET_H diff --git a/src/designer/src/lib/shared/qdesigner_taskmenu.cpp b/src/designer/src/lib/shared/qdesigner_taskmenu.cpp new file mode 100644 index 0000000..8bdf01f --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_taskmenu.cpp @@ -0,0 +1,726 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_taskmenu_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_command2_p.h" +#include "richtexteditor_p.h" +#include "plaintexteditor_p.h" +#include "stylesheeteditor_p.h" +#include "qlayout_widget_p.h" +#include "layout_p.h" +#include "selectsignaldialog_p.h" +#include "spacer_widget_p.h" +#include "textpropertyeditor_p.h" +#include "promotiontaskmenu_p.h" +#include "metadatabase_p.h" +#include "signalslotdialog_p.h" +#include "qdesigner_membersheet_p.h" +#include "qdesigner_propertycommand_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_objectinspector_p.h" +#include "morphmenu_p.h" +#include "formlayoutmenu_p.h" +#include "widgetfactory_p.h" +#include "abstractintrospection_p.h" +#include "widgetdatabase_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static inline QAction *createSeparatorHelper(QObject *parent) { + QAction *rc = new QAction(parent); + rc->setSeparator(true); + return rc; +} + +static QString objName(const QDesignerFormEditorInterface *core, QObject *object) { + QDesignerPropertySheetExtension *sheet + = qt_extension(core->extensionManager(), object); + Q_ASSERT(sheet != nullptr); + + const int index = sheet->indexOf(u"objectName"_s); + const QVariant v = sheet->property(index); + if (v.canConvert()) + return v.value().value(); + return v.toString(); +} + +enum { ApplyMinimumWidth = 0x1, ApplyMinimumHeight = 0x2, ApplyMaximumWidth = 0x4, ApplyMaximumHeight = 0x8 }; + +namespace { +// --------------- ObjectNameDialog +class ObjectNameDialog : public QDialog +{ + public: + ObjectNameDialog(QWidget *parent, const QString &oldName); + QString newObjectName() const; + + private: + qdesigner_internal::TextPropertyEditor *m_editor; +}; + +ObjectNameDialog::ObjectNameDialog(QWidget *parent, const QString &oldName) + : QDialog(parent), + m_editor( new qdesigner_internal::TextPropertyEditor(this, qdesigner_internal::TextPropertyEditor::EmbeddingNone, + qdesigner_internal::ValidationObjectName)) +{ + setWindowTitle(QCoreApplication::translate("ObjectNameDialog", "Change Object Name")); + + QVBoxLayout *vboxLayout = new QVBoxLayout(this); + vboxLayout->addWidget(new QLabel(QCoreApplication::translate("ObjectNameDialog", "Object Name"))); + + m_editor->setText(oldName); + m_editor->selectAll(); + m_editor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + vboxLayout->addWidget(m_editor); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, + Qt::Horizontal, this); + QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); + okButton->setDefault(true); + vboxLayout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +QString ObjectNameDialog::newObjectName() const +{ + return m_editor->text(); +} +} // namespace + +namespace qdesigner_internal { + +// Sub menu displaying the alignment options of a widget in a managed +// grid/box layout cell. +class LayoutAlignmentMenu : public QObject { + Q_OBJECT +public: + explicit LayoutAlignmentMenu(QObject *parent); + + QAction *subMenuAction() const { return m_subMenuAction; } + + // Set up enabled state and checked actions according to widget (managed box/grid) + bool setAlignment(const QDesignerFormEditorInterface *core, QWidget *w); + + // Return the currently checked alignment + Qt::Alignment alignment() const; + +signals: + void changed(); + +private: + enum Actions { HorizNone, Left, HorizCenter, Right, VerticalNone, Top, VerticalCenter, Bottom }; + static QAction *createAction(const QString &text, int data, QMenu *menu, QActionGroup *ag); + + QAction *m_subMenuAction; + QActionGroup *m_horizGroup; + QActionGroup *m_verticalGroup; + QAction *m_actions[Bottom + 1]; +}; + +QAction *LayoutAlignmentMenu::createAction(const QString &text, int data, QMenu *menu, QActionGroup *ag) +{ + QAction * a = new QAction(text, nullptr); + a->setCheckable(true); + a->setData(QVariant(data)); + menu->addAction(a); + ag->addAction(a); + return a; +} + +LayoutAlignmentMenu::LayoutAlignmentMenu(QObject *parent) : QObject(parent), + m_subMenuAction(new QAction(QDesignerTaskMenu::tr("Layout Alignment"), parent)), + m_horizGroup(new QActionGroup(parent)), + m_verticalGroup(new QActionGroup(parent)) +{ + m_horizGroup->setExclusive(true); + m_verticalGroup->setExclusive(true); + connect(m_horizGroup, &QActionGroup::triggered, this, &LayoutAlignmentMenu::changed); + connect(m_verticalGroup, &QActionGroup::triggered, this, &LayoutAlignmentMenu::changed); + + QMenu *menu = new QMenu; + m_subMenuAction->setMenu(menu); + + m_actions[HorizNone] = createAction(QDesignerTaskMenu::tr("No Horizontal Alignment"), 0, menu, m_horizGroup); + m_actions[Left] = createAction(QDesignerTaskMenu::tr("Left"), Qt::AlignLeft, menu, m_horizGroup); + m_actions[HorizCenter] = createAction(QDesignerTaskMenu::tr("Center Horizontally"), Qt::AlignHCenter, menu, m_horizGroup); + m_actions[Right] = createAction(QDesignerTaskMenu::tr("Right"), Qt::AlignRight, menu, m_horizGroup); + menu->addSeparator(); + m_actions[VerticalNone] = createAction(QDesignerTaskMenu::tr("No Vertical Alignment"), 0, menu, m_verticalGroup); + m_actions[Top] = createAction(QDesignerTaskMenu::tr("Top"), Qt::AlignTop, menu, m_verticalGroup); + m_actions[VerticalCenter] = createAction(QDesignerTaskMenu::tr("Center Vertically"), Qt::AlignVCenter, menu, m_verticalGroup); + m_actions[Bottom] = createAction(QDesignerTaskMenu::tr("Bottom"), Qt::AlignBottom, menu, m_verticalGroup); +} + +bool LayoutAlignmentMenu::setAlignment(const QDesignerFormEditorInterface *core, QWidget *w) +{ + bool enabled; + const Qt::Alignment alignment = LayoutAlignmentCommand::alignmentOf(core, w, &enabled); + m_subMenuAction->setEnabled(enabled); + if (!enabled) { + m_actions[HorizNone]->setChecked(true); + m_actions[VerticalNone]->setChecked(true); + return false; + } + // Get alignment + switch (alignment & Qt::AlignHorizontal_Mask) { + case Qt::AlignLeft: + m_actions[Left]->setChecked(true); + break; + case Qt::AlignHCenter: + m_actions[HorizCenter]->setChecked(true); + break; + case Qt::AlignRight: + m_actions[Right]->setChecked(true); + break; + default: + m_actions[HorizNone]->setChecked(true); + break; + } + switch (alignment & Qt::AlignVertical_Mask) { + case Qt::AlignTop: + m_actions[Top]->setChecked(true); + break; + case Qt::AlignVCenter: + m_actions[VerticalCenter]->setChecked(true); + break; + case Qt::AlignBottom: + m_actions[Bottom]->setChecked(true); + break; + default: + m_actions[VerticalNone]->setChecked(true); + break; + } + return true; +} + +Qt::Alignment LayoutAlignmentMenu::alignment() const +{ + Qt::Alignment alignment; + if (const QAction *horizAction = m_horizGroup->checkedAction()) + if (const int horizAlign = horizAction->data().toInt()) + alignment |= static_cast(horizAlign); + if (const QAction *vertAction = m_verticalGroup->checkedAction()) + if (const int vertAlign = vertAction->data().toInt()) + alignment |= static_cast(vertAlign); + return alignment; +} + +// -------------- QDesignerTaskMenuPrivate +class QDesignerTaskMenuPrivate { +public: + QDesignerTaskMenuPrivate(QWidget *widget, QObject *parent); + + QDesignerTaskMenu *m_q; + QPointer m_widget; + QAction *m_separator; + QAction *m_separator2; + QAction *m_separator3; + QAction *m_separator4; + QAction *m_separator5; + QAction *m_separator6; + QAction *m_separator7; + QAction *m_changeObjectNameAction; + QAction *m_changeToolTip; + QAction *m_changeWhatsThis; + QAction *m_changeStyleSheet; + MorphMenu *m_morphMenu; + FormLayoutMenu *m_formLayoutMenu; + + QAction *m_addMenuBar; + QAction *m_addToolBar; + QAction *m_addAreaSubMenu; + QAction *m_addStatusBar; + QAction *m_removeStatusBar; + QAction *m_containerFakeMethods; + QAction *m_navigateToSlot; + PromotionTaskMenu* m_promotionTaskMenu; + QActionGroup *m_sizeActionGroup; + LayoutAlignmentMenu m_layoutAlignmentMenu; + QAction *m_sizeActionsSubMenu; +}; + +QDesignerTaskMenuPrivate::QDesignerTaskMenuPrivate(QWidget *widget, QObject *parent) : + m_q(nullptr), + m_widget(widget), + m_separator(createSeparatorHelper(parent)), + m_separator2(createSeparatorHelper(parent)), + m_separator3(createSeparatorHelper(parent)), + m_separator4(createSeparatorHelper(parent)), + m_separator5(createSeparatorHelper(parent)), + m_separator6(createSeparatorHelper(parent)), + m_separator7(createSeparatorHelper(parent)), + m_changeObjectNameAction(new QAction(QDesignerTaskMenu::tr("Change objectName..."), parent)), + m_changeToolTip(new QAction(QDesignerTaskMenu::tr("Change toolTip..."), parent)), + m_changeWhatsThis(new QAction(QDesignerTaskMenu::tr("Change whatsThis..."), parent)), + m_changeStyleSheet(new QAction(QDesignerTaskMenu::tr("Change styleSheet..."), parent)), + m_morphMenu(new MorphMenu(parent)), + m_formLayoutMenu(new FormLayoutMenu(parent)), + m_addMenuBar(new QAction(QDesignerTaskMenu::tr("Create Menu Bar"), parent)), + m_addToolBar(new QAction(QDesignerTaskMenu::tr("Add Tool Bar"), parent)), + m_addAreaSubMenu(new QAction(QDesignerTaskMenu::tr("Add Tool Bar to Other Area"), parent)), + m_addStatusBar(new QAction(QDesignerTaskMenu::tr("Create Status Bar"), parent)), + m_removeStatusBar(new QAction(QDesignerTaskMenu::tr("Remove Status Bar"), parent)), + m_containerFakeMethods(new QAction(QDesignerTaskMenu::tr("Change signals/slots..."), parent)), + m_navigateToSlot(new QAction(QDesignerTaskMenu::tr("Go to slot..."), parent)), + m_promotionTaskMenu(new PromotionTaskMenu(widget, PromotionTaskMenu::ModeManagedMultiSelection, parent)), + m_sizeActionGroup(new QActionGroup(parent)), + m_layoutAlignmentMenu(parent), + m_sizeActionsSubMenu(new QAction(QDesignerTaskMenu::tr("Size Constraints"), parent)) +{ + QMenu *sizeMenu = new QMenu; + m_sizeActionsSubMenu->setMenu(sizeMenu); + QAction *sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Minimum Width")); + sizeAction->setData(ApplyMinimumWidth); + sizeMenu->addAction(sizeAction); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Minimum Height")); + sizeAction->setData(ApplyMinimumHeight); + sizeMenu->addAction(sizeAction); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Minimum Size")); + sizeAction->setData(ApplyMinimumWidth|ApplyMinimumHeight); + sizeMenu->addAction(sizeAction); + + sizeMenu->addSeparator(); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Maximum Width")); + sizeAction->setData(ApplyMaximumWidth); + sizeMenu->addAction(sizeAction); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Maximum Height")); + sizeAction->setData(ApplyMaximumHeight); + sizeMenu->addAction(sizeAction); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Maximum Size")); + sizeAction->setData(ApplyMaximumWidth|ApplyMaximumHeight); + sizeMenu->addAction(sizeAction); +} + +// --------- QDesignerTaskMenu +QDesignerTaskMenu::QDesignerTaskMenu(QWidget *widget, QObject *parent) : + QObject(parent), + d(new QDesignerTaskMenuPrivate(widget, parent)) +{ + d->m_q = this; + Q_ASSERT(qobject_cast(widget) == 0); + + connect(d->m_changeObjectNameAction, &QAction::triggered, this, &QDesignerTaskMenu::changeObjectName); + connect(d->m_changeToolTip, &QAction::triggered, this, &QDesignerTaskMenu::changeToolTip); + connect(d->m_changeWhatsThis, &QAction::triggered, this, &QDesignerTaskMenu::changeWhatsThis); + connect(d->m_changeStyleSheet, &QAction::triggered, this, &QDesignerTaskMenu::changeStyleSheet); + connect(d->m_addMenuBar, &QAction::triggered, this, &QDesignerTaskMenu::createMenuBar); + connect(d->m_addToolBar, &QAction::triggered, this, + [this] () { this->addToolBar(Qt::TopToolBarArea); }); + auto areaMenu = new QMenu; + d->m_addAreaSubMenu->setMenu(areaMenu); + areaMenu->addAction(QDesignerTaskMenu::tr("Left"), this, + [this] () { this->addToolBar(Qt::LeftToolBarArea); }); + areaMenu->addAction(QDesignerTaskMenu::tr("Right"), this, + [this] () { this->addToolBar(Qt::RightToolBarArea); }); + areaMenu->addAction(QDesignerTaskMenu::tr("Bottom"), this, + [this] () { this->addToolBar(Qt::BottomToolBarArea); }); + connect(d->m_addStatusBar, &QAction::triggered, this, &QDesignerTaskMenu::createStatusBar); + connect(d->m_removeStatusBar, &QAction::triggered, this, &QDesignerTaskMenu::removeStatusBar); + connect(d->m_containerFakeMethods, &QAction::triggered, this, &QDesignerTaskMenu::containerFakeMethods); + connect(d->m_navigateToSlot, &QAction::triggered, this, &QDesignerTaskMenu::slotNavigateToSlot); + connect(d->m_sizeActionGroup, &QActionGroup::triggered, this, &QDesignerTaskMenu::applySize); + connect(&d->m_layoutAlignmentMenu, &LayoutAlignmentMenu::changed, + this, &QDesignerTaskMenu::slotLayoutAlignment); +} + +QDesignerTaskMenu::~QDesignerTaskMenu() +{ + delete d; +} + +QAction *QDesignerTaskMenu::createSeparator() +{ + return createSeparatorHelper(this); +} + +QWidget *QDesignerTaskMenu::widget() const +{ + return d->m_widget; +} + +QDesignerFormWindowInterface *QDesignerTaskMenu::formWindow() const +{ + QDesignerFormWindowInterface *result = QDesignerFormWindowInterface::findFormWindow(widget()); + Q_ASSERT(result != nullptr); + return result; +} + +void QDesignerTaskMenu::createMenuBar() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QMainWindow *mw = qobject_cast(fw->mainContainer()); + if (!mw) { + // ### warning message + return; + } + + CreateMenuBarCommand *cmd = new CreateMenuBarCommand(fw); + cmd->init(mw); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerTaskMenu::addToolBar(Qt::ToolBarArea area) +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QMainWindow *mw = qobject_cast(fw->mainContainer()); + if (!mw) { + // ### warning message + return; + } + + AddToolBarCommand *cmd = new AddToolBarCommand(fw); + cmd->init(mw, area); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerTaskMenu::createStatusBar() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QMainWindow *mw = qobject_cast(fw->mainContainer()); + if (!mw) { + // ### warning message + return; + } + + CreateStatusBarCommand *cmd = new CreateStatusBarCommand(fw); + cmd->init(mw); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerTaskMenu::removeStatusBar() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QMainWindow *mw = qobject_cast(fw->mainContainer()); + if (!mw) { + // ### warning message + return; + } + + DeleteStatusBarCommand *cmd = new DeleteStatusBarCommand(fw); + cmd->init(mw->findChild(QString(), Qt::FindDirectChildrenOnly)); + fw->commandHistory()->push(cmd); + } +} + +QList QDesignerTaskMenu::taskActions() const +{ + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(widget()); + Q_ASSERT(formWindow); + + const bool isMainContainer = formWindow->mainContainer() == widget(); + + QList actions; + + if (const QMainWindow *mw = qobject_cast(formWindow->mainContainer())) { + if (isMainContainer || mw->centralWidget() == widget()) { + if (mw->findChild(QString(), Qt::FindDirectChildrenOnly) == nullptr) + actions.append(d->m_addMenuBar); + + actions.append(d->m_addToolBar); + actions.append(d->m_addAreaSubMenu); + // ### create the status bar + if (mw->findChild(QString(), Qt::FindDirectChildrenOnly)) + actions.append(d->m_removeStatusBar); + else + actions.append(d->m_addStatusBar); + + actions.append(d->m_separator); + } + } + actions.append(d->m_changeObjectNameAction); + d->m_morphMenu->populate(d->m_widget, formWindow, actions); + d->m_formLayoutMenu->populate(d->m_widget, formWindow, actions); + actions.append(d->m_separator2); + actions.append(d->m_changeToolTip); + actions.append(d->m_changeWhatsThis); + actions.append(d->m_changeStyleSheet); + actions.append(d->m_separator6); + actions.append(d->m_sizeActionsSubMenu); + if (d->m_layoutAlignmentMenu.setAlignment(formWindow->core(), d->m_widget)) + actions.append(d->m_layoutAlignmentMenu.subMenuAction()); + + d->m_promotionTaskMenu->setMode(formWindow->isManaged(d->m_widget) ? + PromotionTaskMenu::ModeManagedMultiSelection : PromotionTaskMenu::ModeUnmanagedMultiSelection); + d->m_promotionTaskMenu->addActions(formWindow, PromotionTaskMenu::LeadingSeparator, actions); + + if (isMainContainer && !qt_extension(formWindow->core()->extensionManager(), formWindow->core())) { + actions.append(d->m_separator5); + actions.append(d->m_containerFakeMethods); + } + + if (isSlotNavigationEnabled(formWindow->core())) { + actions.append(d->m_separator7); + actions.append(d->m_navigateToSlot); + } + + return actions; +} + +void QDesignerTaskMenu::changeObjectName() +{ + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw != nullptr); + + const QString oldObjectName = objName(fw->core(), widget()); + + ObjectNameDialog dialog(fw, oldObjectName); + if (dialog.exec() == QDialog::Accepted) { + const QString newObjectName = dialog.newObjectName(); + if (!newObjectName.isEmpty() && newObjectName != oldObjectName ) { + PropertySheetStringValue objectNameValue; + objectNameValue.setValue(newObjectName); + setProperty(fw, CurrentWidgetMode, u"objectName"_s, + QVariant::fromValue(objectNameValue)); + } + } +} + +void QDesignerTaskMenu::changeTextProperty(const QString &propertyName, const QString &windowTitle, PropertyMode pm, Qt::TextFormat desiredFormat) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + Q_ASSERT(d->m_widget->parentWidget() != nullptr); + + const QDesignerPropertySheetExtension *sheet = qt_extension(fw->core()->extensionManager(), d->m_widget); + const int index = sheet->indexOf(propertyName); + if (index == -1) { + qDebug() << "** WARNING Invalid property" << propertyName << " passed to changeTextProperty!"; + return; + } + PropertySheetStringValue textValue = qvariant_cast(sheet->property(index)); + const QString oldText = textValue.value(); + // Pop up respective dialog + bool accepted = false; + QString newText; + switch (desiredFormat) { + case Qt::PlainText: { + PlainTextEditorDialog dlg(fw->core(), fw); + if (!windowTitle.isEmpty()) + dlg.setWindowTitle(windowTitle); + dlg.setDefaultFont(d->m_widget->font()); + dlg.setText(oldText); + accepted = dlg.showDialog() == QDialog::Accepted; + newText = dlg.text(); + } + break; + default: { + RichTextEditorDialog dlg(fw->core(), fw); + if (!windowTitle.isEmpty()) + dlg.setWindowTitle(windowTitle); + dlg.setDefaultFont(d->m_widget->font()); + dlg.setText(oldText); + accepted = dlg.showDialog() == QDialog::Accepted; + newText = dlg.text(desiredFormat); + } + break; + } + // change property + if (!accepted || oldText == newText) + return; + + + textValue.setValue(newText); + setProperty(fw, pm, propertyName, QVariant::fromValue(textValue)); +} + +void QDesignerTaskMenu::changeToolTip() +{ + changeTextProperty(u"toolTip"_s, tr("Edit ToolTip"), MultiSelectionMode, Qt::AutoText); +} + +void QDesignerTaskMenu::changeWhatsThis() +{ + changeTextProperty(u"whatsThis"_s, tr("Edit WhatsThis"), MultiSelectionMode, Qt::AutoText); +} + +void QDesignerTaskMenu::changeStyleSheet() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + StyleSheetPropertyEditorDialog dlg(fw, fw, d->m_widget); + dlg.exec(); + } +} + +void QDesignerTaskMenu::containerFakeMethods() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + SignalSlotDialog::editMetaDataBase(fw, d->m_widget, fw); +} + +bool QDesignerTaskMenu::isSlotNavigationEnabled(const QDesignerFormEditorInterface *core) +{ + return core->integration()->hasFeature(QDesignerIntegration::SlotNavigationFeature); +} + +void QDesignerTaskMenu::slotNavigateToSlot() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + Q_ASSERT(core); + navigateToSlot(core, widget()); +} + +void QDesignerTaskMenu::navigateToSlot(QDesignerFormEditorInterface *core, + QObject *object, + const QString &defaultSignal) +{ + SelectSignalDialog dialog; + dialog.populate(core, object, defaultSignal); + if (dialog.exec() != QDialog::Accepted) + return; + // TODO: Check whether signal is connected to slot + const SelectSignalDialog::Method method = dialog.selectedMethod(); + if (method.isValid()) { + core->integration()->emitNavigateToSlot(objName(core, object), + method.signature, + method.parameterNames); + } +} + +// Add a command that takes over the value of the current geometry as +// minimum/maximum size according to the flags. +static void createSizeCommand(QDesignerFormWindowInterface *fw, QWidget *w, int flags) +{ + const QSize size = w->size(); + if (flags & (ApplyMinimumWidth|ApplyMinimumHeight)) { + QSize minimumSize = w-> minimumSize(); + if (flags & ApplyMinimumWidth) + minimumSize.setWidth(size.width()); + if (flags & ApplyMinimumHeight) + minimumSize.setHeight(size.height()); + SetPropertyCommand* cmd = new SetPropertyCommand(fw); + cmd->init(w, u"minimumSize"_s, minimumSize); + fw->commandHistory()->push(cmd); + } + if (flags & (ApplyMaximumWidth|ApplyMaximumHeight)) { + QSize maximumSize = w-> maximumSize(); + if (flags & ApplyMaximumWidth) + maximumSize.setWidth(size.width()); + if (flags & ApplyMaximumHeight) + maximumSize.setHeight(size.height()); + SetPropertyCommand* cmd = new SetPropertyCommand(fw); + cmd->init(w, u"maximumSize"_s, maximumSize); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerTaskMenu::applySize(QAction *a) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + + const QWidgetList selection = applicableWidgets(fw, MultiSelectionMode); + if (selection.isEmpty()) + return; + + const int mask = a->data().toInt(); + fw->commandHistory()->beginMacro(tr("Set size constraint on %n widget(s)", nullptr, + int(selection.size()))); + for (auto *w : selection) + createSizeCommand(fw, w, mask); + fw->commandHistory()->endMacro(); +} + +template + static void getApplicableObjects(const QDesignerFormWindowInterface *fw, QWidget *current, + QDesignerTaskMenu::PropertyMode pm, Container *c) +{ + // Current is always first + c->push_back(current); + if (pm == QDesignerTaskMenu::CurrentWidgetMode) + return; + QDesignerObjectInspector *designerObjectInspector = qobject_cast(fw->core()->objectInspector()); + if (!designerObjectInspector) + return; // Ooops, someone plugged an old-style Object Inspector + // Add managed or unmanaged selection according to current type, make current first + Selection s; + designerObjectInspector->getSelection(s); + const QWidgetList &source = fw->isManaged(current) ? s.managed : s.unmanaged; + for (auto *w : source) { + if (w != current) // was first + c->append(w); + } +} + +QObjectList QDesignerTaskMenu::applicableObjects(const QDesignerFormWindowInterface *fw, PropertyMode pm) const +{ + QObjectList rc; + getApplicableObjects(fw, d->m_widget, pm, &rc); + return rc; +} + +QWidgetList QDesignerTaskMenu::applicableWidgets(const QDesignerFormWindowInterface *fw, PropertyMode pm) const +{ + QWidgetList rc; + getApplicableObjects(fw, d->m_widget, pm, &rc); + return rc; +} + +void QDesignerTaskMenu::setProperty(QDesignerFormWindowInterface *fw, PropertyMode pm, const QString &name, const QVariant &newValue) +{ + SetPropertyCommand* setPropertyCommand = new SetPropertyCommand(fw); + if (setPropertyCommand->init(applicableObjects(fw, pm), name, newValue, d->m_widget)) { + fw->commandHistory()->push(setPropertyCommand); + } else { + delete setPropertyCommand; + qDebug() << "Unable to set property " << name << '.'; + } +} + +void QDesignerTaskMenu::slotLayoutAlignment() +{ + QDesignerFormWindowInterface *fw = formWindow(); + const Qt::Alignment newAlignment = d->m_layoutAlignmentMenu.alignment(); + LayoutAlignmentCommand *cmd = new LayoutAlignmentCommand(fw); + if (cmd->init(d->m_widget, newAlignment)) { + fw->commandHistory()->push(cmd); + } else { + delete cmd; + } +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "qdesigner_taskmenu.moc" diff --git a/src/designer/src/lib/shared/qdesigner_taskmenu_p.h b/src/designer/src/lib/shared/qdesigner_taskmenu_p.h new file mode 100644 index 0000000..9c504f8 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_taskmenu_p.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_TASKMENU_H +#define QDESIGNER_TASKMENU_H + +#include "shared_global_p.h" +#include "extensionfactory_p.h" +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { +class QDesignerTaskMenuPrivate; + +class QDESIGNER_SHARED_EXPORT QDesignerTaskMenu: public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + QDesignerTaskMenu(QWidget *widget, QObject *parent); + ~QDesignerTaskMenu() override; + + QWidget *widget() const; + + QList taskActions() const override; + + enum PropertyMode { CurrentWidgetMode, MultiSelectionMode }; + + static bool isSlotNavigationEnabled(const QDesignerFormEditorInterface *core); + static void navigateToSlot(QDesignerFormEditorInterface *core, QObject *o, + const QString &defaultSignal = QString()); + +protected: + + QDesignerFormWindowInterface *formWindow() const; + void changeTextProperty(const QString &propertyName, const QString &windowTitle, PropertyMode pm, Qt::TextFormat desiredFormat); + + QAction *createSeparator(); + + /* Retrieve the list of objects the task menu is supposed to act on. Note that a task menu can be invoked for + * an unmanaged widget [as of 4.5], in which case it must not use the cursor selection, + * but the unmanaged selection of the object inspector. */ + QObjectList applicableObjects(const QDesignerFormWindowInterface *fw, PropertyMode pm) const; + QWidgetList applicableWidgets(const QDesignerFormWindowInterface *fw, PropertyMode pm) const; + + void setProperty(QDesignerFormWindowInterface *fw, PropertyMode pm, const QString &name, const QVariant &newValue); + +private slots: + void changeObjectName(); + void changeToolTip(); + void changeWhatsThis(); + void changeStyleSheet(); + void createMenuBar(); + void addToolBar(Qt::ToolBarArea area); + void createStatusBar(); + void removeStatusBar(); + void containerFakeMethods(); + void slotNavigateToSlot(); + void applySize(QAction *a); + void slotLayoutAlignment(); + +private: + QDesignerTaskMenuPrivate *d; +}; + +using QDesignerTaskMenuFactory = ExtensionFactory; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_TASKMENU_H diff --git a/src/designer/src/lib/shared/qdesigner_toolbar.cpp b/src/designer/src/lib/shared/qdesigner_toolbar.cpp new file mode 100644 index 0000000..a5d75d2 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_toolbar.cpp @@ -0,0 +1,463 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_toolbar_p.h" +#include "qdesigner_command_p.h" +#include "actionrepository_p.h" +#include "actionprovider_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_objectinspector_p.h" +#include "promotiontaskmenu_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using ActionList = QList; + +namespace qdesigner_internal { +// ------------------- ToolBarEventFilter +void ToolBarEventFilter::install(QToolBar *tb) +{ + ToolBarEventFilter *tf = new ToolBarEventFilter(tb); + tb->installEventFilter(tf); + tb->setAcceptDrops(true); // ### fake +} + +ToolBarEventFilter::ToolBarEventFilter(QToolBar *tb) : + QObject(tb), + m_toolBar(tb), + m_promotionTaskMenu(nullptr) +{ +} + +ToolBarEventFilter *ToolBarEventFilter::eventFilterOf(const QToolBar *tb) +{ + // Look for 1st order children only..otherwise, we might get filters of nested widgets + for (QObject *o : tb->children()) { + if (!o->isWidgetType()) + if (ToolBarEventFilter *ef = qobject_cast(o)) + return ef; + } + return nullptr; +} + +bool ToolBarEventFilter::eventFilter (QObject *watched, QEvent *event) +{ + if (watched != m_toolBar) + return QObject::eventFilter (watched, event); + + bool handled = false; + switch (event->type()) { + case QEvent::ChildAdded: { + // Children should not interact with the mouse + const QChildEvent *ce = static_cast(event); + if (QWidget *w = qobject_cast(ce->child())) { + w->setAttribute(Qt::WA_TransparentForMouseEvents, true); + w->setFocusPolicy(Qt::NoFocus); + } + } + break; + case QEvent::ContextMenu: + handled = handleContextMenuEvent(static_cast(event)); + break; + case QEvent::DragEnter: + case QEvent::DragMove: + handled = handleDragEnterMoveEvent(static_cast(event)); + break; + case QEvent::DragLeave: + handled = handleDragLeaveEvent(static_cast(event)); + break; + case QEvent::Drop: + handled = handleDropEvent(static_cast(event)); + break; + case QEvent::MouseButtonPress: + handled = handleMousePressEvent(static_cast(event)); + break; + case QEvent::MouseButtonRelease: + handled = handleMouseReleaseEvent(static_cast(event)); + break; + case QEvent::MouseMove: + handled = handleMouseMoveEvent(static_cast(event)); + break; + default: + break; + } + + return handled || QObject::eventFilter(watched, event); +} + +ActionList ToolBarEventFilter::contextMenuActions(const QPoint &globalPos) +{ + ActionList rc; + const int index = actionIndexAt(m_toolBar, m_toolBar->mapFromGlobal(globalPos), m_toolBar->orientation()); + const auto actions = m_toolBar->actions(); + QAction *action = index != -1 ?actions.at(index) : 0; + QVariant itemData; + + // Insert before + if (action && index != 0 && !action->isSeparator()) { + QAction *newSeperatorAct = new QAction(tr("Insert Separator before '%1'").arg(action->objectName()), nullptr); + itemData.setValue(action); + newSeperatorAct->setData(itemData); + connect(newSeperatorAct, &QAction::triggered, this, &ToolBarEventFilter::slotInsertSeparator); + rc.push_back(newSeperatorAct); + } + + // Append separator + if (actions.isEmpty() || !actions.constLast()->isSeparator()) { + QAction *newSeperatorAct = new QAction(tr("Append Separator"), nullptr); + itemData.setValue(static_cast(nullptr)); + newSeperatorAct->setData(itemData); + connect(newSeperatorAct, &QAction::triggered, this, &ToolBarEventFilter::slotInsertSeparator); + rc.push_back(newSeperatorAct); + } + // Promotion + if (!m_promotionTaskMenu) + m_promotionTaskMenu = new PromotionTaskMenu(m_toolBar, PromotionTaskMenu::ModeSingleWidget, this); + m_promotionTaskMenu->addActions(formWindow(), PromotionTaskMenu::LeadingSeparator|PromotionTaskMenu::TrailingSeparator, rc); + // Remove + if (action) { + QAction *a = new QAction(tr("Remove action '%1'").arg(action->objectName()), nullptr); + itemData.setValue(action); + a->setData(itemData); + connect(a, &QAction::triggered, this, &ToolBarEventFilter::slotRemoveSelectedAction); + rc.push_back(a); + } + + QAction *remove_toolbar = new QAction(tr("Remove Toolbar '%1'").arg(m_toolBar->objectName()), nullptr); + connect(remove_toolbar, &QAction::triggered, this, &ToolBarEventFilter::slotRemoveToolBar); + rc.push_back(remove_toolbar); + return rc; +} + +bool ToolBarEventFilter::handleContextMenuEvent(QContextMenuEvent * event ) +{ + event->accept(); + + const QPoint globalPos = event->globalPos(); + const ActionList al = contextMenuActions(event->globalPos()); + + QMenu menu(nullptr); + for (auto *a : al) + menu.addAction(a); + menu.exec(globalPos); + return true; +} + +void ToolBarEventFilter::slotRemoveSelectedAction() +{ + QAction *action = qobject_cast(sender()); + if (!action) + return; + + QAction *a = qvariant_cast(action->data()); + Q_ASSERT(a != nullptr); + + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw); + + const ActionList actions = m_toolBar->actions(); + const int pos = actions.indexOf(a); + QAction *action_before = nullptr; + if (pos != -1 && actions.size() > pos + 1) + action_before = actions.at(pos + 1); + + RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw); + cmd->init(m_toolBar, a, action_before); + fw->commandHistory()->push(cmd); +} + +void ToolBarEventFilter::slotRemoveToolBar() +{ + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw); + DeleteToolBarCommand *cmd = new DeleteToolBarCommand(fw); + cmd->init(m_toolBar); + fw->commandHistory()->push(cmd); +} + +void ToolBarEventFilter::slotInsertSeparator() +{ + QDesignerFormWindowInterface *fw = formWindow(); + QAction *theSender = qobject_cast(sender()); + QAction *previous = qvariant_cast(theSender->data()); + fw->beginCommand(tr("Insert Separator")); + QAction *action = createAction(fw, u"separator"_s, true); + InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); + cmd->init(m_toolBar, action, previous); + fw->commandHistory()->push(cmd); + fw->endCommand(); +} + +QDesignerFormWindowInterface *ToolBarEventFilter::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(m_toolBar); +} + +QAction *ToolBarEventFilter::createAction(QDesignerFormWindowInterface *fw, const QString &objectName, bool separator) +{ + QAction *action = new QAction(fw); + fw->core()->widgetFactory()->initialize(action); + if (separator) + action->setSeparator(true); + + action->setObjectName(objectName); + fw->ensureUniqueObjectName(action); + + qdesigner_internal::AddActionCommand *cmd = new qdesigner_internal::AddActionCommand(fw); + cmd->init(action); + fw->commandHistory()->push(cmd); + + return action; +} + +void ToolBarEventFilter::adjustDragIndicator(const QPoint &pos) +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QDesignerFormEditorInterface *core = fw->core(); + if (QDesignerActionProviderExtension *a = qt_extension(core->extensionManager(), m_toolBar)) + a->adjustIndicator(pos); + } +} + +void ToolBarEventFilter::hideDragIndicator() +{ + adjustDragIndicator(QPoint(-1, -1)); +} + +bool ToolBarEventFilter::handleMousePressEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton || withinHandleArea(m_toolBar, event->position().toPoint())) + return false; + + if (QDesignerFormWindowInterface *fw = formWindow()) { + QDesignerFormEditorInterface *core = fw->core(); + // Keep selection in sync + fw->clearSelection(false); + if (QDesignerObjectInspector *oi = qobject_cast(core->objectInspector())) { + oi->clearSelection(); + oi->selectObject(m_toolBar); + } + core->propertyEditor()->setObject(m_toolBar); + } + const auto pos = m_toolBar->mapFromGlobal(event->globalPosition().toPoint()); + if (actionIndexAt(m_toolBar, pos, m_toolBar->orientation()) != -1) { + m_startPosition = pos; + event->accept(); + return true; + } + return false; +} + +bool ToolBarEventFilter::handleMouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton || m_startPosition.isNull() || withinHandleArea(m_toolBar, event->position().toPoint())) + return false; + + // Accept the event, otherwise, form window selection will trigger + m_startPosition = QPoint(); + event->accept(); + return true; +} + +bool ToolBarEventFilter::handleMouseMoveEvent(QMouseEvent *event) +{ + if (m_startPosition.isNull() || withinHandleArea(m_toolBar, event->position().toPoint())) + return false; + + const QPoint pos = m_toolBar->mapFromGlobal(event->globalPosition().toPoint()); + if ((pos - m_startPosition).manhattanLength() > QApplication::startDragDistance() + && startDrag(m_startPosition, event->modifiers())) { + m_startPosition = QPoint(); + event->accept(); + return true; + } + return false; +} + +bool ToolBarEventFilter::handleDragEnterMoveEvent(QDragMoveEvent *event) +{ + const ActionRepositoryMimeData *d = qobject_cast(event->mimeData()); + if (!d) + return false; + + if (d->actionList().isEmpty()) { + event->ignore(); + hideDragIndicator(); + return true; + } + + QAction *action = d->actionList().first(); + if (!action || action->menu() || m_toolBar->actions().contains(action) || !Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) { + event->ignore(); + hideDragIndicator(); + return true; + } + + d->accept(event); + adjustDragIndicator(event->position().toPoint()); + return true; +} + +bool ToolBarEventFilter::handleDragLeaveEvent(QDragLeaveEvent *) +{ + hideDragIndicator(); + return false; +} + +bool ToolBarEventFilter::handleDropEvent(QDropEvent *event) +{ + const ActionRepositoryMimeData *d = qobject_cast(event->mimeData()); + if (!d) + return false; + + if (d->actionList().isEmpty()) { + event->ignore(); + hideDragIndicator(); + return true; + } + + QAction *action = d->actionList().first(); + + const ActionList actions = m_toolBar->actions(); + if (!action || actions.contains(action)) { + event->ignore(); + hideDragIndicator(); + return true; + } + + // Try to find action to 'insert before'. Click on action or in free area, else ignore. + QAction *beforeAction = nullptr; + const QPoint pos = event->position().toPoint(); + const int index = actionIndexAt(m_toolBar, pos, m_toolBar->orientation()); + if (index != -1) { + beforeAction = actions.at(index); + } else { + if (!freeArea(m_toolBar).contains(pos)) { + event->ignore(); + hideDragIndicator(); + return true; + } + } + + event->acceptProposedAction(); + QDesignerFormWindowInterface *fw = formWindow(); + InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); + cmd->init(m_toolBar, action, beforeAction); + fw->commandHistory()->push(cmd); + hideDragIndicator(); + return true; +} + +bool ToolBarEventFilter::startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers) +{ + const int index = actionIndexAt(m_toolBar, pos, m_toolBar->orientation()); + if (index == - 1) + return false; + + const ActionList actions = m_toolBar->actions(); + QAction *action = actions.at(index); + QDesignerFormWindowInterface *fw = formWindow(); + + const Qt::DropAction dropAction = (modifiers & Qt::ControlModifier) ? Qt::CopyAction : Qt::MoveAction; + if (dropAction == Qt::MoveAction) { + RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw); + const int nextIndex = index + 1; + QAction *nextAction = nextIndex < actions.size() ? actions.at(nextIndex) : 0; + cmd->init(m_toolBar, action, nextAction); + fw->commandHistory()->push(cmd); + } + + QDrag *drag = new QDrag(m_toolBar); + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap( action)); + drag->setMimeData(new ActionRepositoryMimeData(action, dropAction)); + + if (drag->exec(dropAction) == Qt::IgnoreAction) { + hideDragIndicator(); + if (dropAction == Qt::MoveAction) { + const ActionList currentActions = m_toolBar->actions(); + QAction *previous = nullptr; + if (index >= 0 && index < currentActions.size()) + previous = currentActions.at(index); + InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); + cmd->init(m_toolBar, action, previous); + fw->commandHistory()->push(cmd); + } + } + return true; +} + +QAction *ToolBarEventFilter::actionAt(const QToolBar *tb, const QPoint &pos) +{ + const int index = actionIndexAt(tb, pos, tb->orientation()); + if (index == -1) + return nullptr; + return tb->actions().at(index); +} + +//that's a trick to get access to the initStyleOption which is a protected member +class FriendlyToolBar : public QToolBar { +public: + friend class ToolBarEventFilter; +}; + +QRect ToolBarEventFilter::handleArea(const QToolBar *tb) +{ + QStyleOptionToolBar opt; + static_cast(tb)->initStyleOption(&opt); + return tb->style()->subElementRect(QStyle::SE_ToolBarHandle, &opt, tb); +} + +bool ToolBarEventFilter::withinHandleArea(const QToolBar *tb, const QPoint &pos) +{ + return handleArea(tb).contains(pos); +} + +// Determine the free area behind the last action. +QRect ToolBarEventFilter::freeArea(const QToolBar *tb) +{ + QRect rc = QRect(QPoint(0, 0), tb->size()); + const ActionList actionList = tb->actions(); + QRect exclusionRectangle = actionList.isEmpty() + ? handleArea(tb) : tb->actionGeometry(actionList.constLast()); + switch (tb->orientation()) { + case Qt::Horizontal: + switch (tb->layoutDirection()) { + case Qt::LayoutDirectionAuto: // Should never happen + case Qt::LeftToRight: + rc.setX(exclusionRectangle.right() + 1); + break; + case Qt::RightToLeft: + rc.setRight(exclusionRectangle.x()); + break; + } + break; + case Qt::Vertical: + rc.setY(exclusionRectangle.bottom() + 1); + break; + } + return rc; +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_toolbar_p.h b/src/designer/src/lib/shared/qdesigner_toolbar_p.h new file mode 100644 index 0000000..bd897ae --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_toolbar_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_TOOLBAR_H +#define QDESIGNER_TOOLBAR_H + +#include "shared_global_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QToolBar; +class QRect; +class QAction; + +namespace qdesigner_internal { + +class PromotionTaskMenu; + +// Special event filter for tool bars in designer. +// Handles drag and drop to and from. Ensures that each +// child widget is WA_TransparentForMouseEvents to enable drag and drop. + +class QDESIGNER_SHARED_EXPORT ToolBarEventFilter : public QObject { + Q_OBJECT + +public: + static void install(QToolBar *tb); + + // Find action by position. Note that QToolBar::actionAt() will + // not work as designer sets WA_TransparentForMouseEvents on its tool bar buttons + // to be able to drag them. This function will return the dummy + // sentinel action when applied to tool bars created by designer if the position matches. + static QAction *actionAt(const QToolBar *tb, const QPoint &pos); + + static bool withinHandleArea(const QToolBar *tb, const QPoint &pos); + static QRect handleArea(const QToolBar *tb); + static QRect freeArea(const QToolBar *tb); + + // Utility to create an action + static QAction *createAction(QDesignerFormWindowInterface *fw, const QString &objectName, bool separator); + + bool eventFilter (QObject *watched, QEvent *event) override; + + // Helper for task menu extension + QList contextMenuActions(const QPoint &globalPos = QPoint(-1, -1)); + + static ToolBarEventFilter *eventFilterOf(const QToolBar *tb); + +private slots: + void slotRemoveSelectedAction(); + void slotRemoveToolBar(); + void slotInsertSeparator(); + +private: + explicit ToolBarEventFilter(QToolBar *tb); + + bool handleContextMenuEvent(QContextMenuEvent * event); + bool handleDragEnterMoveEvent(QDragMoveEvent *event); + bool handleDragLeaveEvent(QDragLeaveEvent *); + bool handleDropEvent(QDropEvent *event); + bool handleMousePressEvent(QMouseEvent *event); + bool handleMouseReleaseEvent(QMouseEvent *event); + bool handleMouseMoveEvent(QMouseEvent *event); + + QDesignerFormWindowInterface *formWindow() const; + void adjustDragIndicator(const QPoint &pos); + void hideDragIndicator(); + bool startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers); + bool withinHandleArea(const QPoint &pos) const; + + QToolBar *m_toolBar; + PromotionTaskMenu *m_promotionTaskMenu; + QPoint m_startPosition; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_TOOLBAR_H diff --git a/src/designer/src/lib/shared/qdesigner_toolbox.cpp b/src/designer/src/lib/shared/qdesigner_toolbox.cpp new file mode 100644 index 0000000..0295e4d --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_toolbox.cpp @@ -0,0 +1,397 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_toolbox_p.h" +#include "qdesigner_command_p.h" +#include "orderdialog_p.h" +#include "promotiontaskmenu_p.h" +#include "formwindowbase_p.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QToolBoxHelper::QToolBoxHelper(QToolBox *toolbox) : + QObject(toolbox), + m_toolbox(toolbox), + m_actionDeletePage(new QAction(tr("Delete Page"), this)), + m_actionInsertPage(new QAction(tr("Before Current Page"), this)), + m_actionInsertPageAfter(new QAction(tr("After Current Page"), this)), + m_actionChangePageOrder(new QAction(tr("Change Page Order..."), this)), + m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this)) +{ + connect(m_actionDeletePage, &QAction::triggered, this, &QToolBoxHelper::removeCurrentPage); + connect(m_actionInsertPage, &QAction::triggered, this, &QToolBoxHelper::addPage); + connect(m_actionInsertPageAfter, &QAction::triggered, this, &QToolBoxHelper::addPageAfter); + connect(m_actionChangePageOrder, &QAction::triggered, this, &QToolBoxHelper::changeOrder); + + m_toolbox->installEventFilter(this); +} + +void QToolBoxHelper::install(QToolBox *toolbox) +{ + new QToolBoxHelper(toolbox); +} + +bool QToolBoxHelper::eventFilter(QObject *watched, QEvent *event) +{ + switch (event->type()) { + case QEvent::ChildPolished: + // Install on the buttons + if (watched == m_toolbox) { + QChildEvent *ce = static_cast(event); + if (!qstrcmp(ce->child()->metaObject()->className(), "QToolBoxButton")) + ce->child()->installEventFilter(this); + } + break; + case QEvent::ContextMenu: + if (watched != m_toolbox) { + // An action invoked from the passive interactor (ToolBox button) might + // cause its deletion within its event handler, triggering a warning. Re-post + // the event to the toolbox. + QContextMenuEvent *current = static_cast(event); + QContextMenuEvent *copy = new QContextMenuEvent(current->reason(), current->pos(), current-> globalPos(), current->modifiers()); + QApplication::postEvent(m_toolbox, copy); + current->accept(); + return true; + } + break; + case QEvent::MouseButtonRelease: + if (watched != m_toolbox) + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox)) { + fw->clearSelection(); + fw->selectWidget(m_toolbox, true); + } + break; + default: + break; + } + return QObject::eventFilter(watched, event); +} + +QToolBoxHelper *QToolBoxHelper::helperOf(const QToolBox *toolbox) +{ + // Look for 1st order children only..otherwise, we might get filters of nested widgets + for (QObject *o : toolbox->children()) { + if (!o->isWidgetType()) + if (QToolBoxHelper *h = qobject_cast(o)) + return h; + } + return nullptr; +} + +QMenu *QToolBoxHelper::addToolBoxContextMenuActions(const QToolBox *toolbox, QMenu *popup) +{ + QToolBoxHelper *helper = helperOf(toolbox); + if (!helper) + return nullptr; + return helper->addContextMenuActions(popup); +} + +void QToolBoxHelper::removeCurrentPage() +{ + if (m_toolbox->currentIndex() == -1 || !m_toolbox->widget(m_toolbox->currentIndex())) + return; + + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox)) { + qdesigner_internal::DeleteToolBoxPageCommand *cmd = new qdesigner_internal::DeleteToolBoxPageCommand(fw); + cmd->init(m_toolbox); + fw->commandHistory()->push(cmd); + } +} + +void QToolBoxHelper::addPage() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox)) { + qdesigner_internal::AddToolBoxPageCommand *cmd = new qdesigner_internal::AddToolBoxPageCommand(fw); + cmd->init(m_toolbox, qdesigner_internal::AddToolBoxPageCommand::InsertBefore); + fw->commandHistory()->push(cmd); + } +} + +void QToolBoxHelper::changeOrder() +{ + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox); + + if (!fw) + return; + + const QWidgetList oldPages = qdesigner_internal::OrderDialog::pagesOfContainer(fw->core(), m_toolbox); + const int pageCount = oldPages.size(); + if (pageCount < 2) + return; + + qdesigner_internal::OrderDialog dlg(fw); + dlg.setPageList(oldPages); + if (dlg.exec() == QDialog::Rejected) + return; + + const QWidgetList newPages = dlg.pageList(); + if (newPages == oldPages) + return; + + fw->beginCommand(tr("Change Page Order")); + for(int i=0; i < pageCount; ++i) { + if (newPages.at(i) == m_toolbox->widget(i)) + continue; + qdesigner_internal::MoveToolBoxPageCommand *cmd = new qdesigner_internal::MoveToolBoxPageCommand(fw); + cmd->init(m_toolbox, newPages.at(i), i); + fw->commandHistory()->push(cmd); + } + fw->endCommand(); +} + +void QToolBoxHelper::addPageAfter() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox)) { + qdesigner_internal::AddToolBoxPageCommand *cmd = new qdesigner_internal::AddToolBoxPageCommand(fw); + cmd->init(m_toolbox, qdesigner_internal::AddToolBoxPageCommand::InsertAfter); + fw->commandHistory()->push(cmd); + } +} + +QPalette::ColorRole QToolBoxHelper::currentItemBackgroundRole() const +{ + const QWidget *w = m_toolbox->widget(0); + if (!w) + return QPalette::Window; + return w->backgroundRole(); +} + +void QToolBoxHelper::setCurrentItemBackgroundRole(QPalette::ColorRole role) +{ + const int count = m_toolbox->count(); + for (int i = 0; i < count; ++i) { + QWidget *w = m_toolbox->widget(i); + w->setBackgroundRole(role); + w->update(); + } +} + +QMenu *QToolBoxHelper::addContextMenuActions(QMenu *popup) const +{ + QMenu *pageMenu = nullptr; + const int count = m_toolbox->count(); + m_actionDeletePage->setEnabled(count > 1); + if (count) { + const QString pageSubMenuLabel = tr("Page %1 of %2").arg(m_toolbox->currentIndex() + 1).arg(count); + pageMenu = popup->addMenu(pageSubMenuLabel); + + pageMenu->addAction(m_actionDeletePage); + // Set up promotion menu for current widget. + if (QWidget *page = m_toolbox->currentWidget ()) { + m_pagePromotionTaskMenu->setWidget(page); + m_pagePromotionTaskMenu->addActions(QDesignerFormWindowInterface::findFormWindow(m_toolbox), + qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit, + pageMenu); + } + } + QMenu *insertPageMenu = popup->addMenu(tr("Insert Page")); + insertPageMenu->addAction(m_actionInsertPageAfter); + insertPageMenu->addAction(m_actionInsertPage); + if (count > 1) { + popup->addAction(m_actionChangePageOrder); + } + popup->addSeparator(); + return pageMenu; +} + +// -------- QToolBoxWidgetPropertySheet + +static constexpr auto currentItemTextKey = "currentItemText"_L1; +static constexpr auto currentItemNameKey = "currentItemName"_L1; +static constexpr auto currentItemIconKey = "currentItemIcon"_L1; +static constexpr auto currentItemToolTipKey = "currentItemToolTip"_L1; +static constexpr auto tabSpacingKey = "tabSpacing"_L1; + +enum { tabSpacingDefault = -1 }; + +QToolBoxWidgetPropertySheet::QToolBoxWidgetPropertySheet(QToolBox *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_toolBox(object) +{ + createFakeProperty(currentItemTextKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + createFakeProperty(currentItemNameKey, QString()); + createFakeProperty(currentItemIconKey, QVariant::fromValue(qdesigner_internal::PropertySheetIconValue())); + if (formWindowBase()) + formWindowBase()->addReloadableProperty(this, indexOf(currentItemIconKey)); + createFakeProperty(currentItemToolTipKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + createFakeProperty(tabSpacingKey, QVariant(tabSpacingDefault)); +} + +QToolBoxWidgetPropertySheet::ToolBoxProperty QToolBoxWidgetPropertySheet::toolBoxPropertyFromName(const QString &name) +{ + static const QHash toolBoxPropertyHash = { + {currentItemTextKey, PropertyCurrentItemText}, + {currentItemNameKey, PropertyCurrentItemName}, + {currentItemIconKey, PropertyCurrentItemIcon}, + {currentItemToolTipKey, PropertyCurrentItemToolTip}, + {tabSpacingKey, PropertyTabSpacing} + }; + return toolBoxPropertyHash.value(name, PropertyToolBoxNone); +} + +void QToolBoxWidgetPropertySheet::setProperty(int index, const QVariant &value) +{ + const ToolBoxProperty toolBoxProperty = toolBoxPropertyFromName(propertyName(index)); + // independent of index + switch (toolBoxProperty) { + case PropertyTabSpacing: + m_toolBox->layout()->setSpacing(value.toInt()); + return; + case PropertyToolBoxNone: + QDesignerPropertySheet::setProperty(index, value); + return; + default: + break; + } + // index-dependent + const int currentIndex = m_toolBox->currentIndex(); + QWidget *currentWidget = m_toolBox->currentWidget(); + if (!currentWidget) + return; + + switch (toolBoxProperty) { + case PropertyCurrentItemText: + m_toolBox->setItemText(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].text = qvariant_cast(value); + break; + case PropertyCurrentItemName: + currentWidget->setObjectName(value.toString()); + break; + case PropertyCurrentItemIcon: + m_toolBox->setItemIcon(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].icon = qvariant_cast(value); + break; + case PropertyCurrentItemToolTip: + m_toolBox->setItemToolTip(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].tooltip = qvariant_cast(value); + break; + case PropertyTabSpacing: + case PropertyToolBoxNone: + break; + } +} + +bool QToolBoxWidgetPropertySheet::isEnabled(int index) const +{ + switch (toolBoxPropertyFromName(propertyName(index))) { + case PropertyToolBoxNone: // independent of index + case PropertyTabSpacing: + return QDesignerPropertySheet::isEnabled(index); + default: + break; + } + return m_toolBox->currentIndex() != -1; +} + +QVariant QToolBoxWidgetPropertySheet::property(int index) const +{ + const ToolBoxProperty toolBoxProperty = toolBoxPropertyFromName(propertyName(index)); + // independent of index + switch (toolBoxProperty) { + case PropertyTabSpacing: + return m_toolBox->layout()->spacing(); + case PropertyToolBoxNone: + return QDesignerPropertySheet::property(index); + default: + break; + } + // index-dependent + QWidget *currentWidget = m_toolBox->currentWidget(); + if (!currentWidget) { + if (toolBoxProperty == PropertyCurrentItemIcon) + return QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()); + if (toolBoxProperty == PropertyCurrentItemText) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + if (toolBoxProperty == PropertyCurrentItemToolTip) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + return QVariant(QString()); + } + + // index-dependent + switch (toolBoxProperty) { + case PropertyCurrentItemText: + return QVariant::fromValue(m_pageToData.value(currentWidget).text); + case PropertyCurrentItemName: + return currentWidget->objectName(); + case PropertyCurrentItemIcon: + return QVariant::fromValue(m_pageToData.value(currentWidget).icon); + case PropertyCurrentItemToolTip: + return QVariant::fromValue(m_pageToData.value(currentWidget).tooltip); + case PropertyTabSpacing: + case PropertyToolBoxNone: + break; + } + return QVariant(); +} + +bool QToolBoxWidgetPropertySheet::reset(int index) +{ + const ToolBoxProperty toolBoxProperty = toolBoxPropertyFromName(propertyName(index)); + // independent of index + switch (toolBoxProperty) { + case PropertyTabSpacing: + setProperty(index, QVariant(tabSpacingDefault)); + return true; + case PropertyToolBoxNone: + return QDesignerPropertySheet::reset(index); + default: + break; + } + // index-dependent + QWidget *currentWidget = m_toolBox->currentWidget(); + if (!currentWidget) + return false; + + // index-dependent + switch (toolBoxProperty) { + case PropertyCurrentItemName: + setProperty(index, QString()); + break; + case PropertyCurrentItemToolTip: + m_pageToData[currentWidget].tooltip = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentItemText: + m_pageToData[currentWidget].text = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentItemIcon: + m_pageToData[currentWidget].icon = qdesigner_internal::PropertySheetIconValue(); + setProperty(index, QIcon()); + break; + case PropertyTabSpacing: + case PropertyToolBoxNone: + break; + } + return true; +} + +bool QToolBoxWidgetPropertySheet::checkProperty(const QString &propertyName) +{ + switch (toolBoxPropertyFromName(propertyName)) { + case PropertyCurrentItemText: + case PropertyCurrentItemName: + case PropertyCurrentItemToolTip: + case PropertyCurrentItemIcon: + return false; + default: + break; + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_toolbox_p.h b/src/designer/src/lib/shared/qdesigner_toolbox_p.h new file mode 100644 index 0000000..8a86e23 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_toolbox_p.h @@ -0,0 +1,102 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_TOOLBOX_H +#define QDESIGNER_TOOLBOX_H + +#include "shared_global_p.h" +#include "qdesigner_propertysheet_p.h" +#include "qdesigner_utils_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + class PromotionTaskMenu; +} + +class QToolBox; + +class QAction; +class QMenu; + +class QDESIGNER_SHARED_EXPORT QToolBoxHelper : public QObject +{ + Q_OBJECT + + explicit QToolBoxHelper(QToolBox *toolbox); +public: + // Install helper on QToolBox + static void install(QToolBox *toolbox); + static QToolBoxHelper *helperOf(const QToolBox *toolbox); + // Convenience to add a menu on a toolbox + static QMenu *addToolBoxContextMenuActions(const QToolBox *toolbox, QMenu *popup); + + QPalette::ColorRole currentItemBackgroundRole() const; + void setCurrentItemBackgroundRole(QPalette::ColorRole role); + + bool eventFilter(QObject *watched, QEvent *event) override; + // Add context menu and return page submenu or 0. + + QMenu *addContextMenuActions(QMenu *popup) const; + +private slots: + void removeCurrentPage(); + void addPage(); + void addPageAfter(); + void changeOrder(); + +private: + QToolBox *m_toolbox; + QAction *m_actionDeletePage; + QAction *m_actionInsertPage; + QAction *m_actionInsertPageAfter; + QAction *m_actionChangePageOrder; + qdesigner_internal::PromotionTaskMenu* m_pagePromotionTaskMenu; +}; + +// PropertySheet to handle the page properties +class QDESIGNER_SHARED_EXPORT QToolBoxWidgetPropertySheet : public QDesignerPropertySheet { +public: + explicit QToolBoxWidgetPropertySheet(QToolBox *object, QObject *parent = nullptr); + + void setProperty(int index, const QVariant &value) override; + QVariant property(int index) const override; + bool reset(int index) override; + bool isEnabled(int index) const override; + + // Check whether the property is to be saved. Returns false for the page + // properties (as the property sheet has no concept of 'stored') + static bool checkProperty(const QString &propertyName); + +private: + enum ToolBoxProperty { PropertyCurrentItemText, PropertyCurrentItemName, PropertyCurrentItemIcon, + PropertyCurrentItemToolTip, PropertyTabSpacing, PropertyToolBoxNone }; + + static ToolBoxProperty toolBoxPropertyFromName(const QString &name); + QToolBox *m_toolBox; + struct PageData + { + qdesigner_internal::PropertySheetStringValue text; + qdesigner_internal::PropertySheetStringValue tooltip; + qdesigner_internal::PropertySheetIconValue icon; + }; + QHash m_pageToData; +}; + +using QToolBoxWidgetPropertySheetFactory = QDesignerPropertySheetFactory; + +QT_END_NAMESPACE + +#endif // QDESIGNER_TOOLBOX_H diff --git a/src/designer/src/lib/shared/qdesigner_utils.cpp b/src/designer/src/lib/shared/qdesigner_utils.cpp new file mode 100644 index 0000000..d75bbb0 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_utils.cpp @@ -0,0 +1,801 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_utils_p.h" +#include "qdesigner_propertycommand_p.h" +#include "abstractformbuilder.h" +#include "formwindowbase_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal +{ + // ### FIXME Qt 8: Remove (QTBUG-96005) + QString legacyDataDirectory() + { + return QDir::homePath() + u"/.designer"_s; + } + + QString dataDirectory() + { +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + + u'/' + QCoreApplication::organizationName() + u"/Designer"_s; +#else + return legacyDataDirectory(); +#endif + } + + + QDESIGNER_SHARED_EXPORT void designerWarning(const QString &message) + { + qWarning("Designer: %s", qPrintable(message)); + } + + void reloadTreeItem(DesignerIconCache *iconCache, QTreeWidgetItem *item) + { + if (!item) + return; + + for (int c = 0; c < item->columnCount(); c++) { + const QVariant v = item->data(c, Qt::DecorationPropertyRole); + if (v.canConvert()) + item->setIcon(c, iconCache->icon(qvariant_cast(v))); + } + } + + void reloadListItem(DesignerIconCache *iconCache, QListWidgetItem *item) + { + if (!item) + return; + + const QVariant v = item->data(Qt::DecorationPropertyRole); + if (v.canConvert()) + item->setIcon(iconCache->icon(qvariant_cast(v))); + } + + void reloadTableItem(DesignerIconCache *iconCache, QTableWidgetItem *item) + { + if (!item) + return; + + const QVariant v = item->data(Qt::DecorationPropertyRole); + if (v.canConvert()) + item->setIcon(iconCache->icon(qvariant_cast(v))); + } + + void reloadIconResources(DesignerIconCache *iconCache, QObject *object) + { + if (QListWidget *listWidget = qobject_cast(object)) { + for (int i = 0; i < listWidget->count(); i++) + reloadListItem(iconCache, listWidget->item(i)); + } else if (QComboBox *comboBox = qobject_cast(object)) { + for (int i = 0; i < comboBox->count(); i++) { + const QVariant v = comboBox->itemData(i, Qt::DecorationPropertyRole); + if (v.canConvert()) { + QIcon icon = iconCache->icon(qvariant_cast(v)); + comboBox->setItemIcon(i, icon); + comboBox->setItemData(i, icon); + } + } + } else if (QTreeWidget *treeWidget = qobject_cast(object)) { + reloadTreeItem(iconCache, treeWidget->headerItem()); + QQueue itemsQueue; + for (int i = 0; i < treeWidget->topLevelItemCount(); i++) + itemsQueue.enqueue(treeWidget->topLevelItem(i)); + while (!itemsQueue.isEmpty()) { + QTreeWidgetItem *item = itemsQueue.dequeue(); + for (int i = 0; i < item->childCount(); i++) + itemsQueue.enqueue(item->child(i)); + reloadTreeItem(iconCache, item); + } + } else if (QTableWidget *tableWidget = qobject_cast(object)) { + const int columnCount = tableWidget->columnCount(); + const int rowCount = tableWidget->rowCount(); + for (int c = 0; c < columnCount; c++) + reloadTableItem(iconCache, tableWidget->horizontalHeaderItem(c)); + for (int r = 0; r < rowCount; r++) + reloadTableItem(iconCache, tableWidget->verticalHeaderItem(r)); + for (int c = 0; c < columnCount; c++) + for (int r = 0; r < rowCount; r++) + reloadTableItem(iconCache, tableWidget->item(r, c)); + } + } + + // ------------- DesignerMetaEnum + DesignerMetaEnum::DesignerMetaEnum(const QString &name, const QString &scope, const QString &separator) : + MetaEnum(name, scope, separator) + { + } + + + QString DesignerMetaEnum::toString(int value, SerializationMode sm, bool *ok) const + { + // find value + bool valueOk; + const QString item = valueToKey(value, &valueOk); + if (ok) + *ok = valueOk; + + if (!valueOk) + return item; + + QString qualifiedItem; + appendQualifiedName(item, sm, qualifiedItem); + return qualifiedItem; + } + + QString DesignerMetaEnum::messageToStringFailed(int value) const + { + return QCoreApplication::translate("DesignerMetaEnum", + "%1 is not a valid enumeration value of '%2'.") + .arg(value).arg(enumName()); + } + + QString DesignerMetaEnum::messageParseFailed(const QString &s) const + { + return QCoreApplication::translate("DesignerMetaEnum", + "'%1' could not be converted to an enumeration value of type '%2'.") + .arg(s, enumName()); + } + // -------------- DesignerMetaFlags + DesignerMetaFlags::DesignerMetaFlags(const QString &enumName, const QString &scope, + const QString &separator) : + MetaEnum(enumName, scope, separator) + { + } + + QStringList DesignerMetaFlags::flags(int ivalue) const + { + QStringList rc; + const uint v = static_cast(ivalue); + for (auto it = keyToValueMap().begin(), end = keyToValueMap().end(); it != end; ++it) { + const uint itemValue = it->second; + // Check for equality first as flag values can be 0 or -1, too. Takes preference over a bitwise flag + if (v == itemValue) { + rc.clear(); + rc.push_back(it->first); + return rc; + } + // Do not add 0-flags (None-flags) + if (itemValue) + if ((v & itemValue) == itemValue) + rc.push_back(it->first); + } + return rc; + } + + + QString DesignerMetaFlags::toString(int value, SerializationMode sm) const + { + const QStringList flagIds = flags(value); + if (flagIds.isEmpty()) + return QString(); + + QString rc; + for (const auto &id : flagIds) { + if (!rc.isEmpty()) + rc += u'|'; + appendQualifiedName(id, sm, rc); + } + return rc; + } + + + int DesignerMetaFlags::parseFlags(const QString &s, bool *ok) const + { + if (s.isEmpty()) { + if (ok) + *ok = true; + return 0; + } + uint flags = 0; + bool valueOk = true; + const auto keys = QStringView{s}.split(u'|'); + for (const auto &key : keys) { + const uint flagValue = keyToValue(key, &valueOk); + if (!valueOk) { + flags = 0; + break; + } + flags |= flagValue; + } + if (ok) + *ok = valueOk; + return static_cast(flags); + } + + QString DesignerMetaFlags::messageParseFailed(const QString &s) const + { + return QCoreApplication::translate("DesignerMetaFlags", + "'%1' could not be converted to a flag value of type '%2'.") + .arg(s, enumName()); + } + + // ---------- PropertySheetEnumValue + + PropertySheetEnumValue::PropertySheetEnumValue(int v, const DesignerMetaEnum &me) : + value(v), + metaEnum(me) + { + } + + PropertySheetEnumValue::PropertySheetEnumValue() = default; + + // ---------------- PropertySheetFlagValue + PropertySheetFlagValue::PropertySheetFlagValue(int v, const DesignerMetaFlags &mf) : + value(v), + metaFlags(mf) + { + } + + PropertySheetFlagValue::PropertySheetFlagValue() = default; + + // ---------------- PropertySheetPixmapValue + PropertySheetPixmapValue::PropertySheetPixmapValue(const QString &path) : m_path(path) + { + } + + PropertySheetPixmapValue::PropertySheetPixmapValue() = default; + + PropertySheetPixmapValue::PixmapSource PropertySheetPixmapValue::getPixmapSource(QDesignerFormEditorInterface *core, const QString & path) + { + if (const QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) + return lang->isLanguageResource(path) ? LanguageResourcePixmap : FilePixmap; + return path.startsWith(u':') ? ResourcePixmap : FilePixmap; + } + + QString PropertySheetPixmapValue::path() const + { + return m_path; + } + + void PropertySheetPixmapValue::setPath(const QString &path) + { + if (m_path == path) + return; + m_path = path; + } + + // ---------- PropertySheetIconValue + + class PropertySheetIconValueData : public QSharedData { + public: + PropertySheetIconValue::ModeStateToPixmapMap m_paths; + QString m_theme; + int m_themeEnum = -1; + }; + + PropertySheetIconValue::PropertySheetIconValue(const PropertySheetPixmapValue &pixmap) : + m_data(new PropertySheetIconValueData) + { + setPixmap(QIcon::Normal, QIcon::Off, pixmap); + } + + PropertySheetIconValue::PropertySheetIconValue() : + m_data(new PropertySheetIconValueData) + { + } + + PropertySheetIconValue::~PropertySheetIconValue() = default; + + PropertySheetIconValue::PropertySheetIconValue(const PropertySheetIconValue &rhs) noexcept = default; + PropertySheetIconValue &PropertySheetIconValue::operator=(const PropertySheetIconValue &rhs) = default; + + PropertySheetIconValue::PropertySheetIconValue(PropertySheetIconValue &&) noexcept = default; + PropertySheetIconValue &PropertySheetIconValue::operator=(PropertySheetIconValue &&) noexcept = default; + +} // namespace qdesigner_internal + +namespace qdesigner_internal { + + size_t qHash(const PropertySheetIconValue &p, size_t seed) noexcept + { + // qHash for paths making use of the existing QPair hash functions. + const auto *d = p.m_data.constData(); + return qHashMulti(seed, d->m_paths, d->m_themeEnum, d->m_theme); + } + + bool comparesEqual(const PropertySheetIconValue &lhs, + const PropertySheetIconValue &rhs) noexcept + { + const auto *lhsd = lhs.m_data.constData(); + const auto *rhsd = rhs.m_data.constData(); + return lhsd == rhsd + || (lhsd->m_themeEnum == rhsd->m_themeEnum + && lhsd->m_theme == rhsd->m_theme && lhsd->m_paths == rhsd->m_paths); + } + + bool PropertySheetIconValue::isEmpty() const + { + return m_data->m_themeEnum == -1 && m_data->m_theme.isEmpty() + && m_data->m_paths.isEmpty(); + } + + QString PropertySheetIconValue::theme() const + { + return m_data->m_theme; + } + + void PropertySheetIconValue::setTheme(const QString &t) + { + m_data->m_theme = t; + } + + int PropertySheetIconValue::themeEnum() const + { + return m_data->m_themeEnum; + } + + void PropertySheetIconValue::setThemeEnum(int e) + { + m_data->m_themeEnum = e; + } + + PropertySheetPixmapValue PropertySheetIconValue::pixmap(QIcon::Mode mode, QIcon::State state) const + { + const ModeStateKey pair{mode, state}; + return m_data->m_paths.value(pair); + } + + void PropertySheetIconValue::setPixmap(QIcon::Mode mode, QIcon::State state, const PropertySheetPixmapValue &pixmap) + { + const ModeStateKey pair{mode, state}; + if (pixmap.path().isEmpty()) + m_data->m_paths.remove(pair); + else + m_data->m_paths.insert(pair, pixmap); + } + + QPixmap DesignerPixmapCache::pixmap(const PropertySheetPixmapValue &value) const + { + const auto it = m_cache.constFind(value); + if (it != m_cache.constEnd()) + return it.value(); + + QPixmap pix = QPixmap(value.path()); + m_cache.insert(value, pix); + return pix; + } + + void DesignerPixmapCache::clear() + { + m_cache.clear(); + } + + DesignerPixmapCache::DesignerPixmapCache(QObject *parent) + : QObject(parent) + { + } + + QIcon DesignerIconCache::icon(const PropertySheetIconValue &value) const + { + const auto it = m_cache.constFind(value); + if (it != m_cache.constEnd()) + return it.value(); + + // Match on the theme first if it is available. + if (value.themeEnum() != -1) { + const QIcon themeIcon = QIcon::fromTheme(static_cast(value.themeEnum())); + m_cache.insert(value, themeIcon); + return themeIcon; + } + if (!value.theme().isEmpty()) { + const QString theme = value.theme(); + if (QIcon::hasThemeIcon(theme)) { + const QIcon themeIcon = QIcon::fromTheme(theme); + m_cache.insert(value, themeIcon); + return themeIcon; + } + } + + QIcon icon; + const PropertySheetIconValue::ModeStateToPixmapMap &paths = value.paths(); + for (auto it = paths.constBegin(), cend = paths.constEnd(); it != cend; ++it) { + const auto pair = it.key(); + icon.addFile(it.value().path(), QSize(), pair.first, pair.second); + } + m_cache.insert(value, icon); + return icon; + } + + void DesignerIconCache::clear() + { + m_cache.clear(); + } + + DesignerIconCache::DesignerIconCache(DesignerPixmapCache *pixmapCache, QObject *parent) + : QObject(parent), + m_pixmapCache(pixmapCache) + { + + } + + PropertySheetTranslatableData::PropertySheetTranslatableData(bool translatable, const QString &disambiguation, const QString &comment) : + m_translatable(translatable), m_disambiguation(disambiguation), m_comment(comment) { } + + PropertySheetStringValue::PropertySheetStringValue(const QString &value, + bool translatable, const QString &disambiguation, const QString &comment) : + PropertySheetTranslatableData(translatable, disambiguation, comment), m_value(value) {} + + QString PropertySheetStringValue::value() const + { + return m_value; + } + + void PropertySheetStringValue::setValue(const QString &value) + { + m_value = value; + } + + PropertySheetStringListValue::PropertySheetStringListValue(const QStringList &value, + bool translatable, + const QString &disambiguation, + const QString &comment) : + PropertySheetTranslatableData(translatable, disambiguation, comment), m_value(value) + { + } + + QStringList PropertySheetStringListValue::value() const + { + return m_value; + } + + void PropertySheetStringListValue::setValue(const QStringList &value) + { + m_value = value; + } + + QStringList m_value; + + + PropertySheetKeySequenceValue::PropertySheetKeySequenceValue(const QKeySequence &value, + bool translatable, const QString &disambiguation, const QString &comment) + : PropertySheetTranslatableData(translatable, disambiguation, comment), + m_value(value), m_standardKey(QKeySequence::UnknownKey) {} + + PropertySheetKeySequenceValue::PropertySheetKeySequenceValue(const QKeySequence::StandardKey &standardKey, + bool translatable, const QString &disambiguation, const QString &comment) + : PropertySheetTranslatableData(translatable, disambiguation, comment), + m_value(QKeySequence(standardKey)), m_standardKey(standardKey) {} + + QKeySequence PropertySheetKeySequenceValue::value() const + { + return m_value; + } + + void PropertySheetKeySequenceValue::setValue(const QKeySequence &value) + { + m_value = value; + m_standardKey = QKeySequence::UnknownKey; + } + + QKeySequence::StandardKey PropertySheetKeySequenceValue::standardKey() const + { + return m_standardKey; + } + + void PropertySheetKeySequenceValue::setStandardKey(const QKeySequence::StandardKey &standardKey) + { + m_value = QKeySequence(standardKey); + m_standardKey = standardKey; + } + + bool PropertySheetKeySequenceValue::isStandardKey() const + { + return m_standardKey != QKeySequence::UnknownKey; + } + + /* IconSubPropertyMask: Assign each icon sub-property (pixmaps for the + * various states/modes and the theme) a flag bit (see QFont) so that they + * can be handled individually when assigning property values to + * multiselections in the set-property-commands (that is, do not clobber + * other subproperties when assigning just one). + * Provide back-and-forth mapping functions for the icon states. */ + + enum IconSubPropertyMask { + NormalOffIconMask = 0x01, + NormalOnIconMask = 0x02, + DisabledOffIconMask = 0x04, + DisabledOnIconMask = 0x08, + ActiveOffIconMask = 0x10, + ActiveOnIconMask = 0x20, + SelectedOffIconMask = 0x40, + SelectedOnIconMask = 0x80, + ThemeIconMask = 0x10000, + ThemeEnumIconMask = 0x20000 + }; + + static inline uint iconStateToSubPropertyFlag(QIcon::Mode mode, QIcon::State state) + { + switch (mode) { + case QIcon::Disabled: + return state == QIcon::On ? DisabledOnIconMask : DisabledOffIconMask; + case QIcon::Active: + return state == QIcon::On ? ActiveOnIconMask : ActiveOffIconMask; + case QIcon::Selected: + return state == QIcon::On ? SelectedOnIconMask : SelectedOffIconMask; + case QIcon::Normal: + break; + } + return state == QIcon::On ? NormalOnIconMask : NormalOffIconMask; + } + + static inline std::pair subPropertyFlagToIconModeState(unsigned flag) + { + switch (flag) { + case NormalOnIconMask: + return {QIcon::Normal, QIcon::On}; + case DisabledOffIconMask: + return {QIcon::Disabled, QIcon::Off}; + case DisabledOnIconMask: + return {QIcon::Disabled, QIcon::On}; + case ActiveOffIconMask: + return {QIcon::Active, QIcon::Off}; + case ActiveOnIconMask: + return {QIcon::Active, QIcon::On}; + case SelectedOffIconMask: + return {QIcon::Selected, QIcon::Off}; + case SelectedOnIconMask: + return {QIcon::Selected, QIcon::On}; + case NormalOffIconMask: + default: + break; + } + return {QIcon::Normal, QIcon::Off}; + } + + uint PropertySheetIconValue::mask() const + { + uint flags = 0; + for (auto it = m_data->m_paths.constBegin(), cend = m_data->m_paths.constEnd(); it != cend; ++it) + flags |= iconStateToSubPropertyFlag(it.key().first, it.key().second); + if (!m_data->m_theme.isEmpty()) + flags |= ThemeIconMask; + if (m_data->m_themeEnum != -1) + flags |= ThemeEnumIconMask; + return flags; + } + + uint PropertySheetIconValue::compare(const PropertySheetIconValue &other) const + { + uint diffMask = mask() | other.mask(); + for (int i = 0; i < 8; i++) { + const uint flag = 1 << i; + if (diffMask & flag) { // if state is set in both icons, compare the values + const auto state = subPropertyFlagToIconModeState(flag); + if (pixmap(state.first, state.second) == other.pixmap(state.first, state.second)) + diffMask &= ~flag; + } + } + if ((diffMask & ThemeIconMask) && theme() == other.theme()) + diffMask &= ~ThemeIconMask; + if ((diffMask & ThemeEnumIconMask) && themeEnum() == other.themeEnum()) + diffMask &= ~ThemeEnumIconMask; + + return diffMask; + } + + PropertySheetIconValue PropertySheetIconValue::themed() const + { + PropertySheetIconValue rc(*this); + rc.m_data->m_paths.clear(); + return rc; + } + + PropertySheetIconValue PropertySheetIconValue::unthemed() const + { + PropertySheetIconValue rc(*this); + rc.m_data->m_theme.clear(); + rc.m_data->m_themeEnum = -1; + return rc; + } + + void PropertySheetIconValue::assign(const PropertySheetIconValue &other, uint mask) + { + for (int i = 0; i < 8; i++) { + uint flag = 1 << i; + if (mask & flag) { + const ModeStateKey state = subPropertyFlagToIconModeState(flag); + setPixmap(state.first, state.second, other.pixmap(state.first, state.second)); + } + } + if (mask & ThemeIconMask) + setTheme(other.theme()); + if (mask & ThemeEnumIconMask) + setThemeEnum(other.themeEnum()); + } + + const PropertySheetIconValue::ModeStateToPixmapMap &PropertySheetIconValue::paths() const + { + return m_data->m_paths; + } + + QDESIGNER_SHARED_EXPORT QDebug operator<<(QDebug debug, const PropertySheetIconValue &p) + { + QDebugStateSaver saver(debug); + debug.nospace(); + debug.noquote(); + debug << "PropertySheetIconValue(mask=0x" << Qt::hex << p.mask() << Qt::dec << ", "; + if (p.themeEnum() != -1) + debug << "theme=" << p.themeEnum() << ", "; + if (!p.theme().isEmpty()) + debug << "XDG theme=\"" << p.theme() << "\", "; + + const PropertySheetIconValue::ModeStateToPixmapMap &paths = p.paths(); + for (auto it = paths.constBegin(), cend = paths.constEnd(); it != cend; ++it) { + debug << " mode=" << it.key().first << ",state=" << it.key().second + << ", \"" << it.value().path() << '"'; + } + debug << ')'; + return debug; + } + + QDESIGNER_SHARED_EXPORT QDesignerFormWindowCommand *createTextPropertyCommand(const QString &propertyName, const QString &text, QObject *object, QDesignerFormWindowInterface *fw) + { + if (text.isEmpty()) { + ResetPropertyCommand *cmd = new ResetPropertyCommand(fw); + cmd->init(object, propertyName); + return cmd; + } + SetPropertyCommand *cmd = new SetPropertyCommand(fw); + cmd->init(object, propertyName, text); + return cmd; + } + + QDESIGNER_SHARED_EXPORT QAction *preferredEditAction(QDesignerFormEditorInterface *core, QWidget *managedWidget) + { + QAction *action = nullptr; + if (const QDesignerTaskMenuExtension *taskMenu = qt_extension(core->extensionManager(), managedWidget)) { + action = taskMenu->preferredEditAction(); + if (!action) { + const auto actions = taskMenu->taskActions(); + if (!actions.isEmpty()) + action = actions.first(); + } + } + if (!action) { + if (const auto *taskMenu = qobject_cast( + core->extensionManager()->extension(managedWidget, u"QDesignerInternalTaskMenuExtension"_s))) { + action = taskMenu->preferredEditAction(); + if (!action) { + const auto actions = taskMenu->taskActions(); + if (!actions.isEmpty()) + action = actions.first(); + } + } + } + return action; + } + + QDESIGNER_SHARED_EXPORT bool runUIC(const QString &fileName, UicLanguage language, + QByteArray& ba, QString &errorMessage) + { + QProcess uic; + QStringList arguments; + static constexpr auto uicBinary = + QOperatingSystemVersion::currentType() != QOperatingSystemVersion::Windows + ? "/uic"_L1 : "/uic.exe"_L1; + QString binary = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath) + uicBinary; + // In a PySide6 installation, there is no libexec directory; uic.exe is + // in the main wheel directory next to designer.exe. + if (!QFileInfo::exists(binary)) + binary = QCoreApplication::applicationDirPath() + uicBinary; + if (!QFileInfo::exists(binary)) { + errorMessage = QApplication::translate("Designer", "%1 does not exist."). + arg(QDir::toNativeSeparators(binary)); + return false; + } + + switch (language) { + case UicLanguage::Cpp: + break; + case UicLanguage::Python: + arguments << u"-g"_s << u"python"_s; + break; + } + arguments << fileName; + + uic.start(binary, arguments); + if (!uic.waitForStarted()) { + errorMessage = QApplication::translate("Designer", "Unable to launch %1: %2"). + arg(QDir::toNativeSeparators(binary), uic.errorString()); + return false; + } + if (!uic.waitForFinished()) { + errorMessage = QApplication::translate("Designer", "%1 timed out.").arg(binary); + return false; + } + if (uic.exitCode()) { + errorMessage = QString::fromLatin1(uic.readAllStandardError()); + return false; + } + ba = uic.readAllStandardOutput(); + return true; + } + + QDESIGNER_SHARED_EXPORT QString qtify(const QString &name) + { + QString qname = name; + + Q_ASSERT(qname.isEmpty() == false); + + + if (qname.size() > 1 && qname.at(1).isUpper()) { + const QChar first = qname.at(0); + if (first == u'Q' || first == u'K') + qname.remove(0, 1); + } + + const qsizetype len = qname.size(); + for (qsizetype i = 0; i < len && qname.at(i).isUpper(); ++i) + qname[i] = qname.at(i).toLower(); + + return qname; + } + + // --------------- UpdateBlocker + UpdateBlocker::UpdateBlocker(QWidget *w) : + m_widget(w), + m_enabled(w->updatesEnabled() && w->isVisible()) + { + if (m_enabled) + m_widget->setUpdatesEnabled(false); + } + + UpdateBlocker::~UpdateBlocker() + { + if (m_enabled) + m_widget->setUpdatesEnabled(true); + } + +// from qpalette.cpp +quint64 paletteResolveMask(QPalette::ColorGroup colorGroup, + QPalette::ColorRole colorRole) +{ + if (colorRole == QPalette::Accent) + colorRole = QPalette::NoRole; // See qtbase/17c589df94a2245ee92d45839c2cba73566d7310 + const auto offset = quint64(QPalette::NColorRoles - 1) * quint64(colorGroup); + const auto bitPos = quint64(colorRole) + offset; + return 1ull << bitPos; +} + +quint64 paletteResolveMask(QPalette::ColorRole colorRole) +{ + return paletteResolveMask(QPalette::Active, colorRole) + | paletteResolveMask(QPalette::Inactive, colorRole) + | paletteResolveMask(QPalette::Disabled, colorRole); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_utils_p.h b/src/designer/src/lib/shared/qdesigner_utils_p.h new file mode 100644 index 0000000..e774a5d --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_utils_p.h @@ -0,0 +1,553 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_UTILS_H +#define QDESIGNER_UTILS_H + +#include "shared_global_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDebug; + +namespace qdesigner_internal { +class QDesignerFormWindowCommand; +class DesignerIconCache; +class FormWindowBase; + + +QDESIGNER_SHARED_EXPORT QString dataDirectory(); + +QDESIGNER_SHARED_EXPORT QString legacyDataDirectory(); + +QDESIGNER_SHARED_EXPORT void designerWarning(const QString &message); + +QDESIGNER_SHARED_EXPORT void reloadIconResources(DesignerIconCache *iconCache, QObject *object); + +/* Flag/Enumeration helpers for the property sheet: Enumeration or flag values are returned by the property sheet + * as a pair of meta type and integer value. + * The meta type carries all the information required for the property editor and serialization + * by the form builders (names, etc). + * Note that the property editor uses unqualified names ("Cancel") while the form builder serialization (uic) + * requires the whole string + * ("QDialogButtonBox::Cancel" or "org.qt-project.qt.gui.QDialogButtonBox.StandardButton.Cancel").*/ + +/* --------- MetaEnum: Base class representing a QMetaEnum with lookup functions + * in both ways. Template of int type since unsigned is more suitable for flags. + * The keyToValue() is ignorant of scopes, it can handle fully qualified or unqualified names. */ + +template +class MetaEnum +{ +public: + enum SerializationMode { FullyQualified, + Qualified }; // Qt pre 6.7 without enum name + + using KeyToValueMap = std::map>; + + MetaEnum(const QString &enumName, const QString &scope, const QString &separator); + MetaEnum() = default; + void addKey(IntType value, const QString &name); + + QString valueToKey(IntType value, bool *ok = nullptr) const; + // Ignorant of scopes. + IntType keyToValue(QStringView key, bool *ok = nullptr) const; + + const QString &enumName() const { return m_enumName; } + const QString &scope() const { return m_scope; } + const QString &separator() const { return m_separator; } + + const QStringList &keys() const { return m_keys; } + const KeyToValueMap &keyToValueMap() const { return m_keyToValueMap; } + +protected: + void appendQualifiedName(const QString &key, SerializationMode sm, QString &target) const; + +private: + QString m_enumName; + QString m_scope; + QString m_separator; + KeyToValueMap m_keyToValueMap; + QStringList m_keys; +}; + +template +MetaEnum::MetaEnum(const QString &enumName, const QString &scope, const QString &separator) : + m_enumName(enumName), + m_scope(scope), + m_separator(separator) +{ +} + +template +void MetaEnum::addKey(IntType value, const QString &name) +{ + m_keyToValueMap.insert({name, value}); + m_keys.append(name); +} + +template +QString MetaEnum::valueToKey(IntType value, bool *ok) const +{ + QString rc; + for (auto it = m_keyToValueMap.begin(), end = m_keyToValueMap.end(); it != end; ++it) { + if (it->second == value) { + rc = it->first; + break; + } + } + if (ok) + *ok = !rc.isEmpty(); + return rc; +} + +template +IntType MetaEnum::keyToValue(QStringView key, bool *ok) const +{ + const auto lastSep = key.lastIndexOf(m_separator); + if (lastSep != -1) + key = key.sliced(lastSep + m_separator.size()); + const auto it = m_keyToValueMap.find(key); + const bool found = it != m_keyToValueMap.end(); + if (ok) + *ok = found; + return found ? it->second : IntType(0); +} + +template +void MetaEnum::appendQualifiedName(const QString &key, SerializationMode sm, + QString &target) const +{ + if (!m_scope.isEmpty()) { + target += m_scope; + target += m_separator; + } + if (sm == FullyQualified) + target += m_enumName + m_separator; + target += key; +} + +// -------------- DesignerMetaEnum: Meta type for enumerations + +class QDESIGNER_SHARED_EXPORT DesignerMetaEnum : public MetaEnum +{ +public: + DesignerMetaEnum(const QString &name, const QString &scope, const QString &separator); + DesignerMetaEnum() = default; + + QString toString(int value, SerializationMode sm, bool *ok = nullptr) const; + + QString messageToStringFailed(int value) const; + QString messageParseFailed(const QString &s) const; + + // parse a string (ignorant of scopes) + int parseEnum(const QString &s, bool *ok = nullptr) const { return keyToValue(s, ok); } +}; + +// -------------- DesignerMetaFlags: Meta type for flags. +// Note that while the handling of flags is done using unsigned integers, the actual values returned +// by the property system are integers. + +class QDESIGNER_SHARED_EXPORT DesignerMetaFlags : public MetaEnum +{ +public: + explicit DesignerMetaFlags(const QString &enumName, const QString &scope, + const QString &separator); + DesignerMetaFlags() = default; + + QString toString(int value, SerializationMode sm) const; + QStringList flags(int value) const; + + QString messageParseFailed(const QString &s) const; + // parse a string (ignorant of scopes) + int parseFlags(const QString &s, bool *ok = nullptr) const; +}; + +// -------------- EnumValue: Returned by the property sheet for enumerations + +struct QDESIGNER_SHARED_EXPORT PropertySheetEnumValue +{ + PropertySheetEnumValue(int v, const DesignerMetaEnum &me); + PropertySheetEnumValue(); + + int value{0}; + DesignerMetaEnum metaEnum; +}; + +// -------------- FlagValue: Returned by the property sheet for flags + +struct QDESIGNER_SHARED_EXPORT PropertySheetFlagValue +{ + PropertySheetFlagValue(int v, const DesignerMetaFlags &mf); + PropertySheetFlagValue(); + + int value{0}; + DesignerMetaFlags metaFlags; +}; + +// -------------- PixmapValue: Returned by the property sheet for pixmaps +class QDESIGNER_SHARED_EXPORT PropertySheetPixmapValue +{ +public: + PropertySheetPixmapValue(const QString &path); + PropertySheetPixmapValue(); + + // Check where a pixmap comes from + enum PixmapSource { LanguageResourcePixmap , ResourcePixmap, FilePixmap }; + static PixmapSource getPixmapSource(QDesignerFormEditorInterface *core, const QString & path); + + PixmapSource pixmapSource(QDesignerFormEditorInterface *core) const { return getPixmapSource(core, m_path); } + + QString path() const; + void setPath(const QString &path); // passing the empty path resets the pixmap + +private: + friend size_t qHash(const PropertySheetPixmapValue &p, size_t seed = 0) noexcept + { + return qHash(p.m_path, seed); + } + friend bool comparesEqual(const PropertySheetPixmapValue &lhs, + const PropertySheetPixmapValue &rhs) noexcept + { + return lhs.m_path == rhs.m_path; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetPixmapValue) + + QString m_path; +}; + +// -------------- IconValue: Returned by the property sheet for icons + +class PropertySheetIconValueData; + +class QDESIGNER_SHARED_EXPORT PropertySheetIconValue +{ + public: + explicit PropertySheetIconValue(const PropertySheetPixmapValue &pixmap); + PropertySheetIconValue(); + ~PropertySheetIconValue(); + PropertySheetIconValue(const PropertySheetIconValue &) noexcept; + PropertySheetIconValue &operator=(const PropertySheetIconValue &); + PropertySheetIconValue(PropertySheetIconValue &&) noexcept; + PropertySheetIconValue &operator=(PropertySheetIconValue &&) noexcept; + + bool isEmpty() const; + + QString theme() const; + void setTheme(const QString &); + + int themeEnum() const; + void setThemeEnum(int e); + + PropertySheetPixmapValue pixmap(QIcon::Mode mode, QIcon::State state) const; + void setPixmap(QIcon::Mode mode, QIcon::State state, const PropertySheetPixmapValue &path); // passing the empty path resets the pixmap + + uint mask() const; + uint compare(const PropertySheetIconValue &other) const; + void assign(const PropertySheetIconValue &other, uint mask); + + // Convenience accessors to get themed/unthemed icons. + PropertySheetIconValue themed() const; + PropertySheetIconValue unthemed() const; + + using ModeStateKey = std::pair; + using ModeStateToPixmapMap = QMap; + + const ModeStateToPixmapMap &paths() const; + +private: + friend QDESIGNER_SHARED_EXPORT + size_t qHash(const PropertySheetIconValue &p, size_t seed) noexcept; + friend size_t qHash(const PropertySheetIconValue &p) noexcept + { return qHash(p, 0); } + friend QDESIGNER_SHARED_EXPORT + bool comparesEqual(const PropertySheetIconValue &lhs, + const PropertySheetIconValue &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetIconValue) + + QSharedDataPointer m_data; +}; + +QDESIGNER_SHARED_EXPORT QDebug operator<<(QDebug, const PropertySheetIconValue &); + +class QDESIGNER_SHARED_EXPORT DesignerPixmapCache : public QObject +{ + Q_OBJECT +public: + DesignerPixmapCache(QObject *parent = nullptr); + QPixmap pixmap(const PropertySheetPixmapValue &value) const; + void clear(); +signals: + void reloaded(); +private: + mutable QHash m_cache; + friend class FormWindowBase; +}; + +class QDESIGNER_SHARED_EXPORT DesignerIconCache : public QObject +{ + Q_OBJECT +public: + explicit DesignerIconCache(DesignerPixmapCache *pixmapCache, QObject *parent = nullptr); + QIcon icon(const PropertySheetIconValue &value) const; + void clear(); +signals: + void reloaded(); +private: + mutable QHash m_cache; + DesignerPixmapCache *m_pixmapCache; + friend class FormWindowBase; +}; + +// -------------- PropertySheetTranslatableData: Base class for translatable properties. +class QDESIGNER_SHARED_EXPORT PropertySheetTranslatableData +{ +protected: + PropertySheetTranslatableData(bool translatable = true, + const QString &disambiguation = QString(), + const QString &comment = QString()); + +public: + bool translatable() const { return m_translatable; } + void setTranslatable(bool translatable) { m_translatable = translatable; } + QString disambiguation() const { return m_disambiguation; } + void setDisambiguation(const QString &d) { m_disambiguation = d; } + QString comment() const { return m_comment; } + void setComment(const QString &comment) { m_comment = comment; } + QString id() const { return m_id; } + void setId(const QString &id) { m_id = id; } + +private: + friend bool comparesEqual(const PropertySheetTranslatableData &lhs, + const PropertySheetTranslatableData &rhs) noexcept + { + return lhs.m_translatable == rhs.m_translatable + && lhs.m_disambiguation == rhs.m_disambiguation + && lhs.m_comment == rhs.m_comment + && lhs.m_id == rhs.m_id; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetTranslatableData) + + bool m_translatable; + QString m_disambiguation; + QString m_comment; + QString m_id; +}; + +// -------------- StringValue: Returned by the property sheet for strings +class QDESIGNER_SHARED_EXPORT PropertySheetStringValue : public PropertySheetTranslatableData +{ +public: + PropertySheetStringValue(const QString &value = QString(), bool translatable = true, + const QString &disambiguation = QString(), const QString &comment = QString()); + + QString value() const; + void setValue(const QString &value); + +private: + friend bool comparesEqual(const PropertySheetStringValue &lhs, + const PropertySheetStringValue &rhs) noexcept + { + const PropertySheetTranslatableData &upLhs = lhs; + const PropertySheetTranslatableData &upRhs = rhs; + return lhs.m_value == rhs.m_value && upLhs == upRhs; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetStringValue) + + QString m_value; +}; + +// -------------- StringValue: Returned by the property sheet for string lists +class QDESIGNER_SHARED_EXPORT PropertySheetStringListValue : public PropertySheetTranslatableData +{ +public: + PropertySheetStringListValue(const QStringList &value = QStringList(), + bool translatable = true, + const QString &disambiguation = QString(), + const QString &comment = QString()); + + QStringList value() const; + void setValue(const QStringList &value); + +private: + friend bool comparesEqual(const PropertySheetStringListValue &lhs, + const PropertySheetStringListValue &rhs) noexcept + { + const PropertySheetTranslatableData &upLhs = lhs; + const PropertySheetTranslatableData &upRhs = rhs; + return lhs.m_value == rhs.m_value && upLhs == upRhs; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetStringListValue) + + QStringList m_value; +}; + +// -------------- StringValue: Returned by the property sheet for strings +class QDESIGNER_SHARED_EXPORT PropertySheetKeySequenceValue : public PropertySheetTranslatableData +{ +public: + PropertySheetKeySequenceValue(const QKeySequence &value = QKeySequence(), + bool translatable = true, + const QString &disambiguation = QString(), + const QString &comment = QString()); + PropertySheetKeySequenceValue(const QKeySequence::StandardKey &standardKey, + bool translatable = true, + const QString &disambiguation = QString(), + const QString &comment = QString()); + + QKeySequence value() const; + void setValue(const QKeySequence &value); + QKeySequence::StandardKey standardKey() const; + void setStandardKey(const QKeySequence::StandardKey &standardKey); + bool isStandardKey() const; + +private: + friend bool comparesEqual(const PropertySheetKeySequenceValue &lhs, + const PropertySheetKeySequenceValue &rhs) noexcept + { + const PropertySheetTranslatableData &upLhs = lhs; + const PropertySheetTranslatableData &upRhs = rhs; + return lhs.m_value == rhs.m_value && lhs.m_standardKey == rhs.m_standardKey + && upLhs == upRhs; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetKeySequenceValue) + + QKeySequence m_value; + QKeySequence::StandardKey m_standardKey; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + + +// NOTE: Do not move this code, needed for GCC 3.3 +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetEnumValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetFlagValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetPixmapValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetIconValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetStringValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetStringListValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetKeySequenceValue) + + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + + +// Create a command to change a text property (that is, create a reset property command if the text is empty) +QDESIGNER_SHARED_EXPORT QDesignerFormWindowCommand *createTextPropertyCommand(const QString &propertyName, const QString &text, QObject *object, QDesignerFormWindowInterface *fw); + +// Returns preferred task menu action for managed widget +QDESIGNER_SHARED_EXPORT QAction *preferredEditAction(QDesignerFormEditorInterface *core, QWidget *managedWidget); + +enum class UicLanguage +{ + Cpp, + Python, +}; + +// Convenience to run UIC +QDESIGNER_SHARED_EXPORT bool runUIC(const QString &fileName, UicLanguage language, + QByteArray& ba, QString &errorMessage); + +// Find a suitable variable name for a class. +QDESIGNER_SHARED_EXPORT QString qtify(const QString &name); + +/* UpdateBlocker: Blocks the updates of the widget passed on while in scope. + * Does nothing if the incoming widget already has updatesEnabled==false + * which is important to avoid side-effects when putting it into QStackedLayout. */ + +class QDESIGNER_SHARED_EXPORT UpdateBlocker { + Q_DISABLE_COPY_MOVE(UpdateBlocker) + +public: + UpdateBlocker(QWidget *w); + ~UpdateBlocker(); + +private: + QWidget *m_widget; + const bool m_enabled; +}; + +// QPalette helpers: Mask for a single color role/group +QDESIGNER_SHARED_EXPORT quint64 paletteResolveMask(QPalette::ColorGroup colorGroup, + QPalette::ColorRole colorRole); +// Mask for the colors of a role in all groups (Active/Inactive/Disabled) +QDESIGNER_SHARED_EXPORT quint64 paletteResolveMask(QPalette::ColorRole colorRole); + +namespace Utils { + +inline int valueOf(const QVariant &value, bool *ok = nullptr) +{ + if (value.canConvert()) { + if (ok) + *ok = true; + return qvariant_cast(value).value; + } + if (value.canConvert()) { + if (ok) + *ok = true; + return qvariant_cast(value).value; + } + return value.toInt(ok); +} + +inline bool isObjectAncestorOf(QObject *ancestor, QObject *child) +{ + QObject *obj = child; + while (obj != nullptr) { + if (obj == ancestor) + return true; + obj = obj->parent(); + } + return false; +} + +inline bool isCentralWidget(QDesignerFormWindowInterface *fw, QWidget *widget) +{ + if (! fw || ! widget) + return false; + + if (widget == fw->mainContainer()) + return true; + + // ### generalize for other containers + if (QMainWindow *mw = qobject_cast(fw->mainContainer())) { + return mw->centralWidget() == widget; + } + + return false; +} + +} // namespace Utils + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_UTILS_H diff --git a/src/designer/src/lib/shared/qdesigner_widget.cpp b/src/designer/src/lib/shared/qdesigner_widget.cpp new file mode 100644 index 0000000..88f43d5 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_widget.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_widget_p.h" +#include "formwindowbase_p.h" +#include "grid_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/* QDesignerDialog / QDesignerWidget are used to paint a grid on QDialog and QWidget main containers + * and container extension pages. + * The paint routines work as follows: + * We need to clean the background here (to make the parent grid disappear in case we are a container page + * and to make palette background settings take effect), + * which would normally break style sheets with background settings. + * So, we manually make the style paint after cleaning. On top comes the grid + * In addition, this code works around + * the QStyleSheetStyle setting Qt::WA_StyledBackground to false for subclasses of QWidget. + */ + +QDesignerDialog::QDesignerDialog(QDesignerFormWindowInterface *fw, QWidget *parent) : + QDialog(parent), + m_formWindow(qobject_cast(fw)) +{ +} + +void QDesignerDialog::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + QStyleOption opt; + opt.initFrom(this); + p.fillRect(e->rect(), palette().brush(backgroundRole())); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + if (m_formWindow && m_formWindow->gridVisible()) + m_formWindow->designerGrid().paint(p, this, e); +} + +QDesignerWidget::QDesignerWidget(QDesignerFormWindowInterface* formWindow, QWidget *parent) : + QWidget(parent), + m_formWindow(qobject_cast(formWindow)) +{ +} + +QDesignerWidget::~QDesignerWidget() = default; + +QDesignerFormWindowInterface* QDesignerWidget::formWindow() const +{ + return m_formWindow; +} + +void QDesignerWidget::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + QStyleOption opt; + opt.initFrom(this); + p.fillRect(e->rect(), palette().brush(backgroundRole())); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + if (m_formWindow && m_formWindow->gridVisible()) + m_formWindow->designerGrid().paint(p, this, e); +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_widget_p.h b/src/designer/src/lib/shared/qdesigner_widget_p.h new file mode 100644 index 0000000..4deeaaf --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_widget_p.h @@ -0,0 +1,84 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_WIDGET_H +#define QDESIGNER_WIDGET_H + +#include "shared_global_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + class FormWindowBase; +} + +class QDESIGNER_SHARED_EXPORT QDesignerWidget : public QWidget +{ + Q_OBJECT +public: + explicit QDesignerWidget(QDesignerFormWindowInterface* formWindow, QWidget *parent = nullptr); + ~QDesignerWidget() override; + + QDesignerFormWindowInterface* formWindow() const; + + void updatePixmap(); + + QSize minimumSizeHint() const override + { return QWidget::minimumSizeHint().expandedTo(QSize(16, 16)); } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + qdesigner_internal::FormWindowBase* m_formWindow; +}; + +class QDESIGNER_SHARED_EXPORT QDesignerDialog : public QDialog +{ + Q_OBJECT +public: + explicit QDesignerDialog(QDesignerFormWindowInterface *fw, QWidget *parent); + + QSize minimumSizeHint() const override + { return QDialog::minimumSizeHint().expandedTo(QSize(16, 16)); } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + qdesigner_internal::FormWindowBase* m_formWindow; +}; + +class QDESIGNER_SHARED_EXPORT Line : public QFrame +{ + Q_OBJECT + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) +public: + explicit Line(QWidget *parent) : QFrame(parent) + { setAttribute(Qt::WA_MouseNoMask); setFrameStyle(HLine | Sunken); } + + inline void setOrientation(Qt::Orientation orient) + { setFrameShape(orient == Qt::Horizontal ? HLine : VLine); } + + inline Qt::Orientation orientation() const + { return frameShape() == HLine ? Qt::Horizontal : Qt::Vertical; } +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_WIDGET_H diff --git a/src/designer/src/lib/shared/qdesigner_widgetbox.cpp b/src/designer/src/lib/shared/qdesigner_widgetbox.cpp new file mode 100644 index 0000000..260f68a --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_widgetbox.cpp @@ -0,0 +1,233 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_widgetbox_p.h" +#include "qdesigner_utils_p.h" + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QDesignerWidgetBoxWidgetData : public QSharedData +{ +public: + QDesignerWidgetBoxWidgetData(const QString &aname, const QString &xml, + const QString &icon_name, + QDesignerWidgetBoxInterface::Widget::Type atype); + QString m_name; + QString m_xml; + QString m_icon_name; + QDesignerWidgetBoxInterface::Widget::Type m_type; +}; + +QDesignerWidgetBoxWidgetData::QDesignerWidgetBoxWidgetData(const QString &aname, + const QString &xml, + const QString &icon_name, + QDesignerWidgetBoxInterface::Widget::Type atype) : + m_name(aname), m_xml(xml), m_icon_name(icon_name), m_type(atype) +{ +} + +QDesignerWidgetBoxInterface::Widget::Widget(const QString &aname, const QString &xml, + const QString &icon_name, Type atype) : + m_data(new QDesignerWidgetBoxWidgetData(aname, xml, icon_name, atype)) +{ +} + +QDesignerWidgetBoxInterface::Widget::~Widget() = default; + +QDesignerWidgetBoxInterface::Widget::Widget(const Widget &w) : + m_data(w.m_data) +{ +} + +QDesignerWidgetBoxInterface::Widget &QDesignerWidgetBoxInterface::Widget::operator=(const Widget &rhs) +{ + if (this != &rhs) { + m_data = rhs.m_data; + } + return *this; +} + +QString QDesignerWidgetBoxInterface::Widget::name() const +{ + return m_data->m_name; +} + +void QDesignerWidgetBoxInterface::Widget::setName(const QString &aname) +{ + if (m_data->m_name != aname) + m_data->m_name = aname; +} + +QString QDesignerWidgetBoxInterface::Widget::domXml() const +{ + return m_data->m_xml; +} + +void QDesignerWidgetBoxInterface::Widget::setDomXml(const QString &xml) +{ + if (m_data->m_xml != xml) + m_data->m_xml = xml; +} + +QString QDesignerWidgetBoxInterface::Widget::iconName() const +{ + return m_data->m_icon_name; +} + +void QDesignerWidgetBoxInterface::Widget::setIconName(const QString &icon_name) +{ + if (m_data->m_icon_name != icon_name) + m_data->m_icon_name = icon_name; +} + +QDesignerWidgetBoxInterface::Widget::Type QDesignerWidgetBoxInterface::Widget::type() const +{ + return m_data->m_type; +} + +void QDesignerWidgetBoxInterface::Widget::setType(Type atype) +{ + if (m_data->m_type != atype) + m_data->m_type = atype; +} + +bool QDesignerWidgetBoxInterface::Widget::isNull() const +{ + return m_data->m_name.isEmpty(); +} + +namespace qdesigner_internal { +QDesignerWidgetBox::QDesignerWidgetBox(QWidget *parent, Qt::WindowFlags flags) + : QDesignerWidgetBoxInterface(parent, flags) +{ + +} + +QDesignerWidgetBox::LoadMode QDesignerWidgetBox::loadMode() const +{ + return m_loadMode; +} + +void QDesignerWidgetBox::setLoadMode(LoadMode lm) +{ + m_loadMode = lm; +} + +// Convenience to find a widget by class name +bool QDesignerWidgetBox::findWidget(const QDesignerWidgetBoxInterface *wbox, + const QString &className, + const QString &category, + Widget *widgetData) +{ + // Note that entry names do not necessarily match the class name + // (at least, not for the standard widgets), so, + // look in the XML for the class name of the first widget to appear + QString pattern = QStringLiteral("^categoryCount(); + for (int c = 0; c < catCount; c++) { + const Category cat = wbox->category(c); + if (category.isEmpty() || cat.name() == category) { + const int widgetCount = cat.widgetCount(); + for (int w = 0; w < widgetCount; w++) { + const Widget widget = cat.widget(w); + QString xml = widget.domXml(); // Erase the tag that can be present starting from 4.4 + const auto widgetTagIndex = xml.indexOf("").arg(name.toString())); + continue; + } + + if (name.compare("widget"_L1, Qt::CaseInsensitive) == 0) { // 4.3 legacy, wrap into DomUI + ui = new DomUI; + DomWidget *widget = new DomWidget; + widget->read(reader); + ui->setElementWidget(widget); + } else if (name.compare("ui"_L1, Qt::CaseInsensitive) == 0) { // 4.4 + ui = new DomUI; + ui->read(reader); + } else { + reader.raiseError(tr("Unexpected element <%1>").arg(name.toString())); + } + } + } + + if (reader.hasError()) { + delete ui; + *errorMessage = tr("A parse error occurred at line %1, column %2 of the XML code " + "specified for the widget %3: %4\n%5") + .arg(reader.lineNumber()).arg(reader.columnNumber()) + .arg(name, reader.errorString(), xml); + return nullptr; + } + + if (!ui || !ui->elementWidget()) { + delete ui; + *errorMessage = tr("The XML code specified for the widget %1 does not contain " + "any widget elements.\n%2").arg(name, xml); + return nullptr; + } + + if (insertFakeTopLevel) { + DomWidget *fakeTopLevel = new DomWidget; + fakeTopLevel->setAttributeClass(u"QWidget"_s); + QList children; + children.push_back(ui->takeElementWidget()); + fakeTopLevel->setElementWidget(children); + ui->setElementWidget(fakeTopLevel); + } + + return ui; +} + +// Convenience to create a Dom Widget from widget box xml code. +DomUI *QDesignerWidgetBox::xmlToUi(const QString &name, const QString &xml, bool insertFakeTopLevel) +{ + QString errorMessage; + DomUI *rc = xmlToUi(name, xml, insertFakeTopLevel, &errorMessage); + if (!rc) + qdesigner_internal::designerWarning(errorMessage); + return rc; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_widgetbox_p.h b/src/designer/src/lib/shared/qdesigner_widgetbox_p.h new file mode 100644 index 0000000..813d658 --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_widgetbox_p.h @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_WIDGETBOX_H +#define QDESIGNER_WIDGETBOX_H + +#include "shared_global_p.h" +#include + +QT_BEGIN_NAMESPACE + +class DomUI; + +namespace qdesigner_internal { + +// A widget box with a load mode that allows for updating custom widgets. + +class QDESIGNER_SHARED_EXPORT QDesignerWidgetBox : public QDesignerWidgetBoxInterface +{ + Q_OBJECT +public: + enum LoadMode { LoadMerge, LoadReplace, LoadCustomWidgetsOnly }; + + explicit QDesignerWidgetBox(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + + LoadMode loadMode() const; + void setLoadMode(LoadMode lm); + + virtual bool loadContents(const QString &contents) = 0; + + // Convenience to access the widget box icon of a widget. Empty category + // matches all + virtual QIcon iconForWidget(const QString &className, + const QString &category = QString()) const = 0; + + // Convenience to find a widget by class name. Empty category matches all + static bool findWidget(const QDesignerWidgetBoxInterface *wbox, + const QString &className, + const QString &category /* = QString() */, + Widget *widgetData); + // Convenience functions to create a DomWidget from widget box xml. + static DomUI *xmlToUi(const QString &name, const QString &xml, bool insertFakeTopLevel, QString *errorMessage); + static DomUI *xmlToUi(const QString &name, const QString &xml, bool insertFakeTopLevel); + +private: + LoadMode m_loadMode = LoadMerge; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_WIDGETBOX_H diff --git a/src/designer/src/lib/shared/qdesigner_widgetitem.cpp b/src/designer/src/lib/shared/qdesigner_widgetitem.cpp new file mode 100644 index 0000000..b986aed --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_widgetitem.cpp @@ -0,0 +1,308 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_widgetitem_p.h" +#include "qdesigner_widget_p.h" +#include "widgetfactory_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +enum { DebugWidgetItem = 0 }; +enum { MinimumLength = 10 }; + +// Widget item creation function to be registered as factory method with +// QLayoutPrivate +static QWidgetItem *createDesignerWidgetItem(const QLayout *layout, QWidget *widget) +{ + Qt::Orientations orientations; + if (qdesigner_internal::QDesignerWidgetItem::check(layout, widget, &orientations)) { + if (DebugWidgetItem) + qDebug() << "QDesignerWidgetItem: Creating on " << layout << widget << orientations; + return new qdesigner_internal::QDesignerWidgetItem(layout, widget, orientations); + } + if (DebugWidgetItem) + qDebug() << "QDesignerWidgetItem: Noncontainer: " << layout << widget; + + return nullptr; +} + +static QString sizePolicyToString(const QSizePolicy &p) +{ + QString rc; { + QTextStream str(&rc); + str << "Control=" << p.controlType() << " expdirs=" << p.expandingDirections() + << " hasHeightForWidth=" << p.hasHeightForWidth() + << " H: Policy=" << p.horizontalPolicy() + << " stretch=" << p.horizontalStretch() + << " V: Policy=" << p.verticalPolicy() + << " stretch=" << p.verticalStretch(); + } + return rc; +} + +// Find the layout the item is contained in, recursing over +// child layouts +static const QLayout *findLayoutOfItem(const QLayout *haystack, const QLayoutItem *needle) +{ + const int count = haystack->count(); + for (int i = 0; i < count; i++) { + QLayoutItem *item = haystack->itemAt(i); + if (item == needle) + return haystack; + if (QLayout *childLayout = item->layout()) + if (const QLayout *containing = findLayoutOfItem(childLayout, needle)) + return containing; + } + return nullptr; +} + + +namespace qdesigner_internal { + +// ------------------ QDesignerWidgetItem +QDesignerWidgetItem::QDesignerWidgetItem(const QLayout *containingLayout, QWidget *w, Qt::Orientations o) : + QWidgetItemV2(w), + m_orientations(o), + m_nonLaidOutMinSize(w->minimumSizeHint()), + m_nonLaidOutSizeHint(w->sizeHint()), + m_cachedContainingLayout(containingLayout) +{ + // Initialize the minimum size to prevent nonlaid-out frames/widgets + // from being slammed to zero + const QSize minimumSize = w->minimumSize(); + if (!minimumSize.isEmpty()) + m_nonLaidOutMinSize = minimumSize; + expand(&m_nonLaidOutMinSize); + expand(&m_nonLaidOutSizeHint); + w->installEventFilter(this); + connect(containingLayout, &QObject::destroyed, this, &QDesignerWidgetItem::layoutChanged); + if (DebugWidgetItem ) + qDebug() << "QDesignerWidgetItem" << w << sizePolicyToString(w->sizePolicy()) << m_nonLaidOutMinSize << m_nonLaidOutSizeHint; +} + +void QDesignerWidgetItem::expand(QSize *s) const +{ + // Expand the size if its too small + if (m_orientations & Qt::Horizontal && s->width() <= 0) + s->setWidth(MinimumLength); + if (m_orientations & Qt::Vertical && s->height() <= 0) + s->setHeight(MinimumLength); +} + +QSize QDesignerWidgetItem::minimumSize() const +{ + // Just track the size in case we are laid-out or stretched. + const QSize baseMinSize = QWidgetItemV2::minimumSize(); + QWidget * w = constWidget(); + if (w->layout() || subjectToStretch(containingLayout(), w)) { + m_nonLaidOutMinSize = baseMinSize; + return baseMinSize; + } + // Nonlaid out: Maintain last laid-out size + const QSize rc = baseMinSize.expandedTo(m_nonLaidOutMinSize); + if (DebugWidgetItem > 1) + qDebug() << "minimumSize" << constWidget() << baseMinSize << rc; + return rc; +} + +QSize QDesignerWidgetItem::sizeHint() const +{ + // Just track the size in case we are laid-out or stretched. + const QSize baseSizeHint = QWidgetItemV2::sizeHint(); + QWidget * w = constWidget(); + if (w->layout() || subjectToStretch(containingLayout(), w)) { + m_nonLaidOutSizeHint = baseSizeHint; + return baseSizeHint; + } + // Nonlaid out: Maintain last laid-out size + const QSize rc = baseSizeHint.expandedTo(m_nonLaidOutSizeHint); + if (DebugWidgetItem > 1) + qDebug() << "sizeHint" << constWidget() << baseSizeHint << rc; + return rc; +} + +bool QDesignerWidgetItem::subjectToStretch(const QLayout *layout, QWidget *w) +{ + if (!layout) + return false; + // Are we under some stretch factor? + if (const QBoxLayout *bl = qobject_cast(layout)) { + const int index = bl->indexOf(w); + Q_ASSERT(index != -1); + return bl->stretch(index) != 0; + } + if (const QGridLayout *cgl = qobject_cast(layout)) { + QGridLayout *gl = const_cast(cgl); + const int index = cgl->indexOf(w); + Q_ASSERT(index != -1); + int row, column, rowSpan, columnSpan; + gl->getItemPosition (index, &row, &column, &rowSpan, &columnSpan); + const int rend = row + rowSpan; + const int cend = column + columnSpan; + for (int r = row; r < rend; r++) + if (cgl->rowStretch(r) != 0) + return true; + for (int c = column; c < cend; c++) + if (cgl->columnStretch(c) != 0) + return true; + } + return false; +} + +/* Return the orientations mask for a layout, specifying + * in which directions squeezing should be prevented. */ +static Qt::Orientations layoutOrientation(const QLayout *layout) +{ + if (const QBoxLayout *bl = qobject_cast(layout)) { + const QBoxLayout::Direction direction = bl->direction(); + return direction == QBoxLayout::LeftToRight || direction == QBoxLayout::RightToLeft ? Qt::Horizontal : Qt::Vertical; + } + if (qobject_cast(layout)) + return Qt::Vertical; + return Qt::Horizontal|Qt::Vertical; +} + +// Check for a non-container extension container +bool QDesignerWidgetItem::isContainer(const QDesignerFormEditorInterface *core, QWidget *w) +{ + if (!WidgetFactory::isFormEditorObject(w)) + return false; + const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); + const int widx = wdb->indexOfObject(w); + if (widx == -1 || !wdb->item(widx)->isContainer()) + return false; + if (qt_extension(core->extensionManager(), w)) + return false; + return true; +} + +bool QDesignerWidgetItem::check(const QLayout *layout, QWidget *w, Qt::Orientations *ptrToOrientations) +{ + // Check for form-editor non-containerextension-containers (QFrame, etc) + // within laid-out form editor widgets. No check for managed() here as we + // want container pages and widgets in the process of being morphed as + // well. Avoid nested layouts (as the effective stretch cannot be easily + // computed and may mess things up). + if (ptrToOrientations) + *ptrToOrientations = {}; + + const QObject *layoutParent = layout->parent(); + if (!layoutParent || !layoutParent->isWidgetType() || !WidgetFactory::isFormEditorObject(layoutParent)) + return false; + + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w); + if (!fw || !isContainer(fw->core(), w)) + return false; + + // If it is a box, restrict to its orientation + if (ptrToOrientations) + *ptrToOrientations = layoutOrientation(layout); + + return true; +} + +QSize QDesignerWidgetItem::nonLaidOutMinSize() const +{ + return m_nonLaidOutMinSize; +} + +void QDesignerWidgetItem::setNonLaidOutMinSize(const QSize &s) +{ + if (DebugWidgetItem > 1) + qDebug() << "setNonLaidOutMinSize" << constWidget() << s; + m_nonLaidOutMinSize = s; +} + +QSize QDesignerWidgetItem::nonLaidOutSizeHint() const +{ + return m_nonLaidOutSizeHint; +} + +void QDesignerWidgetItem::setNonLaidOutSizeHint(const QSize &s) +{ + if (DebugWidgetItem > 1) + qDebug() << "setNonLaidOutSizeHint" << constWidget() << s; + m_nonLaidOutSizeHint = s; +} + +void QDesignerWidgetItem::install() +{ + QLayoutPrivate::widgetItemFactoryMethod = createDesignerWidgetItem; +} + +void QDesignerWidgetItem::deinstall() +{ + QLayoutPrivate::widgetItemFactoryMethod = nullptr; +} + +const QLayout *QDesignerWidgetItem::containingLayout() const +{ + if (!m_cachedContainingLayout) { + if (QWidget *parentWidget = constWidget()->parentWidget()) + if (QLayout *parentLayout = parentWidget->layout()) { + m_cachedContainingLayout = findLayoutOfItem(parentLayout, this); + if (m_cachedContainingLayout) { + connect(m_cachedContainingLayout, &QObject::destroyed, + this, &QDesignerWidgetItem::layoutChanged); + } + } + if (DebugWidgetItem) + qDebug() << Q_FUNC_INFO << " found " << m_cachedContainingLayout << " after reparenting " << constWidget(); + } + return m_cachedContainingLayout; +} + +void QDesignerWidgetItem::layoutChanged() +{ + if (DebugWidgetItem) + qDebug() << Q_FUNC_INFO; + m_cachedContainingLayout = nullptr; +} + +bool QDesignerWidgetItem::eventFilter(QObject * /* watched */, QEvent *event) +{ + if (event->type() == QEvent::ParentChange) + layoutChanged(); + return false; +} + +// ------------------ QDesignerWidgetItemInstaller + +int QDesignerWidgetItemInstaller::m_instanceCount = 0; + +QDesignerWidgetItemInstaller::QDesignerWidgetItemInstaller() +{ + if (m_instanceCount++ == 0) { + if (DebugWidgetItem) + qDebug() << "QDesignerWidgetItemInstaller: installing"; + QDesignerWidgetItem::install(); + } +} + +QDesignerWidgetItemInstaller::~QDesignerWidgetItemInstaller() +{ + if (--m_instanceCount == 0) { + if (DebugWidgetItem) + qDebug() << "QDesignerWidgetItemInstaller: deinstalling"; + QDesignerWidgetItem::deinstall(); + } +} + +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qdesigner_widgetitem_p.h b/src/designer/src/lib/shared/qdesigner_widgetitem_p.h new file mode 100644 index 0000000..b7a533e --- /dev/null +++ b/src/designer/src/lib/shared/qdesigner_widgetitem_p.h @@ -0,0 +1,109 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DESIGNERWIDGETITEM_H +#define DESIGNERWIDGETITEM_H + +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +// QDesignerWidgetItem: A Layout Item that is used for non-containerextension- +// containers (QFrame, etc) on Designer forms. It prevents its widget +// from being slammed to size 0 if the widget has no layout: +// Pre 4.5, this item ensured only that QWidgets and QFrames were not squeezed +// to size 0 since they have an invalid size hint when non-laid out. +// Since 4.5, the item is used for every non-containerextension-container. +// In case the container has itself a layout, it merely tracks the minimum +// size. If the container has no layout and is not subject to some stretch +// factor, it will return the last valid size. The effect is that after +// breaking a layout on a container within a layout, it just maintains its +// last size and is not slammed to 0,0. In addition, it can be resized. +// The class keeps track of the containing layout by tracking widget reparent +// and destroyed slots as Designer will for example re-create grid layouts to +// shrink them. + +class QDESIGNER_SHARED_EXPORT QDesignerWidgetItem : public QObject, public QWidgetItemV2 { + Q_DISABLE_COPY_MOVE(QDesignerWidgetItem) + Q_OBJECT +public: + explicit QDesignerWidgetItem(const QLayout *containingLayout, QWidget *w, Qt::Orientations o = Qt::Horizontal|Qt::Vertical); + + const QLayout *containingLayout() const; + + inline QWidget *constWidget() const { return const_cast(this)->widget(); } + + QSize minimumSize() const override; + QSize sizeHint() const override; + + // Resize: Takes effect if the contained widget does not have a layout + QSize nonLaidOutMinSize() const; + void setNonLaidOutMinSize(const QSize &s); + + QSize nonLaidOutSizeHint() const; + void setNonLaidOutSizeHint(const QSize &s); + + // Check whether a QDesignerWidgetItem should be installed + static bool check(const QLayout *layout, QWidget *w, Qt::Orientations *ptrToOrientations = nullptr); + + // Register itself using QLayoutPrivate's widget item factory method hook + static void install(); + static void deinstall(); + + // Check for a non-container extension container + static bool isContainer(const QDesignerFormEditorInterface *core, QWidget *w); + + static bool subjectToStretch(const QLayout *layout, QWidget *w); + + bool eventFilter(QObject * watched, QEvent * event) override; + +private slots: + void layoutChanged(); + +private: + void expand(QSize *s) const; + bool subjectToStretch() const; + + const Qt::Orientations m_orientations; + mutable QSize m_nonLaidOutMinSize; + mutable QSize m_nonLaidOutSizeHint; + mutable const QLayout *m_cachedContainingLayout; +}; + +// Helper class that ensures QDesignerWidgetItem is installed while an +// instance is in scope. + +class QDESIGNER_SHARED_EXPORT QDesignerWidgetItemInstaller { + Q_DISABLE_COPY_MOVE(QDesignerWidgetItemInstaller) + +public: + QDesignerWidgetItemInstaller(); + ~QDesignerWidgetItemInstaller(); + +private: + static int m_instanceCount; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/lib/shared/qlayout_widget.cpp b/src/designer/src/lib/shared/qlayout_widget.cpp new file mode 100644 index 0000000..fed8bcf --- /dev/null +++ b/src/designer/src/lib/shared/qlayout_widget.cpp @@ -0,0 +1,2077 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qlayout_widget_p.h" +#include "qdesigner_utils_p.h" +#include "layout_p.h" +#include "layoutinfo_p.h" +#include "invisible_widget_p.h" +#include "qdesigner_widgetitem_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +enum { ShiftValue = 1 }; +enum { debugLayout = 0 }; +enum { FormLayoutColumns = 2 }; +enum { indicatorSize = 2 }; +// Grid/form Helpers: get info (overloads to make templates work) + +namespace { // Do not use static, will break HP-UX due to templates + +QT_USE_NAMESPACE + +// overloads to make templates over QGridLayout/QFormLayout work +inline int gridRowCount(const QGridLayout *gridLayout) +{ + return gridLayout->rowCount(); +} + +inline int gridColumnCount(const QGridLayout *gridLayout) +{ + return gridLayout->columnCount(); +} + +// QGridLayout/QFormLayout Helpers: get item position (overloads to make templates work) +inline void getGridItemPosition(QGridLayout *gridLayout, int index, + int *row, int *column, int *rowspan, int *colspan) +{ + gridLayout->getItemPosition(index, row, column, rowspan, colspan); +} + +QRect gridItemInfo(QGridLayout *grid, int index) +{ + int row, column, rowSpan, columnSpan; + // getItemPosition is not const, grmbl.. + grid->getItemPosition(index, &row, &column, &rowSpan, &columnSpan); + return QRect(column, row, columnSpan, rowSpan); +} + +inline int gridRowCount(const QFormLayout *formLayout) { return formLayout->rowCount(); } +inline int gridColumnCount(const QFormLayout *) { return FormLayoutColumns; } + +inline void getGridItemPosition(QFormLayout *formLayout, int index, int *row, int *column, int *rowspan, int *colspan) +{ + qdesigner_internal::getFormLayoutItemPosition(formLayout, index, row, column, rowspan, colspan); +} +} // namespace anonymous + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto objectNameC = "objectName"_L1; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +static constexpr auto sizeConstraintC = "sizeConstraint"_L1; +#else +static constexpr auto horizSizeConstraintC = "horizontalSizeConstraint"_L1; +static constexpr auto vertSizeConstraintC = "verticalSizeConstraint"_L1; +#endif + +/* A padding spacer element that is used to represent an empty form layout cell. It should grow with its cell. + * Should not be used on a grid as it causes resizing inconsistencies */ +namespace qdesigner_internal { + class PaddingSpacerItem : public QSpacerItem { + public: + PaddingSpacerItem() : QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding) {} + + Qt::Orientations expandingDirections () const override + { return Qt::Vertical | Qt::Horizontal; } + }; +} + +static inline QSpacerItem *createGridSpacer() +{ + return new QSpacerItem(0, 0); +} + +static inline QSpacerItem *createFormSpacer() +{ + return new qdesigner_internal::PaddingSpacerItem; +} + +// QGridLayout/QFormLayout Helpers: Debug items of GridLikeLayout +template +static QDebug debugGridLikeLayout(QDebug str, const GridLikeLayout &gl) +{ + const int count = gl.count(); + str << "Grid: " << gl.objectName() << gridRowCount(&gl) << " rows x " << gridColumnCount(&gl) + << " cols " << count << " items\n"; + for (int i = 0; i < count; i++) { + QLayoutItem *item = gl.itemAt(i); + str << "Item " << i << item << item->widget() << gridItemInfo(const_cast(&gl), i) << " empty=" << qdesigner_internal::LayoutInfo::isEmptyItem(item) << "\n"; + } + return str; +} + +static inline QDebug operator<<(QDebug str, const QGridLayout &gl) { return debugGridLikeLayout(str, gl); } + +static inline bool isEmptyFormLayoutRow(const QFormLayout *fl, int row) +{ + // Spanning can never be empty + if (fl->itemAt(row, QFormLayout::SpanningRole)) + return false; + return qdesigner_internal::LayoutInfo::isEmptyItem(fl->itemAt(row, QFormLayout::LabelRole)) && qdesigner_internal::LayoutInfo::isEmptyItem(fl->itemAt(row, QFormLayout::FieldRole)); +} + +static inline bool canSimplifyFormLayout(const QFormLayout *formLayout, const QRect &restrictionArea) +{ + if (restrictionArea.x() >= FormLayoutColumns) + return false; + // Try to find empty rows + const int bottomCheckRow = qMin(formLayout->rowCount(), restrictionArea.top() + restrictionArea.height()); + for (int r = restrictionArea.y(); r < bottomCheckRow; r++) + if (isEmptyFormLayoutRow(formLayout, r)) + return true; + return false; +} + +// recreate a managed layout (which does not automagically remove +// empty rows/columns like grid or form layout) in case it needs to shrink + +static QLayout *recreateManagedLayout(const QDesignerFormEditorInterface *core, QWidget *w, QLayout *lt) +{ + const qdesigner_internal::LayoutInfo::Type t = qdesigner_internal::LayoutInfo::layoutType(core, lt); + qdesigner_internal::LayoutProperties properties; + const int mask = properties.fromPropertySheet(core, lt, qdesigner_internal::LayoutProperties::AllProperties); + qdesigner_internal::LayoutInfo::deleteLayout(core, w); + QLayout *rc = core->widgetFactory()->createLayout(w, nullptr, t); + properties.toPropertySheet(core, rc, mask, true); + return rc; +} + +// QGridLayout/QFormLayout Helpers: find an item on a form/grid. Return index +template +int findGridItemAt(GridLikeLayout *gridLayout, int at_row, int at_column) +{ + Q_ASSERT(gridLayout); + const int count = gridLayout->count(); + for (int index = 0; index < count; index++) { + int row, column, rowspan, colspan; + getGridItemPosition(gridLayout, index, &row, &column, &rowspan, &colspan); + if (at_row >= row && at_row < (row + rowspan) + && at_column >= column && at_column < (column + colspan)) { + return index; + } + } + return -1; +} +// QGridLayout/QFormLayout Helpers: remove dummy spacers on form/grid +template +static bool removeEmptyCellsOnGrid(GridLikeLayout *grid, const QRect &area) +{ + // check if there are any items in the way. Should be only spacers + // Unique out items that span rows/columns. + QList indexesToBeRemoved; + indexesToBeRemoved.reserve(grid->count()); + const int rightColumn = area.x() + area.width(); + const int bottomRow = area.y() + area.height(); + for (int c = area.x(); c < rightColumn; c++) + for (int r = area.y(); r < bottomRow; r++) { + const int index = findGridItemAt(grid, r ,c); + if (index != -1) + if (QLayoutItem *item = grid->itemAt(index)) { + if (qdesigner_internal::LayoutInfo::isEmptyItem(item)) { + if (indexesToBeRemoved.indexOf(index) == -1) + indexesToBeRemoved.push_back(index); + } else { + return false; + } + } + } + // remove, starting from last + if (!indexesToBeRemoved.isEmpty()) { + std::stable_sort(indexesToBeRemoved.begin(), indexesToBeRemoved.end()); + std::reverse(indexesToBeRemoved.begin(), indexesToBeRemoved.end()); + for (auto i : std::as_const(indexesToBeRemoved)) + delete grid->takeAt(i); + } + return true; +} + +namespace qdesigner_internal { +// --------- LayoutProperties + +LayoutProperties::LayoutProperties() +{ + clear(); +} + +void LayoutProperties::clear() +{ + std::fill(m_margins, m_margins + MarginCount, 0); + std::fill(m_marginsChanged, m_marginsChanged + MarginCount, false); + std::fill(m_spacings, m_spacings + SpacingsCount, 0); + std::fill(m_spacingsChanged, m_spacingsChanged + SpacingsCount, false); + + m_objectName = QVariant(); + m_objectNameChanged = false; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + m_sizeConstraint = QVariant(QLayout::SetDefaultConstraint); + m_sizeConstraintChanged = false; +#else + m_horizSizeConstraint = m_vertSizeConstraint = QVariant(QLayout::SetDefaultConstraint); + m_horizSizeConstraintChanged = m_vertSizeConstraintChanged = false; +#endif + + m_fieldGrowthPolicyChanged = m_rowWrapPolicyChanged = m_labelAlignmentChanged = m_formAlignmentChanged = false; + m_fieldGrowthPolicy = m_rowWrapPolicy = m_formAlignment = QVariant(); + + m_boxStretchChanged = m_gridRowStretchChanged = m_gridColumnStretchChanged = m_gridRowMinimumHeightChanged = false; + m_boxStretch = m_gridRowStretch = m_gridColumnStretch = m_gridRowMinimumHeight = QVariant(); +} + +int LayoutProperties::visibleProperties(const QLayout *layout) +{ + // Grid like layout have 2 spacings. + const bool isFormLayout = qobject_cast(layout); + const bool isGridLike = qobject_cast(layout) || isFormLayout; + int rc = ObjectNameProperty|LeftMarginProperty|TopMarginProperty|RightMarginProperty|BottomMarginProperty| +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + SizeConstraintProperty; +#else + HorizSizeConstraintProperty | VertSizeConstraintProperty; +#endif + rc |= isGridLike ? (HorizSpacingProperty|VertSpacingProperty) : SpacingProperty; + if (isFormLayout) { + rc |= FieldGrowthPolicyProperty|RowWrapPolicyProperty|LabelAlignmentProperty|FormAlignmentProperty; + } else { + if (isGridLike) { + rc |= GridRowStretchProperty|GridColumnStretchProperty|GridRowMinimumHeightProperty|GridColumnMinimumWidthProperty; + } else { + rc |= BoxStretchProperty; + } + } + return rc; +} + +static const char *marginPropertyNamesC[] = {"leftMargin", "topMargin", "rightMargin", "bottomMargin"}; +static const char *spacingPropertyNamesC[] = {"spacing", "horizontalSpacing", "verticalSpacing" }; +static constexpr auto fieldGrowthPolicyPropertyC = "fieldGrowthPolicy"_L1; +static constexpr auto rowWrapPolicyPropertyC = "rowWrapPolicy"_L1; +static constexpr auto labelAlignmentPropertyC = "labelAlignment"_L1; +static constexpr auto formAlignmentPropertyC = "formAlignment"_L1; +static constexpr auto boxStretchPropertyC = "stretch"_L1; +static constexpr auto gridRowStretchPropertyC = "rowStretch"_L1; +static constexpr auto gridColumnStretchPropertyC = "columnStretch"_L1; +static constexpr auto gridRowMinimumHeightPropertyC = "rowMinimumHeight"_L1; +static constexpr auto gridColumnMinimumWidthPropertyC = "columnMinimumWidth"_L1; + +static bool intValueFromSheet(const QDesignerPropertySheetExtension *sheet, const QString &name, int *value, bool *changed) +{ + const int sheetIndex = sheet->indexOf(name); + if (sheetIndex == -1) + return false; + *value = sheet->property(sheetIndex).toInt(); + *changed = sheet->isChanged(sheetIndex); + return true; +} + +static void variantPropertyFromSheet(int mask, int flag, const QDesignerPropertySheetExtension *sheet, const QString &name, + QVariant *value, bool *changed, int *returnMask) +{ + if (mask & flag) { + const int sIndex = sheet->indexOf(name); + if (sIndex != -1) { + *value = sheet->property(sIndex); + *changed = sheet->isChanged(sIndex); + *returnMask |= flag; + } + } +} + +int LayoutProperties::fromPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask) +{ + int rc = 0; + const QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), l); + Q_ASSERT(sheet); + // name + if (mask & ObjectNameProperty) { + const int nameIndex = sheet->indexOf(objectNameC); + Q_ASSERT(nameIndex != -1); + m_objectName = sheet->property(nameIndex); + m_objectNameChanged = sheet->isChanged(nameIndex); + rc |= ObjectNameProperty; + } + // -- Margins + const int marginFlags[MarginCount] = { LeftMarginProperty, TopMarginProperty, RightMarginProperty, BottomMarginProperty}; + for (int i = 0; i < MarginCount; i++) + if (mask & marginFlags[i]) + if (intValueFromSheet(sheet, QLatin1StringView(marginPropertyNamesC[i]), m_margins + i, m_marginsChanged + i)) + rc |= marginFlags[i]; + + const int spacingFlags[] = { SpacingProperty, HorizSpacingProperty, VertSpacingProperty}; + for (int i = 0; i < SpacingsCount; i++) + if (mask & spacingFlags[i]) + if (intValueFromSheet(sheet, QLatin1StringView(spacingPropertyNamesC[i]), m_spacings + i, m_spacingsChanged + i)) + rc |= spacingFlags[i]; + // sizeConstraint, flags +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + variantPropertyFromSheet(mask, SizeConstraintProperty, sheet, sizeConstraintC, + &m_sizeConstraint, &m_sizeConstraintChanged, &rc); +#else + variantPropertyFromSheet(mask, HorizSizeConstraintProperty, sheet, horizSizeConstraintC, + &m_horizSizeConstraint, &m_horizSizeConstraintChanged, &rc); + variantPropertyFromSheet(mask, VertSizeConstraintProperty, sheet, vertSizeConstraintC, + &m_vertSizeConstraint, &m_vertSizeConstraintChanged, &rc); +#endif + variantPropertyFromSheet(mask, FieldGrowthPolicyProperty, sheet, fieldGrowthPolicyPropertyC, &m_fieldGrowthPolicy, &m_fieldGrowthPolicyChanged, &rc); + variantPropertyFromSheet(mask, RowWrapPolicyProperty, sheet, rowWrapPolicyPropertyC, &m_rowWrapPolicy, &m_rowWrapPolicyChanged, &rc); + variantPropertyFromSheet(mask, LabelAlignmentProperty, sheet, labelAlignmentPropertyC, &m_labelAlignment, &m_labelAlignmentChanged, &rc); + variantPropertyFromSheet(mask, FormAlignmentProperty, sheet, formAlignmentPropertyC, &m_formAlignment, &m_formAlignmentChanged, &rc); + variantPropertyFromSheet(mask, BoxStretchProperty, sheet, boxStretchPropertyC, &m_boxStretch, & m_boxStretchChanged, &rc); + variantPropertyFromSheet(mask, GridRowStretchProperty, sheet, gridRowStretchPropertyC, &m_gridRowStretch, &m_gridRowStretchChanged, &rc); + variantPropertyFromSheet(mask, GridColumnStretchProperty, sheet, gridColumnStretchPropertyC, &m_gridColumnStretch, &m_gridColumnStretchChanged, &rc); + variantPropertyFromSheet(mask, GridRowMinimumHeightProperty, sheet, gridRowMinimumHeightPropertyC, &m_gridRowMinimumHeight, &m_gridRowMinimumHeightChanged, &rc); + variantPropertyFromSheet(mask, GridColumnMinimumWidthProperty, sheet, gridColumnMinimumWidthPropertyC, &m_gridColumnMinimumWidth, &m_gridColumnMinimumWidthChanged, &rc); + return rc; +} + +static bool intValueToSheet(QDesignerPropertySheetExtension *sheet, const QString &name, int value, bool changed, bool applyChanged) + +{ + + const int sheetIndex = sheet->indexOf(name); + if (sheetIndex == -1) { + qWarning() << " LayoutProperties: Attempt to set property " << name << " that does not exist for the layout."; + return false; + } + sheet->setProperty(sheetIndex, QVariant(value)); + if (applyChanged) + sheet->setChanged(sheetIndex, changed); + return true; +} + +static void variantPropertyToSheet(int mask, int flag, bool applyChanged, QDesignerPropertySheetExtension *sheet, const QString &name, + const QVariant &value, bool changed, int *returnMask) +{ + if (mask & flag) { + const int sIndex = sheet->indexOf(name); + if (sIndex != -1) { + sheet->setProperty(sIndex, value); + if (applyChanged) + sheet->setChanged(sIndex, changed); + *returnMask |= flag; + } + } +} + +int LayoutProperties::toPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask, bool applyChanged) const +{ + int rc = 0; + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), l); + Q_ASSERT(sheet); + // name + if (mask & ObjectNameProperty) { + const int nameIndex = sheet->indexOf(objectNameC); + Q_ASSERT(nameIndex != -1); + sheet->setProperty(nameIndex, m_objectName); + if (applyChanged) + sheet->setChanged(nameIndex, m_objectNameChanged); + rc |= ObjectNameProperty; + } + // margins + const int marginFlags[MarginCount] = { LeftMarginProperty, TopMarginProperty, RightMarginProperty, BottomMarginProperty}; + for (int i = 0; i < MarginCount; i++) + if (mask & marginFlags[i]) + if (intValueToSheet(sheet, QLatin1StringView(marginPropertyNamesC[i]), m_margins[i], m_marginsChanged[i], applyChanged)) + rc |= marginFlags[i]; + + const int spacingFlags[] = { SpacingProperty, HorizSpacingProperty, VertSpacingProperty}; + for (int i = 0; i < SpacingsCount; i++) + if (mask & spacingFlags[i]) + if (intValueToSheet(sheet, QLatin1StringView(spacingPropertyNamesC[i]), m_spacings[i], m_spacingsChanged[i], applyChanged)) + rc |= spacingFlags[i]; + // sizeConstraint +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + variantPropertyToSheet(mask, SizeConstraintProperty, applyChanged, sheet, + sizeConstraintC, m_sizeConstraint, m_sizeConstraintChanged, &rc); +#else + variantPropertyToSheet(mask, HorizSizeConstraintProperty, applyChanged, sheet, + horizSizeConstraintC, m_horizSizeConstraint, m_horizSizeConstraintChanged, &rc); + variantPropertyToSheet(mask, VertSizeConstraintProperty, applyChanged, sheet, + vertSizeConstraintC, m_vertSizeConstraint, m_vertSizeConstraintChanged, &rc); +#endif + variantPropertyToSheet(mask, FieldGrowthPolicyProperty, applyChanged, sheet, fieldGrowthPolicyPropertyC, m_fieldGrowthPolicy, m_fieldGrowthPolicyChanged, &rc); + variantPropertyToSheet(mask, RowWrapPolicyProperty, applyChanged, sheet, rowWrapPolicyPropertyC, m_rowWrapPolicy, m_rowWrapPolicyChanged, &rc); + variantPropertyToSheet(mask, LabelAlignmentProperty, applyChanged, sheet, labelAlignmentPropertyC, m_labelAlignment, m_labelAlignmentChanged, &rc); + variantPropertyToSheet(mask, FormAlignmentProperty, applyChanged, sheet, formAlignmentPropertyC, m_formAlignment, m_formAlignmentChanged, &rc); + variantPropertyToSheet(mask, BoxStretchProperty, applyChanged, sheet, boxStretchPropertyC, m_boxStretch, m_boxStretchChanged, &rc); + variantPropertyToSheet(mask, GridRowStretchProperty, applyChanged, sheet, gridRowStretchPropertyC, m_gridRowStretch, m_gridRowStretchChanged, &rc); + variantPropertyToSheet(mask, GridColumnStretchProperty, applyChanged, sheet, gridColumnStretchPropertyC, m_gridColumnStretch, m_gridColumnStretchChanged, &rc); + variantPropertyToSheet(mask, GridRowMinimumHeightProperty, applyChanged, sheet, gridRowMinimumHeightPropertyC, m_gridRowMinimumHeight, m_gridRowMinimumHeightChanged, &rc); + variantPropertyToSheet(mask, GridColumnMinimumWidthProperty, applyChanged, sheet, gridColumnMinimumWidthPropertyC, m_gridColumnMinimumWidth, m_gridColumnMinimumWidthChanged, &rc); + return rc; +} + +// ---------------- LayoutHelper +LayoutHelper::LayoutHelper() = default; + +LayoutHelper::~LayoutHelper() = default; + +int LayoutHelper::indexOf(const QLayout *lt, const QWidget *widget) +{ + if (!lt) + return -1; + + const int itemCount = lt->count(); + for (int i = 0; i < itemCount; i++) + if (lt->itemAt(i)->widget() == widget) + return i; + return -1; +} + +QRect LayoutHelper::itemInfo(QLayout *lt, const QWidget *widget) const +{ + const int index = indexOf(lt, widget); + if (index == -1) { + qWarning() << "LayoutHelper::itemInfo: " << widget << " not in layout " << lt; + return QRect(0, 0, 1, 1); + } + return itemInfo(lt, index); +} + + // ---------------- BoxLayoutHelper + class BoxLayoutHelper : public LayoutHelper { + public: + BoxLayoutHelper(const Qt::Orientation orientation) : m_orientation(orientation) {} + + QRect itemInfo(QLayout *lt, int index) const override; + void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override; + void removeWidget(QLayout *lt, QWidget *widget) override; + void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override; + + void pushState(const QDesignerFormEditorInterface *, const QWidget *) override; + void popState(const QDesignerFormEditorInterface *, QWidget *) override; + + bool canSimplify(const QDesignerFormEditorInterface *, const QWidget *, const QRect &) const override { return false; } + void simplify(const QDesignerFormEditorInterface *, QWidget *, const QRect &) override {} + + // Helper for restoring layout states + using LayoutItemVector = QList; + static LayoutItemVector disassembleLayout(QLayout *lt); + static QLayoutItem *findItemOfWidget(const LayoutItemVector &lv, QWidget *w); + + private: + using BoxLayoutState = QList; + + static BoxLayoutState state(const QBoxLayout*lt); + + QStack m_states; + const Qt::Orientation m_orientation; + }; + + QRect BoxLayoutHelper::itemInfo(QLayout * /*lt*/, int index) const + { + return m_orientation == Qt::Horizontal ? QRect(index, 0, 1, 1) : QRect(0, index, 1, 1); + } + + void BoxLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w) + { + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + QBoxLayout *boxLayout = qobject_cast(lt); + Q_ASSERT(boxLayout); + boxLayout->insertWidget(m_orientation == Qt::Horizontal ? info.x() : info.y(), w); + } + + void BoxLayoutHelper::removeWidget(QLayout *lt, QWidget *widget) + { + QBoxLayout *boxLayout = qobject_cast(lt); + Q_ASSERT(boxLayout); + boxLayout->removeWidget(widget); + } + + void BoxLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after) + { + bool ok = false; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + if (QBoxLayout *boxLayout = qobject_cast(lt)) { + const int index = boxLayout->indexOf(before); + if (index != -1) { + const bool visible = before->isVisible(); + delete boxLayout->takeAt(index); + if (visible) + before->hide(); + before->setParent(nullptr); + boxLayout->insertWidget(index, after); + ok = true; + } + } + if (!ok) + qWarning() << "BoxLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt; + } + + BoxLayoutHelper::BoxLayoutState BoxLayoutHelper::state(const QBoxLayout*lt) + { + BoxLayoutState rc; + if (const int count = lt->count()) { + rc.reserve(count); + for (int i = 0; i < count; i++) + if (QWidget *w = lt->itemAt(i)->widget()) + rc.push_back(w); + } + return rc; + } + + void BoxLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *w) + { + const QBoxLayout *boxLayout = qobject_cast(LayoutInfo::managedLayout(core, w)); + Q_ASSERT(boxLayout); + m_states.push(state(boxLayout)); + } + + QLayoutItem *BoxLayoutHelper::findItemOfWidget(const LayoutItemVector &lv, QWidget *w) + { + for (auto *l : lv) { + if (l->widget() == w) + return l; + } + return nullptr; + } + + BoxLayoutHelper::LayoutItemVector BoxLayoutHelper::disassembleLayout(QLayout *lt) + { + // Take items + const int count = lt->count(); + if (count == 0) + return LayoutItemVector(); + LayoutItemVector rc; + rc.reserve(count); + for (int i = count - 1; i >= 0; i--) + rc.push_back(lt->takeAt(i)); + return rc; + } + + void BoxLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *w) + { + QBoxLayout *boxLayout = qobject_cast(LayoutInfo::managedLayout(core, w)); + Q_ASSERT(boxLayout); + const BoxLayoutState savedState = m_states.pop(); + const BoxLayoutState currentState = state(boxLayout); + // Check for equality/empty. Note that this will currently + // always trigger as box layouts do not have a state apart from + // the order and there is no layout order editor yet. + if (savedState == state(boxLayout)) + return; + + Q_ASSERT(savedState.size() == currentState.size()); + // Take items and reassemble in saved order + const LayoutItemVector items = disassembleLayout(boxLayout); + for (auto *w : savedState) { + QLayoutItem *item = findItemOfWidget(items, w); + Q_ASSERT(item); + boxLayout->addItem(item); + } + } + + // Grid Layout state. Datatype storing the state of a GridLayout as a map of + // widgets to QRect(columns, rows) and size. Used to store the state for undo operations + // that do not change the widgets within the layout; also provides some manipulation + // functions and ability to apply the state to a layout provided its widgets haven't changed. + struct GridLayoutState { + GridLayoutState() = default; + + void fromLayout(QGridLayout *l); + void applyToLayout(const QDesignerFormEditorInterface *core, QWidget *w) const; + + void insertRow(int row); + void insertColumn(int column); + + bool simplify(const QRect &r, bool testOnly); + void removeFreeRow(int row); + void removeFreeColumn(int column); + + + // State of a cell in one dimension + enum DimensionCellState { + Free, + Spanned, // Item spans it + Occupied // Item bordering on it + }; + // Horiontal, Vertical pair of state + using CellState = std::pair; + using CellStates = QList; + + // Figure out states of a cell and return as a flat vector of + // [column1, column2,...] (address as row * columnCount + col) + static CellStates cellStates(const QList &rects, int numRows, int numColumns); + + QHash widgetItemMap; + QHash widgetAlignmentMap; + + int rowCount = 0; + int colCount = 0; + }; + + static inline bool needsSpacerItem(const GridLayoutState::CellState &cs) { + return cs.first == GridLayoutState::Free && cs.second == GridLayoutState::Free; + } + + static inline QDebug operator<<(QDebug str, const GridLayoutState &gs) + { + str << "GridLayoutState: " << gs.rowCount << " rows x " << gs.colCount + << " cols " << gs.widgetItemMap.size() << " items\n"; + + const auto wcend = gs.widgetItemMap.constEnd(); + for (auto it = gs.widgetItemMap.constBegin(); it != wcend; ++it) + str << "Item " << it.key() << it.value() << '\n'; + return str; + } + + GridLayoutState::CellStates GridLayoutState::cellStates(const QList &rects, int numRows, int numColumns) + { + CellStates rc = CellStates(numRows * numColumns, CellState(Free, Free)); + for (const auto &rect : rects) { + const int leftColumn = rect.x(); + const int topRow = rect.y(); + const int rightColumn = leftColumn + rect.width() - 1; + const int bottomRow = topRow + rect.height() - 1; + for (int r = topRow; r <= bottomRow; r++) + for (int c = leftColumn; c <= rightColumn; c++) { + const int flatIndex = r * numColumns + c; + // Bordering horizontally? + DimensionCellState &horizState = rc[flatIndex].first; + if (c == leftColumn || c == rightColumn) { + horizState = Occupied; + } else { + if (horizState < Spanned) + horizState = Spanned; + } + // Bordering vertically? + DimensionCellState &vertState = rc[flatIndex].second; + if (r == topRow || r == bottomRow) { + vertState = Occupied; + } else { + if (vertState < Spanned) + vertState = Spanned; + } + } + } + if (debugLayout) { + qDebug() << "GridLayoutState::cellStates: " << numRows << " x " << numColumns; + for (int r = 0; r < numRows; r++) + for (int c = 0; c < numColumns; c++) + qDebug() << " Row: " << r << " column: " << c << rc[r * numColumns + c]; + } + return rc; + } + + void GridLayoutState::fromLayout(QGridLayout *l) + { + rowCount = l->rowCount(); + colCount = l->columnCount(); + const int count = l->count(); + for (int i = 0; i < count; i++) { + QLayoutItem *item = l->itemAt(i); + if (!LayoutInfo::isEmptyItem(item)) { + widgetItemMap.insert(item->widget(), gridItemInfo(l, i)); + if (item->alignment()) + widgetAlignmentMap.insert(item->widget(), item->alignment()); + } + } + } + + void GridLayoutState::applyToLayout(const QDesignerFormEditorInterface *core, QWidget *w) const + { + QGridLayout *grid = qobject_cast(LayoutInfo::managedLayout(core, w)); + Q_ASSERT(grid); + if (debugLayout) + qDebug() << ">GridLayoutState::applyToLayout" << *this << *grid; + const bool shrink = grid->rowCount() > rowCount || grid->columnCount() > colCount; + // Build a map of existing items to rectangles via widget map, delete spacers + QHash itemMap; + while (grid->count()) { + QLayoutItem *item = grid->takeAt(0); + if (!LayoutInfo::isEmptyItem(item)) { + QWidget *itemWidget = item->widget(); + const auto it = widgetItemMap.constFind(itemWidget); + if (it == widgetItemMap.constEnd()) + qFatal("GridLayoutState::applyToLayout: Attempt to apply to a layout that has a widget '%s'/'%s' added after saving the state.", + itemWidget->metaObject()->className(), itemWidget->objectName().toUtf8().constData()); + itemMap.insert(item, it.value()); + } else { + delete item; + } + } + Q_ASSERT(itemMap.size() == widgetItemMap.size()); + // recreate if shrink + if (shrink) + grid = static_cast(recreateManagedLayout(core, w, grid)); + + // Add widgets items + for (auto it = itemMap.cbegin(), icend = itemMap.cend(); it != icend; ++it) { + const QRect info = it.value(); + const Qt::Alignment alignment = widgetAlignmentMap.value(it.key()->widget(), {}); + grid->addItem(it.key(), info.y(), info.x(), info.height(), info.width(), alignment); + } + // create spacers + const CellStates cs = cellStates(itemMap.values(), rowCount, colCount); + for (int r = 0; r < rowCount; r++) + for (int c = 0; c < colCount; c++) + if (needsSpacerItem(cs[r * colCount + c])) + grid->addItem(createGridSpacer(), r, c); + grid->activate(); + if (debugLayout) + qDebug() << "= row) { + it.value().translate(0, 1); + } else { //Over it: Does it span it -> widen? + const int rowSpan = it.value().height(); + if (rowSpan > 1 && topRow + rowSpan > row) + it.value().setHeight(rowSpan + 1); + } + } + } + + void GridLayoutState::insertColumn(int column) + { + colCount++; + for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) { + const int leftColumn = it.value().x(); + if (leftColumn >= column) { + it.value().translate(1, 0); + } else { // Left of it: Does it span it -> widen? + const int colSpan = it.value().width(); + if (colSpan > 1 && leftColumn + colSpan > column) + it.value().setWidth(colSpan + 1); + } + } + } + + // Simplify: Remove empty columns/rows and such ones that are only spanned (shrink + // spanning items). + // 'AB.C.' 'ABC' + // 'DDDD.' ==> 'DDD' + // 'EF.G.' 'EFG' + bool GridLayoutState::simplify(const QRect &r, bool testOnly) + { + // figure out free rows/columns. + QList occupiedRows(rowCount, false); + QList occupiedColumns(colCount, false); + // Mark everything outside restriction rectangle as occupied + const int restrictionLeftColumn = r.x(); + const int restrictionRightColumn = restrictionLeftColumn + r.width(); + const int restrictionTopRow = r.y(); + const int restrictionBottomRow = restrictionTopRow + r.height(); + if (restrictionLeftColumn > 0 || restrictionRightColumn < colCount || + restrictionTopRow > 0 || restrictionBottomRow < rowCount) { + for (int r = 0; r < rowCount; r++) + if (r < restrictionTopRow || r >= restrictionBottomRow) + occupiedRows[r] = true; + for (int c = 0; c < colCount; c++) + if (c < restrictionLeftColumn || c >= restrictionRightColumn) + occupiedColumns[c] = true; + } + // figure out free fields and tick off occupied rows and columns + const CellStates cs = cellStates(widgetItemMap.values(), rowCount, colCount); + for (int r = 0; r < rowCount; r++) + for (int c = 0; c < colCount; c++) { + const CellState &state = cs[r * colCount + c]; + if (state.first == Occupied) + occupiedColumns[c] = true; + if (state.second == Occupied) + occupiedRows[r] = true; + } + // Any free rows/columns? + if (occupiedRows.indexOf(false) == -1 && occupiedColumns.indexOf(false) == -1) + return false; + if (testOnly) + return true; + // remove rows + for (int r = rowCount - 1; r >= 0; r--) + if (!occupiedRows[r]) + removeFreeRow(r); + // remove columns + for (int c = colCount - 1; c >= 0; c--) + if (!occupiedColumns[c]) + removeFreeColumn(c); + return true; + } + + void GridLayoutState::removeFreeRow(int removeRow) + { + for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) { + const int r = it.value().y(); + Q_ASSERT(r != removeRow); // Free rows only + if (r < removeRow) { // Does the item span it? - shrink it + const int rowSpan = it.value().height(); + if (rowSpan > 1) { + const int bottomRow = r + rowSpan; + if (bottomRow > removeRow) + it.value().setHeight(rowSpan - 1); + } + } else + if (r > removeRow) // Item below it? - move. + it.value().translate(0, -1); + } + rowCount--; + } + + void GridLayoutState::removeFreeColumn(int removeColumn) + { + for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) { + const int c = it.value().x(); + Q_ASSERT(c != removeColumn); // Free columns only + if (c < removeColumn) { // Does the item span it? - shrink it + const int colSpan = it.value().width(); + if (colSpan > 1) { + const int rightColumn = c + colSpan; + if (rightColumn > removeColumn) + it.value().setWidth(colSpan - 1); + } + } else + if (c > removeColumn) // Item to the right of it? - move. + it.value().translate(-1, 0); + } + colCount--; + } + + // ---------------- GridLayoutHelper + class GridLayoutHelper : public LayoutHelper { + public: + GridLayoutHelper() = default; + + QRect itemInfo(QLayout *lt, int index) const override; + void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override; + void removeWidget(QLayout *lt, QWidget *widget) override; + void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override; + + void pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) override; + void popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) override; + + bool canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const override; + void simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) override; + + static void insertRow(QGridLayout *grid, int row); + + private: + QStack m_states; + }; + + void GridLayoutHelper::insertRow(QGridLayout *grid, int row) + { + GridLayoutState state; + state.fromLayout(grid); + state.insertRow(row); + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(grid); + state.applyToLayout(fw->core(), grid->parentWidget()); + } + + QRect GridLayoutHelper::itemInfo(QLayout * lt, int index) const + { + QGridLayout *grid = qobject_cast(lt); + Q_ASSERT(grid); + return gridItemInfo(grid, index); + } + + void GridLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w) + { + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + QGridLayout *gridLayout = qobject_cast(lt); + Q_ASSERT(gridLayout); + // check if there are any items. Should be only spacers, else something is wrong + const int row = info.y(); + int column = info.x(); + int colSpan = info.width(); + int rowSpan = info.height(); + // If not empty: A multiselection was dropped on an empty item, insert row + // and spread items along new row + if (!removeEmptyCellsOnGrid(gridLayout, info)) { + int freeColumn = -1; + colSpan = rowSpan = 1; + // First look to the right for a free column + const int columnCount = gridLayout->columnCount(); + for (int c = column; c < columnCount; c++) { + const int idx = findGridItemAt(gridLayout, row, c); + if (idx != -1 && LayoutInfo::isEmptyItem(gridLayout->itemAt(idx))) { + freeColumn = c; + break; + } + } + if (freeColumn != -1) { + removeEmptyCellsOnGrid(gridLayout, QRect(freeColumn, row, 1, 1)); + column = freeColumn; + } else { + GridLayoutHelper::insertRow(gridLayout, row); + column = 0; + } + } + gridLayout->addWidget(w, row , column, rowSpan, colSpan); + } + + void GridLayoutHelper::removeWidget(QLayout *lt, QWidget *widget) + { + QGridLayout *gridLayout = qobject_cast(lt); + Q_ASSERT(gridLayout); + const int index = gridLayout->indexOf(widget); + if (index == -1) { + qWarning() << "GridLayoutHelper::removeWidget : Attempt to remove " << widget << " which is not in the layout."; + return; + } + // delete old item and pad with by spacer items + int row, column, rowspan, colspan; + gridLayout->getItemPosition(index, &row, &column, &rowspan, &colspan); + delete gridLayout->takeAt(index); + const int rightColumn = column + colspan; + const int bottomRow = row + rowspan; + for (int c = column; c < rightColumn; c++) + for (int r = row; r < bottomRow; r++) + gridLayout->addItem(createGridSpacer(), r, c); + } + + void GridLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after) + { + bool ok = false; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + if (QGridLayout *gridLayout = qobject_cast(lt)) { + const int index = gridLayout->indexOf(before); + if (index != -1) { + int row, column, rowSpan, columnSpan; + gridLayout->getItemPosition (index, &row, &column, &rowSpan, &columnSpan); + const bool visible = before->isVisible(); + delete gridLayout->takeAt(index); + if (visible) + before->hide(); + before->setParent(nullptr); + gridLayout->addWidget(after, row, column, rowSpan, columnSpan); + ok = true; + } + } + if (!ok) + qWarning() << "GridLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt; + } + + void GridLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) + { + QGridLayout *gridLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(gridLayout); + GridLayoutState gs; + gs.fromLayout(gridLayout); + m_states.push(gs); + } + + void GridLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) + { + Q_ASSERT(!m_states.isEmpty()); + const GridLayoutState state = m_states.pop(); + state.applyToLayout(core, widgetWithManagedLayout); + } + + bool GridLayoutHelper::canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const + { + QGridLayout *gridLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(gridLayout); + GridLayoutState gs; + gs.fromLayout(gridLayout); + return gs.simplify(restrictionArea, true); + } + + void GridLayoutHelper::simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) + { + QGridLayout *gridLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(gridLayout); + if (debugLayout) + qDebug() << ">GridLayoutHelper::simplify" << *gridLayout; + GridLayoutState gs; + gs.fromLayout(gridLayout); + if (gs.simplify(restrictionArea, false)) + gs.applyToLayout(core, widgetWithManagedLayout); + if (debugLayout) + qDebug() << ">; + + FormLayoutHelper() = default; + + QRect itemInfo(QLayout *lt, int index) const override; + void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override; + void removeWidget(QLayout *lt, QWidget *widget) override; + void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override; + + void pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) override; + void popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) override; + + bool canSimplify(const QDesignerFormEditorInterface *core, const QWidget *, const QRect &) const override; + void simplify(const QDesignerFormEditorInterface *, QWidget *, const QRect &) override; + + private: + static FormLayoutState state(const QFormLayout *lt); + + QStack m_states; + }; + + QRect FormLayoutHelper::itemInfo(QLayout * lt, int index) const + { + QFormLayout *form = qobject_cast(lt); + Q_ASSERT(form); + int row, column, colspan; + getFormLayoutItemPosition(form, index, &row, &column, nullptr, &colspan); + return QRect(column, row, colspan, 1); + } + + void FormLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w) + { + if (debugLayout) + qDebug() << "FormLayoutHelper::insertWidget:" << w << info; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + QFormLayout *formLayout = qobject_cast(lt); + Q_ASSERT(formLayout); + // check if there are any nonspacer items? (Drop on 3rd column or drop of a multiselection + // on an empty item. As the Form layout does not have insert semantics; we need to manually insert a row + const bool insert = !removeEmptyCellsOnGrid(formLayout, info); + formLayoutAddWidget(formLayout, w, info, insert); + QLayoutSupport::createEmptyCells(formLayout); + } + + void FormLayoutHelper::removeWidget(QLayout *lt, QWidget *widget) + { + QFormLayout *formLayout = qobject_cast(lt); + Q_ASSERT(formLayout); + const int index = formLayout->indexOf(widget); + if (index == -1) { + qWarning() << "FormLayoutHelper::removeWidget : Attempt to remove " << widget << " which is not in the layout."; + return; + } + // delete old item and pad with by spacer items + int row, column, colspan; + getFormLayoutItemPosition(formLayout, index, &row, &column, nullptr, &colspan); + if (debugLayout) + qDebug() << "FormLayoutHelper::removeWidget: #" << index << widget << " at " << row << column << colspan; + delete formLayout->takeAt(index); + if (colspan > 1 || column == 0) + formLayout->setItem(row, QFormLayout::LabelRole, createFormSpacer()); + if (colspan > 1 || column == 1) + formLayout->setItem(row, QFormLayout::FieldRole, createFormSpacer()); + } + + void FormLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after) + { + bool ok = false; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + if (QFormLayout *formLayout = qobject_cast(lt)) { + const int index = formLayout->indexOf(before); + if (index != -1) { + int row; + QFormLayout::ItemRole role; + formLayout->getItemPosition (index, &row, &role); + const bool visible = before->isVisible(); + delete formLayout->takeAt(index); + if (visible) + before->hide(); + before->setParent(nullptr); + formLayout->setWidget(row, role, after); + ok = true; + } + } + if (!ok) + qWarning() << "FormLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt; + } + + FormLayoutHelper::FormLayoutState FormLayoutHelper::state(const QFormLayout *lt) + { + const int rowCount = lt->rowCount(); + if (rowCount == 0) + return FormLayoutState(); + FormLayoutState rc(rowCount, {nullptr, nullptr}); + const int count = lt->count(); + int row, column, colspan; + for (int i = 0; i < count; i++) { + QLayoutItem *item = lt->itemAt(i); + if (!LayoutInfo::isEmptyItem(item)) { + QWidget *w = item->widget(); + Q_ASSERT(w); + getFormLayoutItemPosition(lt, i, &row, &column, nullptr, &colspan); + if (colspan > 1 || column == 0) + rc[row].first = w; + if (colspan > 1 || column == 1) + rc[row].second = w; + } + } + if (debugLayout) { + qDebug() << "FormLayoutHelper::state: " << rowCount; + for (int r = 0; r < rowCount; r++) + qDebug() << " Row: " << r << rc[r].first << rc[r].second; + } + return rc; + } + + void FormLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) + { + QFormLayout *formLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(formLayout); + m_states.push(state(formLayout)); + } + + void FormLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) + { + QFormLayout *formLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(!m_states.isEmpty() && formLayout); + + const FormLayoutState storedState = m_states.pop(); + const FormLayoutState currentState = state(formLayout); + if (currentState == storedState) + return; + const int rowCount = storedState.size(); + // clear out, shrink if required, but maintain items via map, pad spacers + const BoxLayoutHelper::LayoutItemVector items = BoxLayoutHelper::disassembleLayout(formLayout); + if (rowCount < formLayout->rowCount()) + formLayout = static_cast(recreateManagedLayout(core, widgetWithManagedLayout, formLayout )); + for (int r = 0; r < rowCount; r++) { + QWidget *widgets[FormLayoutColumns] = { storedState[r].first, storedState[r].second }; + const bool spanning = widgets[0] != nullptr && widgets[0] == widgets[1]; + if (spanning) { + formLayout->setWidget(r, QFormLayout::SpanningRole, widgets[0]); + } else { + for (int c = 0; c < FormLayoutColumns; c++) { + const QFormLayout::ItemRole role = c == 0 ? QFormLayout::LabelRole : QFormLayout::FieldRole; + if (widgets[c] && BoxLayoutHelper::findItemOfWidget(items, widgets[c])) { + formLayout->setWidget(r, role, widgets[c]); + } else { + formLayout->setItem(r, role, createFormSpacer()); + } + } + } + } + } + + bool FormLayoutHelper::canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const + { + const QFormLayout *formLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(formLayout); + return canSimplifyFormLayout(formLayout, restrictionArea); + } + + void FormLayoutHelper::simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) + { + using LayoutItemPair = std::pair; + using LayoutItemPairs = QList; + + QFormLayout *formLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(formLayout); + if (debugLayout) + qDebug() << "FormLayoutHelper::simplify"; + // Transform into vector of item pairs + const int rowCount = formLayout->rowCount(); + LayoutItemPairs pairs(rowCount, LayoutItemPair(0, 0)); + for (int i = formLayout->count() - 1; i >= 0; i--) { + int row, col,colspan; + getFormLayoutItemPosition(formLayout, i, &row, &col, nullptr, &colspan); + if (colspan > 1) { + pairs[row].first = pairs[row].second = formLayout->takeAt(i); + } else { + if (col == 0) + pairs[row].first = formLayout->takeAt(i); + else + pairs[row].second = formLayout->takeAt(i); + } + } + // Weed out empty ones + const int bottomCheckRow = qMin(rowCount, restrictionArea.y() + restrictionArea.height()); + for (int r = bottomCheckRow - 1; r >= restrictionArea.y(); r--) + if (LayoutInfo::isEmptyItem(pairs[r].first) && LayoutInfo::isEmptyItem(pairs[r].second)) { + delete pairs[r].first; + delete pairs[r].second; + pairs.remove(r); + } + const int simpleRowCount = pairs.size(); + if (simpleRowCount < rowCount) + formLayout = static_cast(recreateManagedLayout(core, widgetWithManagedLayout, formLayout)); + // repopulate + for (int r = 0; r < simpleRowCount; r++) { + const bool spanning = pairs[r].first == pairs[r].second; + if (spanning) { + formLayout->setItem(r, QFormLayout::SpanningRole, pairs[r].first); + } else { + formLayout->setItem(r, QFormLayout::LabelRole, pairs[r].first); + formLayout->setItem(r, QFormLayout::FieldRole, pairs[r].second); + } + } + } + +LayoutHelper *LayoutHelper::createLayoutHelper(int type) +{ + LayoutHelper *rc = nullptr; + switch (type) { + case LayoutInfo::HBox: + rc = new BoxLayoutHelper(Qt::Horizontal); + break; + case LayoutInfo::VBox: + rc = new BoxLayoutHelper(Qt::Vertical); + break; + case LayoutInfo::Grid: + rc = new GridLayoutHelper; + break; + case LayoutInfo::Form: + return new FormLayoutHelper; + default: + break; + } + Q_ASSERT(rc); + return rc; +} + +// ---- QLayoutSupport (LayoutDecorationExtension) +QLayoutSupport::QLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, LayoutHelper *helper, QObject *parent) : + QObject(parent), + m_formWindow(formWindow), + m_helper(helper), + m_widget(widget), + m_currentIndex(-1), + m_currentInsertMode(QDesignerLayoutDecorationExtension::InsertWidgetMode) +{ +} + +QLayout * QLayoutSupport::layout() const +{ + return LayoutInfo::managedLayout(m_formWindow->core(), m_widget); +} + +void QLayoutSupport::hideIndicator(Indicator i) +{ + if (m_indicators[i]) + m_indicators[i]->hide(); +} + +void QLayoutSupport::showIndicator(Indicator i, const QRect &geometry, const QPalette &p) +{ + if (!m_indicators[i]) + m_indicators[i] = new qdesigner_internal::InvisibleWidget(m_widget); + QWidget *indicator = m_indicators[i]; + indicator->setAutoFillBackground(true); + indicator->setPalette(p); + indicator->setGeometry(geometry); + indicator->show(); + indicator->raise(); +} + +QLayoutSupport::~QLayoutSupport() +{ + delete m_helper; + for (const QPointer &w : m_indicators) { + if (!w.isNull()) + w->deleteLater(); + } +} + +QGridLayout * QLayoutSupport::gridLayout() const +{ + return qobject_cast(LayoutInfo::managedLayout(m_formWindow->core(), m_widget)); +} + +QRect QLayoutSupport::itemInfo(int index) const +{ + return m_helper->itemInfo(LayoutInfo::managedLayout(m_formWindow->core(), m_widget), index); +} + +void QLayoutSupport::setInsertMode(InsertMode im) +{ + m_currentInsertMode = im; +} + +void QLayoutSupport::setCurrentCell(const std::pair &cell) +{ + m_currentCell = cell; +} + +void QLayoutSupport::adjustIndicator(const QPoint &pos, int index) +{ + if (index == -1) { // first item goes anywhere + hideIndicator(LeftIndicator); + hideIndicator(TopIndicator); + hideIndicator(RightIndicator); + hideIndicator(BottomIndicator); + return; + } + m_currentIndex = index; + m_currentInsertMode = QDesignerLayoutDecorationExtension::InsertWidgetMode; + + QLayoutItem *item = layout()->itemAt(index); + const QRect g = extendedGeometry(index); + // ### cleanup + if (LayoutInfo::isEmptyItem(item)) { + // Empty grid/form cell. Draw a rectangle + QPalette redPalette; + redPalette.setColor(QPalette::Window, Qt::red); + + showIndicator(LeftIndicator, QRect(g.x(), g.y(), indicatorSize, g.height()), redPalette); + showIndicator(TopIndicator, QRect(g.x(), g.y(), g.width(), indicatorSize), redPalette); + showIndicator(RightIndicator, QRect(g.right(), g.y(), indicatorSize, g.height()), redPalette); + showIndicator(BottomIndicator, QRect(g.x(), g.bottom(), g.width(), indicatorSize), redPalette); + setCurrentCellFromIndicatorOnEmptyCell(m_currentIndex); + } else { + // Append/Insert. Draw a bar left/right or above/below + QPalette bluePalette; + bluePalette.setColor(QPalette::Window, Qt::blue); + hideIndicator(LeftIndicator); + hideIndicator(TopIndicator); + + const int fromRight = g.right() - pos.x(); + const int fromBottom = g.bottom() - pos.y(); + + const int fromLeft = pos.x() - g.x(); + const int fromTop = pos.y() - g.y(); + + const int fromLeftRight = qMin(fromRight, fromLeft ); + const int fromBottomTop = qMin(fromBottom, fromTop); + + const Qt::Orientation indicatorOrientation = fromLeftRight < fromBottomTop ? Qt::Vertical : Qt::Horizontal; + + if (supportsIndicatorOrientation(indicatorOrientation)) { + const QRect r(layout()->geometry().topLeft(), layout()->parentWidget()->size()); + switch (indicatorOrientation) { + case Qt::Vertical: { + hideIndicator(BottomIndicator); + const bool closeToLeft = fromLeftRight == fromLeft; + showIndicator(RightIndicator, QRect(closeToLeft ? g.x() : g.right() + 1 - indicatorSize, 0, indicatorSize, r.height()), bluePalette); + + const QWidget *parent = layout()->parentWidget(); + const bool leftToRight = Qt::LeftToRight == (parent ? parent->layoutDirection() : QApplication::layoutDirection()); + const int incr = leftToRight == closeToLeft ? 0 : +1; + setCurrentCellFromIndicator(indicatorOrientation, m_currentIndex, incr); + } + break; + case Qt::Horizontal: { + hideIndicator(RightIndicator); + const bool closeToTop = fromBottomTop == fromTop; + showIndicator(BottomIndicator, QRect(r.x(), closeToTop ? g.y() : g.bottom() + 1 - indicatorSize, r.width(), indicatorSize), bluePalette); + + const int incr = closeToTop ? 0 : +1; + setCurrentCellFromIndicator(indicatorOrientation, m_currentIndex, incr); + } + break; + } + } else { + hideIndicator(RightIndicator); + hideIndicator(BottomIndicator); + } // can handle indicatorOrientation + } +} + +int QLayoutSupport::indexOf(QLayoutItem *i) const +{ + const QLayout *lt = layout(); + if (!lt) + return -1; + + int index = 0; + + while (QLayoutItem *item = lt->itemAt(index)) { + if (item == i) + return index; + + ++index; + } + + return -1; +} + +int QLayoutSupport::indexOf(QWidget *widget) const +{ + const QLayout *lt = layout(); + if (!lt) + return -1; + + int index = 0; + while (QLayoutItem *item = lt->itemAt(index)) { + if (item->widget() == widget) + return index; + + ++index; + } + + return -1; +} + +QWidgetList QLayoutSupport::widgets(QLayout *layout) const +{ + if (!layout) + return QWidgetList(); + + QWidgetList lst; + int index = 0; + while (QLayoutItem *item = layout->itemAt(index)) { + ++index; + + QWidget *widget = item->widget(); + if (widget && formWindow()->isManaged(widget)) + lst.append(widget); + } + + return lst; +} + +int QLayoutSupport::findItemAt(QGridLayout *gridLayout, int at_row, int at_column) +{ + return findGridItemAt(gridLayout, at_row, at_column); +} + +// Quick check whether simplify should be enabled for grids. May return false positives. +// Note: Calculating the occupied area does not work as spanning items may also be simplified. + +bool QLayoutSupport::canSimplifyQuickCheck(const QGridLayout *gl) +{ + if (!gl) + return false; + const int colCount = gl->columnCount(); + const int rowCount = gl->rowCount(); + if (colCount < 2 || rowCount < 2) + return false; + // try to find a spacer. + const int count = gl->count(); + for (int index = 0; index < count; index++) + if (LayoutInfo::isEmptyItem(gl->itemAt(index))) + return true; + return false; +} + +bool QLayoutSupport::canSimplifyQuickCheck(const QFormLayout *fl) +{ + return canSimplifyFormLayout(fl, QRect(QPoint(0, 0), QSize(32767, 32767))); +} + +// remove dummy spacers +bool QLayoutSupport::removeEmptyCells(QGridLayout *grid, const QRect &area) +{ + return removeEmptyCellsOnGrid(grid, area); +} + +void QLayoutSupport::createEmptyCells(QGridLayout *gridLayout) +{ + Q_ASSERT(gridLayout); + GridLayoutState gs; + gs.fromLayout(gridLayout); + + const GridLayoutState::CellStates cs = GridLayoutState::cellStates(gs.widgetItemMap.values(), gs.rowCount, gs.colCount); + for (int c = 0; c < gs.colCount; c++) + for (int r = 0; r < gs.rowCount; r++) + if (needsSpacerItem(cs[r * gs.colCount + c])) { + const int existingItemIndex = findItemAt(gridLayout, r, c); + if (existingItemIndex == -1) + gridLayout->addItem(createGridSpacer(), r, c); + } +} + +bool QLayoutSupport::removeEmptyCells(QFormLayout *formLayout, const QRect &area) +{ + return removeEmptyCellsOnGrid(formLayout, area); +} + +void QLayoutSupport::createEmptyCells(QFormLayout *formLayout) +{ + // No spanning items here.. + if (const int rowCount = formLayout->rowCount()) + for (int c = 0; c < FormLayoutColumns; c++) + for (int r = 0; r < rowCount; r++) + if (findGridItemAt(formLayout, r, c) == -1) + formLayout->setItem(r, c == 0 ? QFormLayout::LabelRole : QFormLayout::FieldRole, createFormSpacer()); +} + +int QLayoutSupport::findItemAt(const QPoint &pos) const +{ + if (!layout()) + return -1; + + const QLayout *lt = layout(); + const int count = lt->count(); + + if (count == 0) + return -1; + + int best = -1; + int bestIndex = -1; + + for (int index = 0; index < count; index++) { + QLayoutItem *item = lt->itemAt(index); + bool visible = true; + // When dragging widgets within layout, the source widget is invisible and must not be hit + if (const QWidget *w = item->widget()) + visible = w->isVisible(); + if (visible) { + const QRect g = item->geometry(); + + const int dist = (g.center() - pos).manhattanLength(); + if (best == -1 || dist < best) { + best = dist; + bestIndex = index; + } + } + } + return bestIndex; +} + +// ------------ QBoxLayoutSupport (LayoutDecorationExtension) +namespace { +class QBoxLayoutSupport: public QLayoutSupport +{ +public: + QBoxLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, Qt::Orientation orientation, QObject *parent = nullptr); + + void insertWidget(QWidget *widget, const std::pair &cell) override; + void removeWidget(QWidget *widget) override; + void simplify() override {} + void insertRow(int /*row*/) override {} + void insertColumn(int /*column*/) override {} + + int findItemAt(int /*at_row*/, int /*at_column*/) const override { return -1; } + using QLayoutSupport::findItemAt; + +private: + void setCurrentCellFromIndicatorOnEmptyCell(int index) override; + void setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) override; + bool supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const override; + QRect extendedGeometry(int index) const override; + + const Qt::Orientation m_orientation; +}; + +void QBoxLayoutSupport::removeWidget(QWidget *widget) +{ + QLayout *lt = layout(); + const int index = lt->indexOf(widget); + // Adjust the current cell in case a widget was dragged within the same layout to a position + // of higher index, which happens as follows: + // Drag start: The widget is hidden + // Drop: Current cell is stored, widget is removed and re-added, causing an index offset that needs to be compensated + std::pair currCell = currentCell(); + switch (m_orientation) { + case Qt::Horizontal: + if (currCell.second > 0 && index < currCell.second ) { + currCell.second--; + setCurrentCell(currCell); + } + break; + case Qt::Vertical: + if (currCell.first > 0 && index < currCell.first) { + currCell.first--; + setCurrentCell(currCell); + } + break; + } + helper()->removeWidget(lt, widget); +} + +QBoxLayoutSupport::QBoxLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, Qt::Orientation orientation, QObject *parent) : + QLayoutSupport(formWindow, widget, new BoxLayoutHelper(orientation), parent), + m_orientation(orientation) +{ +} + +void QBoxLayoutSupport::setCurrentCellFromIndicatorOnEmptyCell(int index) +{ + qDebug() << "QBoxLayoutSupport::setCurrentCellFromIndicatorOnEmptyCell(): Warning: found a fake spacer inside a vbox layout at " << index; + setCurrentCell({0, 0}); +} + +void QBoxLayoutSupport::insertWidget(QWidget *widget, const std::pair &cell) +{ + switch (m_orientation) { + case Qt::Horizontal: + helper()->insertWidget(layout(), QRect(cell.second, 0, 1, 1), widget); + break; + case Qt::Vertical: + helper()->insertWidget(layout(), QRect(0, cell.first, 1, 1), widget); + break; + } +} + +void QBoxLayoutSupport::setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) +{ + if (m_orientation == Qt::Horizontal && indicatorOrientation == Qt::Vertical) + setCurrentCell({0, index + increment}); + else if (m_orientation == Qt::Vertical && indicatorOrientation == Qt::Horizontal) + setCurrentCell({index + increment, 0}); +} + +bool QBoxLayoutSupport::supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const +{ + return m_orientation != indicatorOrientation; +} + +QRect QBoxLayoutSupport::extendedGeometry(int index) const +{ + QLayoutItem *item = layout()->itemAt(index); + // start off with item geometry + QRect g = item->geometry(); + + const QRect info = itemInfo(index); + + // On left border: extend to widget border + if (info.x() == 0) { + QPoint topLeft = g.topLeft(); + topLeft.rx() = layout()->geometry().left(); + g.setTopLeft(topLeft); + } + + // On top border: extend to widget border + if (info.y() == 0) { + QPoint topLeft = g.topLeft(); + topLeft.ry() = layout()->geometry().top(); + g.setTopLeft(topLeft); + } + + // is this the last item? + const QBoxLayout *box = static_cast(layout()); + if (index < box->count() -1) + return g; // Nope. + + // extend to widget border + QPoint bottomRight = g.bottomRight(); + switch (m_orientation) { + case Qt::Vertical: + bottomRight.ry() = layout()->geometry().bottom(); + break; + case Qt::Horizontal: + bottomRight.rx() = layout()->geometry().right(); + break; + } + g.setBottomRight(bottomRight); + return g; +} + +// -------------- Base class for QGridLayout-like support classes (LayoutDecorationExtension) +template +class GridLikeLayoutSupportBase: public QLayoutSupport +{ +public: + + GridLikeLayoutSupportBase(QDesignerFormWindowInterface *formWindow, QWidget *widget, LayoutHelper *helper, QObject *parent = nullptr) : + QLayoutSupport(formWindow, widget, helper, parent) {} + + void insertWidget(QWidget *widget, const std::pair &cell) override; + void removeWidget(QWidget *widget) override { helper()->removeWidget(layout(), widget); } + int findItemAt(int row, int column) const override; + using QLayoutSupport::findItemAt; + +protected: + GridLikeLayout *gridLikeLayout() const { + return qobject_cast(LayoutInfo::managedLayout(formWindow()->core(), widget())); + } + +private: + + void setCurrentCellFromIndicatorOnEmptyCell(int index) override; + void setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) override; + bool supportsIndicatorOrientation(Qt::Orientation) const override { return true; } + + QRect extendedGeometry(int index) const override; + + // Overwrite to check the insertion position (if there are limits) + virtual void checkCellForInsertion(int * /*row*/, int * /*col*/) const {} +}; + +template +void GridLikeLayoutSupportBase::setCurrentCellFromIndicatorOnEmptyCell(int index) +{ + GridLikeLayout *grid = gridLikeLayout(); + Q_ASSERT(grid); + + setInsertMode(InsertWidgetMode); + int row, column, rowspan, colspan; + + getGridItemPosition(grid, index, &row, &column, &rowspan, &colspan); + setCurrentCell({row, column}); +} + +template +void GridLikeLayoutSupportBase::setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) { + const QRect info = itemInfo(index); + switch (indicatorOrientation) { + case Qt::Vertical: { + setInsertMode(InsertColumnMode); + int row = info.top(); + int column = increment ? info.right() + 1 : info.left(); + checkCellForInsertion(&row, &column); + setCurrentCell({row, column}); + } + break; + case Qt::Horizontal: { + setInsertMode(InsertRowMode); + int row = increment ? info.bottom() + 1 : info.top(); + int column = info.left(); + checkCellForInsertion(&row, &column); + setCurrentCell({row, column}); + } + break; + } +} + +template +void GridLikeLayoutSupportBase::insertWidget(QWidget *widget, const std::pair &cell) +{ + helper()->insertWidget(layout(), QRect(cell.second, cell.first, 1, 1), widget); +} + +template +int GridLikeLayoutSupportBase::findItemAt(int at_row, int at_column) const +{ + GridLikeLayout *grid = gridLikeLayout(); + Q_ASSERT(grid); + return findGridItemAt(grid, at_row, at_column); +} + +template +QRect GridLikeLayoutSupportBase::extendedGeometry(int index) const +{ + QLayoutItem *item = layout()->itemAt(index); + // start off with item geometry + QRect g = item->geometry(); + + const QRect info = itemInfo(index); + + // On left border: extend to widget border + if (info.x() == 0) { + QPoint topLeft = g.topLeft(); + topLeft.rx() = layout()->geometry().left(); + g.setTopLeft(topLeft); + } + + // On top border: extend to widget border + if (info.y() == 0) { + QPoint topLeft = g.topLeft(); + topLeft.ry() = layout()->geometry().top(); + g.setTopLeft(topLeft); + } + const GridLikeLayout *grid = gridLikeLayout(); + Q_ASSERT(grid); + + // extend to widget border + QPoint bottomRight = g.bottomRight(); + if (gridRowCount(grid) == info.y()) + bottomRight.ry() = layout()->geometry().bottom(); + if (gridColumnCount(grid) == info.x()) + bottomRight.rx() = layout()->geometry().right(); + g.setBottomRight(bottomRight); + return g; +} + +// -------------- QGridLayoutSupport (LayoutDecorationExtension) +class QGridLayoutSupport: public GridLikeLayoutSupportBase +{ +public: + + QGridLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent = nullptr); + + void simplify() override; + void insertRow(int row) override; + void insertColumn(int column) override; + +private: +}; + +QGridLayoutSupport::QGridLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) : + GridLikeLayoutSupportBase(formWindow, widget, new GridLayoutHelper, parent) +{ +} + +void QGridLayoutSupport::insertRow(int row) +{ + QGridLayout *grid = gridLayout(); + Q_ASSERT(grid); + GridLayoutHelper::insertRow(grid, row); +} + +void QGridLayoutSupport::insertColumn(int column) +{ + QGridLayout *grid = gridLayout(); + Q_ASSERT(grid); + GridLayoutState state; + state.fromLayout(grid); + state.insertColumn(column); + state.applyToLayout(formWindow()->core(), widget()); +} + +void QGridLayoutSupport::simplify() +{ + QGridLayout *grid = gridLayout(); + Q_ASSERT(grid); + GridLayoutState state; + state.fromLayout(grid); + + const QRect fullArea = QRect(0, 0, state.colCount, state.rowCount); + if (state.simplify(fullArea, false)) + state.applyToLayout(formWindow()->core(), widget()); +} + +// -------------- QFormLayoutSupport (LayoutDecorationExtension) +class QFormLayoutSupport: public GridLikeLayoutSupportBase +{ +public: + QFormLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent = nullptr); + + void simplify() override {} + void insertRow(int /*row*/) override {} + void insertColumn(int /*column*/) override {} + +private: + void checkCellForInsertion(int * row, int *col) const override; +}; + +QFormLayoutSupport::QFormLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) : + GridLikeLayoutSupportBase(formWindow, widget, new FormLayoutHelper, parent) +{ +} + +void QFormLayoutSupport::checkCellForInsertion(int *row, int *col) const +{ + if (*col >= FormLayoutColumns) { // Clamp to 2 columns + *col = 1; + (*row)++; + } +} +} // anonymous namespace + +QLayoutSupport *QLayoutSupport::createLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) +{ + const QLayout *layout = LayoutInfo::managedLayout(formWindow->core(), widget); + Q_ASSERT(layout); + QLayoutSupport *rc = nullptr; + switch (LayoutInfo::layoutType(formWindow->core(), layout)) { + case LayoutInfo::HBox: + rc = new QBoxLayoutSupport(formWindow, widget, Qt::Horizontal, parent); + break; + case LayoutInfo::VBox: + rc = new QBoxLayoutSupport(formWindow, widget, Qt::Vertical, parent); + break; + case LayoutInfo::Grid: + rc = new QGridLayoutSupport(formWindow, widget, parent); + break; + case LayoutInfo::Form: + rc = new QFormLayoutSupport(formWindow, widget, parent); + break; + default: + break; + } + Q_ASSERT(rc); + return rc; +} +} // namespace qdesigner_internal + +// -------------- QLayoutWidget +QLayoutWidget::QLayoutWidget(QDesignerFormWindowInterface *formWindow, QWidget *parent) + : QWidget(parent), m_formWindow(formWindow), + m_leftMargin(0), m_topMargin(0), m_rightMargin(0), m_bottomMargin(0) +{ +} + +void QLayoutWidget::paintEvent(QPaintEvent*) +{ + if (m_formWindow->currentTool() != 0) + return; + + // only draw red borders if we're editting widgets + + QPainter p(this); + + QMap > excludedRowsForColumn; + QMap > excludedColumnsForRow; + + QLayout *lt = layout(); + QGridLayout *grid = qobject_cast(lt); + if (lt) { + if (const int count = lt->count()) { + p.setPen(QPen(QColor(255, 0, 0, 35), 1)); + for (int i = 0; i < count; i++) { + QLayoutItem *item = lt->itemAt(i); + if (grid) { + int row, column, rowSpan, columnSpan; + grid->getItemPosition(i, &row, &column, &rowSpan, &columnSpan); + QMap rows; + QMap columns; + for (int i = rowSpan; i > 1; i--) + rows[row + i - 2] = true; + for (int i = columnSpan; i > 1; i--) + columns[column + i - 2] = true; + + while (rowSpan > 0) { + excludedColumnsForRow[row + rowSpan - 1].insert(columns); + rowSpan--; + } + while (columnSpan > 0) { + excludedRowsForColumn[column + columnSpan - 1].insert(rows); + columnSpan--; + } + } + if (item->spacerItem()) { + const QRect geometry = item->geometry(); + if (!geometry.isNull()) + p.drawRect(geometry.adjusted(1, 1, -2, -2)); + } + } + } + } + if (grid) { + p.setPen(QPen(QColor(0, 0x80, 0, 0x80), 1)); + const int rowCount = grid->rowCount(); + const int columnCount = grid->columnCount(); + for (int i = 0; i < rowCount; i++) { + for (int j = 0; j < columnCount; j++) { + const QRect cellRect = grid->cellRect(i, j); + if (j < columnCount - 1 && !excludedColumnsForRow.value(i).value(j, false)) { + const double y0 = (i == 0) + ? 0 : (grid->cellRect(i - 1, j).bottom() + cellRect.top()) / 2.0; + const double y1 = (i == rowCount - 1) + ? height() - 1 : (cellRect.bottom() + grid->cellRect(i + 1, j).top()) / 2.0; + const double x = (cellRect.right() + grid->cellRect(i, j + 1).left()) / 2.0; + p.drawLine(QPointF(x, y0), QPointF(x, y1)); + } + if (i < rowCount - 1 && !excludedRowsForColumn.value(j).value(i, false)) { + const double x0 = (j == 0) + ? 0 : (grid->cellRect(i, j - 1).right() + cellRect.left()) / 2.0; + const double x1 = (j == columnCount - 1) + ? width() - 1 : (cellRect.right() + grid->cellRect(i, j + 1).left()) / 2.0; + const double y = (cellRect.bottom() + grid->cellRect(i + 1, j).top()) / 2.0; + p.drawLine(QPointF(x0, y), QPointF(x1, y)); + } + } + } + } + p.setPen(QPen(QColor(255, 0, 0, 128), 1)); + p.drawRect(0, 0, width() - 1, height() - 1); +} + +bool QLayoutWidget::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::LayoutRequest: { + (void) QWidget::event(e); + // Magic: We are layouted, but the parent is not.. + if (layout() && qdesigner_internal::LayoutInfo::layoutType(formWindow()->core(), parentWidget()) == qdesigner_internal::LayoutInfo::NoLayout) { + resize(layout()->totalMinimumSize().expandedTo(size())); + } + + update(); + + return true; + } + + default: + break; + } + + return QWidget::event(e); +} + +int QLayoutWidget::layoutLeftMargin() const +{ + if (m_leftMargin < 0 && layout()) { + int margin; + layout()->getContentsMargins(&margin, nullptr, nullptr, nullptr); + return margin; + } + return m_leftMargin; +} + +void QLayoutWidget::setLayoutLeftMargin(int layoutMargin) +{ + m_leftMargin = layoutMargin; + if (layout()) { + int newMargin = m_leftMargin; + if (newMargin >= 0 && newMargin < ShiftValue) + newMargin = ShiftValue; + int left, top, right, bottom; + layout()->getContentsMargins(&left, &top, &right, &bottom); + layout()->setContentsMargins(newMargin, top, right, bottom); + } +} + +int QLayoutWidget::layoutTopMargin() const +{ + if (m_topMargin < 0 && layout()) { + int margin; + layout()->getContentsMargins(nullptr, &margin, nullptr, nullptr); + return margin; + } + return m_topMargin; +} + +void QLayoutWidget::setLayoutTopMargin(int layoutMargin) +{ + m_topMargin = layoutMargin; + if (layout()) { + int newMargin = m_topMargin; + if (newMargin >= 0 && newMargin < ShiftValue) + newMargin = ShiftValue; + int left, top, right, bottom; + layout()->getContentsMargins(&left, &top, &right, &bottom); + layout()->setContentsMargins(left, newMargin, right, bottom); + } +} + +int QLayoutWidget::layoutRightMargin() const +{ + if (m_rightMargin < 0 && layout()) { + int margin; + layout()->getContentsMargins(nullptr, nullptr, &margin, nullptr); + return margin; + } + return m_rightMargin; +} + +void QLayoutWidget::setLayoutRightMargin(int layoutMargin) +{ + m_rightMargin = layoutMargin; + if (layout()) { + int newMargin = m_rightMargin; + if (newMargin >= 0 && newMargin < ShiftValue) + newMargin = ShiftValue; + int left, top, right, bottom; + layout()->getContentsMargins(&left, &top, &right, &bottom); + layout()->setContentsMargins(left, top, newMargin, bottom); + } +} + +int QLayoutWidget::layoutBottomMargin() const +{ + if (m_bottomMargin < 0 && layout()) { + int margin; + layout()->getContentsMargins(nullptr, nullptr, nullptr, &margin); + return margin; + } + return m_bottomMargin; +} + +void QLayoutWidget::setLayoutBottomMargin(int layoutMargin) +{ + m_bottomMargin = layoutMargin; + if (layout()) { + int newMargin = m_bottomMargin; + if (newMargin >= 0 && newMargin < ShiftValue) + newMargin = ShiftValue; + int left, top, right, bottom; + layout()->getContentsMargins(&left, &top, &right, &bottom); + layout()->setContentsMargins(left, top, right, newMargin); + } +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qlayout_widget_p.h b/src/designer/src/lib/shared/qlayout_widget_p.h new file mode 100644 index 0000000..dfff4a7 --- /dev/null +++ b/src/designer/src/lib/shared/qlayout_widget_p.h @@ -0,0 +1,269 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QLAYOUT_WIDGET_H +#define QLAYOUT_WIDGET_H + +#include "shared_global_p.h" + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QGridLayout; +class QFormLayout; + +namespace qdesigner_internal { +// ---- LayoutProperties: Helper struct that stores all layout-relevant properties +// with functions to retrieve and apply to property sheets. Can be used to store the state +// for undo commands and while rebuilding layouts. +struct QDESIGNER_SHARED_EXPORT LayoutProperties +{ + LayoutProperties(); + void clear(); + + enum Margins { LeftMargin, TopMargin, RightMargin, BottomMargin, MarginCount }; + enum Spacings { Spacing, HorizSpacing, VertSpacing, SpacingsCount }; + + enum PropertyMask { + ObjectNameProperty = 0x1, + LeftMarginProperty = 0x2, TopMarginProperty = 0x4, RightMarginProperty = 0x8, BottomMarginProperty = 0x10, + SpacingProperty = 0x20, HorizSpacingProperty = 0x40, VertSpacingProperty = 0x80, +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + SizeConstraintProperty = 0x100, +#else + HorizSizeConstraintProperty = 0x100, VertSizeConstraintProperty = 0x200, +#endif + FieldGrowthPolicyProperty = 0x400, RowWrapPolicyProperty = 0x800, + LabelAlignmentProperty = 0x1000, FormAlignmentProperty = 0x2000, + BoxStretchProperty = 0x4000, GridRowStretchProperty = 0x8000, GridColumnStretchProperty = 0x10000, + GridRowMinimumHeightProperty = 0x20000, GridColumnMinimumWidthProperty = 0x40000, + AllProperties = 0xFFFFF}; + + // return a PropertyMask of visible properties + static int visibleProperties(const QLayout *layout); + + // Retrieve from /apply to sheet: A property mask is returned indicating the properties found in the sheet + int fromPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask = AllProperties); + int toPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask = AllProperties, bool applyChanged = true) const; + + int m_margins[MarginCount]; + bool m_marginsChanged[MarginCount]; + + int m_spacings[SpacingsCount]; + bool m_spacingsChanged[SpacingsCount]; + + QVariant m_objectName; // receives a PropertySheetStringValue + bool m_objectNameChanged; +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + QVariant m_sizeConstraint; + bool m_sizeConstraintChanged; +#else + QVariant m_horizSizeConstraint; + bool m_horizSizeConstraintChanged; + QVariant m_vertSizeConstraint; + bool m_vertSizeConstraintChanged; +#endif + + bool m_fieldGrowthPolicyChanged; + QVariant m_fieldGrowthPolicy; + bool m_rowWrapPolicyChanged; + QVariant m_rowWrapPolicy; + bool m_labelAlignmentChanged; + QVariant m_labelAlignment; + bool m_formAlignmentChanged; + QVariant m_formAlignment; + + bool m_boxStretchChanged; + QVariant m_boxStretch; + + bool m_gridRowStretchChanged; + QVariant m_gridRowStretch; + + bool m_gridColumnStretchChanged; + QVariant m_gridColumnStretch; + + bool m_gridRowMinimumHeightChanged; + QVariant m_gridRowMinimumHeight; + + bool m_gridColumnMinimumWidthChanged; + QVariant m_gridColumnMinimumWidth; +}; + +// -- LayoutHelper: For use with the 'insert widget'/'delete widget' command, +// able to store and restore states. +// This could become part of 'QDesignerLayoutDecorationExtensionV2', +// but to keep any existing old extensions working, it is provided as +// separate class with a factory function. +class LayoutHelper { +protected: + LayoutHelper(); + +public: + Q_DISABLE_COPY(LayoutHelper) + + virtual ~LayoutHelper(); + + static LayoutHelper *createLayoutHelper(int type); + + static int indexOf(const QLayout *lt, const QWidget *widget); + + // Return area of an item (x == columns) + QRect itemInfo(QLayout *lt, const QWidget *widget) const; + + virtual QRect itemInfo(QLayout *lt, int index) const = 0; + virtual void insertWidget(QLayout *lt, const QRect &info, QWidget *w) = 0; + virtual void removeWidget(QLayout *lt, QWidget *widget) = 0; + // Since 4.5: The 'morphing' feature requires an API for replacing widgets on layouts. + virtual void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) = 0; + + // Simplify a grid, remove empty columns, rows within the rectangle + // The DeleteWidget command restricts the area to be simplified. + virtual bool canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const = 0; + virtual void simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) = 0; + + // Push and pop a state. Can be used for implementing undo for + // simplify/row, column insertion commands, provided that + // the widgets remain the same. + virtual void pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) = 0; + virtual void popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) = 0; +}; + +// Base class for layout decoration extensions. +class QDESIGNER_SHARED_EXPORT QLayoutSupport: public QObject, public QDesignerLayoutDecorationExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerLayoutDecorationExtension) + +protected: + QLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, LayoutHelper *helper, QObject *parent = nullptr); + +public: + ~QLayoutSupport() override; + + inline QDesignerFormWindowInterface *formWindow() const { return m_formWindow; } + + // DecorationExtension V2 + LayoutHelper* helper() const { return m_helper; } + + // DecorationExtension + int currentIndex() const override { return m_currentIndex; } + + InsertMode currentInsertMode() const override { return m_currentInsertMode; } + + std::pair currentCell() const override { return m_currentCell; } + + int findItemAt(const QPoint &pos) const override; + int indexOf(QWidget *widget) const override; + int indexOf(QLayoutItem *item) const override; + + void adjustIndicator(const QPoint &pos, int index) override; + + QWidgetList widgets(QLayout *layout) const override; + + // Pad empty cells with dummy spacers. Called by layouting commands. + static void createEmptyCells(QGridLayout *gridLayout); + // remove dummy spacers in the area. Returns false if there are non-empty items in the way + static bool removeEmptyCells(QGridLayout *gridLayout, const QRect &area); + static void createEmptyCells(QFormLayout *formLayout); // ditto. + static bool removeEmptyCells(QFormLayout *formLayout, const QRect &area); + + // grid helpers: find item index + static int findItemAt(QGridLayout *, int row, int column); + using QDesignerLayoutDecorationExtension::findItemAt; + // grid helpers: Quick check whether simplify should be enabled for grids. May return false positives. + static bool canSimplifyQuickCheck(const QGridLayout *); + static bool canSimplifyQuickCheck(const QFormLayout *fl); + // Factory function, create layout support according to layout type of widget + static QLayoutSupport *createLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent = nullptr); + +protected: + // figure out insertion position and mode from indicator on empty cell if supported + virtual void setCurrentCellFromIndicatorOnEmptyCell(int index) = 0; + // figure out insertion position and mode from indicator + virtual void setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) = 0; + + // Overwrite to return the extended geometry of an item, that is, + // if it is a border item, include the widget border for the indicator to work correctly + virtual QRect extendedGeometry(int index) const = 0; + virtual bool supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const = 0; + + QRect itemInfo(int index) const override; + QLayout *layout() const; + QGridLayout *gridLayout() const; + QWidget *widget() const { return m_widget; } + + void setInsertMode(InsertMode im); + void setCurrentCell(const std::pair &cell); + +private: + enum Indicator { LeftIndicator, TopIndicator, RightIndicator, BottomIndicator, NumIndicators }; + + void hideIndicator(Indicator i); + void showIndicator(Indicator i, const QRect &geometry, const QPalette &); + + QDesignerFormWindowInterface *m_formWindow; + LayoutHelper* m_helper; + + QPointer m_widget; + QPointer m_indicators[NumIndicators]; + int m_currentIndex; + InsertMode m_currentInsertMode; + std::pair m_currentCell; +}; +} // namespace qdesigner_internal + +// Red layout widget. +class QDESIGNER_SHARED_EXPORT QLayoutWidget: public QWidget +{ + Q_OBJECT +public: + explicit QLayoutWidget(QDesignerFormWindowInterface *formWindow, QWidget *parent = nullptr); + + int layoutLeftMargin() const; + void setLayoutLeftMargin(int layoutMargin); + + int layoutTopMargin() const; + void setLayoutTopMargin(int layoutMargin); + + int layoutRightMargin() const; + void setLayoutRightMargin(int layoutMargin); + + int layoutBottomMargin() const; + void setLayoutBottomMargin(int layoutMargin); + + inline QDesignerFormWindowInterface *formWindow() const { return m_formWindow; } + +protected: + bool event(QEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private: + QDesignerFormWindowInterface *m_formWindow; + int m_leftMargin; + int m_topMargin; + int m_rightMargin; + int m_bottomMargin; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_WIDGET_H diff --git a/src/designer/src/lib/shared/qsimpleresource.cpp b/src/designer/src/lib/shared/qsimpleresource.cpp new file mode 100644 index 0000000..2b0ef4a --- /dev/null +++ b/src/designer/src/lib/shared/qsimpleresource.cpp @@ -0,0 +1,233 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qsimpleresource_p.h" +#include "widgetfactory_p.h" +#include "widgetdatabase_p.h" +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +bool QSimpleResource::m_warningsEnabled = true; + +QSimpleResource::QSimpleResource(QDesignerFormEditorInterface *core) : + QAbstractFormBuilder(), + m_core(core) +{ + setWorkingDirectory(QDir(dataDirectory())); +} + +QSimpleResource::~QSimpleResource() = default; + +QBrush QSimpleResource::setupBrush(DomBrush *brush) +{ + return QAbstractFormBuilder::setupBrush(brush); +} + +DomBrush *QSimpleResource::saveBrush(const QBrush &brush) +{ + return QAbstractFormBuilder::saveBrush(brush); +} + +void QSimpleResource::addExtensionDataToDOM(QAbstractFormBuilder * /* afb */, + QDesignerFormEditorInterface *core, + DomWidget *ui_widget, QWidget *widget) +{ + QExtensionManager *emgr = core->extensionManager(); + if (QDesignerExtraInfoExtension *extra = qt_extension(emgr, widget)) { + extra->saveWidgetExtraInfo(ui_widget); + } +} + +void QSimpleResource::applyExtensionDataFromDOM(QAbstractFormBuilder * /* afb */, + QDesignerFormEditorInterface *core, + DomWidget *ui_widget, QWidget *widget) +{ + QExtensionManager *emgr = core->extensionManager(); + if (QDesignerExtraInfoExtension *extra = qt_extension(emgr, widget)) { + extra->loadWidgetExtraInfo(ui_widget); + } +} + +QString QSimpleResource::customWidgetScript(QDesignerFormEditorInterface *core, QObject *object) +{ + return customWidgetScript(core, qdesigner_internal::WidgetFactory::classNameOf(core, object)); +} + +bool QSimpleResource::hasCustomWidgetScript(QDesignerFormEditorInterface *, QObject *) +{ + return false; +} + +QString QSimpleResource::customWidgetScript(QDesignerFormEditorInterface *, const QString &) +{ + return QString(); +} + +// Custom widgets handling helpers + +// Add unique fake slots and signals to lists +bool QSimpleResource::addFakeMethods(const DomSlots *domSlots, QStringList &fakeSlots, QStringList &fakeSignals) +{ + if (!domSlots) + return false; + + bool rc = false; + const QStringList &elementSlots = domSlots->elementSlot(); + for (const QString &fakeSlot : elementSlots) + if (fakeSlots.indexOf(fakeSlot) == -1) { + fakeSlots += fakeSlot; + rc = true; + } + + const QStringList &elementSignals = domSlots->elementSignal(); + for (const QString &fakeSignal : elementSignals) + if (fakeSignals.indexOf(fakeSignal) == -1) { + fakeSignals += fakeSignal; + rc = true; + } + return rc; +} + +void QSimpleResource::addFakeMethodsToWidgetDataBase(const DomCustomWidget *domCustomWidget, WidgetDataBaseItem *item) +{ + const DomSlots *domSlots = domCustomWidget->elementSlots(); + if (!domSlots) + return; + + // Merge in new slots, signals + QStringList fakeSlots = item->fakeSlots(); + QStringList fakeSignals = item->fakeSignals(); + if (addFakeMethods(domSlots, fakeSlots, fakeSignals)) { + item->setFakeSlots(fakeSlots); + item->setFakeSignals(fakeSignals); + } +} + +// Perform one iteration of adding the custom widgets to the database, +// looking up the base class and inheriting its data. +// Remove the succeeded custom widgets from the list. +// Classes whose base class could not be found are left in the list. + +void QSimpleResource::addCustomWidgetsToWidgetDatabase(const QDesignerFormEditorInterface *core, + QList &custom_widget_list) +{ + QDesignerWidgetDataBaseInterface *db = core->widgetDataBase(); + for (qsizetype i = 0; i < custom_widget_list.size(); ) { + bool classInserted = false; + DomCustomWidget *custom_widget = custom_widget_list.at(i); + const QString customClassName = custom_widget->elementClass(); + const QString base_class = custom_widget->elementExtends(); + QString includeFile; + IncludeType includeType = IncludeLocal; + if (const DomHeader *header = custom_widget->elementHeader()) { + includeFile = header->text(); + if (header->hasAttributeLocation() && header->attributeLocation() == "global"_L1) + includeType = IncludeGlobal; + } + const bool domIsContainer = custom_widget->elementContainer(); + // Append a new item + if (base_class.isEmpty()) { + WidgetDataBaseItem *item = new WidgetDataBaseItem(customClassName); + item->setPromoted(false); + item->setGroup(QCoreApplication::translate("Designer", "Custom Widgets")); + item->setIncludeFile(buildIncludeFile(includeFile, includeType)); + item->setContainer(domIsContainer); + item->setCustom(true); + addFakeMethodsToWidgetDataBase(custom_widget, item); + db->append(item); + custom_widget_list.removeAt(i); + classInserted = true; + } else { + // Create a new entry cloned from base class. Note that this will ignore existing + // classes, eg, plugin custom widgets. + QDesignerWidgetDataBaseItemInterface *item = + appendDerived(db, customClassName, QCoreApplication::translate("Designer", "Promoted Widgets"), + base_class, + buildIncludeFile(includeFile, includeType), + true,true); + // Ok, base class found. + if (item) { + // Hack to accommodate for old UI-files in which "container" is not set properly: + // Apply "container" from DOM only if true (else, eg classes from QFrame might not accept + // dropping child widgets on them as container=false). This also allows for + // QWidget-derived stacked pages. + if (domIsContainer) + item->setContainer(domIsContainer); + + addFakeMethodsToWidgetDataBase(custom_widget, static_cast(item)); + custom_widget_list.removeAt(i); + classInserted = true; + } + } + // Skip failed item. + if (!classInserted) + i++; + } + +} + +void QSimpleResource::handleDomCustomWidgets(const QDesignerFormEditorInterface *core, + const DomCustomWidgets *dom_custom_widgets) +{ + if (dom_custom_widgets == nullptr) + return; + auto custom_widget_list = dom_custom_widgets->elementCustomWidget(); + // Attempt to insert each item derived from its base class. + // This should at most require two iterations in the event that the classes are out of order + // (derived first, max depth: promoted custom plugin = 2) + for (int iteration = 0; iteration < 2; iteration++) { + addCustomWidgetsToWidgetDatabase(core, custom_widget_list); + if (custom_widget_list.isEmpty()) + return; + } + // Oops, there are classes left whose base class could not be found. + // Default them to QWidget with warnings. + const QString fallBackBaseClass = u"QWidget"_s; + for (DomCustomWidget *custom_widget : std::as_const(custom_widget_list)) { + const QString customClassName = custom_widget->elementClass(); + const QString base_class = custom_widget->elementExtends(); + qDebug() << "** WARNING The base class " << base_class << " of the custom widget class " << customClassName + << " could not be found. Defaulting to " << fallBackBaseClass << '.'; + custom_widget->setElementExtends(fallBackBaseClass); + } + // One more pass. + addCustomWidgetsToWidgetDatabase(core, custom_widget_list); +} + +// ------------ FormBuilderClipboard + +FormBuilderClipboard::FormBuilderClipboard(QWidget *w) +{ + m_widgets += w; +} + +bool FormBuilderClipboard::empty() const +{ + return m_widgets.isEmpty() && m_actions.isEmpty(); +} +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/qsimpleresource_p.h b/src/designer/src/lib/shared/qsimpleresource_p.h new file mode 100644 index 0000000..37b9f5c --- /dev/null +++ b/src/designer/src/lib/shared/qsimpleresource_p.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QSIMPLERESOURCE_H +#define QSIMPLERESOURCE_H + +#include "abstractformbuilder.h" +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class DomCustomWidgets; +class DomCustomWidget; +class DomSlots; + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class WidgetDataBaseItem; + +class QDESIGNER_SHARED_EXPORT QSimpleResource : public QAbstractFormBuilder +{ +public: + explicit QSimpleResource(QDesignerFormEditorInterface *core); + ~QSimpleResource() override; + + QBrush setupBrush(DomBrush *brush); + DomBrush *saveBrush(const QBrush &brush); + + inline QDesignerFormEditorInterface *core() const + { return m_core; } + + // Query extensions for additional data + static void addExtensionDataToDOM(QAbstractFormBuilder *afb, + QDesignerFormEditorInterface *core, + DomWidget *ui_widget, QWidget *widget); + static void applyExtensionDataFromDOM(QAbstractFormBuilder *afb, + QDesignerFormEditorInterface *core, + DomWidget *ui_widget, QWidget *widget); + // Return the script returned by the CustomWidget codeTemplate API + static QString customWidgetScript(QDesignerFormEditorInterface *core, QObject *object); + static QString customWidgetScript(QDesignerFormEditorInterface *core, const QString &className); + static bool hasCustomWidgetScript(QDesignerFormEditorInterface *core, QObject *object); + + // Implementation for FormBuilder::createDomCustomWidgets() that adds + // the custom widgets to the widget database + static void handleDomCustomWidgets(const QDesignerFormEditorInterface *core, + const DomCustomWidgets *dom_custom_widgets); + +protected: + static bool addFakeMethods(const DomSlots *domSlots, QStringList &fakeSlots, QStringList &fakeSignals); + +private: + static void addCustomWidgetsToWidgetDatabase(const QDesignerFormEditorInterface *core, + QList &custom_widget_list); + static void addFakeMethodsToWidgetDataBase(const DomCustomWidget *domCustomWidget, WidgetDataBaseItem *item); + + static bool m_warningsEnabled; + QDesignerFormEditorInterface *m_core; +}; + +// Contents of clipboard for formbuilder copy and paste operations +// (Actions and widgets) +struct QDESIGNER_SHARED_EXPORT FormBuilderClipboard { + using ActionList = QList; + + FormBuilderClipboard() = default; + FormBuilderClipboard(QWidget *w); + + bool empty() const; + + QWidgetList m_widgets; + ActionList m_actions; +}; + +// Base class for a form builder used in the editor that +// provides copy and paste.(move into base interface) +class QDESIGNER_SHARED_EXPORT QEditorFormBuilder : public QSimpleResource +{ +public: + explicit QEditorFormBuilder(QDesignerFormEditorInterface *core) : QSimpleResource(core) {} + + virtual bool copy(QIODevice *dev, const FormBuilderClipboard &selection) = 0; + virtual DomUI *copy(const FormBuilderClipboard &selection) = 0; + + // A widget parent needs to be specified, otherwise, the widget factory cannot locate the form window via parent + // and thus is not able to construct special widgets (QLayoutWidget). + virtual FormBuilderClipboard paste(DomUI *ui, QWidget *widgetParent, QObject *actionParent = nullptr) = 0; + virtual FormBuilderClipboard paste(QIODevice *dev, QWidget *widgetParent, QObject *actionParent = nullptr) = 0; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/lib/shared/qtresourceeditordialog.cpp b/src/designer/src/lib/shared/qtresourceeditordialog.cpp new file mode 100644 index 0000000..76e4d04 --- /dev/null +++ b/src/designer/src/lib/shared/qtresourceeditordialog.cpp @@ -0,0 +1,2175 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtresourceeditordialog_p.h" +#include "ui_qtresourceeditordialog.h" +#include "qtresourcemodel_p.h" +#include "iconloader_p.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto rccRootTag = "RCC"_L1; +static constexpr auto rccTag = "qresource"_L1; +static constexpr auto rccFileTag = "file"_L1; +static constexpr auto rccAliasAttribute = "alias"_L1; +static constexpr auto rccPrefixAttribute = "prefix"_L1; +static constexpr auto rccLangAttribute = "lang"_L1; +static constexpr auto SplitterPosition = "SplitterPosition"_L1; +static constexpr auto ResourceEditorGeometry = "Geometry"_L1; +static constexpr auto QrcDialogC = "QrcDialog"_L1; + +static QString msgOverwrite(const QString &fname) +{ + return QCoreApplication::translate("QtResourceEditorDialog", "%1 already exists.\nDo you want to replace it?").arg(fname); +} + +static QString msgTagMismatch(const QString &got, const QString &expected) +{ + return QCoreApplication::translate("QtResourceEditorDialog", "The file does not appear to be a resource file; element '%1' was found where '%2' was expected.").arg(got, expected); +} + +namespace qdesigner_internal { + +// below 3 data classes should be derived from QSharedData and made implicit shared class +struct QtResourceFileData +{ + QString path; + QString alias; + + friend bool comparesEqual(const QtResourceFileData &lhs, + const QtResourceFileData &rhs) noexcept + { + return lhs.path == rhs.path && lhs.alias == rhs.alias; + } + Q_DECLARE_EQUALITY_COMPARABLE(QtResourceFileData) +}; + +struct QtResourcePrefixData +{ + QString prefix; + QString language; + QList resourceFileList; + + friend bool comparesEqual(const QtResourcePrefixData &lhs, + const QtResourcePrefixData &rhs) noexcept + { + return lhs.prefix == rhs.prefix && lhs.language == rhs.language + && lhs.resourceFileList == rhs.resourceFileList; + } + Q_DECLARE_EQUALITY_COMPARABLE(QtResourcePrefixData) +}; + +struct QtQrcFileData +{ + QString qrcPath; + QList resourceList; + + friend bool comparesEqual(const QtQrcFileData &lhs, + const QtQrcFileData &rhs) noexcept + { + return lhs.qrcPath == rhs.qrcPath && lhs.resourceList == rhs.resourceList; + } + Q_DECLARE_EQUALITY_COMPARABLE(QtQrcFileData) +}; + +} // namespace qdesigner_internal + +using QtResourcePrefixData = qdesigner_internal::QtResourcePrefixData; +using QtResourceFileData = qdesigner_internal::QtResourceFileData; +using QtQrcFileData = qdesigner_internal::QtQrcFileData; + +static bool loadResourceFileData(const QDomElement &fileElem, QtResourceFileData *fileData, + QString *errorMessage) +{ + if (!fileData) + return false; + + if (fileElem.tagName() != rccFileTag) { + *errorMessage = msgTagMismatch(fileElem.tagName(), rccFileTag); + return false; + } + + QtResourceFileData &data = *fileData; + + data.path = fileElem.text(); + data.alias = fileElem.attribute(rccAliasAttribute); + + return true; +} + +static bool loadResourcePrefixData(const QDomElement &prefixElem, QtResourcePrefixData *prefixData, QString *errorMessage) +{ + if (!prefixData) + return false; + + if (prefixElem.tagName() != rccTag) { + *errorMessage = msgTagMismatch(prefixElem.tagName(), rccTag); + return false; + } + + QtResourcePrefixData &data = *prefixData; + + data.prefix = prefixElem.attribute(rccPrefixAttribute); + data.language = prefixElem.attribute(rccLangAttribute); + QDomElement fileElem = prefixElem.firstChildElement(); + while (!fileElem.isNull()) { + QtResourceFileData fileData; + if (!loadResourceFileData(fileElem, &fileData, errorMessage)) + return false; + data.resourceFileList.append(fileData); + fileElem = fileElem.nextSiblingElement(); + } + return true; +} + +static bool loadQrcFileData(const QDomDocument &doc, const QString &path, QtQrcFileData *qrcFileData, QString *errorMessage) +{ + if (!qrcFileData) + return false; + + QtQrcFileData &data = *qrcFileData; + + QDomElement docElem = doc.documentElement(); + if (docElem.tagName() != rccRootTag) { + *errorMessage = msgTagMismatch(docElem.tagName(), rccRootTag); + return false; + } + + QDomElement prefixElem = docElem.firstChildElement(); + while (!prefixElem.isNull()) { + QtResourcePrefixData prefixData; + if (!loadResourcePrefixData(prefixElem, &prefixData, errorMessage)) + return false; + data.resourceList.append(prefixData); + prefixElem = prefixElem.nextSiblingElement(); + } + + data.qrcPath = path; + + return true; +} + +static QDomElement saveResourceFileData(QDomDocument &doc, const QtResourceFileData &fileData) +{ + QDomElement fileElem = doc.createElement(rccFileTag); + if (!fileData.alias.isEmpty()) + fileElem.setAttribute(rccAliasAttribute, fileData.alias); + + QDomText textElem = doc.createTextNode(fileData.path); + fileElem.appendChild(textElem); + + return fileElem; +} + +static QDomElement saveResourcePrefixData(QDomDocument &doc, const QtResourcePrefixData &prefixData) +{ + QDomElement prefixElem = doc.createElement(rccTag); + if (!prefixData.prefix.isEmpty()) + prefixElem.setAttribute(rccPrefixAttribute, prefixData.prefix); + if (!prefixData.language.isEmpty()) + prefixElem.setAttribute(rccLangAttribute, prefixData.language); + + for (const QtResourceFileData &rfd : prefixData.resourceFileList) { + QDomElement fileElem = saveResourceFileData(doc, rfd); + prefixElem.appendChild(fileElem); + } + + return prefixElem; +} + +static QDomDocument saveQrcFileData(const QtQrcFileData &qrcFileData) +{ + QDomDocument doc; + QDomElement docElem = doc.createElement(rccRootTag); + for (const QtResourcePrefixData &prefixData : qrcFileData.resourceList) { + QDomElement prefixElem = saveResourcePrefixData(doc, prefixData); + + docElem.appendChild(prefixElem); + } + doc.appendChild(docElem); + + return doc; +} + +namespace qdesigner_internal { + +// --------------- QtResourceFile +class QtResourceFile { +public: + friend class QtQrcManager; + + QString path() const { return m_path; } + QString alias() const { return m_alias; } + QString fullPath() const { return m_fullPath; } +private: + QtResourceFile() = default; + + QString m_path; + QString m_alias; + QString m_fullPath; +}; + +class QtResourcePrefix { +public: + friend class QtQrcManager; + + QString prefix() const { return m_prefix; } + QString language() const { return m_language; } + QList resourceFiles() const { return m_resourceFiles; } +private: + QtResourcePrefix() = default; + + QString m_prefix; + QString m_language; + QList m_resourceFiles; + +}; +// ------------------- QtQrcFile +class QtQrcFile { +public: + friend class QtQrcManager; + + QString path() const { return m_path; } + QString fileName() const { return m_fileName; } + QList resourcePrefixList() const { return m_resourcePrefixes; } + QtQrcFileData initialState() const { return m_initialState; } + +private: + QtQrcFile() = default; + + void setPath(const QString &path) { + m_path = path; + QFileInfo fi(path); + m_fileName = fi.fileName(); + } + + QString m_path; + QString m_fileName; + QList m_resourcePrefixes; + QtQrcFileData m_initialState; +}; + +// ------------------ QtQrcManager +class QtQrcManager : public QObject +{ + Q_OBJECT +public: + QtQrcManager(QObject *parent = nullptr); + ~QtQrcManager() override; + + QList qrcFiles() const; + + // helpers + QtQrcFile *qrcFileOf(const QString &path) const; + QtQrcFile *qrcFileOf(QtResourcePrefix *resourcePrefix) const; + QtResourcePrefix *resourcePrefixOf(QtResourceFile *resourceFile) const; + + QtQrcFile *importQrcFile(const QtQrcFileData &qrcFileData, QtQrcFile *beforeQrcFile = nullptr); + void exportQrcFile(QtQrcFile *qrcFile, QtQrcFileData *qrcFileData) const; + + QIcon icon(const QString &resourceFullPath) const; + bool exists(const QString &resourceFullPath) const; + bool exists(QtQrcFile *qrcFile) const; + + QtQrcFile *prevQrcFile(QtQrcFile *qrcFile) const; + QtQrcFile *nextQrcFile(QtQrcFile *qrcFile) const; + QtResourcePrefix *prevResourcePrefix(QtResourcePrefix *resourcePrefix) const; + QtResourcePrefix *nextResourcePrefix(QtResourcePrefix *resourcePrefix) const; + QtResourceFile *prevResourceFile(QtResourceFile *resourceFile) const; + QtResourceFile *nextResourceFile(QtResourceFile *resourceFile) const; + + void clear(); + +public slots: + + QtQrcFile *insertQrcFile(const QString &path, qdesigner_internal::QtQrcFile *beforeQrcFile = nullptr, + bool newFile = false); + void moveQrcFile(QtQrcFile *qrcFile, qdesigner_internal::QtQrcFile *beforeQrcFile); + void setInitialState(qdesigner_internal::QtQrcFile *qrcFile, + const QtQrcFileData &initialState); + void removeQrcFile(qdesigner_internal::QtQrcFile *qrcFile); + + QtResourcePrefix *insertResourcePrefix(qdesigner_internal::QtQrcFile *qrcFile, + const QString &prefix, const QString &language, + QtResourcePrefix *beforeResourcePrefix = nullptr); + void moveResourcePrefix(qdesigner_internal::QtResourcePrefix *resourcePrefix, QtResourcePrefix *beforeResourcePrefix); // the same qrc file??? + void changeResourcePrefix(qdesigner_internal::QtResourcePrefix *resourcePrefix, const QString &newPrefix); + void changeResourceLanguage(qdesigner_internal::QtResourcePrefix *resourcePrefix, const QString &newLanguage); + void removeResourcePrefix(qdesigner_internal::QtResourcePrefix *resourcePrefix); + + QtResourceFile *insertResourceFile(qdesigner_internal::QtResourcePrefix *resourcePrefix, + const QString &path, const QString &alias, + qdesigner_internal::QtResourceFile *beforeResourceFile = nullptr); + void moveResourceFile(qdesigner_internal::QtResourceFile *resourceFile, + qdesigner_internal::QtResourceFile *beforeResourceFile); // the same prefix??? + void changeResourceAlias(qdesigner_internal::QtResourceFile *resourceFile, const QString &newAlias); + void removeResourceFile(qdesigner_internal::QtResourceFile *resourceFile); + +signals: + void qrcFileInserted(qdesigner_internal::QtQrcFile *qrcFile); + void qrcFileMoved(qdesigner_internal::QtQrcFile *qrcFile, + qdesigner_internal::QtQrcFile *oldBeforeQrcFile); + void qrcFileRemoved(qdesigner_internal::QtQrcFile *qrcFile); + + void resourcePrefixInserted(qdesigner_internal::QtResourcePrefix *resourcePrefix); + void resourcePrefixMoved(qdesigner_internal::QtResourcePrefix *resourcePrefix, + qdesigner_internal::QtResourcePrefix *oldBeforeResourcePrefix); + void resourcePrefixChanged(qdesigner_internal::QtResourcePrefix *resourcePrefix, + const QString &oldPrefix); + void resourceLanguageChanged(qdesigner_internal::QtResourcePrefix *resourcePrefix, + const QString &oldLanguage); + void resourcePrefixRemoved(qdesigner_internal::QtResourcePrefix *resourcePrefix); + + void resourceFileInserted(qdesigner_internal::QtResourceFile *resourceFile); + void resourceFileMoved(qdesigner_internal::QtResourceFile *resourceFile, + qdesigner_internal::QtResourceFile *oldBeforeResourceFile); + void resourceAliasChanged(qdesigner_internal::QtResourceFile *resourceFile, + const QString &oldAlias); + void resourceFileRemoved(qdesigner_internal::QtResourceFile *resourceFile); +private: + + QList m_qrcFiles; + QMap m_pathToQrc; + QHash m_qrcFileToExists; + QHash m_prefixToQrc; + QHash m_fileToPrefix; + QMap > m_fullPathToResourceFiles; + QMap m_fullPathToIcon; + QMap m_fullPathToExists; +}; + +QtQrcManager::QtQrcManager(QObject *parent) + : QObject(parent) +{ + +} + +QtQrcManager::~QtQrcManager() +{ + clear(); +} + +QList QtQrcManager::qrcFiles() const +{ + return m_qrcFiles; +} + +QtQrcFile *QtQrcManager::qrcFileOf(const QString &path) const +{ + return m_pathToQrc.value(path); +} + +QtQrcFile *QtQrcManager::qrcFileOf(QtResourcePrefix *resourcePrefix) const +{ + return m_prefixToQrc.value(resourcePrefix); +} + +QtResourcePrefix *QtQrcManager::resourcePrefixOf(QtResourceFile *resourceFile) const +{ + return m_fileToPrefix.value(resourceFile); +} + +QtQrcFile *QtQrcManager::importQrcFile(const QtQrcFileData &qrcFileData, QtQrcFile *beforeQrcFile) +{ + QtQrcFile *qrcFile = insertQrcFile(qrcFileData.qrcPath, beforeQrcFile); + if (!qrcFile) + return nullptr; + for (const QtResourcePrefixData &prefixData : qrcFileData.resourceList) { + QtResourcePrefix *resourcePrefix = insertResourcePrefix(qrcFile, prefixData.prefix, prefixData.language, nullptr); + for (const QtResourceFileData &fileData : prefixData.resourceFileList) + insertResourceFile(resourcePrefix, fileData.path, fileData.alias, nullptr); + } + setInitialState(qrcFile, qrcFileData); + return qrcFile; +} + +void QtQrcManager::exportQrcFile(QtQrcFile *qrcFile, QtQrcFileData *qrcFileData) const +{ + if (!qrcFileData) + return; + + if (!qrcFile) + return; + + QtQrcFileData &data = *qrcFileData; + + QList resourceList; + + const auto resourcePrefixes = qrcFile->resourcePrefixList(); + for (const QtResourcePrefix *prefix : resourcePrefixes) { + QList resourceFileList; + const auto resourceFiles = prefix->resourceFiles(); + for (QtResourceFile *file : resourceFiles) { + QtResourceFileData fileData; + fileData.path = file->path(); + fileData.alias = file->alias(); + resourceFileList << fileData; + } + QtResourcePrefixData prefixData; + prefixData.prefix = prefix->prefix(); + prefixData.language = prefix->language(); + prefixData.resourceFileList = resourceFileList; + + resourceList << prefixData; + } + data = QtQrcFileData(); + data.qrcPath = qrcFile->path(); + data.resourceList = resourceList; +} + +QIcon QtQrcManager::icon(const QString &resourceFullPath) const +{ + return m_fullPathToIcon.value(resourceFullPath); +} + +bool QtQrcManager::exists(const QString &resourceFullPath) const +{ + return m_fullPathToExists.value(resourceFullPath, false); +} + +bool QtQrcManager::exists(QtQrcFile *qrcFile) const +{ + return m_qrcFileToExists.value(qrcFile, false); +} + +QtQrcFile *QtQrcManager::prevQrcFile(QtQrcFile *qrcFile) const +{ + if (!qrcFile) + return nullptr; + const int idx = m_qrcFiles.indexOf(qrcFile); + if (idx <= 0) + return nullptr; + return m_qrcFiles.at(idx - 1); +} + +QtQrcFile *QtQrcManager::nextQrcFile(QtQrcFile *qrcFile) const +{ + if (!qrcFile) + return nullptr; + const int idx = m_qrcFiles.indexOf(qrcFile); + if (idx < 0 || idx == m_qrcFiles.size() - 1) + return nullptr; + return m_qrcFiles.at(idx + 1); +} + +QtResourcePrefix *QtQrcManager::prevResourcePrefix(QtResourcePrefix *resourcePrefix) const +{ + if (!resourcePrefix) + return nullptr; + const auto prefixes = qrcFileOf(resourcePrefix)->resourcePrefixList(); + const int idx = prefixes.indexOf(resourcePrefix); + if (idx <= 0) + return nullptr; + return prefixes.at(idx - 1); +} + +QtResourcePrefix *QtQrcManager::nextResourcePrefix(QtResourcePrefix *resourcePrefix) const +{ + if (!resourcePrefix) + return nullptr; + const auto prefixes = qrcFileOf(resourcePrefix)->resourcePrefixList(); + const int idx = prefixes.indexOf(resourcePrefix); + if (idx < 0 || idx == prefixes.size() - 1) + return nullptr; + return prefixes.at(idx + 1); +} + +QtResourceFile *QtQrcManager::prevResourceFile(QtResourceFile *resourceFile) const +{ + if (!resourceFile) + return nullptr; + const auto files = resourcePrefixOf(resourceFile)->resourceFiles(); + const int idx = files.indexOf(resourceFile); + if (idx <= 0) + return nullptr; + return files.at(idx - 1); +} + +QtResourceFile *QtQrcManager::nextResourceFile(QtResourceFile *resourceFile) const +{ + if (!resourceFile) + return nullptr; + const auto files = resourcePrefixOf(resourceFile)->resourceFiles(); + const int idx = files.indexOf(resourceFile); + if (idx < 0 || idx == files.size() - 1) + return nullptr; + return files.at(idx + 1); +} + +void QtQrcManager::clear() +{ + const auto oldQrcFiles = qrcFiles(); + for (QtQrcFile *qf : oldQrcFiles) + removeQrcFile(qf); +} + +QtQrcFile *QtQrcManager::insertQrcFile(const QString &path, QtQrcFile *beforeQrcFile, bool newFile) +{ + if (m_pathToQrc.contains(path)) + return nullptr; + + int idx = m_qrcFiles.indexOf(beforeQrcFile); + if (idx < 0) + idx = m_qrcFiles.size(); + + QtQrcFile *qrcFile = new QtQrcFile(); + qrcFile->setPath(path); + + m_qrcFiles.insert(idx, qrcFile); + m_pathToQrc[path] = qrcFile; + + const QFileInfo fi(path); + m_qrcFileToExists[qrcFile] = fi.exists() || newFile; + + emit qrcFileInserted(qrcFile); + return qrcFile; +} + +void QtQrcManager::moveQrcFile(QtQrcFile *qrcFile, QtQrcFile *beforeQrcFile) +{ + if (qrcFile == beforeQrcFile) + return; + + const int idx = m_qrcFiles.indexOf(qrcFile); + if (idx < 0) + return; + + int beforeIdx = m_qrcFiles.indexOf(beforeQrcFile); + if (beforeIdx < 0) + beforeIdx = m_qrcFiles.size(); + + if (idx == beforeIdx - 1) // the same position, nothing changes + return; + + QtQrcFile *oldBefore = nullptr; + if (idx < m_qrcFiles.size() - 1) + oldBefore = m_qrcFiles.at(idx + 1); + + m_qrcFiles.removeAt(idx); + if (idx < beforeIdx) + beforeIdx -= 1; + + m_qrcFiles.insert(beforeIdx, qrcFile); + + emit qrcFileMoved(qrcFile, oldBefore); +} + +void QtQrcManager::setInitialState(QtQrcFile *qrcFile, const QtQrcFileData &initialState) +{ + qrcFile->m_initialState = initialState; +} + +void QtQrcManager::removeQrcFile(QtQrcFile *qrcFile) +{ + const int idx = m_qrcFiles.indexOf(qrcFile); + if (idx < 0) + return; + + const auto resourcePrefixes = qrcFile->resourcePrefixList(); + for (QtResourcePrefix *rp : resourcePrefixes) + removeResourcePrefix(rp); + + emit qrcFileRemoved(qrcFile); + + m_qrcFiles.removeAt(idx); + m_pathToQrc.remove(qrcFile->path()); + m_qrcFileToExists.remove(qrcFile); + delete qrcFile; +} + +QtResourcePrefix *QtQrcManager::insertResourcePrefix(QtQrcFile *qrcFile, const QString &prefix, + const QString &language, QtResourcePrefix *beforeResourcePrefix) +{ + if (!qrcFile) + return nullptr; + + int idx = qrcFile->m_resourcePrefixes.indexOf(beforeResourcePrefix); + if (idx < 0) + idx = qrcFile->m_resourcePrefixes.size(); + + QtResourcePrefix *resourcePrefix = new QtResourcePrefix(); + resourcePrefix->m_prefix = prefix; + resourcePrefix->m_language = language; + + qrcFile->m_resourcePrefixes.insert(idx, resourcePrefix); + m_prefixToQrc[resourcePrefix] = qrcFile; + + emit resourcePrefixInserted(resourcePrefix); + return resourcePrefix; +} + +void QtQrcManager::moveResourcePrefix(QtResourcePrefix *resourcePrefix, QtResourcePrefix *beforeResourcePrefix) +{ + if (resourcePrefix == beforeResourcePrefix) + return; + + QtQrcFile *qrcFile = qrcFileOf(resourcePrefix); + if (!qrcFile) + return; + + if (beforeResourcePrefix && qrcFileOf(beforeResourcePrefix) != qrcFile) + return; + + const int idx = qrcFile->m_resourcePrefixes.indexOf(resourcePrefix); + + int beforeIdx = qrcFile->m_resourcePrefixes.indexOf(beforeResourcePrefix); + if (beforeIdx < 0) + beforeIdx = qrcFile->m_resourcePrefixes.size(); + + if (idx == beforeIdx - 1) // the same position, nothing changes + return; + + QtResourcePrefix *oldBefore = nullptr; + if (idx < qrcFile->m_resourcePrefixes.size() - 1) + oldBefore = qrcFile->m_resourcePrefixes.at(idx + 1); + + qrcFile->m_resourcePrefixes.removeAt(idx); + if (idx < beforeIdx) + beforeIdx -= 1; + + qrcFile->m_resourcePrefixes.insert(beforeIdx, resourcePrefix); + + emit resourcePrefixMoved(resourcePrefix, oldBefore); +} + +void QtQrcManager::changeResourcePrefix(QtResourcePrefix *resourcePrefix, const QString &newPrefix) +{ + if (!resourcePrefix) + return; + + const QString oldPrefix = resourcePrefix->m_prefix; + if (oldPrefix == newPrefix) + return; + + resourcePrefix->m_prefix = newPrefix; + + emit resourcePrefixChanged(resourcePrefix, oldPrefix); +} + +void QtQrcManager::changeResourceLanguage(QtResourcePrefix *resourcePrefix, const QString &newLanguage) +{ + if (!resourcePrefix) + return; + + const QString oldLanguage = resourcePrefix->m_language; + if (oldLanguage == newLanguage) + return; + + resourcePrefix->m_language = newLanguage; + + emit resourceLanguageChanged(resourcePrefix, oldLanguage); +} + +void QtQrcManager::removeResourcePrefix(QtResourcePrefix *resourcePrefix) +{ + QtQrcFile *qrcFile = qrcFileOf(resourcePrefix); + if (!qrcFile) + return; + + const int idx = qrcFile->m_resourcePrefixes.indexOf(resourcePrefix); + + const auto resourceFiles = resourcePrefix->resourceFiles(); + for (QtResourceFile *rf : resourceFiles) + removeResourceFile(rf); + + emit resourcePrefixRemoved(resourcePrefix); + + qrcFile->m_resourcePrefixes.removeAt(idx); + m_prefixToQrc.remove(resourcePrefix); + delete resourcePrefix; +} + +QtResourceFile *QtQrcManager::insertResourceFile(QtResourcePrefix *resourcePrefix, const QString &path, + const QString &alias, QtResourceFile *beforeResourceFile) +{ + if (!resourcePrefix) + return nullptr; + + int idx = resourcePrefix->m_resourceFiles.indexOf(beforeResourceFile); + if (idx < 0) + idx = resourcePrefix->m_resourceFiles.size(); + + QtResourceFile *resourceFile = new QtResourceFile(); + resourceFile->m_path = path; + resourceFile->m_alias = alias; + const QFileInfo fi(qrcFileOf(resourcePrefix)->path()); + const QDir dir(fi.absolutePath()); + const QString fullPath = dir.absoluteFilePath(path); + resourceFile->m_fullPath = fullPath; + + resourcePrefix->m_resourceFiles.insert(idx, resourceFile); + m_fileToPrefix[resourceFile] = resourcePrefix; + m_fullPathToResourceFiles[fullPath].append(resourceFile); + if (!m_fullPathToIcon.contains(fullPath)) { + m_fullPathToIcon[fullPath] = QIcon(fullPath); + const QFileInfo fullInfo(fullPath); + m_fullPathToExists[fullPath] = fullInfo.exists(); + } + + emit resourceFileInserted(resourceFile); + return resourceFile; +} + +void QtQrcManager::moveResourceFile(QtResourceFile *resourceFile, QtResourceFile *beforeResourceFile) +{ + if (resourceFile == beforeResourceFile) + return; + + QtResourcePrefix *resourcePrefix = resourcePrefixOf(resourceFile); + if (!resourcePrefix) + return; + + if (beforeResourceFile && resourcePrefixOf(beforeResourceFile) != resourcePrefix) + return; + + const int idx = resourcePrefix->m_resourceFiles.indexOf(resourceFile); + + int beforeIdx = resourcePrefix->m_resourceFiles.indexOf(beforeResourceFile); + if (beforeIdx < 0) + beforeIdx = resourcePrefix->m_resourceFiles.size(); + + if (idx == beforeIdx - 1) // the same position, nothing changes + return; + + QtResourceFile *oldBefore = nullptr; + if (idx < resourcePrefix->m_resourceFiles.size() - 1) + oldBefore = resourcePrefix->m_resourceFiles.at(idx + 1); + + resourcePrefix->m_resourceFiles.removeAt(idx); + if (idx < beforeIdx) + beforeIdx -= 1; + + resourcePrefix->m_resourceFiles.insert(beforeIdx, resourceFile); + + emit resourceFileMoved(resourceFile, oldBefore); +} + +void QtQrcManager::changeResourceAlias(QtResourceFile *resourceFile, const QString &newAlias) +{ + if (!resourceFile) + return; + + const QString oldAlias = resourceFile->m_alias; + if (oldAlias == newAlias) + return; + + resourceFile->m_alias = newAlias; + + emit resourceAliasChanged(resourceFile, oldAlias); +} + +void QtQrcManager::removeResourceFile(QtResourceFile *resourceFile) +{ + QtResourcePrefix *resourcePrefix = resourcePrefixOf(resourceFile); + if (!resourcePrefix) + return; + + const int idx = resourcePrefix->m_resourceFiles.indexOf(resourceFile); + + emit resourceFileRemoved(resourceFile); + + resourcePrefix->m_resourceFiles.removeAt(idx); + m_fileToPrefix.remove(resourceFile); + const QString fullPath = resourceFile->fullPath(); + m_fullPathToResourceFiles[fullPath].removeAll(resourceFile); // optimize me + if (m_fullPathToResourceFiles[fullPath].isEmpty()) { + m_fullPathToResourceFiles.remove(fullPath); + m_fullPathToIcon.remove(fullPath); + m_fullPathToExists.remove(fullPath); + } + delete resourceFile; +} + +} // namespace qdesigner_internal + +using QtResourceFile = qdesigner_internal::QtResourceFile; +using QtResourcePrefix = qdesigner_internal::QtResourcePrefix; +using QtQrcFile = qdesigner_internal::QtQrcFile; +using QtQrcManager = qdesigner_internal::QtQrcManager; + +// ----------------- QtResourceEditorDialogPrivate +class QtResourceEditorDialogPrivate +{ + QtResourceEditorDialog *q_ptr{}; + Q_DECLARE_PUBLIC(QtResourceEditorDialog) +public: + QtResourceEditorDialogPrivate() = default; + + void slotQrcFileInserted(QtQrcFile *qrcFile); + void slotQrcFileMoved(QtQrcFile *qrcFile); + void slotQrcFileRemoved(QtQrcFile *qrcFile); + + QStandardItem *insertResourcePrefix(QtResourcePrefix *resourcePrefix); + + void slotResourcePrefixInserted(QtResourcePrefix *resourcePrefix) { insertResourcePrefix(resourcePrefix); } + void slotResourcePrefixMoved(QtResourcePrefix *resourcePrefix); + void slotResourcePrefixChanged(QtResourcePrefix *resourcePrefix); + void slotResourceLanguageChanged(QtResourcePrefix *resourcePrefix); + void slotResourcePrefixRemoved(QtResourcePrefix *resourcePrefix); + void slotResourceFileInserted(QtResourceFile *resourceFile); + void slotResourceFileMoved(QtResourceFile *resourceFile); + void slotResourceAliasChanged(QtResourceFile *resourceFile); + void slotResourceFileRemoved(QtResourceFile *resourceFile); + + void slotCurrentQrcFileChanged(QListWidgetItem *item); + void slotCurrentTreeViewItemChanged(const QModelIndex &index); + void slotListWidgetContextMenuRequested(const QPoint &pos); + void slotTreeViewContextMenuRequested(const QPoint &pos); + void slotTreeViewItemChanged(QStandardItem *item); + + void slotNewQrcFile(); + void slotImportQrcFile(); + void slotRemoveQrcFile(); + void slotMoveUpQrcFile(); + void slotMoveDownQrcFile(); + + void slotNewPrefix(); + void slotAddFiles(); + void slotChangePrefix(); + void slotChangeLanguage(); + void slotChangeAlias(); + void slotClonePrefix(); + void slotRemove(); + void slotMoveUp(); + void slotMoveDown(); + + bool loadQrcFile(const QString &path, QtQrcFileData *qrcFileData, QString *errorMessage); + bool loadQrcFile(const QString &path, QtQrcFileData *qrcFileData); + bool saveQrcFile(const QtQrcFileData &qrcFileData); + + QString qrcFileText(QtQrcFile *qrcFile) const; + + QMessageBox::StandardButton warning(const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) const; + + QString browseForNewLocation(const QString &resourceFile, const QDir &rootDir) const; + QString copyResourceFile(const QString &resourceFile, const QString &destPath) const; + QtResourceFile *getCurrentResourceFile() const; + QtResourcePrefix *getCurrentResourcePrefix() const; + void selectTreeRow(QStandardItem *item); + QString getSaveFileNameWithExtension(QWidget *parent, + const QString &title, QString dir, const QString &filter, const QString &extension) const; + QString qrcStartDirectory() const; + + Ui::QtResourceEditorDialog m_ui; + QDesignerFormEditorInterface *m_core = nullptr; + QtResourceModel *m_resourceModel = nullptr; + QDesignerDialogGuiInterface *m_dlgGui = nullptr; + QtQrcManager *m_qrcManager = nullptr; + QList m_initialState; + + QHash m_qrcFileToItem; + QHash m_itemToQrcFile; + QHash m_resourcePrefixToPrefixItem; + QHash m_resourcePrefixToLanguageItem; + QHash m_prefixItemToResourcePrefix; + QHash m_languageItemToResourcePrefix; + QHash m_resourceFileToPathItem; + QHash m_resourceFileToAliasItem; + QHash m_pathItemToResourceFile; + QHash m_aliasItemToResourceFile; + + bool m_ignoreCurrentChanged = false; + bool m_firstQrcFileDialog = true; + QtQrcFile *m_currentQrcFile = nullptr; + + QAction *m_newQrcFileAction = nullptr; + QAction *m_importQrcFileAction = nullptr; + QAction *m_removeQrcFileAction = nullptr; + QAction *m_moveUpQrcFileAction = nullptr; + QAction *m_moveDownQrcFileAction = nullptr; + + QAction *m_newPrefixAction = nullptr; + QAction *m_addResourceFileAction = nullptr; + QAction *m_changePrefixAction = nullptr; + QAction *m_changeLanguageAction = nullptr; + QAction *m_changeAliasAction = nullptr; + QAction *m_clonePrefixAction = nullptr; + QAction *m_moveUpAction = nullptr; + QAction *m_moveDownAction = nullptr; + QAction *m_removeAction = nullptr; + + QStandardItemModel *m_treeModel = nullptr; + QItemSelectionModel *m_treeSelection = nullptr; +}; + +QMessageBox::StandardButton QtResourceEditorDialogPrivate::warning(const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) const +{ + return m_dlgGui->message(q_ptr, QDesignerDialogGuiInterface::ResourceEditorMessage, QMessageBox::Warning, title, text, buttons, defaultButton); +} + +QString QtResourceEditorDialogPrivate::qrcFileText(QtQrcFile *qrcFile) const +{ + const QString path = qrcFile->path(); + const QString fileName = qrcFile->fileName(); + const QFileInfo fi(path); + if (fi.exists() && !fi.isWritable()) + return QApplication::translate("QtResourceEditorDialog", "%1 [read-only]").arg(fileName); + if (!m_qrcManager->exists(qrcFile)) + return QApplication::translate("QtResourceEditorDialog", "%1 [missing]").arg(fileName); + return fileName; +} + +void QtResourceEditorDialogPrivate::slotQrcFileInserted(QtQrcFile *qrcFile) +{ + QListWidgetItem *currentItem = m_ui.qrcFileList->currentItem(); + int idx = m_ui.qrcFileList->count(); + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(qrcFile); + QListWidgetItem *nextItem = m_qrcFileToItem.value(nextQrcFile); + if (nextItem) { + const int row = m_ui.qrcFileList->row(nextItem); + if (row >= 0) + idx = row; + } + const QString path = qrcFile->path(); + QListWidgetItem *item = new QListWidgetItem(qrcFileText(qrcFile)); + item->setToolTip(path); + m_ignoreCurrentChanged = true; + m_ui.qrcFileList->insertItem(idx, item); + m_ui.qrcFileList->setCurrentItem(currentItem); + m_ignoreCurrentChanged = false; + m_qrcFileToItem[qrcFile] = item; + m_itemToQrcFile[item] = qrcFile; + if (!m_qrcManager->exists(qrcFile)) + item->setForeground(QBrush(Qt::red)); +} + +void QtResourceEditorDialogPrivate::slotQrcFileMoved(QtQrcFile *qrcFile) +{ + QListWidgetItem *currentItem = m_ui.qrcFileList->currentItem(); + QListWidgetItem *item = m_qrcFileToItem.value(qrcFile); + m_ignoreCurrentChanged = true; + m_ui.qrcFileList->takeItem(m_ui.qrcFileList->row(item)); + + int idx = m_ui.qrcFileList->count(); + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(qrcFile); + QListWidgetItem *nextItem = m_qrcFileToItem.value(nextQrcFile); + if (nextItem) { + int row = m_ui.qrcFileList->row(nextItem); + if (row >= 0) + idx = row; + } + m_ui.qrcFileList->insertItem(idx, item); + if (currentItem == item) + m_ui.qrcFileList->setCurrentItem(item); + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotQrcFileRemoved(QtQrcFile *qrcFile) +{ + QListWidgetItem *item = m_qrcFileToItem.value(qrcFile); + if (item == m_ui.qrcFileList->currentItem()) + m_ui.qrcFileList->setCurrentItem(nullptr); // this should trigger list view signal currentItemChanged(0), and slot should set m_currentQrcFile to 0 + m_ignoreCurrentChanged = true; + delete item; + m_ignoreCurrentChanged = false; + m_itemToQrcFile.remove(item); + m_qrcFileToItem.remove(qrcFile); +} + +QStandardItem *QtResourceEditorDialogPrivate::insertResourcePrefix(QtResourcePrefix *resourcePrefix) +{ + if (m_qrcManager->qrcFileOf(resourcePrefix) != m_currentQrcFile) + return nullptr; + + QtResourcePrefix *prevResourcePrefix = m_qrcManager->prevResourcePrefix(resourcePrefix); + QStandardItem *prevItem = m_resourcePrefixToPrefixItem.value(prevResourcePrefix); + + int row = 0; + if (prevItem) + row = m_treeModel->indexFromItem(prevItem).row() + 1; + + QStandardItem *prefixItem = new QStandardItem(); + QStandardItem *languageItem = new QStandardItem(); + QList items; + items << prefixItem; + items << languageItem; + m_treeModel->insertRow(row, items); + const QModelIndex newIndex = m_treeModel->indexFromItem(prefixItem); + m_ui.resourceTreeView->setExpanded(newIndex, true); + prefixItem->setFlags(prefixItem->flags() | Qt::ItemIsEditable); + languageItem->setFlags(languageItem->flags() | Qt::ItemIsEditable); + m_resourcePrefixToPrefixItem[resourcePrefix] = prefixItem; + m_resourcePrefixToLanguageItem[resourcePrefix] = languageItem; + m_prefixItemToResourcePrefix[prefixItem] = resourcePrefix; + m_languageItemToResourcePrefix[languageItem] = resourcePrefix; + slotResourcePrefixChanged(resourcePrefix); + slotResourceLanguageChanged(resourcePrefix); + return prefixItem; +} + +void QtResourceEditorDialogPrivate::slotResourcePrefixMoved(QtResourcePrefix *resourcePrefix) +{ + QStandardItem *prefixItem = m_resourcePrefixToPrefixItem.value(resourcePrefix); + if (!prefixItem) + return; + + QStandardItem *languageItem = m_resourcePrefixToLanguageItem.value(resourcePrefix); + if (!languageItem) + return; + + const QModelIndex index = m_treeModel->indexFromItem(prefixItem); + const bool expanded = m_ui.resourceTreeView->isExpanded(index); + m_ignoreCurrentChanged = true; + const auto items = m_treeModel->takeRow(index.row()); + + int row = m_treeModel->rowCount(); + QtResourcePrefix *nextResourcePrefix = m_qrcManager->nextResourcePrefix(resourcePrefix); + QStandardItem *nextItem = m_resourcePrefixToPrefixItem.value(nextResourcePrefix); + if (nextItem) + row = m_treeModel->indexFromItem(nextItem).row(); + m_treeModel->insertRow(row, items); + m_ignoreCurrentChanged = false; + m_ui.resourceTreeView->setExpanded(m_treeModel->indexFromItem(items.at(0)), expanded); +} + +void QtResourceEditorDialogPrivate::slotResourcePrefixChanged(QtResourcePrefix *resourcePrefix) +{ + QStandardItem *item = m_resourcePrefixToPrefixItem.value(resourcePrefix); + if (!item) + return; + + m_ignoreCurrentChanged = true; + QString prefix = resourcePrefix->prefix(); + if (prefix.isEmpty()) + prefix = QCoreApplication::translate("QtResourceEditorDialog", ""); + item->setText(prefix); + item->setToolTip(prefix); + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotResourceLanguageChanged(QtResourcePrefix *resourcePrefix) +{ + QStandardItem *item = m_resourcePrefixToLanguageItem.value(resourcePrefix); + if (!item) + return; + + m_ignoreCurrentChanged = true; + const QString language = resourcePrefix->language(); + item->setText(language); + item->setToolTip(language); + + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotResourcePrefixRemoved(QtResourcePrefix *resourcePrefix) +{ + QStandardItem *prefixItem = m_resourcePrefixToPrefixItem.value(resourcePrefix); + if (!prefixItem) + return; + + QStandardItem *languageItem = m_resourcePrefixToLanguageItem.value(resourcePrefix); + if (!languageItem) + return; + + m_ignoreCurrentChanged = true; + m_treeModel->takeRow(m_treeModel->indexFromItem(prefixItem).row()); + delete prefixItem; + delete languageItem; + m_ignoreCurrentChanged = false; + m_prefixItemToResourcePrefix.remove(prefixItem); + m_languageItemToResourcePrefix.remove(languageItem); + m_resourcePrefixToPrefixItem.remove(resourcePrefix); + m_resourcePrefixToLanguageItem.remove(resourcePrefix); +} + +void QtResourceEditorDialogPrivate::slotResourceFileInserted(QtResourceFile *resourceFile) +{ + QtResourcePrefix *resourcePrefix = m_qrcManager->resourcePrefixOf(resourceFile); + if (m_qrcManager->qrcFileOf(resourcePrefix) != m_currentQrcFile) + return; + + QtResourceFile *prevResourceFile = m_qrcManager->prevResourceFile(resourceFile); + QStandardItem *prevItem = m_resourceFileToPathItem.value(prevResourceFile); + + QStandardItem *pathItem = new QStandardItem(resourceFile->path()); + QStandardItem *aliasItem = new QStandardItem(); + QStandardItem *parentItem = m_resourcePrefixToPrefixItem.value(resourcePrefix); + QList items; + items << pathItem; + items << aliasItem; + + int row = 0; + if (prevItem) + row = m_treeModel->indexFromItem(prevItem).row() + 1; + + parentItem->insertRow(row, items); + + pathItem->setFlags(pathItem->flags() & ~Qt::ItemIsEditable); + aliasItem->setFlags(aliasItem->flags() | Qt::ItemIsEditable); + m_resourceFileToPathItem[resourceFile] = pathItem; + m_resourceFileToAliasItem[resourceFile] = aliasItem; + m_pathItemToResourceFile[pathItem] = resourceFile; + m_aliasItemToResourceFile[aliasItem] = resourceFile; + pathItem->setToolTip(resourceFile->path()); + pathItem->setIcon(m_qrcManager->icon(resourceFile->fullPath())); + if (!m_qrcManager->exists(resourceFile->fullPath())) { + pathItem->setText(QApplication::translate("QtResourceEditorDialog", "%1 [missing]").arg(resourceFile->path())); + QBrush redBrush(Qt::red); + pathItem->setForeground(redBrush); + aliasItem->setForeground(redBrush); + } + slotResourceAliasChanged(resourceFile); +} + +void QtResourceEditorDialogPrivate::slotResourceFileMoved(QtResourceFile *resourceFile) +{ + QStandardItem *pathItem = m_resourceFileToPathItem.value(resourceFile); + if (!pathItem) + return; + + QStandardItem *aliasItem = m_resourceFileToAliasItem.value(resourceFile); + if (!aliasItem) + return; + + QStandardItem *parentItem = pathItem->parent(); + m_ignoreCurrentChanged = true; + const auto items = parentItem->takeRow(m_treeModel->indexFromItem(pathItem).row()); + + int row = parentItem->rowCount(); + QtResourceFile *nextResourceFile = m_qrcManager->nextResourceFile(resourceFile); + QStandardItem *nextItem = m_resourceFileToPathItem.value(nextResourceFile); + if (nextItem) + row = m_treeModel->indexFromItem(nextItem).row(); + parentItem->insertRow(row, items); + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotResourceAliasChanged(QtResourceFile *resourceFile) +{ + QStandardItem *item = m_resourceFileToAliasItem.value(resourceFile); + if (!item) + return; + + m_ignoreCurrentChanged = true; + const QString alias = resourceFile->alias(); + item->setText(alias); + item->setToolTip(alias); + + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotResourceFileRemoved(QtResourceFile *resourceFile) +{ + QStandardItem *pathItem = m_resourceFileToPathItem.value(resourceFile); + if (!pathItem) + return; + + QStandardItem *aliasItem = m_resourceFileToAliasItem.value(resourceFile); + if (!aliasItem) + return; + + QStandardItem *parentItem = pathItem->parent(); + + m_ignoreCurrentChanged = true; + parentItem->takeRow(m_treeModel->indexFromItem(pathItem).row()); + delete pathItem; + delete aliasItem; + m_ignoreCurrentChanged = false; + m_pathItemToResourceFile.remove(pathItem); + m_aliasItemToResourceFile.remove(aliasItem); + m_resourceFileToPathItem.remove(resourceFile); + m_resourceFileToAliasItem.remove(resourceFile); +} + + +void QtResourceEditorDialogPrivate::slotCurrentQrcFileChanged(QListWidgetItem *item) +{ + if (m_ignoreCurrentChanged) + return; + + QtQrcFile *newCurrentQrcFile = m_itemToQrcFile.value(item); + + if (newCurrentQrcFile == m_currentQrcFile) + return; + + if (m_currentQrcFile) { + QHash currentPrefixList = m_resourcePrefixToPrefixItem; + for (auto it = currentPrefixList.cbegin(), end = currentPrefixList.cend(); it != end; ++it) { + QtResourcePrefix *resourcePrefix = it.key(); + const auto currentResourceFiles = resourcePrefix->resourceFiles(); + for (QtResourceFile *rf : currentResourceFiles) + slotResourceFileRemoved(rf); + slotResourcePrefixRemoved(resourcePrefix); + } + } + + m_currentQrcFile = newCurrentQrcFile; + slotCurrentTreeViewItemChanged(QModelIndex()); + QStandardItem *firstPrefix = nullptr; // select first prefix + if (m_currentQrcFile) { + const auto newPrefixList = m_currentQrcFile->resourcePrefixList(); + for (QtResourcePrefix *resourcePrefix : newPrefixList) { + if (QStandardItem *newPrefixItem = insertResourcePrefix(resourcePrefix)) + if (!firstPrefix) + firstPrefix = newPrefixItem; + const auto newResourceFiles = resourcePrefix->resourceFiles(); + for (QtResourceFile *rf : newResourceFiles) + slotResourceFileInserted(rf); + } + } + m_ui.resourceTreeView->setCurrentIndex(firstPrefix ? m_treeModel->indexFromItem(firstPrefix) : QModelIndex()); + + m_removeQrcFileAction->setEnabled(m_currentQrcFile); + m_moveUpQrcFileAction->setEnabled(m_currentQrcFile && m_qrcManager->prevQrcFile(m_currentQrcFile)); + m_moveDownQrcFileAction->setEnabled(m_currentQrcFile && m_qrcManager->nextQrcFile(m_currentQrcFile)); +} + +void QtResourceEditorDialogPrivate::slotCurrentTreeViewItemChanged(const QModelIndex &index) +{ + QStandardItem *item = m_treeModel->itemFromIndex(index); + QtResourceFile *resourceFile = m_pathItemToResourceFile.value(item); + if (!resourceFile) + resourceFile = m_aliasItemToResourceFile.value(item); + QtResourcePrefix *resourcePrefix = m_prefixItemToResourcePrefix.value(item); + if (!resourcePrefix) + resourcePrefix = m_languageItemToResourcePrefix.value(item); + + bool moveUpEnabled = false; + bool moveDownEnabled = false; + bool currentItem = resourceFile || resourcePrefix; + + if (resourceFile) { + if (m_qrcManager->prevResourceFile(resourceFile)) + moveUpEnabled = true; + if (m_qrcManager->nextResourceFile(resourceFile)) + moveDownEnabled = true; + } else if (resourcePrefix) { + if (m_qrcManager->prevResourcePrefix(resourcePrefix)) + moveUpEnabled = true; + if (m_qrcManager->nextResourcePrefix(resourcePrefix)) + moveDownEnabled = true; + } + + m_newPrefixAction->setEnabled(m_currentQrcFile); + m_addResourceFileAction->setEnabled(currentItem); + m_changePrefixAction->setEnabled(currentItem); + m_changeLanguageAction->setEnabled(currentItem); + m_changeAliasAction->setEnabled(resourceFile); + m_removeAction->setEnabled(currentItem); + m_moveUpAction->setEnabled(moveUpEnabled); + m_moveDownAction->setEnabled(moveDownEnabled); + m_clonePrefixAction->setEnabled(currentItem); +} + +void QtResourceEditorDialogPrivate::slotListWidgetContextMenuRequested(const QPoint &pos) +{ + QMenu menu(q_ptr); + menu.addAction(m_newQrcFileAction); + menu.addAction(m_importQrcFileAction); + menu.addAction(m_removeQrcFileAction); + menu.addSeparator(); + menu.addAction(m_moveUpQrcFileAction); + menu.addAction(m_moveDownQrcFileAction); + menu.exec(m_ui.qrcFileList->mapToGlobal(pos)); +} + +void QtResourceEditorDialogPrivate::slotTreeViewContextMenuRequested(const QPoint &pos) +{ + QMenu menu(q_ptr); + menu.addAction(m_newPrefixAction); + menu.addAction(m_addResourceFileAction); + menu.addAction(m_removeAction); + menu.addSeparator(); + menu.addAction(m_changePrefixAction); + menu.addAction(m_changeLanguageAction); + menu.addAction(m_changeAliasAction); + menu.addSeparator(); + menu.addAction(m_clonePrefixAction); + menu.addSeparator(); + menu.addAction(m_moveUpAction); + menu.addAction(m_moveDownAction); + menu.exec(m_ui.resourceTreeView->mapToGlobal(pos)); +} + +void QtResourceEditorDialogPrivate::slotTreeViewItemChanged(QStandardItem *item) +{ + if (m_ignoreCurrentChanged) + return; + + const QString newValue = item->text(); + QtResourceFile *resourceFile = m_aliasItemToResourceFile.value(item); + if (resourceFile) { + m_qrcManager->changeResourceAlias(resourceFile, newValue); + return; + } + + QtResourcePrefix *resourcePrefix = m_prefixItemToResourcePrefix.value(item); + if (resourcePrefix) { + m_qrcManager->changeResourcePrefix(resourcePrefix, newValue); + return; + } + + resourcePrefix = m_languageItemToResourcePrefix.value(item); + if (resourcePrefix) { + m_qrcManager->changeResourceLanguage(resourcePrefix, newValue); + return; + } +} + +QString QtResourceEditorDialogPrivate::getSaveFileNameWithExtension(QWidget *parent, + const QString &title, QString dir, const QString &filter, const QString &extension) const +{ + QString saveFile; + while (true) { + saveFile = m_dlgGui->getSaveFileName(parent, title, dir, filter, nullptr, QFileDialog::DontConfirmOverwrite); + if (saveFile.isEmpty()) + return saveFile; + + const QFileInfo fInfo(saveFile); + if (fInfo.suffix().isEmpty() && !fInfo.fileName().endsWith(u'.')) + saveFile += u'.' + extension; + + const QFileInfo fi(saveFile); + if (!fi.exists()) + break; + + if (warning(title, msgOverwrite(fi.fileName()), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) + break; + + dir = saveFile; + } + return saveFile; +} + +QString QtResourceEditorDialogPrivate::qrcStartDirectory() const +{ + if (!m_currentQrcFile) + return QString(); + const QDir dir = QFileInfo(m_currentQrcFile->path()).dir(); + return dir.exists() ? dir.absolutePath() : QString(); +} + +void QtResourceEditorDialogPrivate::slotNewQrcFile() +{ + const QString qrcPath = getSaveFileNameWithExtension(q_ptr, + QCoreApplication::translate("QtResourceEditorDialog", "New Resource File"), + m_firstQrcFileDialog ? qrcStartDirectory() : QString(), + QCoreApplication::translate("QtResourceEditorDialog", "Resource files (*.qrc)"), + u"qrc"_s); + if (qrcPath.isEmpty()) + return; + + m_firstQrcFileDialog = false; + if (QtQrcFile *sameQrcFile = m_qrcManager->qrcFileOf(qrcPath)) { + // QMessageBox ??? + QListWidgetItem *item = m_qrcFileToItem.value(sameQrcFile); + m_ui.qrcFileList->setCurrentItem(item); + item->setSelected(true); + return; + } + + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(m_currentQrcFile); + + QtQrcFile *qrcFile = m_qrcManager->insertQrcFile(qrcPath, nextQrcFile, true); + m_ui.qrcFileList->setCurrentItem(m_qrcFileToItem.value(qrcFile)); +} + +void QtResourceEditorDialogPrivate::slotImportQrcFile() +{ + const QString qrcPath = m_dlgGui->getOpenFileName(q_ptr, + QCoreApplication::translate("QtResourceEditorDialog", "Import Resource File"), + m_firstQrcFileDialog ? qrcStartDirectory() : QString(), + QCoreApplication::translate("QtResourceEditorDialog", "Resource files (*.qrc)")); + if (qrcPath.isEmpty()) + return; + m_firstQrcFileDialog = false; + if (QtQrcFile *sameQrcFile = m_qrcManager->qrcFileOf(qrcPath)) { + // QMessageBox ??? + QListWidgetItem *item = m_qrcFileToItem.value(sameQrcFile); + m_ui.qrcFileList->setCurrentItem(item); + item->setSelected(true); + return; + } + + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(m_currentQrcFile); + + QtQrcFileData qrcFileData; + loadQrcFile(qrcPath, &qrcFileData); + QtQrcFile *qrcFile = m_qrcManager->importQrcFile(qrcFileData, nextQrcFile); + m_ui.qrcFileList->setCurrentItem(m_qrcFileToItem.value(qrcFile)); +} + +void QtResourceEditorDialogPrivate::slotRemoveQrcFile() +{ + if (!m_currentQrcFile) + return; + + QtQrcFile *currentQrcFile = m_qrcManager->nextQrcFile(m_currentQrcFile); + if (!currentQrcFile) + currentQrcFile = m_qrcManager->prevQrcFile(m_currentQrcFile); + + m_qrcManager->removeQrcFile(m_currentQrcFile); + QListWidgetItem *item = m_qrcFileToItem.value(currentQrcFile); + if (item) { + m_ui.qrcFileList->setCurrentItem(item); + item->setSelected(true); + } +} + +void QtResourceEditorDialogPrivate::slotMoveUpQrcFile() +{ + if (!m_currentQrcFile) + return; + + QtQrcFile *prevQrcFile = m_qrcManager->prevQrcFile(m_currentQrcFile); + if (!prevQrcFile) + return; + + m_qrcManager->moveQrcFile(m_currentQrcFile, prevQrcFile); +} + +void QtResourceEditorDialogPrivate::slotMoveDownQrcFile() +{ + if (!m_currentQrcFile) + return; + + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(m_currentQrcFile); + if (!nextQrcFile) + return; + nextQrcFile = m_qrcManager->nextQrcFile(nextQrcFile); + + m_qrcManager->moveQrcFile(m_currentQrcFile, nextQrcFile); +} + +QtResourceFile *QtResourceEditorDialogPrivate::getCurrentResourceFile() const +{ + QStandardItem *currentItem = m_treeModel->itemFromIndex(m_treeSelection->currentIndex()); + + + QtResourceFile *currentResourceFile = nullptr; + if (currentItem) { + currentResourceFile = m_pathItemToResourceFile.value(currentItem); + if (!currentResourceFile) + currentResourceFile = m_aliasItemToResourceFile.value(currentItem); + } + return currentResourceFile; +} + +QtResourcePrefix *QtResourceEditorDialogPrivate::getCurrentResourcePrefix() const +{ + QStandardItem *currentItem = m_treeModel->itemFromIndex(m_treeSelection->currentIndex()); + + QtResourcePrefix *currentResourcePrefix = nullptr; + if (currentItem) { + currentResourcePrefix = m_prefixItemToResourcePrefix.value(currentItem); + if (!currentResourcePrefix) { + currentResourcePrefix = m_languageItemToResourcePrefix.value(currentItem); + if (!currentResourcePrefix) { + QtResourceFile *currentResourceFile = getCurrentResourceFile(); + if (currentResourceFile) + currentResourcePrefix = m_qrcManager->resourcePrefixOf(currentResourceFile); + } + } + } + return currentResourcePrefix; +} + +void QtResourceEditorDialogPrivate::selectTreeRow(QStandardItem *item) +{ + const QModelIndex index = m_treeModel->indexFromItem(item); + m_treeSelection->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + m_treeSelection->setCurrentIndex(index, QItemSelectionModel::Select); +} + +void QtResourceEditorDialogPrivate::slotNewPrefix() +{ + if (!m_currentQrcFile) + return; + + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + QtResourcePrefix *nextResourcePrefix = m_qrcManager->nextResourcePrefix(currentResourcePrefix); + QtResourcePrefix *newResourcePrefix = m_qrcManager->insertResourcePrefix(m_currentQrcFile, + QCoreApplication::translate("QtResourceEditorDialog", "newPrefix"), + QString(), nextResourcePrefix); + if (!newResourcePrefix) + return; + + QStandardItem *newItem = m_resourcePrefixToPrefixItem.value(newResourcePrefix); + if (!newItem) + return; + + const QModelIndex index = m_treeModel->indexFromItem(newItem); + selectTreeRow(newItem); + m_ui.resourceTreeView->edit(index); +} + +static inline QString outOfPathWarning(const QString &fname) +{ + return QApplication::translate("QtResourceEditorDialog", + "

    Warning: The file

    " + "

    %1

    " + "

    is outside of the current resource file's parent directory.

    ").arg(fname); +} + +static inline QString outOfPathWarningInfo() +{ + return QApplication::translate("QtResourceEditorDialog", + "

    To resolve the issue, press:

    " + "" + "" + "" + "
    Copyto copy the file to the resource file's parent directory.
    Copy As...to copy the file into a subdirectory of the resource file's parent directory.
    Keepto use its current location.
    "); +} + +void QtResourceEditorDialogPrivate::slotAddFiles() +{ + if (!m_currentQrcFile) + return; + + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + QtResourceFile *currentResourceFile = getCurrentResourceFile(); + if (!currentResourcePrefix) + return; + + QString initialPath = m_currentQrcFile->path(); + if (currentResourceFile) { + QFileInfo fi(currentResourceFile->fullPath()); + initialPath = fi.absolutePath(); + } + + const QStringList resourcePaths = m_dlgGui->getOpenImageFileNames(q_ptr, + QCoreApplication::translate("QtResourceEditorDialog", "Add Files"), + initialPath); + if (resourcePaths.isEmpty()) + return; + + QtResourceFile *nextResourceFile = m_qrcManager->nextResourceFile(currentResourceFile); + if (!currentResourceFile) { + const auto resourceFiles = currentResourcePrefix->resourceFiles(); + if (!resourceFiles.isEmpty()) + nextResourceFile = resourceFiles.first(); + } + + const QFileInfo fi(m_currentQrcFile->path()); + const QString destDir = fi.absolutePath(); + const QDir dir(fi.absolutePath()); + for (QString resourcePath : resourcePaths) { + QString relativePath = dir.relativeFilePath(resourcePath); + if (relativePath.startsWith(".."_L1)) { + QMessageBox msgBox(QMessageBox::Warning, + QCoreApplication::translate("QtResourceEditorDialog", "Incorrect Path"), + outOfPathWarning(relativePath), QMessageBox::Cancel); + msgBox.setInformativeText(outOfPathWarningInfo()); + QPushButton *copyButton = msgBox.addButton(QCoreApplication::translate("QtResourceEditorDialog", + "Copy"), QMessageBox::ActionRole); + QPushButton *copyAsButton = msgBox.addButton(QCoreApplication::translate("QtResourceEditorDialog", + "Copy As..."), QMessageBox::ActionRole); + QPushButton *keepButton = msgBox.addButton(QCoreApplication::translate("QtResourceEditorDialog", + "Keep"), QMessageBox::ActionRole); + QPushButton *skipButton = msgBox.addButton(QCoreApplication::translate("QtResourceEditorDialog", + "Skip"), QMessageBox::ActionRole); + msgBox.setEscapeButton(QMessageBox::Cancel); + msgBox.setDefaultButton(copyButton); + msgBox.exec(); + QString destPath; + if (msgBox.clickedButton() == keepButton) { + // nothing + } else if (msgBox.clickedButton() == copyButton) { + QFileInfo resInfo(resourcePath); + QDir dd(destDir); + destPath = dd.absoluteFilePath(resInfo.fileName()); + if (dd.exists(resInfo.fileName())) { + if (warning(QCoreApplication::translate("QtResourceEditorDialog", "Copy"), + msgOverwrite(resInfo.fileName()), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Yes) + continue; + } + resourcePath = copyResourceFile(resourcePath, destPath); // returns empty string in case copy failed or was canceled + } else if (msgBox.clickedButton() == copyAsButton) { + destPath = browseForNewLocation(resourcePath, dir); // returns empty string in case browsing was canceled + if (destPath.isEmpty()) + continue; + resourcePath = copyResourceFile(resourcePath, destPath); // returns empty string in case copy failed or was canceled + } else if (msgBox.clickedButton() == skipButton) { // skipped + continue; + } else { // canceled + return; + } + if (resourcePath.isEmpty()) + continue; + } + relativePath = dir.relativeFilePath(resourcePath); + QtResourceFile *newResourceFile = m_qrcManager->insertResourceFile(currentResourcePrefix, relativePath, QString(), nextResourceFile); + + QStandardItem *newItem = m_resourceFileToPathItem.value(newResourceFile); + if (newItem) + selectTreeRow(newItem); + } +} + +void QtResourceEditorDialogPrivate::slotChangePrefix() +{ + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + if (!currentResourcePrefix) + return; + + QStandardItem *item = m_resourcePrefixToPrefixItem.value(currentResourcePrefix); + QModelIndex index = m_treeModel->indexFromItem(item); + selectTreeRow(item); + m_ui.resourceTreeView->scrollTo(index); + m_ui.resourceTreeView->edit(index); +} + +void QtResourceEditorDialogPrivate::slotChangeLanguage() +{ + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + if (!currentResourcePrefix) + return; + + QStandardItem *item = m_resourcePrefixToLanguageItem.value(currentResourcePrefix); + QModelIndex index = m_treeModel->indexFromItem(item); + selectTreeRow(item); + m_ui.resourceTreeView->scrollTo(index); + m_ui.resourceTreeView->edit(index); +} + +void QtResourceEditorDialogPrivate::slotChangeAlias() +{ + QtResourceFile *currentResourceFile = getCurrentResourceFile(); + if (!currentResourceFile) + return; + + QStandardItem *item = m_resourceFileToAliasItem.value(currentResourceFile); + QModelIndex index = m_treeModel->indexFromItem(item); + selectTreeRow(item); + m_ui.resourceTreeView->scrollTo(index); + m_ui.resourceTreeView->edit(index); +} + +void QtResourceEditorDialogPrivate::slotClonePrefix() +{ + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + if (!currentResourcePrefix) + return; + + bool ok; + QString suffix = QInputDialog::getText(q_ptr, QApplication::translate("QtResourceEditorDialog", "Clone Prefix"), + QCoreApplication::translate("QtResourceEditorDialog", "Enter the suffix which you want to add to the names of the cloned files.\n" + "This could for example be a language extension like \"_de\"."), + QLineEdit::Normal, QString(), &ok); + if (!ok) + return; + + QtResourcePrefix *newResourcePrefix = m_qrcManager->insertResourcePrefix(m_currentQrcFile, currentResourcePrefix->prefix(), + currentResourcePrefix->language(), m_qrcManager->nextResourcePrefix(currentResourcePrefix)); + if (newResourcePrefix) { + const auto files = currentResourcePrefix->resourceFiles(); + for (QtResourceFile *resourceFile : files) { + QString path = resourceFile->path(); + QFileInfo fi(path); + QDir dir(fi.dir()); + QString oldSuffix = fi.completeSuffix(); + if (!oldSuffix.isEmpty()) + oldSuffix = u'.' + oldSuffix; + const QString newBaseName = fi.baseName() + suffix + oldSuffix; + const QString newPath = QDir::cleanPath(dir.filePath(newBaseName)); + m_qrcManager->insertResourceFile(newResourcePrefix, newPath, + resourceFile->alias()); + } + } +} + +void QtResourceEditorDialogPrivate::slotRemove() +{ + QStandardItem *item = m_treeModel->itemFromIndex(m_treeSelection->currentIndex()); + if (!item) + return; + + QtResourceFile *resourceFile = m_pathItemToResourceFile.value(item); + if (!resourceFile) + resourceFile = m_aliasItemToResourceFile.value(item); + QtResourcePrefix *resourcePrefix = m_prefixItemToResourcePrefix.value(item); + if (!resourcePrefix) + resourcePrefix = m_languageItemToResourcePrefix.value(item); + + QStandardItem *newCurrentItem = nullptr; + + if (resourceFile) { + QtResourceFile *nextFile = m_qrcManager->nextResourceFile(resourceFile); + if (!nextFile) + nextFile = m_qrcManager->prevResourceFile(resourceFile); + newCurrentItem = m_resourceFileToPathItem.value(nextFile); + if (!newCurrentItem) + newCurrentItem = m_resourcePrefixToPrefixItem.value(m_qrcManager->resourcePrefixOf(resourceFile)); + } + if (!newCurrentItem) { + QtResourcePrefix *nextPrefix = m_qrcManager->nextResourcePrefix(resourcePrefix); + if (!nextPrefix) + nextPrefix = m_qrcManager->prevResourcePrefix(resourcePrefix); + newCurrentItem = m_resourcePrefixToPrefixItem.value(nextPrefix); + } + + selectTreeRow(newCurrentItem); + + if (resourcePrefix) + m_qrcManager->removeResourcePrefix(resourcePrefix); + else if (resourceFile) + m_qrcManager->removeResourceFile(resourceFile); +} + +void QtResourceEditorDialogPrivate::slotMoveUp() +{ + if (QtResourceFile *resourceFile = getCurrentResourceFile()) { + QtResourceFile *prevFile = m_qrcManager->prevResourceFile(resourceFile); + + if (!prevFile) + return; + + m_qrcManager->moveResourceFile(resourceFile, prevFile); + selectTreeRow(m_resourceFileToPathItem.value(resourceFile)); + } else if (QtResourcePrefix *resourcePrefix = getCurrentResourcePrefix()) { + QtResourcePrefix *prevPrefix = m_qrcManager->prevResourcePrefix(resourcePrefix); + + if (!prevPrefix) + return; + + m_qrcManager->moveResourcePrefix(resourcePrefix, prevPrefix); + selectTreeRow(m_resourcePrefixToPrefixItem.value(resourcePrefix)); + } +} + +void QtResourceEditorDialogPrivate::slotMoveDown() +{ + if (QtResourceFile *resourceFile = getCurrentResourceFile()) { + QtResourceFile *nextFile = m_qrcManager->nextResourceFile(resourceFile); + + if (!nextFile) + return; + + m_qrcManager->moveResourceFile(resourceFile, m_qrcManager->nextResourceFile(nextFile)); + selectTreeRow(m_resourceFileToPathItem.value(resourceFile)); + } else if (QtResourcePrefix *resourcePrefix = getCurrentResourcePrefix()) { + QtResourcePrefix *nextPrefix = m_qrcManager->nextResourcePrefix(resourcePrefix); + + if (!nextPrefix) + return; + + m_qrcManager->moveResourcePrefix(resourcePrefix, m_qrcManager->nextResourcePrefix(nextPrefix)); + selectTreeRow(m_resourcePrefixToPrefixItem.value(resourcePrefix)); + } +} + +QString QtResourceEditorDialogPrivate::browseForNewLocation(const QString &resourceFile, const QDir &rootDir) const +{ + QFileInfo fi(resourceFile); + const QString initialPath = rootDir.absoluteFilePath(fi.fileName()); + while (true) { + QString newPath = m_dlgGui->getSaveFileName(q_ptr, + QCoreApplication::translate("QtResourceEditorDialog", "Copy As"), + initialPath); + QString relativePath = rootDir.relativeFilePath(newPath); + if (relativePath.startsWith(".."_L1)) { + if (warning(QCoreApplication::translate("QtResourceEditorDialog", "Copy As"), + QCoreApplication::translate("QtResourceEditorDialog", "

    The selected file:

    " + "

    %1

    is outside of the current resource file's directory:

    %2

    " + "

    Please select another path within this directory.

    "). + arg(relativePath, rootDir.absolutePath()), + QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) != QMessageBox::Ok) + return QString(); + } else { + return newPath; + } + } + + return QString(); +} + +QString QtResourceEditorDialogPrivate::copyResourceFile(const QString &resourceFile, const QString &destPath) const +{ + QFileInfo fi(destPath); + if (fi.exists()) { + while (fi.exists() && !QFile::remove(destPath)) { + if (warning(QCoreApplication::translate("QtResourceEditorDialog", "Copy"), + QCoreApplication::translate("QtResourceEditorDialog", "Could not overwrite %1.").arg(fi.fileName()), + QMessageBox::Retry | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Retry) + return QString(); + } + } + while (!QFile::copy(resourceFile, destPath)) { + if (warning(QCoreApplication::translate("QtResourceEditorDialog", "Copy"), + QCoreApplication::translate("QtResourceEditorDialog", "Could not copy\n%1\nto\n%2") + .arg(resourceFile, destPath), + QMessageBox::Retry | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Retry) + return QString(); + } + return destPath; +} +bool QtResourceEditorDialogPrivate::loadQrcFile(const QString &path, QtQrcFileData *qrcFileData) +{ + QString errorMessage; + const bool rc = loadQrcFile(path, qrcFileData, &errorMessage); +// if (!rc) +// warning(QApplication::translate("QtResourceEditorDialog", "Resource File Load Error"), errorMessage); + return rc; +} +bool QtResourceEditorDialogPrivate::loadQrcFile(const QString &path, QtQrcFileData *qrcFileData, QString *errorMessage) +{ + if (!qrcFileData) + return false; + + qrcFileData->qrcPath = path; + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + // there is sufficient hint while loading a form and after opening the editor (qrc marked marked with red and with [missing] text) + //*errorMessage = QApplication::translate("QtResourceEditorDialog", "Unable to open %1 for reading: %2").arg(path).arg(file.errorString()); + return false; + } + + QByteArray dataArray = file.readAll(); + file.close(); + + QDomDocument doc; + if (QDomDocument::ParseResult result = doc.setContent(dataArray)) { + return loadQrcFileData(doc, path, qrcFileData, errorMessage); + } else { + *errorMessage = + QCoreApplication::translate("QtResourceEditorDialog", + "A parse error occurred at line %1, column %2 of %3:\n%4") + .arg(result.errorLine).arg(result.errorColumn).arg(path, result.errorMessage); + return false; + } +} + +bool QtResourceEditorDialogPrivate::saveQrcFile(const QtQrcFileData &qrcFileData) +{ + QFile file(qrcFileData.qrcPath); + while (!file.open(QIODevice::WriteOnly)) { + QMessageBox msgBox(QMessageBox::Warning, + QCoreApplication::translate("QtResourceEditorDialog", + "Save Resource File"), + QCoreApplication::translate("QtResourceEditorDialog", + "Could not write %1: %2") + .arg(qrcFileData.qrcPath, + file.errorString()), + QMessageBox::Cancel|QMessageBox::Ignore|QMessageBox::Retry); + msgBox.setEscapeButton(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Ignore); + switch (msgBox.exec()) { + case QMessageBox::Retry: + break; // nothing + case QMessageBox::Ignore: + return true; + default: + return false; + } + } + + QDomDocument doc = saveQrcFileData(qrcFileData); + + QByteArray dataArray = doc.toByteArray(2); + file.write(dataArray); + + file.close(); + return true; +} + +QtResourceEditorDialog::QtResourceEditorDialog(QDesignerFormEditorInterface *core, QDesignerDialogGuiInterface *dlgGui, QWidget *parent) + : QDialog(parent), d_ptr(new QtResourceEditorDialogPrivate()) +{ + d_ptr->q_ptr = this; + d_ptr->m_ui.setupUi(this); + d_ptr->m_qrcManager = new QtQrcManager(this); + d_ptr->m_dlgGui = dlgGui; + d_ptr->m_core = core; + + setWindowTitle(tr("Edit Resources")); + + connect(d_ptr->m_qrcManager, &QtQrcManager::qrcFileInserted, + this, [this](QtQrcFile *file) { d_ptr->slotQrcFileInserted(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::qrcFileMoved, + this, [this](QtQrcFile *file) { d_ptr->slotQrcFileMoved(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::qrcFileRemoved, + this, [this](QtQrcFile *file) { d_ptr->slotQrcFileRemoved(file); }); + + connect(d_ptr->m_qrcManager, &QtQrcManager::resourcePrefixInserted, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourcePrefixInserted(prefix); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourcePrefixMoved, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourcePrefixMoved(prefix); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourcePrefixChanged, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourcePrefixChanged(prefix); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceLanguageChanged, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourceLanguageChanged(prefix); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourcePrefixRemoved, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourcePrefixRemoved(prefix); }); + + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceFileInserted, + this, [this](QtResourceFile *file) { d_ptr->slotResourceFileInserted(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceFileMoved, + this, [this](QtResourceFile *file) { d_ptr->slotResourceFileMoved(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceAliasChanged, + this, [this](QtResourceFile *file) { d_ptr->slotResourceAliasChanged(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceFileRemoved, + this, [this](QtResourceFile *file) { d_ptr->slotResourceFileRemoved(file); }); + + QIcon upIcon = qdesigner_internal::createIconSet("up.png"_L1); + QIcon downIcon = qdesigner_internal::createIconSet("down.png"_L1); + QIcon minusIcon = qdesigner_internal::createIconSet("minus-16.png"_L1); + QIcon newIcon = qdesigner_internal::createIconSet("filenew-16.png"_L1); + QIcon openIcon = qdesigner_internal::createIconSet("fileopen-16.png"_L1); + QIcon removeIcon = qdesigner_internal::createIconSet("editdelete-16.png"_L1); + QIcon addPrefixIcon = qdesigner_internal::createIconSet("prefix-add.png"_L1); + + d_ptr->m_newQrcFileAction = new QAction(newIcon, tr("New..."), this); + d_ptr->m_newQrcFileAction->setToolTip(tr("New Resource File")); + d_ptr->m_importQrcFileAction = new QAction(openIcon, tr("Open..."), this); + d_ptr->m_importQrcFileAction->setToolTip(tr("Open Resource File")); + d_ptr->m_removeQrcFileAction = new QAction(removeIcon, tr("Remove"), this); + d_ptr->m_moveUpQrcFileAction = new QAction(upIcon, tr("Move Up"), this); + d_ptr->m_moveDownQrcFileAction = new QAction(downIcon, tr("Move Down"), this); + + d_ptr->m_newPrefixAction = new QAction(addPrefixIcon, tr("Add Prefix"), this); + d_ptr->m_newPrefixAction->setToolTip(tr("Add Prefix")); + d_ptr->m_addResourceFileAction = new QAction(openIcon, tr("Add Files..."), this); + d_ptr->m_changePrefixAction = new QAction(tr("Change Prefix"), this); + d_ptr->m_changeLanguageAction = new QAction(tr("Change Language"), this); + d_ptr->m_changeAliasAction = new QAction(tr("Change Alias"), this); + d_ptr->m_clonePrefixAction = new QAction(tr("Clone Prefix..."), this); + d_ptr->m_removeAction = new QAction(minusIcon, tr("Remove"), this); + d_ptr->m_moveUpAction = new QAction(upIcon, tr("Move Up"), this); + d_ptr->m_moveDownAction = new QAction(downIcon, tr("Move Down"), this); + + d_ptr->m_ui.newQrcButton->setDefaultAction(d_ptr->m_newQrcFileAction); + d_ptr->m_ui.importQrcButton->setDefaultAction(d_ptr->m_importQrcFileAction); + d_ptr->m_ui.removeQrcButton->setDefaultAction(d_ptr->m_removeQrcFileAction); + + d_ptr->m_ui.newResourceButton->setDefaultAction(d_ptr->m_newPrefixAction); + d_ptr->m_ui.addResourceButton->setDefaultAction(d_ptr->m_addResourceFileAction); + d_ptr->m_ui.removeResourceButton->setDefaultAction(d_ptr->m_removeAction); + + connect(d_ptr->m_newQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotNewQrcFile(); }); + connect(d_ptr->m_importQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotImportQrcFile(); }); + connect(d_ptr->m_removeQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotRemoveQrcFile(); }); + connect(d_ptr->m_moveUpQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotMoveUpQrcFile(); }); + connect(d_ptr->m_moveDownQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotMoveDownQrcFile(); }); + + connect(d_ptr->m_newPrefixAction, &QAction::triggered, + this, [this] { d_ptr->slotNewPrefix(); }); + connect(d_ptr->m_addResourceFileAction, &QAction::triggered, + this, [this] { d_ptr->slotAddFiles(); }); + connect(d_ptr->m_changePrefixAction, &QAction::triggered, + this, [this] { d_ptr->slotChangePrefix(); }); + connect(d_ptr->m_changeLanguageAction, &QAction::triggered, + this, [this] { d_ptr->slotChangeLanguage(); }); + connect(d_ptr->m_changeAliasAction, &QAction::triggered, + this, [this] { d_ptr->slotChangeAlias(); }); + connect(d_ptr->m_clonePrefixAction, &QAction::triggered, + this, [this] { d_ptr->slotClonePrefix(); }); + connect(d_ptr->m_removeAction, &QAction::triggered, + this, [this] { d_ptr->slotRemove(); }); + connect(d_ptr->m_moveUpAction, &QAction::triggered, + this, [this] { d_ptr->slotMoveUp(); }); + connect(d_ptr->m_moveDownAction, &QAction::triggered, + this, [this] { d_ptr->slotMoveDown(); }); + + d_ptr->m_ui.qrcFileList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(d_ptr->m_ui.qrcFileList, &QListWidget::customContextMenuRequested, + this, [this](const QPoint &point) { d_ptr->slotListWidgetContextMenuRequested(point); }); + connect(d_ptr->m_ui.qrcFileList, &QListWidget::currentItemChanged, + this, [this](QListWidgetItem *item) { d_ptr->slotCurrentQrcFileChanged(item); }); + + d_ptr->m_treeModel = new QStandardItemModel(this); + d_ptr->m_treeModel->setColumnCount(2); + d_ptr->m_treeModel->setHorizontalHeaderItem(0, new QStandardItem(tr("Prefix / Path"))); + d_ptr->m_treeModel->setHorizontalHeaderItem(1, new QStandardItem(tr("Language / Alias"))); + d_ptr->m_ui.resourceTreeView->setModel(d_ptr->m_treeModel); + d_ptr->m_ui.resourceTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + d_ptr->m_treeSelection = d_ptr->m_ui.resourceTreeView->selectionModel(); + connect(d_ptr->m_ui.resourceTreeView->header(), &QHeaderView::sectionDoubleClicked, + d_ptr->m_ui.resourceTreeView, &QTreeView::resizeColumnToContents); + d_ptr->m_ui.resourceTreeView->setTextElideMode(Qt::ElideLeft); + + connect(d_ptr->m_ui.resourceTreeView, &QTreeView::customContextMenuRequested, + this, [this](const QPoint &point) { d_ptr->slotTreeViewContextMenuRequested(point); }); + connect(d_ptr->m_treeModel, &QStandardItemModel::itemChanged, + this, [this](QStandardItem *item) { d_ptr->slotTreeViewItemChanged(item); }); + connect(d_ptr->m_treeSelection, &QItemSelectionModel::currentChanged, + this, [this](const QModelIndex &index) { d_ptr->slotCurrentTreeViewItemChanged(index); }); + + d_ptr->m_ui.resourceTreeView->setColumnWidth(0, 200); + + d_ptr->slotCurrentTreeViewItemChanged(QModelIndex()); + d_ptr->m_removeQrcFileAction->setEnabled(false); + d_ptr->m_moveUpQrcFileAction->setEnabled(false); + d_ptr->m_moveDownQrcFileAction->setEnabled(false); + + QDesignerSettingsInterface *settings = core->settingsManager(); + settings->beginGroup(QrcDialogC); + + d_ptr->m_ui.splitter->restoreState(settings->value(SplitterPosition).toByteArray()); + const QVariant geometry = settings->value(ResourceEditorGeometry); + if (geometry.metaType().id() == QMetaType::QByteArray) // Used to be a QRect up until 5.4.0, QTBUG-43374 + restoreGeometry(geometry.toByteArray()); + + settings->endGroup(); +} + +QtResourceEditorDialog::~QtResourceEditorDialog() +{ + QDesignerSettingsInterface *settings = d_ptr->m_core->settingsManager(); + settings->beginGroup(QrcDialogC); + + settings->setValue(SplitterPosition, d_ptr->m_ui.splitter->saveState()); + settings->setValue(ResourceEditorGeometry, saveGeometry()); + settings->endGroup(); + + disconnect(d_ptr->m_qrcManager, nullptr, this, nullptr); +} + +QtResourceModel *QtResourceEditorDialog::model() const +{ + return d_ptr->m_resourceModel; +} + +void QtResourceEditorDialog::setResourceModel(QtResourceModel *model) +{ + d_ptr->m_resourceModel = model; + + QtResourceSet *resourceSet = d_ptr->m_resourceModel->currentResourceSet(); + if (!resourceSet) { + // disable everything but cancel button + return; + } + + d_ptr->m_initialState.clear(); + + // enable qrcBox + + const QStringList paths = resourceSet->activeResourceFilePaths(); + for (const QString &path : paths) { + QtQrcFileData qrcFileData; + d_ptr->loadQrcFile(path, &qrcFileData); + d_ptr->m_initialState << qrcFileData; + d_ptr->m_qrcManager->importQrcFile(qrcFileData); + } + if (d_ptr->m_ui.qrcFileList->count() > 0) { + d_ptr->m_ui.qrcFileList->item(0)->setSelected(true); + } +} + +QString QtResourceEditorDialog::selectedResource() const +{ + //QtResourcePrefix *currentResourcePrefix = d_ptr->m_qrcManager->resourcePrefixOf(currentResourceFile); + QtResourcePrefix *currentResourcePrefix = d_ptr->getCurrentResourcePrefix(); + if (!currentResourcePrefix) + return QString(); + + const QChar slash(u'/'); + QString resource = currentResourcePrefix->prefix(); + if (!resource.startsWith(slash)) + resource.prepend(slash); + if (!resource.endsWith(slash)) + resource.append(slash); + resource.prepend(u':'); + + QtResourceFile *currentResourceFile = d_ptr->getCurrentResourceFile(); + if (!currentResourceFile) + return resource; + + QString resourceEnding = currentResourceFile->path(); + if (!currentResourceFile->alias().isEmpty()) + resourceEnding = currentResourceFile->alias(); + + const auto dotSlash = "./"_L1; + const auto dotDotSlash = "../"_L1; + while (true) { + if (resourceEnding.startsWith(slash)) + resourceEnding = resourceEnding.mid(1); + else if (resourceEnding.startsWith(dotSlash)) + resourceEnding = resourceEnding.mid(dotSlash.size()); + else if (resourceEnding.startsWith(dotDotSlash)) + resourceEnding = resourceEnding.mid(dotDotSlash.size()); + else + break; + } + + resource.append(resourceEnding); + + return resource; +} + +void QtResourceEditorDialog::displayResourceFailures(const QString &logOutput, QDesignerDialogGuiInterface *dlgGui, QWidget *parent) +{ + const QString msg = tr("

    Warning: There have been problems while reloading the resources:

    %1
    ").arg(logOutput); + dlgGui->message(parent, QDesignerDialogGuiInterface::ResourceEditorMessage, QMessageBox::Warning, + tr("Resource Warning"), msg); +} + +void QtResourceEditorDialog::accept() +{ + QStringList newQrcPaths; + QList currentState; + + const auto qrcFiles = d_ptr->m_qrcManager->qrcFiles(); + for (QtQrcFile *qrcFile : qrcFiles) { + QtQrcFileData qrcFileData; + d_ptr->m_qrcManager->exportQrcFile(qrcFile, &qrcFileData); + currentState << qrcFileData; + if (qrcFileData == qrcFile->initialState()) { + // nothing + } else { + d_ptr->m_resourceModel->setWatcherEnabled(qrcFileData.qrcPath, false); + bool ok = d_ptr->saveQrcFile(qrcFileData); + d_ptr->m_resourceModel->setWatcherEnabled(qrcFileData.qrcPath, true); + if (!ok) + return; + + d_ptr->m_resourceModel->setModified(qrcFileData.qrcPath); + } + newQrcPaths << qrcFileData.qrcPath; + } + + if (currentState == d_ptr->m_initialState) { + // nothing + } else { + int errorCount; + QString errorMessages; + d_ptr->m_resourceModel->currentResourceSet()->activateResourceFilePaths(newQrcPaths, &errorCount, &errorMessages); + if (errorCount) + displayResourceFailures(errorMessages, d_ptr->m_dlgGui, this); + } + QDialog::accept(); +} + +QString QtResourceEditorDialog::editResources(QDesignerFormEditorInterface *core, + QtResourceModel *model, + QDesignerDialogGuiInterface *dlgGui, + QWidget *parent) +{ + QtResourceEditorDialog dialog(core, dlgGui, parent); + dialog.setResourceModel(model); + if (dialog.exec() == QDialog::Accepted) + return dialog.selectedResource(); + return QString(); +} + +QT_END_NAMESPACE + +#include "moc_qtresourceeditordialog_p.cpp" +#include "qtresourceeditordialog.moc" diff --git a/src/designer/src/lib/shared/qtresourceeditordialog.ui b/src/designer/src/lib/shared/qtresourceeditordialog.ui new file mode 100644 index 0000000..fa760d9 --- /dev/null +++ b/src/designer/src/lib/shared/qtresourceeditordialog.ui @@ -0,0 +1,177 @@ + + QtResourceEditorDialog + + + + 0 + 0 + 469 + 317 + + + + Dialog + + + + + + Qt::Horizontal + + + false + + + + + + + + 0 + 0 + + + + + + + + New File + + + N + + + + + + + Remove File + + + R + + + + + + + Qt::Horizontal + + + QSizePolicy::Ignored + + + + 21 + 20 + + + + + + + + I + + + + + + + + + + + + + + New Resource + + + N + + + + + + + A + + + + + + + Remove Resource or File + + + R + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + QtResourceEditorDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QtResourceEditorDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/designer/src/lib/shared/qtresourceeditordialog_p.h b/src/designer/src/lib/shared/qtresourceeditordialog_p.h new file mode 100644 index 0000000..cc832a8 --- /dev/null +++ b/src/designer/src/lib/shared/qtresourceeditordialog_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTRESOURCEEDITOR_H +#define QTRESOURCEEDITOR_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QtResourceModel; +class QDesignerDialogGuiInterface; +class QDesignerFormEditorInterface; + +class QtResourceEditorDialog : public QDialog +{ + Q_OBJECT +public: + QtResourceModel *model() const; + void setResourceModel(QtResourceModel *model); + + QString selectedResource() const; + + static QString editResources(QDesignerFormEditorInterface *core, QtResourceModel *model, + QDesignerDialogGuiInterface *dlgGui, QWidget *parent = nullptr); + + // Helper to display a message box with rcc logs in case of errors. + static void displayResourceFailures(const QString &logOutput, QDesignerDialogGuiInterface *dlgGui, QWidget *parent = nullptr); + +public slots: + void accept() override; + +private: + QtResourceEditorDialog(QDesignerFormEditorInterface *core, QDesignerDialogGuiInterface *dlgGui, QWidget *parent = nullptr); + ~QtResourceEditorDialog() override; + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceEditorDialog) + Q_DISABLE_COPY_MOVE(QtResourceEditorDialog) +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/designer/src/lib/shared/qtresourcemodel.cpp b/src/designer/src/lib/shared/qtresourcemodel.cpp new file mode 100644 index 0000000..acee5cc --- /dev/null +++ b/src/designer/src/lib/shared/qtresourcemodel.cpp @@ -0,0 +1,573 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtresourcemodel_p.h" +#include "rcc_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +enum { debugResourceModel = 0 }; + +// ------------------- QtResourceSetPrivate +class QtResourceSetPrivate +{ + QtResourceSet *q_ptr; + Q_DECLARE_PUBLIC(QtResourceSet) +public: + QtResourceSetPrivate(QtResourceModel *model = nullptr); + + QtResourceModel *m_resourceModel; +}; + +QtResourceSetPrivate::QtResourceSetPrivate(QtResourceModel *model) : + q_ptr(nullptr), + m_resourceModel(model) +{ +} + +// -------------------- QtResourceModelPrivate +class QtResourceModelPrivate +{ + QtResourceModel *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtResourceModel) + Q_DISABLE_COPY_MOVE(QtResourceModelPrivate) +public: + QtResourceModelPrivate(); + void activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCount = nullptr, QString *errorMessages = nullptr); + void removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths); + + QMap m_pathToModified; + QHash m_resourceSetToPaths; + QHash m_resourceSetToReload; // while path is recreated it needs to be reregistered + // (it is - in the new current resource set, but when the path was used in + // other resource set + // then later when that resource set is activated it needs to be reregistered) + QHash m_newlyCreated; // all created but not activated yet + // (if was active at some point and it's not now it will not be on that map) + QMap> m_pathToResourceSet; + QtResourceSet *m_currentResourceSet = nullptr; + + QMap m_pathToData; + + QMap m_pathToContents; // qrc path to its contents. + QMap m_fileToQrc; // this map contains the content of active resource set only. + // Activating different resource set changes the contents. + + QFileSystemWatcher *m_fileWatcher = nullptr; + bool m_fileWatcherEnabled = true; + QMap m_fileWatchedMap; +private: + void registerResourceSet(QtResourceSet *resourceSet); + void unregisterResourceSet(QtResourceSet *resourceSet); + void setWatcherEnabled(const QString &path, bool enable); + void addWatcher(const QString &path); + void removeWatcher(const QString &path); + + void slotFileChanged(const QString &); + + const QByteArray *createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const; + void deleteResource(const QByteArray *data) const; +}; + +QtResourceModelPrivate::QtResourceModelPrivate() = default; + +// --------------------- QtResourceSet +QtResourceSet::QtResourceSet() : + d_ptr(new QtResourceSetPrivate) +{ + d_ptr->q_ptr = this; +} + +QtResourceSet::QtResourceSet(QtResourceModel *model) : + d_ptr(new QtResourceSetPrivate(model)) +{ + d_ptr->q_ptr = this; +} + +QtResourceSet::~QtResourceSet() = default; + +QStringList QtResourceSet::activeResourceFilePaths() const +{ + QtResourceSet *that = const_cast(this); + return d_ptr->m_resourceModel->d_ptr->m_resourceSetToPaths.value(that); +} + +void QtResourceSet::activateResourceFilePaths(const QStringList &paths, int *errorCount, QString *errorMessages) +{ + d_ptr->m_resourceModel->d_ptr->activate(this, paths, errorCount, errorMessages); +} + +bool QtResourceSet::isModified(const QString &path) const +{ + return d_ptr->m_resourceModel->isModified(path); +} + +void QtResourceSet::setModified(const QString &path) +{ + d_ptr->m_resourceModel->setModified(path); +} + +// ------------------- QtResourceModelPrivate +const QByteArray *QtResourceModelPrivate::createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const +{ + using ResourceDataFileMap = RCCResourceLibrary::ResourceDataFileMap; + const QByteArray *rc = nullptr; + *errorCount = -1; + contents->clear(); + do { + // run RCC + RCCResourceLibrary library(3); + library.setVerbose(true); + library.setInputFiles(QStringList(path)); + library.setFormat(RCCResourceLibrary::Binary); + + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + if (!library.readFiles(/* ignore errors*/ true, errorDevice)) + break; + // return code cannot be fully trusted, might still be empty + const ResourceDataFileMap resMap = library.resourceDataFileMap(); + if (!library.output(buffer, buffer /* tempfile, unused */, errorDevice)) + break; + + *errorCount = library.failedResources().size(); + *contents = resMap.keys(); + + if (resMap.isEmpty()) + break; + + buffer.close(); + rc = new QByteArray(buffer.data()); + } while (false); + + if (debugResourceModel) + qDebug() << "createResource" << path << "returns data=" << rc << " hasWarnings=" << *errorCount; + return rc; +} + +void QtResourceModelPrivate::deleteResource(const QByteArray *data) const +{ + if (data) { + if (debugResourceModel) + qDebug() << "deleteResource"; + delete data; + } +} + +void QtResourceModelPrivate::registerResourceSet(QtResourceSet *resourceSet) +{ + if (!resourceSet) + return; + + // unregister old paths (all because the order of registration is important), later it can be optimized a bit + const QStringList toRegister = resourceSet->activeResourceFilePaths(); + for (const QString &path : toRegister) { + if (debugResourceModel) + qDebug() << "registerResourceSet " << path; + const auto itRcc = m_pathToData.constFind(path); + if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet + const QByteArray *data = itRcc.value(); + if (data) { + if (!QResource::registerResource(reinterpret_cast(data->constData()))) { + qWarning() << "** WARNING: Failed to register " << path << " (QResource failure)."; + } else { + const QStringList contents = m_pathToContents.value(path); + for (const QString &filePath : contents) { + if (!m_fileToQrc.contains(filePath)) // the first loaded resource has higher priority in qt resource system + m_fileToQrc.insert(filePath, path); + } + } + } + } + } +} + +void QtResourceModelPrivate::unregisterResourceSet(QtResourceSet *resourceSet) +{ + if (!resourceSet) + return; + + // unregister old paths (all because the order of registration is importans), later it can be optimized a bit + const QStringList toUnregister = resourceSet->activeResourceFilePaths(); + for (const QString &path : toUnregister) { + if (debugResourceModel) + qDebug() << "unregisterResourceSet " << path; + const auto itRcc = m_pathToData.constFind(path); + if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet + const QByteArray *data = itRcc.value(); + if (data) { + if (!QResource::unregisterResource(reinterpret_cast(itRcc.value()->constData()))) + qWarning() << "** WARNING: Failed to unregister " << path << " (QResource failure)."; + } + } + } + m_fileToQrc.clear(); +} + +void QtResourceModelPrivate::activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCountPtr, QString *errorMessages) +{ + if (debugResourceModel) + qDebug() << "activate " << resourceSet; + if (errorCountPtr) + *errorCountPtr = 0; + if (errorMessages) + errorMessages->clear(); + + QBuffer errorStream; + errorStream.open(QIODevice::WriteOnly); + + int errorCount = 0; + int generatedCount = 0; + bool newResourceSetChanged = false; + + if (resourceSet && resourceSet->activeResourceFilePaths() != newPaths && !m_newlyCreated.contains(resourceSet)) + newResourceSetChanged = true; + + auto newPathToData = m_pathToData; + + for (const QString &path : newPaths) { + if (resourceSet && !m_pathToResourceSet[path].contains(resourceSet)) + m_pathToResourceSet[path].append(resourceSet); + const auto itMod = m_pathToModified.find(path); + if (itMod == m_pathToModified.end() || itMod.value()) { // new path or path is already created, but needs to be recreated + QStringList contents; + int qrcErrorCount; + generatedCount++; + const QByteArray *data = createResource(path, &contents, &qrcErrorCount, errorStream); + + newPathToData.insert(path, data); + if (qrcErrorCount) // Count single failed files as sort of 1/2 error + errorCount++; + addWatcher(path); + + m_pathToModified.insert(path, false); + m_pathToContents.insert(path, contents); + newResourceSetChanged = true; + const auto itReload = m_pathToResourceSet.find(path); + if (itReload != m_pathToResourceSet.end()) { + const auto resources = itReload.value(); + for (QtResourceSet *res : resources) { + if (res != resourceSet) { + m_resourceSetToReload[res] = true; + } + } + } + } else { // path is already created, don't need to recreate + } + } + + const auto oldData = m_pathToData.values(); + const auto newData = newPathToData.values(); + + QList toDelete; + for (const QByteArray *array : oldData) { + if (array && !newData.contains(array)) + toDelete.append(array); + } + + // Nothing can fail below here? + if (generatedCount) { + if (errorCountPtr) + *errorCountPtr = errorCount; + errorStream.close(); + const QString stderrOutput = QString::fromUtf8(errorStream.data()); + if (debugResourceModel) + qDebug() << "Output: (" << errorCount << ")\n" << stderrOutput; + if (errorMessages) + *errorMessages = stderrOutput; + } + // register + const auto itReload = m_resourceSetToReload.find(resourceSet); + if (itReload != m_resourceSetToReload.end()) { + if (itReload.value()) { + newResourceSetChanged = true; + m_resourceSetToReload.insert(resourceSet, false); + } + } + + QStringList oldActivePaths; + if (m_currentResourceSet) + oldActivePaths = m_currentResourceSet->activeResourceFilePaths(); + + const bool needReregister = (oldActivePaths != newPaths) || newResourceSetChanged; + + const auto itNew = m_newlyCreated.find(resourceSet); + if (itNew != m_newlyCreated.end()) { + m_newlyCreated.remove(resourceSet); + if (needReregister) + newResourceSetChanged = true; + } + + if (!newResourceSetChanged && !needReregister && (m_currentResourceSet == resourceSet)) { + for (const QByteArray *data : std::as_const(toDelete)) + deleteResource(data); + + return; // nothing changed + } + + if (needReregister) + unregisterResourceSet(m_currentResourceSet); + + for (const QByteArray *data : std::as_const(toDelete)) + deleteResource(data); + + m_pathToData = newPathToData; + m_currentResourceSet = resourceSet; + + if (resourceSet) + removeOldPaths(resourceSet, newPaths); + + if (needReregister) + registerResourceSet(m_currentResourceSet); + + emit q_ptr->resourceSetActivated(m_currentResourceSet, newResourceSetChanged); + + // deactivates the paths from old current resource set + // add new paths to the new current resource set + // reloads all paths which are marked as modified from the current resource set; + // activates the paths from current resource set + // emits resourceSetActivated() (don't emit only in case when old resource set is the same as new one + // AND no path was reloaded AND the list of paths is exactly the same) +} + +void QtResourceModelPrivate::removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths) +{ + const QStringList oldPaths = m_resourceSetToPaths.value(resourceSet); + if (oldPaths != newPaths) { + // remove old + for (const QString &oldPath : oldPaths) { + if (!newPaths.contains(oldPath)) { + const auto itRemove = m_pathToResourceSet.find(oldPath); + if (itRemove != m_pathToResourceSet.end()) { + const int idx = itRemove.value().indexOf(resourceSet); + if (idx >= 0) + itRemove.value().removeAt(idx); + if (itRemove.value().isEmpty()) { + const auto it = m_pathToData.find(oldPath); + if (it != m_pathToData.end()) + deleteResource(it.value()); + m_pathToResourceSet.erase(itRemove); + m_pathToModified.remove(oldPath); + m_pathToContents.remove(oldPath); + m_pathToData.remove(oldPath); + removeWatcher(oldPath); + } + } + } + } + m_resourceSetToPaths[resourceSet] = newPaths; + } +} + +void QtResourceModelPrivate::setWatcherEnabled(const QString &path, bool enable) +{ + if (!enable) { + m_fileWatcher->removePath(path); + return; + } + + QFileInfo fi(path); + if (fi.exists()) + m_fileWatcher->addPath(path); +} + +void QtResourceModelPrivate::addWatcher(const QString &path) +{ + const auto it = m_fileWatchedMap.constFind(path); + if (it != m_fileWatchedMap.constEnd() && !it.value()) + return; + + m_fileWatchedMap.insert(path, true); + if (!m_fileWatcherEnabled) + return; + setWatcherEnabled(path, true); +} + +void QtResourceModelPrivate::removeWatcher(const QString &path) +{ + if (!m_fileWatchedMap.contains(path)) + return; + + m_fileWatchedMap.remove(path); + if (!m_fileWatcherEnabled) + return; + setWatcherEnabled(path, false); +} + +void QtResourceModelPrivate::slotFileChanged(const QString &path) +{ + setWatcherEnabled(path, false); + emit q_ptr->qrcFileModifiedExternally(path); + setWatcherEnabled(path, true); //readd +} + +// ----------------------- QtResourceModel +QtResourceModel::QtResourceModel(QObject *parent) : + QObject(parent), + d_ptr(new QtResourceModelPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_fileWatcher = new QFileSystemWatcher(this); + connect(d_ptr->m_fileWatcher, &QFileSystemWatcher::fileChanged, + this, [this](const QString &fileName) { d_ptr->slotFileChanged(fileName); }); +} + +QtResourceModel::~QtResourceModel() +{ + blockSignals(true); + const auto resourceList = resourceSets(); + for (QtResourceSet *rs : resourceList) + removeResourceSet(rs); + blockSignals(false); +} + +QStringList QtResourceModel::loadedQrcFiles() const +{ + return d_ptr->m_pathToModified.keys(); +} + +bool QtResourceModel::isModified(const QString &path) const +{ + return d_ptr->m_pathToModified.value(path, true); +} + +void QtResourceModel::setModified(const QString &path) +{ + if (!d_ptr->m_pathToModified.contains(path)) + return; + + d_ptr->m_pathToModified[path] = true; + const auto it = d_ptr->m_pathToResourceSet.constFind(path); + if (it == d_ptr->m_pathToResourceSet.constEnd()) + return; + + const auto resourceList = it.value(); + for (QtResourceSet *rs : resourceList) + d_ptr->m_resourceSetToReload.insert(rs, true); +} + +QList QtResourceModel::resourceSets() const +{ + return d_ptr->m_resourceSetToPaths.keys(); +} + +QtResourceSet *QtResourceModel::currentResourceSet() const +{ + return d_ptr->m_currentResourceSet; +} + +void QtResourceModel::setCurrentResourceSet(QtResourceSet *resourceSet, int *errorCount, QString *errorMessages) +{ + d_ptr->activate(resourceSet, d_ptr->m_resourceSetToPaths.value(resourceSet), errorCount, errorMessages); +} + +QtResourceSet *QtResourceModel::addResourceSet(const QStringList &paths) +{ + QtResourceSet *newResource = new QtResourceSet(this); + d_ptr->m_resourceSetToPaths.insert(newResource, paths); + d_ptr->m_resourceSetToReload.insert(newResource, false); + d_ptr->m_newlyCreated.insert(newResource, true); + for (const QString &path : paths) + d_ptr->m_pathToResourceSet[path].append(newResource); + return newResource; +} + +// TODO +void QtResourceModel::removeResourceSet(QtResourceSet *resourceSet) +{ + if (!resourceSet) + return; + if (currentResourceSet() == resourceSet) + setCurrentResourceSet(nullptr); + + // remove rcc files for those paths which are not used in any other resource set + d_ptr->removeOldPaths(resourceSet, QStringList()); + + d_ptr->m_resourceSetToPaths.remove(resourceSet); + d_ptr->m_resourceSetToReload.remove(resourceSet); + d_ptr->m_newlyCreated.remove(resourceSet); + delete resourceSet; +} + +void QtResourceModel::reload(const QString &path, int *errorCount, QString *errorMessages) +{ + setModified(path); + + d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages); +} + +void QtResourceModel::reload(int *errorCount, QString *errorMessages) +{ + for (auto it = d_ptr->m_pathToModified.begin(), end = d_ptr->m_pathToModified.end(); it != end; ++it) + it.value() = true; + + // empty resourceSets could be omitted here + for (auto itReload = d_ptr->m_resourceSetToReload.begin(), end = d_ptr->m_resourceSetToReload.end(); itReload != end; ++itReload) + itReload.value() = true; + + d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages); +} + +QMap QtResourceModel::contents() const +{ + return d_ptr->m_fileToQrc; +} + +QString QtResourceModel::qrcPath(const QString &file) const +{ + return d_ptr->m_fileToQrc.value(file); +} + +void QtResourceModel::setWatcherEnabled(bool enable) +{ + if (d_ptr->m_fileWatcherEnabled == enable) + return; + + d_ptr->m_fileWatcherEnabled = enable; + + if (!d_ptr->m_fileWatchedMap.isEmpty()) + d_ptr->setWatcherEnabled(d_ptr->m_fileWatchedMap.firstKey(), d_ptr->m_fileWatcherEnabled); +} + +bool QtResourceModel::isWatcherEnabled() const +{ + return d_ptr->m_fileWatcherEnabled; +} + +void QtResourceModel::setWatcherEnabled(const QString &path, bool enable) +{ + const auto it = d_ptr->m_fileWatchedMap.find(path); + if (it == d_ptr->m_fileWatchedMap.end()) + return; + + if (it.value() == enable) + return; + + it.value() = enable; + + if (!d_ptr->m_fileWatcherEnabled) + return; + + d_ptr->setWatcherEnabled(it.key(), enable); +} + +bool QtResourceModel::isWatcherEnabled(const QString &path) +{ + return d_ptr->m_fileWatchedMap.value(path, false); +} + +QT_END_NAMESPACE + +#include "moc_qtresourcemodel_p.cpp" diff --git a/src/designer/src/lib/shared/qtresourcemodel_p.h b/src/designer/src/lib/shared/qtresourcemodel_p.h new file mode 100644 index 0000000..43afe5c --- /dev/null +++ b/src/designer/src/lib/shared/qtresourcemodel_p.h @@ -0,0 +1,105 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTRESOURCEMODEL_H +#define QTRESOURCEMODEL_H + +#include "shared_global_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtResourceModel; + +class QDESIGNER_SHARED_EXPORT QtResourceSet // one instance per one form +{ +public: + QStringList activeResourceFilePaths() const; + + // activateQrcPaths(): if this QtResourceSet is active it emits resourceSetActivated(); + // otherwise only in case if active QtResource set contains one of + // paths which was marked as modified by this resource set, the signal + // is emitted (with reload = true); + // If new path appears on the list it is automatically added to + // loaded list of QtResourceModel. In addition it is marked as modified in case + // QtResourceModel didn't contain the path. + // If some path is removed from that list (and is not used in any other + // resource set) it is automatically unloaded. The removed file can also be + // marked as modified (later when another resource set which contains + // removed path is activated will be reloaded) + void activateResourceFilePaths(const QStringList &paths, int *errorCount = nullptr, QString *errorMessages = nullptr); + + bool isModified(const QString &path) const; // for all paths in resource model (redundant here, maybe it should be removed from here) + void setModified(const QString &path); // for all paths in resource model (redundant here, maybe it should be removed from here) +private: + QtResourceSet(); + QtResourceSet(QtResourceModel *model); + ~QtResourceSet(); + friend class QtResourceModel; + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceSet) + Q_DISABLE_COPY_MOVE(QtResourceSet) +}; + +class QDESIGNER_SHARED_EXPORT QtResourceModel : public QObject // one instance per whole designer +{ + Q_OBJECT +public: + QtResourceModel(QObject *parent = nullptr); + ~QtResourceModel(); + + QStringList loadedQrcFiles() const; + bool isModified(const QString &path) const; // only for paths which are on loadedQrcFiles() list + void setModified(const QString &path); // only for paths which are on loadedQrcPaths() list + + QList resourceSets() const; + + QtResourceSet *currentResourceSet() const; + void setCurrentResourceSet(QtResourceSet *resourceSet, int *errorCount = nullptr, QString *errorMessages = nullptr); + + QtResourceSet *addResourceSet(const QStringList &paths); + void removeResourceSet(QtResourceSet *resourceSet); + + void reload(const QString &path, int *errorCount = nullptr, QString *errorMessages = nullptr); + void reload(int *errorCount = nullptr, QString *errorMessages = nullptr); + + // Contents of the current resource set (content file to qrc path) + QMap contents() const; + // Find the qrc file belonging to the contained file (from current resource set) + QString qrcPath(const QString &file) const; + + void setWatcherEnabled(bool enable); + bool isWatcherEnabled() const; + + void setWatcherEnabled(const QString &path, bool enable); + bool isWatcherEnabled(const QString &path); + +signals: + void resourceSetActivated(QtResourceSet *resourceSet, bool resourceSetChanged); // resourceSetChanged since last time it was activated! + void qrcFileModifiedExternally(const QString &path); + +private: + friend class QtResourceSet; + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceModel) + Q_DISABLE_COPY_MOVE(QtResourceModel) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/lib/shared/qtresourceview.cpp b/src/designer/src/lib/shared/qtresourceview.cpp new file mode 100644 index 0000000..5663b0d --- /dev/null +++ b/src/designer/src/lib/shared/qtresourceview.cpp @@ -0,0 +1,868 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtresourceview_p.h" +#include "qtresourcemodel_p.h" +#include "qtresourceeditordialog_p.h" +#include "iconloader_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +# include +#endif +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto elementResourceData = "resource"_L1; +static constexpr auto typeAttribute = "type"_L1; +static constexpr auto typeImage = "image"_L1; +static constexpr auto typeStyleSheet = "stylesheet"_L1; +static constexpr auto typeOther = "other"_L1; +static constexpr auto fileAttribute = "file"_L1; +static constexpr auto qrvSplitterPosition = "SplitterPosition"_L1; +static constexpr auto qrvGeometry = "Geometry"_L1; +static constexpr auto ResourceViewDialogC = "ResourceDialog"_L1; + +// ---------------- ResourceListWidget: A list widget that has drag enabled +class ResourceListWidget : public QListWidget { +public: + ResourceListWidget(QWidget *parent = nullptr); + +protected: + void startDrag(Qt::DropActions supportedActions) override; +}; + +ResourceListWidget::ResourceListWidget(QWidget *parent) : + QListWidget(parent) +{ + setDragEnabled(true); +} + +void ResourceListWidget::startDrag(Qt::DropActions supportedActions) +{ + if (supportedActions == Qt::MoveAction) + return; + + QListWidgetItem *item = currentItem(); + if (!item) + return; + + const QString filePath = item->data(Qt::UserRole).toString(); + const QIcon icon = item->icon(); + + QMimeData *mimeData = new QMimeData; + const QtResourceView::ResourceType type = icon.isNull() ? QtResourceView::ResourceOther : QtResourceView::ResourceImage; + mimeData->setText(QtResourceView::encodeMimeData(type , filePath)); + + QDrag *drag = new QDrag(this); + if (!icon.isNull()) { + const QSize size = icon.actualSize(iconSize()); + drag->setPixmap(icon.pixmap(size)); + drag->setHotSpot(QPoint(size.width() / 2, size.height() / 2)); + } + + drag->setMimeData(mimeData); + drag->exec(Qt::CopyAction); +} + +/* TODO + + 1) load the icons in separate thread...Hmm..if Qt is configured with threads.... +*/ + +// ---------------------------- QtResourceViewPrivate +class QtResourceViewPrivate +{ + QtResourceView *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtResourceView) +public: + QtResourceViewPrivate(QDesignerFormEditorInterface *core); + + void slotResourceSetActivated(QtResourceSet *resourceSet); + void slotCurrentPathChanged(QTreeWidgetItem *); + void slotCurrentResourceChanged(QListWidgetItem *); + void slotResourceActivated(QListWidgetItem *); + void slotEditResources(); + void slotReloadResources(); +#if QT_CONFIG(clipboard) + void slotCopyResourcePath(); +#endif + void slotListWidgetContextMenuRequested(const QPoint &pos); + void slotFilterChanged(const QString &pattern); + void createPaths(); + QTreeWidgetItem *createPath(const QString &path, QTreeWidgetItem *parent); + void createResources(const QString &path); + void storeExpansionState(); + void applyExpansionState(); + void restoreSettings(); + void saveSettings(); + void updateActions(); + void filterOutResources(); + + QPixmap makeThumbnail(const QPixmap &pix) const; + + QDesignerFormEditorInterface *m_core; + QtResourceModel *m_resourceModel = nullptr; + QToolBar *m_toolBar; + QWidget *m_filterWidget = nullptr; + QTreeWidget *m_treeWidget; + QListWidget *m_listWidget; + QSplitter *m_splitter = nullptr; + QMap m_pathToContents; // full path to contents file names (full path to its resource filenames) + QMap m_pathToParentPath; // full path to full parent path + QMap m_pathToSubPaths; // full path to full sub paths + QMap m_pathToItem; + QHash m_itemToPath; + QMap m_resourceToItem; + QHash m_itemToResource; + QAction *m_editResourcesAction = nullptr; + QAction *m_reloadResourcesAction = nullptr; + QAction *m_copyResourcePathAction = nullptr; + + QMap m_expansionState; + + QString m_settingsKey; + QString m_filterPattern; + bool m_ignoreGuiSignals = false; + bool m_resourceEditingEnabled = true; +}; + +QtResourceViewPrivate::QtResourceViewPrivate(QDesignerFormEditorInterface *core) : + m_core(core), + m_toolBar(new QToolBar), + m_treeWidget(new QTreeWidget), + m_listWidget(new ResourceListWidget) +{ + m_toolBar->setIconSize(QSize(22, 22)); +} + +void QtResourceViewPrivate::restoreSettings() +{ + if (m_settingsKey.isEmpty()) + return; + + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(m_settingsKey); + + m_splitter->restoreState(settings->value(qrvSplitterPosition).toByteArray()); + settings->endGroup(); +} + +void QtResourceViewPrivate::saveSettings() +{ + if (m_settingsKey.isEmpty()) + return; + + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(m_settingsKey); + + settings->setValue(qrvSplitterPosition, m_splitter->saveState()); + settings->endGroup(); +} + +void QtResourceViewPrivate::slotEditResources() +{ + const QString selectedResource + = QtResourceEditorDialog::editResources(m_core, m_resourceModel, + m_core->dialogGui(), q_ptr); + if (!selectedResource.isEmpty()) + q_ptr->selectResource(selectedResource); +} + +void QtResourceViewPrivate::slotReloadResources() +{ + if (m_resourceModel) { + int errorCount; + QString errorMessages; + m_resourceModel->reload(&errorCount, &errorMessages); + if (errorCount) + QtResourceEditorDialog::displayResourceFailures(errorMessages, m_core->dialogGui(), q_ptr); + } +} + +#if QT_CONFIG(clipboard) +void QtResourceViewPrivate::slotCopyResourcePath() +{ + const QString path = q_ptr->selectedResource(); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(path); +} +#endif + +void QtResourceViewPrivate::slotListWidgetContextMenuRequested(const QPoint &pos) +{ + QMenu menu(q_ptr); + menu.addAction(m_copyResourcePathAction); + menu.exec(m_listWidget->mapToGlobal(pos)); +} + +void QtResourceViewPrivate::slotFilterChanged(const QString &pattern) +{ + m_filterPattern = pattern; + filterOutResources(); +} + +void QtResourceViewPrivate::storeExpansionState() +{ + for (auto it = m_pathToItem.cbegin(), end = m_pathToItem.cend(); it != end; ++it) + m_expansionState.insert(it.key(), it.value()->isExpanded()); +} + +void QtResourceViewPrivate::applyExpansionState() +{ + for (auto it = m_pathToItem.cbegin(), end = m_pathToItem.cend(); it != end; ++it) + it.value()->setExpanded(m_expansionState.value(it.key(), true)); +} + +QPixmap QtResourceViewPrivate::makeThumbnail(const QPixmap &pix) const +{ + int w = qMax(48, pix.width()); + int h = qMax(48, pix.height()); + QRect imgRect(0, 0, w, h); + QImage img(w, h, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + if (!pix.isNull()) { + QRect r(0, 0, pix.width(), pix.height()); + r.moveCenter(imgRect.center()); + QPainter p(&img); + p.drawPixmap(r.topLeft(), pix); + } + return QPixmap::fromImage(img); +} + +void QtResourceViewPrivate::updateActions() +{ + bool resourceActive = false; + if (m_resourceModel) + resourceActive = m_resourceModel->currentResourceSet(); + + m_editResourcesAction->setVisible(m_resourceEditingEnabled); + m_editResourcesAction->setEnabled(resourceActive); + m_reloadResourcesAction->setEnabled(resourceActive); + m_filterWidget->setEnabled(resourceActive); +} + +void QtResourceViewPrivate::slotResourceSetActivated(QtResourceSet *resourceSet) +{ + Q_UNUSED(resourceSet); + + updateActions(); + + storeExpansionState(); + const QString currentPath = m_itemToPath.value(m_treeWidget->currentItem()); + const QString currentResource = m_itemToResource.value(m_listWidget->currentItem()); + m_treeWidget->clear(); + m_pathToContents.clear(); + m_pathToParentPath.clear(); + m_pathToSubPaths.clear(); + m_pathToItem.clear(); + m_itemToPath.clear(); + m_listWidget->clear(); + m_resourceToItem.clear(); + m_itemToResource.clear(); + + createPaths(); + applyExpansionState(); + + if (!currentResource.isEmpty()) + q_ptr->selectResource(currentResource); + else if (!currentPath.isEmpty()) + q_ptr->selectResource(currentPath); + filterOutResources(); +} + +void QtResourceViewPrivate::slotCurrentPathChanged(QTreeWidgetItem *item) +{ + if (m_ignoreGuiSignals) + return; + + m_listWidget->clear(); + m_resourceToItem.clear(); + m_itemToResource.clear(); + + if (!item) + return; + + const QString currentPath = m_itemToPath.value(item); + createResources(currentPath); +} + +void QtResourceViewPrivate::slotCurrentResourceChanged(QListWidgetItem *item) +{ + m_copyResourcePathAction->setEnabled(item); + if (m_ignoreGuiSignals) + return; + + emit q_ptr->resourceSelected(m_itemToResource.value(item)); +} + +void QtResourceViewPrivate::slotResourceActivated(QListWidgetItem *item) +{ + if (m_ignoreGuiSignals) + return; + + emit q_ptr->resourceActivated(m_itemToResource.value(item)); +} + +void QtResourceViewPrivate::createPaths() +{ + if (!m_resourceModel) + return; + + // Resource root up until 4.6 was ':', changed to ":/" as of 4.7 + const QString root(u":/"_s); + + QMap contents = m_resourceModel->contents(); + for (auto it = contents.cbegin(), end = contents.cend(); it != end; ++it) { + const QFileInfo fi(it.key()); + QString dirPath = fi.absolutePath(); + m_pathToContents[dirPath].append(fi.fileName()); + while (!m_pathToParentPath.contains(dirPath) && dirPath != root) { // create all parent paths + const QFileInfo fd(dirPath); + const QString parentDirPath = fd.absolutePath(); + m_pathToParentPath[dirPath] = parentDirPath; + m_pathToSubPaths[parentDirPath].append(dirPath); + dirPath = parentDirPath; + } + } + + QQueue> pathToParentItemQueue; + pathToParentItemQueue.enqueue(std::make_pair(root, static_cast(nullptr))); + while (!pathToParentItemQueue.isEmpty()) { + std::pair pathToParentItem = pathToParentItemQueue.dequeue(); + const QString path = pathToParentItem.first; + QTreeWidgetItem *item = createPath(path, pathToParentItem.second); + const QStringList subPaths = m_pathToSubPaths.value(path); + for (const QString &subPath : subPaths) + pathToParentItemQueue.enqueue(std::make_pair(subPath, item)); + } +} + +void QtResourceViewPrivate::filterOutResources() +{ + QMap pathToMatchingContents; // true means the path has any matching contents + QMap pathToVisible; // true means the path has to be shown + + // 1) we go from root path recursively. + // 2) we check every path if it contains at least one matching resource - if empty we add it + // to pathToMatchingContents and pathToVisible with false, if non empty + // we add it with true and change every parent path in pathToVisible to true. + // 3) we hide these items which has pathToVisible value false. + + const bool matchAll = m_filterPattern.isEmpty(); + const QString root(u":/"_s); + + QQueue pathQueue; + pathQueue.enqueue(root); + while (!pathQueue.isEmpty()) { + const QString path = pathQueue.dequeue(); + + bool hasContents = matchAll; + if (!matchAll) { // the case filter is not empty - we check if the path contains anything + // the path contains at least one resource which matches the filter + const QStringList fileNames = m_pathToContents.value(path); + hasContents = + std::any_of(fileNames.cbegin(), fileNames.cend(), + [this] (const QString &f) { return f.contains(this->m_filterPattern, Qt::CaseInsensitive); }); + } + + pathToMatchingContents[path] = hasContents; + pathToVisible[path] = hasContents; + + if (hasContents) { // if the path is going to be shown we need to show all its parent paths + QString parentPath = m_pathToParentPath.value(path); + while (!parentPath.isEmpty()) { + QString p = parentPath; + if (pathToVisible.value(p)) // parent path is already shown, we break the loop + break; + pathToVisible[p] = true; + parentPath = m_pathToParentPath.value(p); + } + } + + const QStringList subPaths = m_pathToSubPaths.value(path); // we do the same for children paths + for (const QString &subPath : subPaths) + pathQueue.enqueue(subPath); + } + + // we setup here new path and resource to be activated + const QString currentPath = m_itemToPath.value(m_treeWidget->currentItem()); + QString newCurrentPath = currentPath; + QString currentResource = m_itemToResource.value(m_listWidget->currentItem()); + if (!matchAll) { + bool searchForNewPathWithContents = true; + + if (!currentPath.isEmpty()) { // if the currentPath is empty we will search for a new path too + const auto it = pathToMatchingContents.constFind(currentPath); + if (it != pathToMatchingContents.constEnd() && it.value()) // the current item has contents, we don't need to search for another path + searchForNewPathWithContents = false; + } + + if (searchForNewPathWithContents) { + // we find the first path with the matching contents + for (auto itContents = pathToMatchingContents.cbegin(), cend = pathToMatchingContents.cend(); itContents != cend; ++itContents) { + if (itContents.value()) { + newCurrentPath = itContents.key(); // the new path will be activated + break; + } + } + } + + QFileInfo fi(currentResource); + if (!fi.fileName().contains(m_filterPattern, Qt::CaseInsensitive)) { // the case when the current resource is filtered out + const QStringList fileNames = m_pathToContents.value(newCurrentPath); + // we try to select the first matching resource from the newCurrentPath + for (const QString &fileName : fileNames) { + if (fileName.contains(m_filterPattern, Qt::CaseInsensitive)) { + QDir dirPath(newCurrentPath); + currentResource = dirPath.absoluteFilePath(fileName); // the new resource inside newCurrentPath will be activated + break; + } + } + } + } + + QTreeWidgetItem *newCurrentItem = m_pathToItem.value(newCurrentPath); + if (currentPath != newCurrentPath) + m_treeWidget->setCurrentItem(newCurrentItem); + else + slotCurrentPathChanged(newCurrentItem); // trigger filtering on the current path + + QListWidgetItem *currentResourceItem = m_resourceToItem.value(currentResource); + if (currentResourceItem) { + m_listWidget->setCurrentItem(currentResourceItem); + m_listWidget->scrollToItem(currentResourceItem); + } + + // hide all paths filtered out + for (auto it = pathToVisible.cbegin(), end = pathToVisible.cend(); it != end; ++it) { + if (QTreeWidgetItem *item = m_pathToItem.value(it.key())) + item->setHidden(!it.value()); + } +} + +QTreeWidgetItem *QtResourceViewPrivate::createPath(const QString &path, QTreeWidgetItem *parent) +{ + QTreeWidgetItem *item = nullptr; + if (parent) + item = new QTreeWidgetItem(parent); + else + item = new QTreeWidgetItem(m_treeWidget); + m_pathToItem[path] = item; + m_itemToPath[item] = path; + QString substPath; + if (parent) { + QFileInfo di(path); + substPath = di.fileName(); + } else { + substPath = u""_s; + } + item->setText(0, substPath); + item->setToolTip(0, path); + return item; +} + +void QtResourceViewPrivate::createResources(const QString &path) +{ + const bool matchAll = m_filterPattern.isEmpty(); + + QDir dir(path); + const QStringList fileNames = m_pathToContents.value(path); + for (const QString &fileName : fileNames) { + const bool showProperty = matchAll || fileName.contains(m_filterPattern, Qt::CaseInsensitive); + if (showProperty) { + QString filePath = dir.absoluteFilePath(fileName); + QFileInfo fi(filePath); + if (fi.isFile()) { + QListWidgetItem *item = new QListWidgetItem(fi.fileName(), m_listWidget); + const QPixmap pix = QPixmap(filePath); + if (pix.isNull()) { + item->setToolTip(filePath); + } else { + item->setIcon(QIcon(makeThumbnail(pix))); + const QSize size = pix.size(); + item->setToolTip(QtResourceView::tr("Size: %1 x %2\n%3").arg(size.width()).arg(size.height()).arg(filePath)); + } + item->setFlags(item->flags() | Qt::ItemIsDragEnabled); + item->setData(Qt::UserRole, filePath); + m_itemToResource[item] = filePath; + m_resourceToItem[filePath] = item; + } + } + } +} + +// -------------- QtResourceView + +QtResourceView::QtResourceView(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + d_ptr(new QtResourceViewPrivate(core)) +{ + d_ptr->q_ptr = this; + + QIcon editIcon = qdesigner_internal::createIconSet(QIcon::ThemeIcon::DocumentProperties, + "edit.png"_L1); + d_ptr->m_editResourcesAction = new QAction(editIcon, tr("Edit Resources..."), this); + d_ptr->m_toolBar->addAction(d_ptr->m_editResourcesAction); + connect(d_ptr->m_editResourcesAction, &QAction::triggered, + this, [this] { d_ptr->slotEditResources(); }); + d_ptr->m_editResourcesAction->setEnabled(false); + + QIcon refreshIcon = qdesigner_internal::createIconSet(QIcon::ThemeIcon::ViewRefresh, + "reload.png"_L1); + d_ptr->m_reloadResourcesAction = new QAction(refreshIcon, tr("Reload"), this); + + d_ptr->m_toolBar->addAction(d_ptr->m_reloadResourcesAction); + connect(d_ptr->m_reloadResourcesAction, &QAction::triggered, + this, [this] { d_ptr->slotReloadResources(); }); + d_ptr->m_reloadResourcesAction->setEnabled(false); + +#if QT_CONFIG(clipboard) + QIcon copyIcon = qdesigner_internal::createIconSet(QIcon::ThemeIcon::EditCopy, + "editcopy.png"_L1); + d_ptr->m_copyResourcePathAction = new QAction(copyIcon, tr("Copy Path"), this); + connect(d_ptr->m_copyResourcePathAction, &QAction::triggered, + this, [this] { d_ptr->slotCopyResourcePath(); }); + d_ptr->m_copyResourcePathAction->setEnabled(false); +#endif + + d_ptr->m_filterWidget = new QWidget(d_ptr->m_toolBar); + QHBoxLayout *filterLayout = new QHBoxLayout(d_ptr->m_filterWidget); + filterLayout->setContentsMargins(0, 0, 0, 0); + QLineEdit *filterLineEdit = new QLineEdit(d_ptr->m_filterWidget); + connect(filterLineEdit, &QLineEdit::textChanged, + this, [this](const QString &text) { d_ptr->slotFilterChanged(text); }); + filterLineEdit->setPlaceholderText(tr("Filter")); + filterLineEdit->setClearButtonEnabled(true); + filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored)); + filterLayout->addWidget(filterLineEdit); + d_ptr->m_toolBar->addWidget(d_ptr->m_filterWidget); + + d_ptr->m_splitter = new QSplitter; + d_ptr->m_splitter->setChildrenCollapsible(false); + d_ptr->m_splitter->addWidget(d_ptr->m_treeWidget); + d_ptr->m_splitter->addWidget(d_ptr->m_listWidget); + + QLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + layout->addWidget(d_ptr->m_toolBar); + layout->addWidget(d_ptr->m_splitter); + + d_ptr->m_treeWidget->setColumnCount(1); + d_ptr->m_treeWidget->header()->hide(); + d_ptr->m_treeWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); + + d_ptr->m_listWidget->setViewMode(QListView::IconMode); + d_ptr->m_listWidget->setResizeMode(QListView::Adjust); + d_ptr->m_listWidget->setIconSize(QSize(48, 48)); + d_ptr->m_listWidget->setGridSize(QSize(64, 64)); + + connect(d_ptr->m_treeWidget, &QTreeWidget::currentItemChanged, + this, [this](QTreeWidgetItem *item) { d_ptr->slotCurrentPathChanged(item); }); + connect(d_ptr->m_listWidget, &QListWidget::currentItemChanged, + this, [this](QListWidgetItem *item) { d_ptr->slotCurrentResourceChanged(item); }); + connect(d_ptr->m_listWidget, &QListWidget::itemActivated, + this, [this](QListWidgetItem *item) { d_ptr->slotResourceActivated(item); }); + d_ptr->m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(d_ptr->m_listWidget, &QListWidget::customContextMenuRequested, + this, [this](const QPoint &point) { d_ptr->slotListWidgetContextMenuRequested(point); }); +} + +QtResourceView::~QtResourceView() +{ + if (!d_ptr->m_settingsKey.isEmpty()) + d_ptr->saveSettings(); +} + +bool QtResourceView::event(QEvent *event) +{ + if (event->type() == QEvent::Show) { + d_ptr->m_listWidget->scrollToItem(d_ptr->m_listWidget->currentItem()); + d_ptr->m_treeWidget->scrollToItem(d_ptr->m_treeWidget->currentItem()); + } + return QWidget::event(event); +} + +QtResourceModel *QtResourceView::model() const +{ + return d_ptr->m_resourceModel; +} + +QString QtResourceView::selectedResource() const +{ + QListWidgetItem *item = d_ptr->m_listWidget->currentItem(); + return d_ptr->m_itemToResource.value(item); +} + +void QtResourceView::selectResource(const QString &resource) +{ + if (resource.isEmpty()) + return; + QFileInfo fi(resource); + QDir dir = fi.absoluteDir(); + if (fi.isDir()) + dir = QDir(resource); + QString dirPath = dir.absolutePath(); + const auto cend = d_ptr->m_pathToItem.constEnd(); + auto it = cend; + while ((it = d_ptr->m_pathToItem.constFind(dirPath)) == cend) { + if (!dir.cdUp()) + break; + dirPath = dir.absolutePath(); + } + if (it != cend) { + QTreeWidgetItem *treeItem = it.value(); + d_ptr->m_treeWidget->setCurrentItem(treeItem); + d_ptr->m_treeWidget->scrollToItem(treeItem); + // expand all up to current one is done by qt + // list widget is already propagated (currrent changed was sent by qt) + QListWidgetItem *item = d_ptr->m_resourceToItem.value(resource); + if (item) { + d_ptr->m_listWidget->setCurrentItem(item); + d_ptr->m_listWidget->scrollToItem(item); + } + } +} + +QString QtResourceView::settingsKey() const +{ + return d_ptr->m_settingsKey; +} + +void QtResourceView::setSettingsKey(const QString &key) +{ + if (d_ptr->m_settingsKey == key) + return; + + d_ptr->m_settingsKey = key; + + if (key.isEmpty()) + return; + + d_ptr->restoreSettings(); +} + +void QtResourceView::setResourceModel(QtResourceModel *model) +{ + if (d_ptr->m_resourceModel) + disconnect(d_ptr->m_resourceModel, &QtResourceModel::resourceSetActivated, this, nullptr); + + // clear here + d_ptr->m_treeWidget->clear(); + d_ptr->m_listWidget->clear(); + + d_ptr->m_resourceModel = model; + + if (!d_ptr->m_resourceModel) + return; + + connect(d_ptr->m_resourceModel, &QtResourceModel::resourceSetActivated, + this, [this](QtResourceSet *resource) { d_ptr->slotResourceSetActivated(resource); }); + + // fill new here + d_ptr->slotResourceSetActivated(d_ptr->m_resourceModel->currentResourceSet()); +} + +bool QtResourceView::isResourceEditingEnabled() const +{ + return d_ptr->m_resourceEditingEnabled; +} + +void QtResourceView::setResourceEditingEnabled(bool enable) +{ + d_ptr->m_resourceEditingEnabled = enable; + d_ptr->updateActions(); +} + +void QtResourceView::setDragEnabled(bool dragEnabled) +{ + d_ptr->m_listWidget->setDragEnabled(dragEnabled); +} + +bool QtResourceView::dragEnabled() const +{ + return d_ptr->m_listWidget->dragEnabled(); +} + +QString QtResourceView::encodeMimeData(ResourceType resourceType, const QString &path) +{ + QDomDocument doc; + QDomElement elem = doc.createElement(elementResourceData); + switch (resourceType) { + case ResourceImage: + elem.setAttribute(typeAttribute, typeImage); + break; + case ResourceStyleSheet: + elem.setAttribute(typeAttribute, typeStyleSheet); + break; + case ResourceOther: + elem.setAttribute(typeAttribute, typeOther); + break; + } + elem.setAttribute(fileAttribute, path); + doc.appendChild(elem); + return doc.toString(); +} + +bool QtResourceView::decodeMimeData(const QMimeData *md, ResourceType *t, QString *file) +{ + return md->hasText() ? decodeMimeData(md->text(), t, file) : false; +} + +bool QtResourceView::decodeMimeData(const QString &text, ResourceType *t, QString *file) +{ + + static auto docElementName = elementResourceData; + static const QString docElementString = u'<' + docElementName; + + if (text.isEmpty() || text.indexOf(docElementString) == -1) + return false; + + QDomDocument doc; + if (!doc.setContent(text)) + return false; + + const QDomElement domElement = doc.documentElement(); + if (domElement.tagName() != docElementName) + return false; + + if (t) { + const QString typeAttr = typeAttribute; + if (domElement.hasAttribute (typeAttr)) { + const QString typeValue = domElement.attribute(typeAttr, typeOther); + if (typeValue == typeImage) { + *t = ResourceImage; + } else { + *t = typeValue == typeStyleSheet ? ResourceStyleSheet : ResourceOther; + } + } + } + if (file) { + const QString fileAttr = fileAttribute; + if (domElement.hasAttribute(fileAttr)) { + *file = domElement.attribute(fileAttr, QString()); + } else { + file->clear(); + } + } + return true; +} + +// ---------------------------- QtResourceViewDialogPrivate + +class QtResourceViewDialogPrivate +{ + QtResourceViewDialog *q_ptr; + Q_DECLARE_PUBLIC(QtResourceViewDialog) +public: + QtResourceViewDialogPrivate(QDesignerFormEditorInterface *core); + + void slotResourceSelected(const QString &resource) { setOkButtonEnabled(!resource.isEmpty()); } + void setOkButtonEnabled(bool v) { m_box->button(QDialogButtonBox::Ok)->setEnabled(v); } + + QDesignerFormEditorInterface *m_core; + QtResourceView *m_view; + QDialogButtonBox *m_box; +}; + +QtResourceViewDialogPrivate::QtResourceViewDialogPrivate(QDesignerFormEditorInterface *core) : + q_ptr(nullptr), + m_core(core), + m_view(new QtResourceView(core)), + m_box(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)) +{ + m_view->setSettingsKey(ResourceViewDialogC); +} + +// ------------ QtResourceViewDialog +QtResourceViewDialog::QtResourceViewDialog(QDesignerFormEditorInterface *core, QWidget *parent) : + QDialog(parent), + d_ptr(new QtResourceViewDialogPrivate(core)) +{ + setWindowTitle(tr("Select Resource")); + d_ptr->q_ptr = this; + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(d_ptr->m_view); + layout->addWidget(d_ptr->m_box); + connect(d_ptr->m_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(d_ptr->m_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(d_ptr->m_view, &QtResourceView::resourceActivated, this, &QDialog::accept); + connect(d_ptr->m_view, &QtResourceView::resourceSelected, + this, [this](const QString &resource) { d_ptr->slotResourceSelected(resource); }); + d_ptr->setOkButtonEnabled(false); + d_ptr->m_view->setResourceModel(core->resourceModel()); + + QDesignerSettingsInterface *settings = core->settingsManager(); + settings->beginGroup(ResourceViewDialogC); + + const QVariant geometry = settings->value(qrvGeometry); + if (geometry.metaType().id() == QMetaType::QByteArray) // Used to be a QRect up until 5.4.0, QTBUG-43374. + restoreGeometry(geometry.toByteArray()); + + settings->endGroup(); +} + +QtResourceViewDialog::~QtResourceViewDialog() +{ + QDesignerSettingsInterface *settings = d_ptr->m_core->settingsManager(); + settings->beginGroup(ResourceViewDialogC); + + settings->setValue(qrvGeometry, saveGeometry()); + + settings->endGroup(); +} + +QString QtResourceViewDialog::selectedResource() const +{ + return d_ptr->m_view->selectedResource(); +} + +void QtResourceViewDialog::selectResource(const QString &path) +{ + d_ptr->m_view->selectResource(path); +} + +bool QtResourceViewDialog::isResourceEditingEnabled() const +{ + return d_ptr->m_view->isResourceEditingEnabled(); +} + +void QtResourceViewDialog::setResourceEditingEnabled(bool enable) +{ + d_ptr->m_view->setResourceEditingEnabled(enable); +} + +QT_END_NAMESPACE + +#include "moc_qtresourceview_p.cpp" diff --git a/src/designer/src/lib/shared/qtresourceview_p.h b/src/designer/src/lib/shared/qtresourceview_p.h new file mode 100644 index 0000000..7d2ed62 --- /dev/null +++ b/src/designer/src/lib/shared/qtresourceview_p.h @@ -0,0 +1,92 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTRESOURCEVIEW_H +#define QTRESOURCEVIEW_H + +#include "shared_global_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QtResourceModel; +class QtResourceSet; +class QDesignerFormEditorInterface; +class QMimeData; + +class QDESIGNER_SHARED_EXPORT QtResourceView : public QWidget +{ + Q_OBJECT +public: + explicit QtResourceView(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~QtResourceView(); + + void setDragEnabled(bool dragEnabled); + bool dragEnabled() const; + + QtResourceModel *model() const; + void setResourceModel(QtResourceModel *model); + + QString selectedResource() const; + void selectResource(const QString &resource); + + QString settingsKey() const; + void setSettingsKey(const QString &key); + + bool isResourceEditingEnabled() const; + void setResourceEditingEnabled(bool enable); + + // Helpers for handling the drag originating in QtResourceView (Xml/text) + enum ResourceType { ResourceImage, ResourceStyleSheet, ResourceOther }; + static QString encodeMimeData(ResourceType resourceType, const QString &path); + + static bool decodeMimeData(const QMimeData *md, ResourceType *t = nullptr, QString *file = nullptr); + static bool decodeMimeData(const QString &text, ResourceType *t = nullptr, QString *file = nullptr); + +signals: + void resourceSelected(const QString &resource); + void resourceActivated(const QString &resource); + +protected: + bool event(QEvent *event) override; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceView) + Q_DISABLE_COPY_MOVE(QtResourceView) +}; + +class QDESIGNER_SHARED_EXPORT QtResourceViewDialog : public QDialog +{ + Q_OBJECT +public: + explicit QtResourceViewDialog(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~QtResourceViewDialog() override; + + QString selectedResource() const; + void selectResource(const QString &path); + + bool isResourceEditingEnabled() const; + void setResourceEditingEnabled(bool enable); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceViewDialog) + Q_DISABLE_COPY_MOVE(QtResourceViewDialog) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/designer/src/lib/shared/rcc.cpp b/src/designer/src/lib/shared/rcc.cpp new file mode 100644 index 0000000..99ca18d --- /dev/null +++ b/src/designer/src/lib/shared/rcc.cpp @@ -0,0 +1,1549 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +/* Note: This is a copy of qtbase/src/tools/rcc/rcc.cpp. */ + +#include "rcc_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if QT_CONFIG(zstd) +# include +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { + CONSTANT_USENAMESPACE = 1, + CONSTANT_COMPRESSLEVEL_DEFAULT = -1, + CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea + CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data + CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 +}; + +void RCCResourceLibrary::write(const char *str, int len) +{ + int n = m_out.size(); + m_out.resize(n + len); + memcpy(m_out.data() + n, str, len); +} + +void RCCResourceLibrary::writeByteArray(const QByteArray &other) +{ + if (m_format == Pass2) { + m_outDevice->write(other); + } else { + m_out.append(other); + } +} + +static inline QString msgOpenReadFailed(const QString &fname, const QString &why) +{ + return QString::fromLatin1("Unable to open %1 for reading: %2\n").arg(fname, why); +} + + +/////////////////////////////////////////////////////////// +// +// RCCFileInfo +// +/////////////////////////////////////////////////////////// + +class RCCFileInfo +{ +public: + enum Flags + { + // must match qresource.cpp + NoFlags = 0x00, + Compressed = 0x01, + Directory = 0x02, + CompressedZstd = 0x04 + }; + + + RCCFileInfo() = default; + RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, + QLocale::Territory territory, uint flags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, + int compressThreshold, bool noZstd, bool isEmpty); + + ~RCCFileInfo(); + RCCFileInfo(const RCCFileInfo &) = delete; + RCCFileInfo &operator=(const RCCFileInfo &) = delete; + RCCFileInfo(RCCFileInfo &&) = default; + RCCFileInfo &operator=(RCCFileInfo &&other) = delete; + + QString resourceName() const; + +public: + qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); + qint64 writeDataName(RCCResourceLibrary &, qint64 offset); + void writeDataInfo(RCCResourceLibrary &lib); + + int m_flags = NoFlags; + QLocale::Language m_language = QLocale::C; + QLocale::Territory m_territory = QLocale::AnyTerritory; + QString m_name; + QFileInfo m_fileInfo; + RCCFileInfo *m_parent = nullptr; + QMultiHash m_children; + + RCCResourceLibrary::CompressionAlgorithm m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Best; + int m_compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT; + int m_compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT; + bool m_noZstd = false; + bool m_isEmpty = false; + + qint64 m_nameOffset = 0; + qint64 m_dataOffset = 0; + qint64 m_childOffset = 0; +}; + +RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, + QLocale::Territory territory, uint flags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, + int compressThreshold, bool noZstd, bool isEmpty) + : m_flags(flags), + m_language(language), + m_territory(territory), + m_name(name), + m_fileInfo(fileInfo), + m_compressAlgo(compressAlgo), + m_compressLevel(compressLevel), + m_compressThreshold(compressThreshold), + m_noZstd(noZstd), + m_isEmpty(isEmpty) +{ +} + +RCCFileInfo::~RCCFileInfo() +{ + qDeleteAll(m_children); +} + +QString RCCFileInfo::resourceName() const +{ + QString resource = m_name; + for (RCCFileInfo *p = m_parent; p; p = p->m_parent) + resource = resource.prepend(p->m_name + u'/'); + resource.prepend(u':'); + return resource; +} + +void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool python = lib.m_format == RCCResourceLibrary::Python_Code; + //some info + if (text || pass1) { + if (m_language != QLocale::C) { + lib.writeString(" // "); + lib.writeByteArray(resourceName().toLocal8Bit()); + lib.writeString(" ["); + lib.writeByteArray(QByteArray::number(m_territory)); + lib.writeString("::"); + lib.writeByteArray(QByteArray::number(m_language)); + lib.writeString("[\n "); + } else { + lib.writeString(" // "); + lib.writeByteArray(resourceName().toLocal8Bit()); + lib.writeString("\n "); + } + } + + //pointer data + if (m_flags & RCCFileInfo::Directory) { + // name offset + lib.writeNumber4(m_nameOffset); + + // flags + lib.writeNumber2(m_flags); + + // child count + lib.writeNumber4(m_children.size()); + + // first child offset + lib.writeNumber4(m_childOffset); + } else { + // name offset + lib.writeNumber4(m_nameOffset); + + // flags + lib.writeNumber2(m_flags); + + // locale + lib.writeNumber2(m_territory); + lib.writeNumber2(m_language); + + //data offset + lib.writeNumber4(m_dataOffset); + } + if (text || pass1) + lib.writeChar('\n'); + else if (python) + lib.writeString("\\\n"); + + if (lib.formatVersion() >= 2) { + // last modified time stamp + const QDateTime lastModified = m_fileInfo.lastModified(QTimeZone::UTC); + quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); + static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); + if (sourceDate != 0) + lastmod = sourceDate; + static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH").toULongLong(); + if (sourceDate2 != 0) + lastmod = sourceDate2; + lib.writeNumber8(lastmod); + if (text || pass1) + lib.writeChar('\n'); + else if (python) + lib.writeString("\\\n"); + } +} + +qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, + QString *errorMessage) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2; + const bool binary = lib.m_format == RCCResourceLibrary::Binary; + const bool python = lib.m_format == RCCResourceLibrary::Python_Code; + + //capture the offset + m_dataOffset = offset; + QByteArray data; + + if (!m_isEmpty) { + //find the data to be written + QFile file(m_fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) { + *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); + return 0; + } + + data = file.readAll(); + } + + // Check if compression is useful for this file + if (data.size() != 0) { +#if QT_CONFIG(zstd) + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best && !m_noZstd) { + m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd; + m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental + } + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd && !m_noZstd) { + if (lib.m_zstdCCtx == nullptr) + lib.m_zstdCCtx = ZSTD_createCCtx(); + qsizetype size = data.size(); + size = ZSTD_COMPRESSBOUND(size); + + int compressLevel = m_compressLevel; + if (compressLevel < 0) + compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK; + + QByteArray compressed(size, Qt::Uninitialized); + char *dst = const_cast(compressed.constData()); + size_t n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, + data.constData(), data.size(), + compressLevel); + if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) { + // compressing is worth it + if (m_compressLevel < 0) { + // heuristic compression, so recompress + n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, + data.constData(), data.size(), + CONSTANT_ZSTDCOMPRESSLEVEL_STORE); + } + if (ZSTD_isError(n)) { + QString msg = QString::fromLatin1("%1: error: compression with zstd failed: %2\n") + .arg(m_name, QString::fromUtf8(ZSTD_getErrorName(n))); + lib.m_errorDevice->write(msg.toUtf8()); + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n") + .arg(m_name).arg(data.size()).arg(n); + lib.m_errorDevice->write(msg.toUtf8()); + } + + lib.m_overallFlags |= CompressedZstd; + m_flags |= CompressedZstd; + data = std::move(compressed); + data.truncate(n); + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); + lib.m_errorDevice->write(msg.toUtf8()); + } + } +#endif +#ifndef QT_NO_COMPRESS + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { + m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib; + m_compressLevel = 9; + } + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) { + QByteArray compressed = + qCompress(reinterpret_cast(data.data()), data.size(), m_compressLevel); + + int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); + if (compressRatio >= m_compressThreshold) { + if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n") + .arg(m_name).arg(data.size()).arg(compressed.size()); + lib.m_errorDevice->write(msg.toUtf8()); + } + data = std::move(compressed); + lib.m_overallFlags |= Compressed; + m_flags |= Compressed; + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); + lib.m_errorDevice->write(msg.toUtf8()); + } + } +#endif // QT_NO_COMPRESS + } + + // some info + if (text || pass1) { + lib.writeString(" // "); + lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); + lib.writeString("\n "); + } + + // write the length + if (text || binary || pass2 || python) + lib.writeNumber4(data.size()); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 4; + + // write the payload + const char *p = data.constData(); + if (text || python) { + for (int i = data.size(), j = 0; --i >= 0; --j) { + lib.writeHex(*p++); + if (j == 0) { + if (text) + lib.writeString("\n "); + else + lib.writeString("\\\n"); + j = 16; + } + } + } else if (binary || pass2) { + lib.writeByteArray(data); + } + offset += data.size(); + + // done + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + + return offset; +} + +qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool python = lib.m_format == RCCResourceLibrary::Python_Code; + + // capture the offset + m_nameOffset = offset; + + // some info + if (text || pass1) { + lib.writeString(" // "); + lib.writeByteArray(m_name.toLocal8Bit()); + lib.writeString("\n "); + } + + // write the length + lib.writeNumber2(m_name.size()); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 2; + + // write the hash + lib.writeNumber4(qt_hash(m_name)); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 4; + + // write the m_name + const QChar *unicode = m_name.unicode(); + for (int i = 0; i < m_name.size(); ++i) { + lib.writeNumber2(unicode[i].unicode()); + if ((text || pass1) && i % 16 == 0) + lib.writeString("\n "); + else if (python && i % 16 == 0) + lib.writeString("\\\n"); + } + offset += m_name.size()*2; + + // done + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + + return offset; +} + + +/////////////////////////////////////////////////////////// +// +// RCCResourceLibrary +// +/////////////////////////////////////////////////////////// + +RCCResourceLibrary::Strings::Strings() : + TAG_RCC("RCC"_L1), + TAG_RESOURCE("qresource"_L1), + TAG_FILE("file"_L1), + ATTRIBUTE_LANG("lang"_L1), + ATTRIBUTE_PREFIX("prefix"_L1), + ATTRIBUTE_ALIAS("alias"_L1), + ATTRIBUTE_EMPTY("empty"_L1), + ATTRIBUTE_THRESHOLD("threshold"_L1), + ATTRIBUTE_COMPRESS("compress"_L1), + ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm")) +{ +} + +RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) + : m_root(nullptr), + m_format(C_Code), + m_verbose(false), + m_compressionAlgo(CompressionAlgorithm::Best), + m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), + m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), + m_treeOffset(0), + m_namesOffset(0), + m_dataOffset(0), + m_overallFlags(0), + m_useNameSpace(CONSTANT_USENAMESPACE), + m_errorDevice(nullptr), + m_outDevice(nullptr), + m_formatVersion(formatVersion), + m_noZstd(false) +{ + m_out.reserve(30 * 1000 * 1000); +#if QT_CONFIG(zstd) + m_zstdCCtx = nullptr; +#endif +} + +RCCResourceLibrary::~RCCResourceLibrary() +{ + delete m_root; +#if QT_CONFIG(zstd) + ZSTD_freeCCtx(m_zstdCCtx); +#endif +} + +enum RCCXmlTag { + RccTag, + ResourceTag, + FileTag +}; +Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE); + +static bool parseBoolean(QStringView value, QString *errorMsg) +{ + if (value.compare("true"_L1, Qt::CaseInsensitive) == 0) + return true; + if (value.compare("false"_L1, Qt::CaseInsensitive) == 0) + return false; + + *errorMsg = QString::fromLatin1("Invalid value for boolean attribute: '%1'").arg(value); + return false; +} + +bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, + const QString &fname, QString currentPath, bool listMode) +{ + Q_ASSERT(m_errorDevice); + const QChar slash = u'/'; + if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) + currentPath += slash; + + QXmlStreamReader reader(inputDevice); + QStack tokens; + + QString prefix; + QLocale::Language language = QLocale::c().language(); + QLocale::Territory territory = QLocale::c().territory(); + QString alias; + bool empty = false; + auto compressAlgo = m_compressionAlgo; + int compressLevel = m_compressLevel; + int compressThreshold = m_compressThreshold; + + while (!reader.atEnd()) { + QXmlStreamReader::TokenType t = reader.readNext(); + switch (t) { + case QXmlStreamReader::StartElement: + if (reader.name() == m_strings.TAG_RCC) { + if (!tokens.isEmpty()) + reader.raiseError("expected tag"_L1); + else + tokens.push(RccTag); + } else if (reader.name() == m_strings.TAG_RESOURCE) { + if (tokens.isEmpty() || tokens.top() != RccTag) { + reader.raiseError("unexpected tag"_L1); + } else { + tokens.push(ResourceTag); + + QXmlStreamAttributes attributes = reader.attributes(); + language = QLocale::c().language(); + territory = QLocale::c().territory(); + + if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) { + QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString(); + QLocale lang = QLocale(attribute); + language = lang.language(); + if (2 == attribute.size()) { + // Language only + territory = QLocale::AnyTerritory; + } else { + territory = lang.territory(); + } + } + + prefix.clear(); + if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX)) + prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString(); + if (!prefix.startsWith(slash)) + prefix.prepend(slash); + if (!prefix.endsWith(slash)) + prefix += slash; + } + } else if (reader.name() == m_strings.TAG_FILE) { + if (tokens.isEmpty() || tokens.top() != ResourceTag) { + reader.raiseError("unexpected tag"_L1); + } else { + tokens.push(FileTag); + + QXmlStreamAttributes attributes = reader.attributes(); + alias.clear(); + if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS)) + alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString(); + + compressAlgo = m_compressionAlgo; + compressLevel = m_compressLevel; + compressThreshold = m_compressThreshold; + + QString errorString; + if (attributes.hasAttribute(m_strings.ATTRIBUTE_EMPTY)) + empty = parseBoolean(attributes.value(m_strings.ATTRIBUTE_EMPTY), &errorString); + else + empty = false; + + if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO)) + compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString); + if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) { + QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString(); + compressLevel = parseCompressionLevel(compressAlgo, value, &errorString); + } + + // Special case for -no-compress + if (m_compressLevel == -2) + compressAlgo = CompressionAlgorithm::None; + + if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) + compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); + + if (!errorString.isEmpty()) + reader.raiseError(errorString); + } + } else { + reader.raiseError("unexpected tag: %1"_L1.arg(reader.name().toString())); + } + break; + + case QXmlStreamReader::EndElement: + if (reader.name() == m_strings.TAG_RCC) { + if (!tokens.isEmpty() && tokens.top() == RccTag) + tokens.pop(); + else + reader.raiseError("unexpected closing tag"_L1); + } else if (reader.name() == m_strings.TAG_RESOURCE) { + if (!tokens.isEmpty() && tokens.top() == ResourceTag) + tokens.pop(); + else + reader.raiseError("unexpected closing tag"_L1); + } else if (reader.name() == m_strings.TAG_FILE) { + if (!tokens.isEmpty() && tokens.top() == FileTag) + tokens.pop(); + else + reader.raiseError("unexpected closing tag"_L1); + } + break; + + case QXmlStreamReader::Characters: + if (reader.isWhitespace()) + break; + if (tokens.isEmpty() || tokens.top() != FileTag) { + reader.raiseError("unexpected text"_L1); + } else { + QString fileName = reader.text().toString(); + if (fileName.isEmpty()) { + const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + } + + if (alias.isNull()) + alias = fileName; + + alias = QDir::cleanPath(alias); + while (alias.startsWith("../"_L1)) + alias.remove(0, 3); + alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; + + QString absFileName = fileName; + if (QDir::isRelativePath(absFileName)) + absFileName.prepend(currentPath); + QFileInfo file(absFileName); + if (file.isDir()) { + QDir dir(file.filePath()); + if (!alias.endsWith(slash)) + alias += slash; + + QStringList filePaths; + QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + if (it.fileName() == "."_L1 || it.fileName() == ".."_L1) + continue; + filePaths.append(it.filePath()); + } + + // make rcc output deterministic + std::sort(filePaths.begin(), filePaths.end()); + + for (const QString &filePath : filePaths) { + QFileInfo child(filePath); + const bool arc = + addFile(alias + child.fileName(), + RCCFileInfo(child.fileName(), child, language, territory, + child.isDir() ? RCCFileInfo::Directory + : RCCFileInfo::NoFlags, + compressAlgo, compressLevel, compressThreshold, + m_noZstd, empty)); + if (!arc) + m_failedResources.push_back(child.fileName()); + } + } else if (listMode || file.isFile()) { + const bool arc = + addFile(alias, + RCCFileInfo(alias.section(slash, -1), + file, + language, + territory, + RCCFileInfo::NoFlags, + compressAlgo, + compressLevel, + compressThreshold, + m_noZstd, empty) + ); + if (!arc) + m_failedResources.push_back(absFileName); + } else if (file.exists()) { + m_failedResources.push_back(absFileName); + const QString msg = QString::fromLatin1("RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n") + .arg(fname, fileName); + m_errorDevice->write(msg.toUtf8()); + return false; + } else { + m_failedResources.push_back(absFileName); + const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n") + .arg(fname, fileName); + m_errorDevice->write(msg.toUtf8()); + return false; + } + } + break; + + default: + break; + } + } + + if (reader.hasError()) { + int errorLine = reader.lineNumber(); + int errorColumn = reader.columnNumber(); + QString errorMessage = reader.errorString(); + QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage); + m_errorDevice->write(msg.toUtf8()); + return false; + } + + if (m_root == nullptr) { + const QString msg = QString::fromLatin1("RCC: Warning: No resources in '%1'.\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + if (!listMode && m_format == Binary) { + // create dummy entry, otherwise loading with QResource will crash + m_root = new RCCFileInfo{}; + m_root->m_flags = RCCFileInfo::Directory; + } + } + + return true; +} + +bool RCCResourceLibrary::addFile(const QString &alias, RCCFileInfo file) +{ + Q_ASSERT(m_errorDevice); + if (file.m_fileInfo.size() > 0xffffffff) { + const QString msg = QString::fromLatin1("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath()); + m_errorDevice->write(msg.toUtf8()); + return false; + } + if (!m_root) { + m_root = new RCCFileInfo{}; + m_root->m_flags = RCCFileInfo::Directory; + } + + RCCFileInfo *parent = m_root; + const QStringList nodes = alias.split(u'/'); + for (int i = 1; i < nodes.size()-1; ++i) { + const QString node = nodes.at(i); + if (node.isEmpty()) + continue; + if (!parent->m_children.contains(node)) { + RCCFileInfo *s = new RCCFileInfo{}; + s->m_name = node; + s->m_flags = RCCFileInfo::Directory; + s->m_parent = parent; + parent->m_children.insert(node, s); + parent = s; + } else { + parent = *parent->m_children.constFind(node); + } + } + + const QString filename = nodes.at(nodes.size()-1); + RCCFileInfo *s = new RCCFileInfo(std::move(file)); + s->m_parent = parent; + auto cbegin = parent->m_children.constFind(filename); + auto cend = parent->m_children.constEnd(); + for (auto it = cbegin; it != cend; ++it) { + if (it.key() == filename && it.value()->m_language == s->m_language && + it.value()->m_territory == s->m_territory) { + for (const QString &name : std::as_const(m_fileNames)) { + qWarning("%s: Warning: potential duplicate alias detected: '%s'", + qPrintable(name), qPrintable(filename)); + } + break; + } + } + parent->m_children.insert(filename, s); + return true; +} + +void RCCResourceLibrary::reset() +{ + if (m_root) { + delete m_root; + m_root = nullptr; + } + m_errorDevice = nullptr; + m_failedResources.clear(); +} + + +bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice) +{ + reset(); + m_errorDevice = &errorDevice; + //read in data + if (m_verbose) { + const QString msg = QString::fromLatin1("Processing %1 files [listMode=%2]\n") + .arg(m_fileNames.size()).arg(static_cast(listMode)); + m_errorDevice->write(msg.toUtf8()); + } + for (int i = 0; i < m_fileNames.size(); ++i) { + QFile fileIn; + QString fname = m_fileNames.at(i); + QString pwd; + if (fname == "-"_L1) { + fname = "(stdin)"_L1; + pwd = QDir::currentPath(); + fileIn.setFileName(fname); + if (!fileIn.open(stdin, QIODevice::ReadOnly)) { + m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); + return false; + } + } else { + pwd = QFileInfo(fname).path(); + fileIn.setFileName(fname); + if (!fileIn.open(QIODevice::ReadOnly)) { + m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); + return false; + } + } + if (m_verbose) { + const QString msg = QString::fromLatin1("Interpreting %1\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + } + + if (!interpretResourceFile(&fileIn, fname, pwd, listMode)) + return false; + } + return true; +} + +QStringList RCCResourceLibrary::dataFiles() const +{ + QStringList ret; + QStack pending; + + if (!m_root) + return ret; + pending.push(m_root); + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (auto it = file->m_children.begin(); + it != file->m_children.end(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + else + ret.append(child->m_fileInfo.filePath()); + } + } + return ret; +} + +// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion +static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) +{ + const QChar slash = u'/'; + const auto cend = m_root->m_children.constEnd(); + for (auto it = m_root->m_children.constBegin(); it != cend; ++it) { + const RCCFileInfo *child = it.value(); + const QString childName = path + slash + child->m_name; + if (child->m_flags & RCCFileInfo::Directory) { + resourceDataFileMapRecursion(child, childName, m); + } else { + m.insert(childName, child->m_fileInfo.filePath()); + } + } +} + +RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const +{ + ResourceDataFileMap rc; + if (m_root) + resourceDataFileMapRecursion(m_root, QString(u':'), rc); + return rc; +} + +RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) +{ + if (value == "best"_L1) + return CompressionAlgorithm::Best; + if (value == "zlib"_L1) { +#ifdef QT_NO_COMPRESS + *errorMsg = "zlib support not compiled in"_L1; +#else + return CompressionAlgorithm::Zlib; +#endif + } else if (value == "zstd"_L1) { +#if QT_CONFIG(zstd) + return CompressionAlgorithm::Zstd; +#else + *errorMsg = "Zstandard support not compiled in"_L1; +#endif + } else if (value != "none"_L1) { + *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value); + } + + return CompressionAlgorithm::None; +} + +int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg) +{ + bool ok; + int c = level.toInt(&ok); + if (ok) { + switch (algo) { + case CompressionAlgorithm::None: + case CompressionAlgorithm::Best: + return 0; + case CompressionAlgorithm::Zlib: + if (c >= 1 && c <= 9) + return c; + break; + case CompressionAlgorithm::Zstd: +#if QT_CONFIG(zstd) + if (c >= 0 && c <= ZSTD_maxCLevel()) + return c; +#endif + break; + } + } + + *errorMsg = QString::fromLatin1("invalid compression level '%1'").arg(level); + return 0; +} + +bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice) +{ + m_errorDevice = &errorDevice; + + if (m_format == Pass2) { + const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' }; + bool foundSignature = false; + + while (true) { + char c; + for (int i = 0; i < 8; ) { + if (!tempDevice.getChar(&c)) { + if (foundSignature) + return true; + m_errorDevice->write("No data signature found\n"); + return false; + } + + if (c != pattern[i]) { + for (int k = 0; k < i; ++k) + outDevice.putChar(pattern[k]); + i = 0; + } + + if (c == pattern[i]) { + ++i; + } else { + outDevice.putChar(c); + } + } + + m_outDevice = &outDevice; + quint64 start = outDevice.pos(); + writeDataBlobs(); + quint64 len = outDevice.pos() - start; + + tempDevice.seek(tempDevice.pos() + len - 8); + foundSignature = true; + } + } + + //write out + if (m_verbose) + m_errorDevice->write("Outputting code\n"); + if (!writeHeader()) { + m_errorDevice->write("Could not write header\n"); + return false; + } + if (m_root) { + if (!writeDataBlobs()) { + m_errorDevice->write("Could not write data blobs.\n"); + return false; + } + if (!writeDataNames()) { + m_errorDevice->write("Could not write file names\n"); + return false; + } + if (!writeDataStructure()) { + m_errorDevice->write("Could not write data tree\n"); + return false; + } + } + if (!writeInitializer()) { + m_errorDevice->write("Could not write footer\n"); + return false; + } + outDevice.write(m_out.constData(), m_out.size()); + return true; +} + +void RCCResourceLibrary::writeDecimal(int value) +{ + Q_ASSERT(m_format != RCCResourceLibrary::Binary); + char buf[std::numeric_limits::digits10 + 2]; + int n = snprintf(buf, sizeof(buf), "%d", value); + write(buf, n); +} + +static const char hexDigits[] = "0123456789abcdef"; + +inline void RCCResourceLibrary::write2HexDigits(quint8 number) +{ + writeChar(hexDigits[number >> 4]); + writeChar(hexDigits[number & 0xf]); +} + +void RCCResourceLibrary::writeHex(quint8 tmp) +{ + switch (m_format) { + case RCCResourceLibrary::Python_Code: + if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') { + writeChar(char(tmp)); + } else { + writeChar('\\'); + writeChar('x'); + write2HexDigits(tmp); + } + break; + default: + writeChar('0'); + writeChar('x'); + if (tmp < 16) + writeChar(hexDigits[tmp]); + else + write2HexDigits(tmp); + writeChar(','); + break; + } +} + +void RCCResourceLibrary::writeNumber2(quint16 number) +{ + if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 8); + writeHex(number); + } +} + +void RCCResourceLibrary::writeNumber4(quint32 number) +{ + if (m_format == RCCResourceLibrary::Pass2) { + m_outDevice->putChar(char(number >> 24)); + m_outDevice->putChar(char(number >> 16)); + m_outDevice->putChar(char(number >> 8)); + m_outDevice->putChar(char(number)); + } else if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 24); + writeChar(number >> 16); + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 24); + writeHex(number >> 16); + writeHex(number >> 8); + writeHex(number); + } +} + +void RCCResourceLibrary::writeNumber8(quint64 number) +{ + if (m_format == RCCResourceLibrary::Pass2) { + m_outDevice->putChar(char(number >> 56)); + m_outDevice->putChar(char(number >> 48)); + m_outDevice->putChar(char(number >> 40)); + m_outDevice->putChar(char(number >> 32)); + m_outDevice->putChar(char(number >> 24)); + m_outDevice->putChar(char(number >> 16)); + m_outDevice->putChar(char(number >> 8)); + m_outDevice->putChar(char(number)); + } else if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 56); + writeChar(number >> 48); + writeChar(number >> 40); + writeChar(number >> 32); + writeChar(number >> 24); + writeChar(number >> 16); + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 56); + writeHex(number >> 48); + writeHex(number >> 40); + writeHex(number >> 32); + writeHex(number >> 24); + writeHex(number >> 16); + writeHex(number >> 8); + writeHex(number); + } +} + +bool RCCResourceLibrary::writeHeader() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("/****************************************************************************\n"); + writeString("** Resource object code\n"); + writeString("**\n"); + writeString("** Created by: The Resource Compiler for Qt version "); + writeByteArray(QT_VERSION_STR); + writeString("\n**\n"); + writeString("** WARNING! All changes made in this file will be lost!\n"); + writeString( "*****************************************************************************/\n\n"); + break; + case Python_Code: + writeString("# Resource object code (Python 3)\n"); + writeString("# Created by: object code\n"); + writeString("# Created by: The Resource Compiler for Qt version "); + writeByteArray(QT_VERSION_STR); + writeString("\n"); + writeString("# WARNING! All changes made in this file will be lost!\n\n"); + writeString("from PySide"); + writeByteArray(QByteArray::number(QT_VERSION_MAJOR)); + writeString(" import QtCore\n\n"); + break; + case Binary: + writeString("qres"); + writeNumber4(0); + writeNumber4(0); + writeNumber4(0); + writeNumber4(0); + if (m_formatVersion >= 3) + writeNumber4(m_overallFlags); + break; + default: + break; + } + return true; +} + +bool RCCResourceLibrary::writeDataBlobs() +{ + Q_ASSERT(m_errorDevice); + switch (m_format) { + case C_Code: + writeString("static const unsigned char qt_resource_data[] = {\n"); + break; + case Python_Code: + writeString("qt_resource_data = b\"\\\n"); + break; + case Binary: + m_dataOffset = m_out.size(); + break; + default: + break; + } + + if (!m_root) + return false; + + QStack pending; + pending.push(m_root); + qint64 offset = 0; + QString errorMessage; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + else { + offset = child->writeDataBlob(*this, offset, &errorMessage); + if (offset == 0) { + m_errorDevice->write(errorMessage.toUtf8()); + return false; + } + } + } + } + switch (m_format) { + case C_Code: + writeString("\n};\n\n"); + break; + case Python_Code: + writeString("\"\n\n"); + break; + case Pass1: + if (offset < 8) + offset = 8; + writeString("\nstatic const unsigned char qt_resource_data["); + writeByteArray(QByteArray::number(offset)); + writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n"); + break; + default: + break; + } + return true; +} + +bool RCCResourceLibrary::writeDataNames() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("static const unsigned char qt_resource_name[] = {\n"); + break; + case Python_Code: + writeString("qt_resource_name = b\"\\\n"); + break; + case Binary: + m_namesOffset = m_out.size(); + break; + default: + break; + } + + QHash names; + QStack pending; + + if (!m_root) + return false; + + pending.push(m_root); + qint64 offset = 0; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + if (names.contains(child->m_name)) { + child->m_nameOffset = names.value(child->m_name); + } else { + names.insert(child->m_name, offset); + offset = child->writeDataName(*this, offset); + } + } + } + switch (m_format) { + case C_Code: + case Pass1: + writeString("\n};\n\n"); + break; + case Python_Code: + writeString("\"\n\n"); + break; + default: + break; + } + return true; +} + +struct qt_rcc_compare_hash +{ + typedef bool result_type; + result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const + { + return qt_hash(left->m_name) < qt_hash(right->m_name); + } +}; + +bool RCCResourceLibrary::writeDataStructure() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("static const unsigned char qt_resource_struct[] = {\n"); + break; + case Python_Code: + writeString("qt_resource_struct = b\"\\\n"); + break; + case Binary: + m_treeOffset = m_out.size(); + break; + default: + break; + } + + QStack pending; + + if (!m_root) + return false; + + //calculate the child offsets (flat) + pending.push(m_root); + int offset = 1; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + file->m_childOffset = offset; + + //sort by hash value for binary lookup + QList m_children = file->m_children.values(); + std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); + + //write out the actual data now + for (int i = 0; i < m_children.size(); ++i) { + RCCFileInfo *child = m_children.at(i); + ++offset; + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + } + } + + //write out the structure (ie iterate again!) + pending.push(m_root); + m_root->writeDataInfo(*this); + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + + //sort by hash value for binary lookup + QList m_children = file->m_children.values(); + std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); + + //write out the actual data now + for (int i = 0; i < m_children.size(); ++i) { + RCCFileInfo *child = m_children.at(i); + child->writeDataInfo(*this); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + } + } + switch (m_format) { + case C_Code: + case Pass1: + writeString("\n};\n\n"); + break; + case Python_Code: + writeString("\"\n\n"); + break; + default: + break; + } + + return true; +} + +void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) +{ + if (m_useNameSpace) { + writeString("QT_RCC_MANGLE_NAMESPACE("); + writeByteArray(name); + writeChar(')'); + } else { + writeByteArray(name); + } +} + +void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) +{ + if (m_useNameSpace) { + writeString("QT_RCC_PREPEND_NAMESPACE("); + writeByteArray(name); + writeChar(')'); + } else { + writeByteArray(name); + } +} + +bool RCCResourceLibrary::writeInitializer() +{ + if (m_format == C_Code || m_format == Pass1) { + //write("\nQT_BEGIN_NAMESPACE\n"); + QString initNameStr = m_initName; + if (!initNameStr.isEmpty()) { + initNameStr.prepend(u'_'); + auto isAsciiLetterOrNumber = [] (QChar c) -> bool { + ushort ch = c.unicode(); + return (ch >= '0' && ch <= '9') || + (ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z') || + ch == '_'; + }; + for (QChar &c : initNameStr) { + if (!isAsciiLetterOrNumber(c)) + c = u'_'; + } + } + QByteArray initName = initNameStr.toLatin1(); + + //init + if (m_useNameSpace) { + writeString("#ifdef QT_NAMESPACE\n" + "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n" + "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n" + "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n" + "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n" + "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n" + " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n" + "#else\n" + "# define QT_RCC_PREPEND_NAMESPACE(name) name\n" + "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" + "#endif\n\n"); + + writeString("#ifdef QT_NAMESPACE\n" + "namespace QT_NAMESPACE {\n" + "#endif\n\n"); + } + + if (m_root) { + writeString("bool qRegisterResourceData" + "(int, const unsigned char *, " + "const unsigned char *, const unsigned char *);\n"); + writeString("bool qUnregisterResourceData" + "(int, const unsigned char *, " + "const unsigned char *, const unsigned char *);\n\n"); + + if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { + // use variable relocations with ELF and Mach-O + writeString("#if defined(__ELF__) || defined(__APPLE__)\n"); + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("static inline unsigned char qResourceFeatureZlib()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZlib;\n" + " return qt_resourceFeatureZlib;\n" + "}\n"); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("static inline unsigned char qResourceFeatureZstd()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZstd;\n" + " return qt_resourceFeatureZstd;\n" + "}\n"); + } + writeString("#else\n"); + if (m_overallFlags & RCCFileInfo::Compressed) + writeString("unsigned char qResourceFeatureZlib();\n"); + if (m_overallFlags & RCCFileInfo::CompressedZstd) + writeString("unsigned char qResourceFeatureZstd();\n"); + writeString("#endif\n\n"); + } + } + + if (m_useNameSpace) + writeString("#ifdef QT_NAMESPACE\n}\n#endif\n\n"); + + QByteArray initResources = "qInitResources"; + initResources += initName; + + // Work around -Wmissing-declarations warnings. + writeString("int "); + writeMangleNamespaceFunction(initResources); + writeString("();\n"); + + writeString("int "); + writeMangleNamespaceFunction(initResources); + writeString("()\n{\n"); + + if (m_root) { + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); + writeAddNamespaceFunction("qRegisterResourceData"); + writeString("\n (version, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); + } + writeString(" return 1;\n"); + writeString("}\n\n"); + + //cleanup + QByteArray cleanResources = "qCleanupResources"; + cleanResources += initName; + + // Work around -Wmissing-declarations warnings. + writeString("int "); + writeMangleNamespaceFunction(cleanResources); + writeString("();\n"); + + writeString("int "); + writeMangleNamespaceFunction(cleanResources); + writeString("()\n{\n"); + if (m_root) { + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); + + // ODR-use certain symbols from QtCore if we require optional features + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZlib()"); + writeString(";\n "); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZstd()"); + writeString(";\n "); + } + + writeAddNamespaceFunction("qUnregisterResourceData"); + writeString("\n (version, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); + } + writeString(" return 1;\n"); + writeString("}\n\n"); + + // -Wexit-time-destructors was added to clang 3.0.0 in 2011. + writeString("#ifdef __clang__\n" + "# pragma clang diagnostic push\n" + "# pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n" + "#endif\n\n"); + + writeString("namespace {\n" + " struct initializer {\n"); + + if (m_useNameSpace) { + writeByteArray(" initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n" + " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n"); + } else { + writeByteArray(" initializer() { " + initResources + "(); }\n" + " ~initializer() { " + cleanResources + "(); }\n"); + } + writeString(" } dummy;\n" + "}\n\n"); + + writeString("#ifdef __clang__\n" + "# pragma clang diagnostic pop\n" + "#endif\n"); + + + } else if (m_format == Binary) { + int i = 4; + char *p = m_out.data(); + p[i++] = 0; + p[i++] = 0; + p[i++] = 0; + p[i++] = m_formatVersion; + + p[i++] = (m_treeOffset >> 24) & 0xff; + p[i++] = (m_treeOffset >> 16) & 0xff; + p[i++] = (m_treeOffset >> 8) & 0xff; + p[i++] = (m_treeOffset >> 0) & 0xff; + + p[i++] = (m_dataOffset >> 24) & 0xff; + p[i++] = (m_dataOffset >> 16) & 0xff; + p[i++] = (m_dataOffset >> 8) & 0xff; + p[i++] = (m_dataOffset >> 0) & 0xff; + + p[i++] = (m_namesOffset >> 24) & 0xff; + p[i++] = (m_namesOffset >> 16) & 0xff; + p[i++] = (m_namesOffset >> 8) & 0xff; + p[i++] = (m_namesOffset >> 0) & 0xff; + + if (m_formatVersion >= 3) { + p[i++] = (m_overallFlags >> 24) & 0xff; + p[i++] = (m_overallFlags >> 16) & 0xff; + p[i++] = (m_overallFlags >> 8) & 0xff; + p[i++] = (m_overallFlags >> 0) & 0xff; + } + } else if (m_format == Python_Code) { + writeString("def qInitResources():\n"); + writeString(" QtCore.qRegisterResourceData(0x"); + write2HexDigits(m_formatVersion); + writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); + writeString("def qCleanupResources():\n"); + writeString(" QtCore.qUnregisterResourceData(0x"); + write2HexDigits(m_formatVersion); + writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); + writeString("qInitResources()\n"); + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/designer/src/lib/shared/rcc_p.h b/src/designer/src/lib/shared/rcc_p.h new file mode 100644 index 0000000..82d17c8 --- /dev/null +++ b/src/designer/src/lib/shared/rcc_p.h @@ -0,0 +1,166 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// Note: This is a copy of qtbase/src/tools/rcc/rcc.h. + +#ifndef RCC_H +#define RCC_H + +#include +#include +#include + +typedef struct ZSTD_CCtx_s ZSTD_CCtx; + +QT_BEGIN_NAMESPACE + +class RCCFileInfo; +class QIODevice; +class QTextStream; + + +class RCCResourceLibrary +{ + RCCResourceLibrary(const RCCResourceLibrary &); + RCCResourceLibrary &operator=(const RCCResourceLibrary &); + +public: + RCCResourceLibrary(quint8 formatVersion); + ~RCCResourceLibrary(); + + bool output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice); + + bool readFiles(bool listMode, QIODevice &errorDevice); + + enum Format { Binary, C_Code, Pass1, Pass2, Python_Code }; + void setFormat(Format f) { m_format = f; } + Format format() const { return m_format; } + + void setInputFiles(const QStringList &files) { m_fileNames = files; } + QStringList inputFiles() const { return m_fileNames; } + + QStringList dataFiles() const; + + // Return a map of resource identifier (':/newPrefix/images/p1.png') to file. + typedef QHash ResourceDataFileMap; + ResourceDataFileMap resourceDataFileMap() const; + + void setVerbose(bool b) { m_verbose = b; } + bool verbose() const { return m_verbose; } + + void setInitName(const QString &name) { m_initName = name; } + QString initName() const { return m_initName; } + + void setOutputName(const QString &name) { m_outputName = name; } + QString outputName() const { return m_outputName; } + + enum class CompressionAlgorithm { + Zlib, + Zstd, + + Best = 99, + None = -1 + }; + + static CompressionAlgorithm parseCompressionAlgorithm(QStringView algo, QString *errorMsg); + void setCompressionAlgorithm(CompressionAlgorithm algo) { m_compressionAlgo = algo; } + CompressionAlgorithm compressionAlgorithm() const { return m_compressionAlgo; } + + static int parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg); + void setCompressLevel(int c) { m_compressLevel = c; } + int compressLevel() const { return m_compressLevel; } + + void setCompressThreshold(int t) { m_compressThreshold = t; } + int compressThreshold() const { return m_compressThreshold; } + + void setResourceRoot(const QString &root) { m_resourceRoot = root; } + QString resourceRoot() const { return m_resourceRoot; } + + void setUseNameSpace(bool v) { m_useNameSpace = v; } + bool useNameSpace() const { return m_useNameSpace; } + + QStringList failedResources() const { return m_failedResources; } + + int formatVersion() const { return m_formatVersion; } + + void setNoZstd(bool v) { m_noZstd = v; } + bool noZstd() const { return m_noZstd; } + +private: + struct Strings { + Strings(); + const QString TAG_RCC; + const QString TAG_RESOURCE; + const QString TAG_FILE; + const QString ATTRIBUTE_LANG; + const QString ATTRIBUTE_PREFIX; + const QString ATTRIBUTE_ALIAS; + const QString ATTRIBUTE_EMPTY; + const QString ATTRIBUTE_THRESHOLD; + const QString ATTRIBUTE_COMPRESS; + const QString ATTRIBUTE_COMPRESSALGO; + }; + friend class RCCFileInfo; + void reset(); + bool addFile(const QString &alias, RCCFileInfo file); + bool interpretResourceFile(QIODevice *inputDevice, const QString &file, + QString currentPath = QString(), bool listMode = false); + bool writeHeader(); + bool writeDataBlobs(); + bool writeDataNames(); + bool writeDataStructure(); + bool writeInitializer(); + void writeMangleNamespaceFunction(const QByteArray &name); + void writeAddNamespaceFunction(const QByteArray &name); + void writeDecimal(int value); + void writeHex(quint8 number); + void write2HexDigits(quint8 number); + void writeNumber2(quint16 number); + void writeNumber4(quint32 number); + void writeNumber8(quint64 number); + void writeChar(char c) { m_out.append(c); } + void writeByteArray(const QByteArray &); + void write(const char *, int len); + void writeString(const char *s) { write(s, static_cast(strlen(s))); } + +#if QT_CONFIG(zstd) + ZSTD_CCtx *m_zstdCCtx; +#endif + + const Strings m_strings; + RCCFileInfo *m_root; + QStringList m_fileNames; + QString m_resourceRoot; + QString m_initName; + QString m_outputName; + Format m_format; + bool m_verbose; + CompressionAlgorithm m_compressionAlgo; + int m_compressLevel; + int m_compressThreshold; + int m_treeOffset; + int m_namesOffset; + int m_dataOffset; + quint32 m_overallFlags; + bool m_useNameSpace; + QStringList m_failedResources; + QIODevice *m_errorDevice; + QIODevice *m_outDevice; + QByteArray m_out; + quint8 m_formatVersion; + bool m_noZstd; +}; + +QT_END_NAMESPACE + +#endif // RCC_H diff --git a/src/designer/src/lib/shared/richtexteditor.cpp b/src/designer/src/lib/shared/richtexteditor.cpp new file mode 100644 index 0000000..e6b4141 --- /dev/null +++ b/src/designer/src/lib/shared/richtexteditor.cpp @@ -0,0 +1,883 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "richtexteditor_p.h" +#include "htmlhighlighter_p.h" +#include "iconselector_p.h" +#include "ui_addlinkdialog.h" + +#include "iconloader_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto RichTextDialogGroupC = "RichTextDialog"_L1; +static constexpr auto GeometryKeyC = "Geometry"_L1; +static constexpr auto TabKeyC = "Tab"_L1; + +const bool simplifyRichTextDefault = true; + +namespace qdesigner_internal { + +// Richtext simplification filter helpers: Elements to be discarded +static inline bool filterElement(QStringView name) +{ + return name != "meta"_L1 && name != "style"_L1; +} + +// Richtext simplification filter helpers: Filter attributes of elements +static inline void filterAttributes(QStringView name, + QXmlStreamAttributes *atts, + bool *paragraphAlignmentFound) +{ + if (atts->isEmpty()) + return; + + // No style attributes for + if (name == "body"_L1) { + atts->clear(); + return; + } + + // Clean out everything except 'align' for 'p' + if (name == "p"_L1) { + for (auto it = atts->begin(); it != atts->end(); ) { + if (it->name() == "align"_L1) { + ++it; + *paragraphAlignmentFound = true; + } else { + it = atts->erase(it); + } + } + return; + } +} + +// Richtext simplification filter helpers: Check for blank QStringView. +static inline bool isWhiteSpace(QStringView in) +{ + return std::all_of(in.cbegin(), in.cend(), + [](QChar c) { return c.isSpace(); }); +} + +// Richtext simplification filter: Remove hard-coded font settings, +//